Initial import from Docker volume

This commit is contained in:
root
2025-12-26 13:11:43 +00:00
commit 4998dc066a
13336 changed files with 1767801 additions and 0 deletions

36
modules/party/__init__.py Executable file
View File

@@ -0,0 +1,36 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.pool import Pool
from . import (
address, category, configuration, contact_mechanism, country, ir, party)
def register():
Pool.register(
country.PostalCode,
category.Category,
party.Party,
party.PartyLang,
party.PartyCategory,
party.Identifier,
party.CheckVIESResult,
party.ReplaceAsk,
party.EraseAsk,
address.Address,
address.AddressFormat,
address.SubdivisionType,
contact_mechanism.ContactMechanism,
contact_mechanism.ContactMechanismLanguage,
configuration.Configuration,
configuration.ConfigurationSequence,
configuration.ConfigurationLang,
ir.Email,
ir.EmailTemplate,
module='party', type_='model')
Pool.register(
party.CheckVIES,
party.Replace,
party.Erase,
module='party', type_='wizard')

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

462
modules/party/address.py Executable file
View File

@@ -0,0 +1,462 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
'Address'
from string import Template
from sql import Literal
from sql.functions import Substring
from sql.operators import Equal
from trytond.cache import Cache
from trytond.i18n import gettext
from trytond.model import (
DeactivableMixin, Exclude, MatchMixin, ModelSQL, ModelView, fields,
sequence_ordered)
from trytond.model.exceptions import AccessError
from trytond.pool import Pool
from trytond.pyson import Eval, If
from trytond.rpc import RPC
from trytond.transaction import Transaction
from .contact_mechanism import _ContactMechanismMixin
from .exceptions import InvalidFormat
class Address(
DeactivableMixin, sequence_ordered(), _ContactMechanismMixin,
ModelSQL, ModelView):
"Address"
__name__ = 'party.address'
party = fields.Many2One(
'party.party', "Party", required=True, ondelete='CASCADE',
states={
'readonly': Eval('id', 0) > 0,
})
party_name = fields.Char(
"Party Name",
help="If filled, replace the name of the party for address formatting")
name = fields.Char("Building Name")
street = fields.Text("Street")
street_single_line = fields.Function(
fields.Char("Street"),
'on_change_with_street_single_line',
searcher='search_street_single_line')
postal_code = fields.Char("Postal Code")
city = fields.Char("City")
country = fields.Many2One('country.country', "Country")
subdivision_types = fields.Function(
fields.MultiSelection(
'get_subdivision_types', "Subdivision Types"),
'on_change_with_subdivision_types')
subdivision = fields.Many2One("country.subdivision",
'Subdivision',
domain=[
('country', '=', Eval('country', -1)),
If(Eval('subdivision_types', []),
('type', 'in', Eval('subdivision_types', [])),
()
),
])
full_address = fields.Function(fields.Text('Full Address'),
'get_full_address')
identifiers = fields.One2Many(
'party.identifier', 'address', "Identifiers",
domain=[
('party', '=', Eval('party')),
('type', 'in', ['fr_siret']),
])
contact_mechanisms = fields.One2Many(
'party.contact_mechanism', 'address', "Contact Mechanisms",
domain=[
('party', '=', Eval('party', -1)),
])
@classmethod
def __setup__(cls):
super(Address, cls).__setup__()
cls._order.insert(0, ('party', 'ASC'))
cls.__rpc__.update(
autocomplete_postal_code=RPC(instantiate=0, cache=dict(days=1)),
autocomplete_city=RPC(instantiate=0, cache=dict(days=1)),
)
@classmethod
def __register__(cls, module_name):
table = cls.__table_handler__(module_name)
# Migration from 5.8: rename zip to postal code
table.column_rename('zip', 'postal_code')
super(Address, cls).__register__(module_name)
@fields.depends('street')
def on_change_with_street_single_line(self, name=None):
if self.street:
return " ".join(self.street.splitlines())
@classmethod
def search_street_single_line(cls, name, domain):
return [('street',) + tuple(domain[1:])]
_autocomplete_limit = 100
@fields.depends('country', 'subdivision')
def _autocomplete_domain(self):
domain = []
if self.country:
domain.append(('country', '=', self.country.id))
if self.subdivision:
domain.append(['OR',
('subdivision', 'child_of',
[self.subdivision.id], 'parent'),
('subdivision', '=', None),
])
return domain
def _autocomplete_search(self, domain, name):
pool = Pool()
PostalCode = pool.get('country.postal_code')
if domain:
records = PostalCode.search(domain, limit=self._autocomplete_limit)
if len(records) < self._autocomplete_limit:
return sorted({getattr(z, name) for z in records})
return []
@fields.depends('city', methods=['_autocomplete_domain'])
def autocomplete_postal_code(self):
domain = self._autocomplete_domain()
if self.city:
domain.append(('city', 'ilike', '%%%s%%' % self.city))
return self._autocomplete_search(domain, 'postal_code')
@fields.depends('postal_code', methods=['_autocomplete_domain'])
def autocomplete_city(self):
domain = self._autocomplete_domain()
if self.postal_code:
domain.append(('postal_code', 'ilike', '%s%%' % self.postal_code))
return self._autocomplete_search(domain, 'city')
def get_full_address(self, name):
pool = Pool()
AddressFormat = pool.get('party.address.format')
full_address = Template(AddressFormat.get_format(self)).substitute(
**self._get_address_substitutions())
return '\n'.join(
filter(None, (x.strip() for x in full_address.splitlines())))
def _get_address_substitutions(self):
pool = Pool()
Country = pool.get('country.country')
context = Transaction().context
subdivision_code = ''
if getattr(self, 'subdivision', None):
subdivision_code = self.subdivision.code or ''
if '-' in subdivision_code:
subdivision_code = subdivision_code.split('-', 1)[1]
country_name = ''
if getattr(self, 'country', None):
with Transaction().set_context(language='en'):
country_name = Country(self.country.id).name
substitutions = {
'party_name': '',
'attn': '',
'name': getattr(self, 'name', None) or '',
'street': getattr(self, 'street', None) or '',
'postal_code': getattr(self, 'postal_code', None) or '',
'city': getattr(self, 'city', None) or '',
'subdivision': (self.subdivision.name
if getattr(self, 'subdivision', None) else ''),
'subdivision_code': subdivision_code,
'country': country_name,
'country_code': (self.country.code or ''
if getattr(self, 'country', None) else ''),
}
# Keep zip for backward compatibility
substitutions['zip'] = substitutions['postal_code']
if context.get('address_from_country') == getattr(self, 'country', ''):
substitutions['country'] = ''
if context.get('address_with_party', False):
substitutions['party_name'] = self.party_full_name
if context.get('address_attention_party', False):
substitutions['attn'] = (
context['address_attention_party'].full_name)
for key, value in list(substitutions.items()):
substitutions[key.upper()] = value.upper()
return substitutions
@property
def party_full_name(self):
name = ''
if self.party_name:
name = self.party_name
elif self.party:
name = self.party.full_name
return name
def get_rec_name(self, name):
party = self.party_full_name
if self.street_single_line:
street = self.street_single_line
else:
street = None
if self.country:
country = self.country.code
else:
country = None
return ', '.join(
filter(None, [
party,
self.name,
street,
self.postal_code,
self.city,
country]))
@classmethod
def search_rec_name(cls, name, clause):
if clause[1].startswith('!') or clause[1].startswith('not '):
bool_op = 'AND'
else:
bool_op = 'OR'
return [bool_op,
('party',) + tuple(clause[1:]),
('name',) + tuple(clause[1:]),
('street',) + tuple(clause[1:]),
('postal_code',) + tuple(clause[1:]),
('city',) + tuple(clause[1:]),
('country',) + tuple(clause[1:]),
]
@classmethod
def write(cls, *args):
actions = iter(args)
for addresses, values in zip(actions, actions):
if 'party' in values:
for address in addresses:
if address.party.id != values['party']:
raise AccessError(
gettext('party.msg_address_change_party',
address=address.rec_name))
super(Address, cls).write(*args)
@fields.depends('subdivision', 'country')
def on_change_country(self):
if (self.subdivision
and self.subdivision.country != self.country):
self.subdivision = None
@classmethod
def get_subdivision_types(cls):
pool = Pool()
Subdivision = pool.get('country.subdivision')
selection = Subdivision.fields_get(['type'])['type']['selection']
return [(k, v) for k, v in selection if k is not None]
@fields.depends('country')
def on_change_with_subdivision_types(self, name=None):
pool = Pool()
Types = pool.get('party.address.subdivision_type')
return Types.get_types(self.country)
def contact_mechanism_get(self, types=None, usage=None):
mechanism = super().contact_mechanism_get(types=types, usage=usage)
if mechanism is None:
mechanism = self.party.contact_mechanism_get(
types=types, usage=usage)
return mechanism
class AddressFormat(DeactivableMixin, MatchMixin, ModelSQL, ModelView):
"Address Format"
__name__ = 'party.address.format'
country_code = fields.Char("Country Code", size=2)
language_code = fields.Char("Language Code", size=2)
format_ = fields.Text("Format", required=True,
help="Available variables (also in upper case):\n"
"- ${party_name}\n"
"- ${name}\n"
"- ${attn}\n"
"- ${street}\n"
"- ${postal_code}\n"
"- ${city}\n"
"- ${subdivision}\n"
"- ${subdivision_code}\n"
"- ${country}\n"
"- ${country_code}")
_get_format_cache = Cache('party.address.format.get_format')
@classmethod
def __setup__(cls):
super(AddressFormat, cls).__setup__()
cls._order.insert(0, ('country_code', 'ASC NULLS LAST'))
cls._order.insert(1, ('language_code', 'ASC NULLS LAST'))
@classmethod
def __register__(cls, module_name):
pool = Pool()
Country = pool.get('country.country')
Language = pool.get('ir.lang')
country = Country.__table__()
language = Language.__table__()
table = cls.__table__()
cursor = Transaction().connection.cursor()
super().__register__(module_name)
table_h = cls.__table_handler__()
# Migration from 5.2: replace country by country_code
if table_h.column_exist('country'):
query = table.update(
[table.country_code],
country.select(
country.code,
where=country.id == table.country))
cursor.execute(*query)
table_h.drop_column('country')
# Migration from 5.2: replace language by language_code
if table_h.column_exist('language'):
query = table.update(
[table.language_code],
language.select(
Substring(language.code, 0, 2),
where=language.id == table.language))
cursor.execute(*query)
table_h.drop_column('language')
@classmethod
def default_format_(cls):
return """${attn}
${party_name}
${name}
${street}
${postal_code} ${city}
${subdivision}
${COUNTRY}"""
@classmethod
def create(cls, *args, **kwargs):
records = super(AddressFormat, cls).create(*args, **kwargs)
cls._get_format_cache.clear()
return records
@classmethod
def write(cls, *args, **kwargs):
super(AddressFormat, cls).write(*args, **kwargs)
cls._get_format_cache.clear()
@classmethod
def delete(cls, *args, **kwargs):
super(AddressFormat, cls).delete(*args, **kwargs)
cls._get_format_cache.clear()
@classmethod
def validate_fields(cls, formats, field_names):
super().validate_fields(formats, field_names)
cls.check_format(formats, field_names)
@classmethod
def check_format(cls, formats, field_names=None):
pool = Pool()
Address = pool.get('party.address')
if field_names and 'format_' not in field_names:
return
address = Address()
substitutions = address._get_address_substitutions()
for format_ in formats:
try:
Template(format_.format_).substitute(**substitutions)
except Exception as exception:
raise InvalidFormat(gettext('party.invalid_format',
format=format_.format_,
exception=exception)) from exception
@classmethod
def get_format(cls, address, pattern=None):
if pattern is None:
pattern = {}
else:
pattern = pattern.copy()
pattern.setdefault(
'country_code', address.country.code if address.country else None)
pattern.setdefault('language_code', Transaction().language[:2])
key = tuple(sorted(pattern.items()))
format_ = cls._get_format_cache.get(key)
if format_ is not None:
return format_
for record in cls.search([]):
if record.match(pattern):
format_ = record.format_
break
else:
format_ = cls.default_format_()
cls._get_format_cache.set(key, format_)
return format_
class SubdivisionType(DeactivableMixin, ModelSQL, ModelView):
"Address Subdivision Type"
__name__ = 'party.address.subdivision_type'
country_code = fields.Char("Country Code", size=2, required=True)
types = fields.MultiSelection('get_subdivision_types', "Subdivision Types")
_get_types_cache = Cache('party.address.subdivision_type.get_types')
@classmethod
def __setup__(cls):
super().__setup__()
t = cls.__table__()
cls._sql_constraints = [
('country_code_unique',
Exclude(t, (t.country_code, Equal),
where=t.active == Literal(True)),
'party.msg_address_subdivision_country_code_unique')
]
cls._order.insert(0, ('country_code', 'ASC NULLS LAST'))
@classmethod
def get_subdivision_types(cls):
pool = Pool()
Subdivision = pool.get('country.subdivision')
selection = Subdivision.fields_get(['type'])['type']['selection']
return [(k, v) for k, v in selection if k is not None]
@classmethod
def create(cls, *args, **kwargs):
records = super().create(*args, **kwargs)
cls._get_types_cache.clear()
return records
@classmethod
def write(cls, *args, **kwargs):
super().write(*args, **kwargs)
cls._get_types_cache.clear()
@classmethod
def delete(cls, *args, **kwargs):
super().delete(*args, **kwargs)
cls._get_types_cache.clear()
@classmethod
def get_types(cls, country):
key = country.code if country else None
types = cls._get_types_cache.get(key)
if types is not None:
return list(types)
records = cls.search([
('country_code', '=', country.code if country else None),
])
if records:
record, = records
types = record.types
else:
types = []
cls._get_types_cache.set(key, types)
return types

794
modules/party/address.xml Executable file
View File

@@ -0,0 +1,794 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="address_view_tree">
<field name="model">party.address</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">address_tree</field>
</record>
<record model="ir.ui.view" id="address_view_tree_sequence">
<field name="model">party.address</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="name">address_tree_sequence</field>
</record>
<record model="ir.ui.view" id="address_view_form">
<field name="model">party.address</field>
<field name="type">form</field>
<field name="name">address_form</field>
</record>
<record model="ir.ui.view" id="address_view_form_simple">
<field name="model">party.address</field>
<field name="type">form</field>
<field name="name">address_form_simple</field>
</record>
<record model="ir.action.act_window" id="act_address_form">
<field name="name">Addresses</field>
<field name="res_model">party.address</field>
</record>
<record model="ir.action.act_window.view" id="act_address_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="address_view_tree"/>
<field name="act_window" ref="act_address_form"/>
</record>
<record model="ir.action.act_window.view" id="act_address_form_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="address_view_form"/>
<field name="act_window" ref="act_address_form"/>
</record>
<menuitem
parent="menu_party"
action="act_address_form"
sequence="20"
id="menu_address_form"/>
<record model="ir.ui.view" id="address_format_view_list">
<field name="model">party.address.format</field>
<field name="type">tree</field>
<field name="name">address_format_list</field>
</record>
<record model="ir.ui.view" id="address_format_view_form">
<field name="model">party.address.format</field>
<field name="type">form</field>
<field name="name">address_format_form</field>
</record>
<record model="ir.action.act_window" id="act_address_format_form">
<field name="name">Address Formats</field>
<field name="res_model">party.address.format</field>
</record>
<record model="ir.action.act_window.view" id="act_address_format_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="address_format_view_list"/>
<field name="act_window" ref="act_address_format_form"/>
</record>
<record model="ir.action.act_window.view" id="act_address_format_form_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="address_format_view_form"/>
<field name="act_window" ref="act_address_format_form"/>
</record>
<menuitem
parent="menu_configuration"
action="act_address_format_form"
sequence="50"
id="menu_address_format_form"/>
<record model="ir.model.access" id="access_address_format">
<field name="model">party.address.format</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_address_format_admin">
<field name="model">party.address.format</field>
<field name="group" ref="group_party_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.ui.view" id="address_subdivision_type_view_list">
<field name="model">party.address.subdivision_type</field>
<field name="type">tree</field>
<field name="name">address_subdivision_type_list</field>
</record>
<record model="ir.ui.view" id="address_subdivision_type_view_form">
<field name="model">party.address.subdivision_type</field>
<field name="type">form</field>
<field name="name">address_subdivision_type_form</field>
</record>
<record model="ir.action.act_window" id="act_address_subdivision_type_form">
<field name="name">Address Subdivision Types</field>
<field name="res_model">party.address.subdivision_type</field>
</record>
<record model="ir.action.act_window.view" id="act_address_subdivision_type_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="address_subdivision_type_view_list"/>
<field name="act_window" ref="act_address_subdivision_type_form"/>
</record>
<record model="ir.action.act_window.view" id="act_address_subdivision_type_form_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="address_subdivision_type_view_form"/>
<field name="act_window" ref="act_address_subdivision_type_form"/>
</record>
<menuitem
parent="menu_configuration"
action="act_address_subdivision_type_form"
sequence="50"
id="menu_address_subdivision_type_form"/>
<record model="ir.model.access" id="access_address_subdivision_type">
<field name="model">party.address.subdivision_type</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_address_subdivision_type_admin">
<field name="model">party.address.subdivision_type</field>
<field name="group" ref="group_party_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
</data>
<data noupdate="1" grouped="1">
<!-- From https://en.wikipedia.org/wiki/Address_(geography) -->
<record model="party.address.format" id="address_format_ar">
<field name="country_code">AR</field>
<field name="format_">${party_name}
${name}
${street}
${subdivision}
${POSTAL_CODE}, ${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_au">
<field name="country_code">AU</field>
<field name="format_">${party_name}
${attn}
${name}
${street}
${CITY} ${SUBDIVISION} ${POSTAL_CODE}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_at">
<field name="country_code">AT</field>
<field name="format_">${party_name}
${attn}
${name}
${street}
${postal_code} ${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_bd">
<field name="country_code">BD</field>
<field name="format_">${party_name}
${attn}
${name}
${street}
${city}-${postal_code}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_by">
<field name="country_code">BY</field>
<field name="format_">${party_name}
${name}
${street}
${postal_code}, ${city}
${subdivision}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_be">
<field name="country_code">BE</field>
<field name="format_">${attn}
${party_name}
${name}
${street}
${postal_code} ${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_br">
<field name="country_code">BR</field>
<field name="format_">${party_name}
${street}
${name}
${city} - ${subdivision_code}
${postal_code}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_bg">
<field name="country_code">BG</field>
<field name="format_">${party_name}
${street}
${name}
${postal_code} ${city}
${subdivision}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_ca_fr">
<field name="country_code">CA</field>
<field name="language_code">fr</field>
<field name="format_">${attn}
${party_name}
${name}
${street}
${city} (${subdivision}) ${postal_code}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_ca">
<field name="country_code">CA</field>
<field name="format_">${ATTN}
${PARTY_NAME}
${NAME}
${STREET}
${CITY} ${SUBDIVISION_CODE} ${POSTAL_CODE}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_cl">
<field name="country_code">CL</field>
<field name="format_">${party_name}
${street}
${name}
${postal_code}
${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_cn_zh_CN">
<field name="country_code">CN</field>
<field name="language_code">zh</field>
<field name="format_">${COUNTRY} ${POSTAL_CODE}
${subdivision}${city}${street}${name}
${party_name}</field>
</record>
<record model="party.address.format" id="address_format_cn">
<field name="country_code">CN</field>
<field name="format_">${COUNTRY} ${POSTAL_CODE}
${subdivision}, ${city}, ${street}, ${name}
${party_name}</field>
</record>
<record model="party.address.format" id="address_format_hr">
<field name="country_code">HR</field>
<field name="format_">${party_name}
${street}
${COUNTRY_CODE}-${POSTAL_CODE} ${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_cz">
<field name="country_code">CZ</field>
<field name="format_">${party_name}
${attn}
${street}
${COUNTRY_CODE}-${POSTAL_CODE} ${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_dk">
<field name="country_code">DK</field>
<field name="format_">${party_name}
${street}
${postal_code} ${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_ee">
<field name="country_code">EE</field>
<field name="format_">${party_name}
${street}
${postal_code} ${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_fi">
<field name="country_code">FI</field>
<field name="format_">${attn}
${party_name}
${street}
${postal_code} ${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_fr">
<field name="country_code">FR</field>
<field name="format_">${party_name}
${attn}
${name}
${street}
${POSTAL_CODE} ${CITY}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_de">
<field name="country_code">DE</field>
<field name="format_">${party_name}
${attn}
${name}
${street}
${postal_code} ${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_gr">
<field name="country_code">GR</field>
<field name="format_">${party_name}
${street}
${COUNTRY_CODE}-${POSTAL_CODE} ${CITY}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_hk">
<field name="country_code">HK</field>
<field name="format_">${party_name}
${name}
${street}
${subdivision}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_hu">
<field name="country_code">HU</field>
<field name="format_">${party_name}
${city}
${street}
${postal_code}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_is">
<field name="country_code">IS</field>
<field name="format_">${party_name}
${street}
${name}
${postal_code} ${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_in">
<field name="country_code">IN</field>
<field name="format_">${party_name}
${name}
${street}
${CITY} ${postal_code}
${subdivision}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_id">
<field name="country_code">ID</field>
<field name="format_">${party_name}
${name}
${street}
${city} ${postal_code}
${subdivision}
${COUNTRY}</field>
</record>
<record model="party.address.subdivision_type" id="address_subdivision_type_id">
<field name="country_code">ID</field>
<field name="types" eval="['province', 'autonomous province', 'special district', 'special region']"/>
</record>
<record model="party.address.format" id="address_format_ir">
<field name="country_code">IR</field>
<field name="format_">${party_name}
${name}
${city}
${street}
${subdivision}
${postal_code}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_iq">
<field name="country_code">IQ</field>
<field name="format_">${party_name}
${street}
${name}
${subdivision}
${postal_code}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_ie">
<field name="country_code">IE</field>
<field name="format_">${party_name}
${street}
${city} ${postal_code}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_il">
<field name="country_code">IL</field>
<field name="format_">${party_name}
${street}
${city} ${postal_code}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_it">
<field name="country_code">IT</field>
<field name="format_">${party_name}
${attn}
${name}
${street}
${postal_code} ${city} ${SUBDIVISION_CODE}
${COUNTRY}</field>
</record>
<record model="party.address.subdivision_type" id="address_subdivision_it">
<field name="country_code">IT</field>
<field name="types" eval="['province']"/>
</record>
<record model="party.address.format" id="address_format_jp_jp">
<field name="country_code">JP</field>
<field name="language_code">jp</field>
<field name="format_">${COUNTRY}
${postal_code}
${subdivision}${city}${street}
${party_name}</field>
</record>
<record model="party.address.format" id="address_format_jp">
<field name="country_code">JP</field>
<field name="format_">${party_name}
${street}
${city}, ${SUBDIVISION} ${postal_code}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_kr_ko">
<field name="country_code">KR</field>
<field name="language_code">ko</field>
<field name="format_">${COUNTRY}
${street}
${party_name}
${postal_code}</field>
</record>
<record model="party.address.format" id="address_format_kr">
<field name="country_code">KR</field>
<field name="format_">${party_name}
${street}
${city}, ${subdivision} ${postal_code}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_lv">
<field name="country_code">LV</field>
<field name="format_">${party_name}
${street}
${city}
${subdivision}
${COUNTRY_CODE}-${POSTAL_CODE}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_mo_zh_CN">
<field name="country_code">MO</field>
<field name="language_code">zh</field>
<field name="format_">${COUNTRY}
${city}
${street}
${party_name}</field>
</record>
<record model="party.address.format" id="address_format_mo">
<field name="country_code">MO</field>
<field name="format_">${party_name}
${street}
${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_my">
<field name="country_code">MY</field>
<field name="format_">${attn}
${party_name}
${name}
${street}
${postal_code} ${CITY}
${SUBDIVISION}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_mx">
<field name="country_code">MX</field>
<field name="format_">${attn}
${party_name}
${street}
${name}
${postal_code}, ${city}, ${subdivision}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_nl">
<field name="country_code">NL</field>
<field name="format_">${party_name}
${attn}
${name}
${street}
${postal_code} ${CITY}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_nz">
<field name="country_code">NZ</field>
<field name="format_">${party_name}
${street}
${city} ${postal_code}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_no">
<field name="country_code">NO</field>
<field name="format_">${party_name}
${street}
${postal_code} ${CITY}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_om">
<field name="country_code">OM</field>
<field name="format_">${party_name}
${street}
${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_pk">
<field name="country_code">PK</field>
<field name="format_">${party_name}
${street}
${city}
${postal_code}
${subdivision}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_pe">
<field name="country_code">PE</field>
<field name="format_">${party_name}
${street}
${name}
${city}
${subdivision}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_ph">
<field name="country_code">PH</field>
<field name="format_">${party_name}
${street}
${postal_code} ${CITY}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_pl">
<field name="country_code">PL</field>
<field name="format_">${attn} ${party_name}
${street}
${postal_code} ${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_pt">
<field name="country_code">PT</field>
<field name="format_">${party_name}
${street}
${postal_code} ${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_qa">
<field name="country_code">QA</field>
<field name="format_">${party_name}
${street}
${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_ro">
<field name="country_code">RO</field>
<field name="format_">${attn} ${party_name}
${street}
${city}
${postal_code}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_ru">
<field name="country_code">RU</field>
<field name="format_">${party_name}
${street}
${city}
${subdivision}
${COUNTRY}
${postal_code}</field>
</record>
<record model="party.address.format" id="address_format_sa">
<field name="country_code">SA</field>
<field name="format_">${party_name}
${street}
${city} ${postal_code}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_rs">
<field name="country_code">RS</field>
<field name="format_">${party_name}
${street}
${postal_code} ${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_sg">
<field name="country_code">SG</field>
<field name="format_">${party_name}
${street}
${name}
${CITY} ${POSTAL_CODE}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_sk">
<field name="country_code">SK</field>
<field name="format_">${attn}
${party_name}
${street}
${postal_code} ${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_sl">
<field name="country_code">SL</field>
<field name="format_">${party_name}
${attn}
${street}
${postal_code} ${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_es">
<field name="country_code">ES</field>
<field name="format_">${party_name}
${street}
${postal_code} ${city}
${subdivision}
${COUNTRY}</field>
</record>
<record model="party.address.subdivision_type" id="address_subdivision_es">
<field name="country_code">ES</field>
<field name="types" eval="['province', 'autonomous city']"/>
</record>
<record model="party.address.format" id="address_format_lk">
<field name="country_code">LK</field>
<field name="format_">${party_name}
${street}
${CITY}
${postal_code}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_se">
<field name="country_code">SE</field>
<field name="format_">${party_name}
${street}
${postal_code} ${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_ch">
<field name="country_code">CH</field>
<field name="format_">${party_name}
${street}
${postal_code} ${city}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_tw_zh_CN">
<field name="country_code">TW</field>
<field name="language_code">zh</field>
<field name="format_">${COUNTRY}
${postal_code}
${street}
${party_name}</field>
</record>
<record model="party.address.format" id="address_format_tw">
<field name="country_code">TW</field>
<field name="format_">${party_name}
${street}
${city}, ${subdivision} ${postal_code}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_th">
<field name="country_code">TH</field>
<field name="format_">${party_name}
${street}
${name}
${subdivision}
${COUNTRY}
${postal_code}</field>
</record>
<record model="party.address.format" id="address_format_tr">
<field name="country_code">TR</field>
<field name="format_">${party_name}
${attn}
${street}
${name}
${postal_code} ${city} ${subdivision}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_ua">
<field name="country_code">UA</field>
<field name="format_">${party_name}
${street}
${city}
${subdivision}
${postal_code}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_gb">
<field name="country_code">GB</field>
<field name="format_">${party_name}
${street}
${CITY}
${postal_code}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_us">
<field name="country_code">US</field>
<field name="format_">${attn}
${party_name}
${street}
${city}, ${subdivision_code} ${postal_code}
${COUNTRY}</field>
</record>
<record model="party.address.format" id="address_format_vn">
<field name="country_code">VN</field>
<field name="format_">${party_name}
${street}
${city}
${subdivision}
${COUNTRY}</field>
</record>
</data>
</tryton>

32
modules/party/category.py Executable file
View File

@@ -0,0 +1,32 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from sql.conditionals import Coalesce
from sql.operators import Equal
from trytond.model import (
DeactivableMixin, Exclude, ModelSQL, ModelView, fields, tree)
class Category(DeactivableMixin, tree(separator=' / '), ModelSQL, ModelView):
"Category"
__name__ = 'party.category'
name = fields.Char(
"Name", required=True, translate=True,
help="The main identifier of the category.")
parent = fields.Many2One(
'party.category', "Parent",
help="Add the category below the parent.")
childs = fields.One2Many(
'party.category', 'parent', "Children",
help="Add children below the category.")
@classmethod
def __setup__(cls):
super(Category, cls).__setup__()
t = cls.__table__()
cls._sql_constraints = [
('name_parent_exclude',
Exclude(t, (t.name, Equal), (Coalesce(t.parent, -1), Equal)),
'party.msg_category_name_unique'),
]
cls._order.insert(0, ('name', 'ASC'))

81
modules/party/category.xml Executable file
View File

@@ -0,0 +1,81 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="category_view_form">
<field name="model">party.category</field>
<field name="type">form</field>
<field name="name">category_form</field>
</record>
<record model="ir.ui.view" id="category_view_tree">
<field name="model">party.category</field>
<field name="type">tree</field>
<field name="field_childs">childs</field>
<field name="name">category_tree</field>
</record>
<record model="ir.ui.view" id="category_view_list">
<field name="model">party.category</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">category_list</field>
</record>
<record model="ir.action.act_window" id="act_category_tree">
<field name="name">Categories</field>
<field name="res_model">party.category</field>
<field name="domain" eval="[('parent', '=', None)]" pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_category_tree_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="category_view_tree"/>
<field name="act_window" ref="act_category_tree"/>
</record>
<record model="ir.action.act_window.view" id="act_category_tree_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="category_view_form"/>
<field name="act_window" ref="act_category_tree"/>
</record>
<menuitem
parent="menu_party"
action="act_category_tree"
sequence="50"
id="menu_category_tree"/>
<record model="ir.action.act_window" id="act_category_list">
<field name="name">Categories</field>
<field name="res_model">party.category</field>
</record>
<record model="ir.action.act_window.view" id="act_category_list_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="category_view_list"/>
<field name="act_window" ref="act_category_list"/>
</record>
<record model="ir.action.act_window.view" id="act_category_list_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="category_view_form"/>
<field name="act_window" ref="act_category_list"/>
</record>
<menuitem
parent="menu_category_tree"
sequence="10"
action="act_category_list"
id="menu_category_list"/>
<record model="ir.model.access" id="access_party_category">
<field name="model">party.category</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_party_category_admin">
<field name="model">party.category</field>
<field name="group" ref="group_party_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
</data>
</tryton>

115
modules/party/configuration.py Executable file
View File

@@ -0,0 +1,115 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.i18n import gettext
from trytond.model import (
ModelSingleton, ModelSQL, ModelView, MultiValueMixin, ValueMixin, fields)
from trytond.model.exceptions import AccessError
from trytond.pool import Pool
from trytond.pyson import Id
from .party import IDENTIFIER_TYPES, replace_vat
party_sequence = fields.Many2One('ir.sequence', 'Party Sequence',
domain=[
('sequence_type', '=', Id('party', 'sequence_type_party')),
],
help="Used to generate the party code.")
party_lang = fields.Many2One("ir.lang", 'Party Language',
help="The default language for new parties.")
class Configuration(ModelSingleton, ModelSQL, ModelView, MultiValueMixin):
'Party Configuration'
__name__ = 'party.configuration'
party_sequence = fields.MultiValue(party_sequence)
party_lang = fields.MultiValue(party_lang)
identifier_types = fields.MultiSelection(
IDENTIFIER_TYPES, "Identifier Types",
help="Defines which identifier types are available.\n"
"Leave empty for all of them.")
@classmethod
def __register__(cls, module):
super().__register__(module)
# Migration from 6.8: Use vat alias
configuration = cls(1)
if configuration.identifier_types:
configuration.identifier_types = list(map(
replace_vat, configuration.identifier_types))
@classmethod
def default_party_sequence(cls, **pattern):
pool = Pool()
ModelData = pool.get('ir.model.data')
try:
return ModelData.get_id('party', 'sequence_party')
except KeyError:
return None
def get_identifier_types(self):
selection = self.fields_get(
['identifier_types'])['identifier_types']['selection']
if self.identifier_types:
selection = [
(k, v) for k, v in selection if k in self.identifier_types]
return selection
@classmethod
def create(cls, vlist):
records = super().create(vlist)
ModelView._fields_view_get_cache.clear()
return records
@classmethod
def write(cls, *args):
super().write(*args)
ModelView._fields_view_get_cache.clear()
@classmethod
def delete(cls, records):
super().delete(records)
ModelView._fields_view_get_cache.clear()
@classmethod
def validate_fields(cls, records, field_names):
super().validate_fields(records, field_names)
cls(1).check_identifier_types(field_names)
def check_identifier_types(self, field_names=None):
pool = Pool()
Identifier = pool.get('party.identifier')
if field_names and 'identifier_types' not in field_names:
return
if self.identifier_types:
identifier_types = [None, ''] + list(self.identifier_types)
identifiers = Identifier.search([
('type', 'not in', identifier_types),
], limit=1, order=[])
if identifiers:
identifier, = identifiers
selection = self.fields_get(
['identifier_types'])['identifier_types']['selection']
selection = dict(selection)
raise AccessError(gettext(
'party.msg_identifier_type_remove',
type=selection.get(identifier.type, identifier.type),
identifier=identifier.rec_name,
))
class ConfigurationSequence(ModelSQL, ValueMixin):
'Party Configuration Sequence'
__name__ = 'party.configuration.party_sequence'
party_sequence = party_sequence
@classmethod
def check_xml_record(cls, records, values):
pass
class ConfigurationLang(ModelSQL, ValueMixin):
'Party Configuration Lang'
__name__ = 'party.configuration.party_lang'
party_lang = party_lang

50
modules/party/configuration.xml Executable file
View File

@@ -0,0 +1,50 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="party_configuration_view_form">
<field name="model">party.configuration</field>
<field name="type">form</field>
<field name="name">configuration_form</field>
</record>
<record model="ir.action.act_window" id="act_party_configuration_form">
<field name="name">Configuration</field>
<field name="res_model">party.configuration</field>
</record>
<record model="ir.action.act_window.view"
id="act_party_configuration_view1">
<field name="sequence" eval="1"/>
<field name="view" ref="party_configuration_view_form"/>
<field name="act_window" ref="act_party_configuration_form"/>
</record>
<menuitem
parent="menu_configuration"
action="act_party_configuration_form"
sequence="10"
id="menu_party_configuration"
icon="tryton-list"/>
<record model="ir.model.access" id="access_party_configuration">
<field name="model">party.configuration</field>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.model.access" id="access_party_configuration_party_admin">
<field name="model">party.configuration</field>
<field name="group" ref="group_party_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
</data>
<data noupdate="1">
<record model="party.configuration.party_sequence"
id="configuration_party_sequence">
<field name="party_sequence" ref="sequence_party"/>
</record>
</data>
</tryton>

View File

@@ -0,0 +1,360 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from itertools import chain
try:
import phonenumbers
from phonenumbers import NumberParseException, PhoneNumberFormat
except ImportError:
phonenumbers = None
from trytond.i18n import gettext
from trytond.model import (
DeactivableMixin, Index, ModelSQL, ModelView, MultiValueMixin, ValueMixin,
fields, sequence_ordered)
from trytond.model.exceptions import AccessError
from trytond.pyson import Eval
from trytond.tools.email_ import (
EmailNotValidError, normalize_email, validate_email)
from trytond.transaction import Transaction
from .exceptions import InvalidEMail, InvalidPhoneNumber
_TYPES = [
('phone', 'Phone'),
('mobile', 'Mobile'),
('fax', 'Fax'),
('email', 'E-Mail'),
('website', 'Website'),
('skype', 'Skype'),
('sip', 'SIP'),
('irc', 'IRC'),
('jabber', 'Jabber'),
('other', 'Other'),
]
_PHONE_TYPES = {
'phone',
'mobile',
'fax',
}
class _ContactMechanismMixin:
__slots__ = ()
def contact_mechanism_get(self, types=None, usage=None):
"""
Try to find a contact mechanism for the given types and usage, if no
usage matches the first mechanism of the given types is returned.
"""
default_mechanism = None
if types:
if isinstance(types, str):
types = {types}
mechanisms = [m for m in self.contact_mechanisms
if m.type in types]
else:
mechanisms = self.contact_mechanisms
if mechanisms:
default_mechanism = mechanisms[0]
if usage:
for mechanism in mechanisms:
if getattr(mechanism, usage):
return mechanism
return default_mechanism
class ContactMechanism(
DeactivableMixin, sequence_ordered(), ModelSQL, ModelView,
MultiValueMixin):
"Contact Mechanism"
__name__ = 'party.contact_mechanism'
_rec_name = 'value'
type = fields.Selection(_TYPES, "Type", required=True, sort=False)
value = fields.Char(
"Value",
# Add all function fields to ensure to always fill them via on_change
depends={
'email', 'website', 'skype', 'sip', 'other_value',
'value_compact'})
value_compact = fields.Char('Value Compact', readonly=True)
name = fields.Char("Name")
comment = fields.Text("Comment")
party = fields.Many2One(
'party.party', "Party", required=True, ondelete='CASCADE')
address = fields.Many2One(
'party.address', "Address", ondelete='CASCADE',
domain=[
('party', '=', Eval('party', -1)),
])
language = fields.MultiValue(
fields.Many2One('ir.lang', "Language",
help="Used to translate communication made "
"using the contact mechanism.\n"
"Leave empty for the party language."))
languages = fields.One2Many(
'party.contact_mechanism.language', 'contact_mechanism', "Languages")
email = fields.Function(fields.Char('E-Mail', states={
'invisible': Eval('type') != 'email',
'required': Eval('type') == 'email',
}, depends={'value'}),
'get_value', setter='set_value')
website = fields.Function(fields.Char('Website', states={
'invisible': Eval('type') != 'website',
'required': Eval('type') == 'website',
}, depends={'value'}),
'get_value', setter='set_value')
skype = fields.Function(fields.Char('Skype', states={
'invisible': Eval('type') != 'skype',
'required': Eval('type') == 'skype',
}, depends={'value'}),
'get_value', setter='set_value')
sip = fields.Function(fields.Char('SIP', states={
'invisible': Eval('type') != 'sip',
'required': Eval('type') == 'sip',
}, depends={'value'}),
'get_value', setter='set_value')
other_value = fields.Function(fields.Char('Value', states={
'invisible': Eval('type').in_(['email', 'website', 'skype', 'sip']),
'required': ~Eval('type').in_(['email', 'website']),
}, depends={'value'}),
'get_value', setter='set_value')
url = fields.Function(fields.Char('URL', states={
'invisible': ~Eval('url'),
}),
'on_change_with_url')
@classmethod
def __setup__(cls):
cls.value.search_unaccented = False
cls.value_compact.search_unaccented = False
super(ContactMechanism, cls).__setup__()
t = cls.__table__()
cls._sql_indexes.add(
Index(t, (Index.Unaccent(t.value_compact), Index.Similarity())))
cls._order.insert(0, ('party.distance', 'ASC NULLS LAST'))
cls._order.insert(1, ('party', 'ASC'))
@staticmethod
def default_type():
return 'phone'
@classmethod
def default_party(cls):
return Transaction().context.get('related_party')
@fields.depends('address', '_parent_address.party')
def on_change_address(self):
if self.address:
self.party = self.address.party
@classmethod
def get_value(cls, mechanisms, names):
return dict((name, dict((m.id, m.value) for m in mechanisms))
for name in names)
@fields.depends('type', 'value')
def on_change_with_url(self, name=None, value=None):
if value is None:
value = self.value
if self.type == 'email':
return 'mailto:%s' % value
elif self.type == 'website':
return value
elif self.type == 'skype':
return 'callto:%s' % value
elif self.type == 'sip':
return 'sip:%s' % value
elif self.type in {'phone', 'mobile'}:
return 'tel:%s' % value
elif self.type == 'fax':
return 'fax:%s' % value
return None
@fields.depends('party', '_parent_party.addresses')
def _phone_country_codes(self):
if self.party:
for address in self.party.addresses:
if address.country:
yield address.country.code
@fields.depends(methods=['_phone_country_codes'])
def _parse_phonenumber(self, value):
for country_code in chain(self._phone_country_codes(), [None]):
try:
# Country code is ignored if value has an international prefix
phonenumber = phonenumbers.parse(value, country_code)
except NumberParseException:
pass
else:
if phonenumbers.is_valid_number(phonenumber):
return phonenumber
return None
@fields.depends(methods=['_parse_phonenumber'])
def format_value(self, value=None, type_=None):
if phonenumbers and type_ in _PHONE_TYPES:
phonenumber = self._parse_phonenumber(value)
if phonenumber:
value = phonenumbers.format_number(
phonenumber, PhoneNumberFormat.INTERNATIONAL)
if type_ == 'email' and value:
value = normalize_email(value)
return value
@fields.depends(methods=['_parse_phonenumber'])
def format_value_compact(self, value=None, type_=None):
if phonenumbers and type_ in _PHONE_TYPES:
phonenumber = self._parse_phonenumber(value)
if phonenumber:
value = phonenumbers.format_number(
phonenumber, PhoneNumberFormat.E164)
return value
@classmethod
def set_value(cls, mechanisms, name, value):
# Setting value is done by on_changes
pass
@fields.depends(
methods=['on_change_with_url', 'format_value', 'format_value_compact'])
def _change_value(self, value, type_):
self.value = self.format_value(value=value, type_=type_)
self.value_compact = self.format_value_compact(
value=value, type_=type_)
self.website = value
self.email = value
self.skype = value
self.sip = value
self.other_value = value
self.url = self.on_change_with_url(value=value)
@fields.depends('value', 'type', methods=['_change_value'])
def on_change_value(self):
return self._change_value(self.value, self.type)
@fields.depends('website', 'type', methods=['_change_value'])
def on_change_website(self):
return self._change_value(self.website, self.type)
@fields.depends('email', 'type', methods=['_change_value'])
def on_change_email(self):
return self._change_value(self.email, self.type)
@fields.depends('skype', 'type', methods=['_change_value'])
def on_change_skype(self):
return self._change_value(self.skype, self.type)
@fields.depends('sip', 'type', methods=['_change_value'])
def on_change_sip(self):
return self._change_value(self.sip, self.type)
@fields.depends('other_value', 'type', methods=['_change_value'])
def on_change_other_value(self):
return self._change_value(self.other_value, self.type)
def get_rec_name(self, name):
name = self.name or self.party.rec_name
return '%s <%s>' % (name, self.value_compact or self.value)
@classmethod
def search_rec_name(cls, name, clause):
if clause[1].startswith('!') or clause[1].startswith('not '):
bool_op = 'AND'
else:
bool_op = 'OR'
return [bool_op,
('value',) + tuple(clause[1:]),
('value_compact',) + tuple(clause[1:]),
]
@classmethod
def _format_values(cls, mechanisms):
for mechanism in mechanisms:
value = mechanism.format_value(
value=mechanism.value, type_=mechanism.type)
if value != mechanism.value:
mechanism.value = value
value_compact = mechanism.format_value_compact(
value=mechanism.value, type_=mechanism.type)
if value_compact != mechanism.value_compact:
mechanism.value_compact = value_compact
cls.save(mechanisms)
@classmethod
def create(cls, vlist):
mechanisms = super(ContactMechanism, cls).create(vlist)
cls._format_values(mechanisms)
return mechanisms
@classmethod
def write(cls, *args):
actions = iter(args)
all_mechanisms = []
for mechanisms, values in zip(actions, actions):
all_mechanisms.extend(mechanisms)
if 'party' in values:
for mechanism in mechanisms:
if mechanism.party.id != values['party']:
raise AccessError(
gettext('party'
'.msg_contact_mechanism_change_party') % {
'contact': mechanism.rec_name,
})
super(ContactMechanism, cls).write(*args)
cls._format_values(all_mechanisms)
@classmethod
def validate_fields(cls, mechanisms, field_names):
super().validate_fields(mechanisms, field_names)
cls.check_valid_phonenumber(mechanisms, field_names)
cls.check_valid_email(mechanisms, field_names)
@classmethod
def check_valid_phonenumber(cls, mechanisms, field_names=None):
if field_names and not (field_names & {'type', 'value'}):
return
if not phonenumbers:
return
for mechanism in mechanisms:
if mechanism.type not in _PHONE_TYPES:
continue
if not mechanism._parse_phonenumber(mechanism.value):
raise InvalidPhoneNumber(
gettext('party.msg_invalid_phone_number',
phone=mechanism.value, party=mechanism.party.rec_name))
@classmethod
def check_valid_email(cls, mechanisms, field_names=None):
if field_names and not (field_names & {'type', 'value'}):
return
for mechanism in mechanisms:
if mechanism.type == 'email' and mechanism.value:
try:
validate_email(mechanism.value)
except EmailNotValidError as e:
raise InvalidEMail(gettext(
'party.msg_email_invalid',
email=mechanism.value,
party=mechanism.party.rec_name),
str(e)) from e
@classmethod
def usages(cls, _fields=None):
"Returns the selection list of usage"
usages = [(None, "")]
if _fields:
for name, desc in cls.fields_get(_fields).items():
usages.append((name, desc['string']))
return usages
class ContactMechanismLanguage(ModelSQL, ValueMixin):
"Contact Mechanism Language"
__name__ = 'party.contact_mechanism.language'
contact_mechanism = fields.Many2One(
'party.contact_mechanism', "Contact Mechanism",
ondelete='CASCADE')
language = fields.Many2One('ir.lang', "Language")

View File

@@ -0,0 +1,49 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="contact_mechanism_view_tree">
<field name="model">party.contact_mechanism</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">contact_mechanism_tree</field>
</record>
<record model="ir.ui.view" id="contact_mechanism_view_tree_sequence">
<field name="model">party.contact_mechanism</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="name">contact_mechanism_tree_sequence</field>
</record>
<record model="ir.ui.view" id="contact_mechanism_view_form">
<field name="model">party.contact_mechanism</field>
<field name="type">form</field>
<field name="name">contact_mechanism_form</field>
</record>
<record model="ir.action.act_window" id="act_contact_mechanism_form">
<field name="name">Contact Mechanisms</field>
<field name="res_model">party.contact_mechanism</field>
</record>
<record model="ir.action.act_window.view"
id="act_contact_mechanism_form_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="contact_mechanism_view_tree"/>
<field name="act_window" ref="act_contact_mechanism_form"/>
</record>
<record model="ir.action.act_window.view"
id="act_contact_mechanism_form_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="contact_mechanism_view_form"/>
<field name="act_window" ref="act_contact_mechanism_form"/>
</record>
<menuitem
parent="menu_party"
sequence="20"
action="act_contact_mechanism_form"
id="menu_contact_mechanism_form"/>
</data>
</tryton>

21
modules/party/country.py Executable file
View File

@@ -0,0 +1,21 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.model import Index
from trytond.pool import PoolMeta
class PostalCode(metaclass=PoolMeta):
__name__ = 'country.postal_code'
@classmethod
def __setup__(cls):
super().__setup__()
t = cls.__table__()
cls._sql_indexes.update({
Index(
t,
(Index.Unaccent(t.city), Index.Similarity()),
(t.country, Index.Equality()),
(t.subdivision, Index.Equality())),
})

32
modules/party/exceptions.py Executable file
View File

@@ -0,0 +1,32 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.exceptions import UserError, UserWarning
from trytond.model.exceptions import ValidationError
class InvalidIdentifierCode(ValidationError):
pass
class InvalidPhoneNumber(ValidationError):
pass
class InvalidEMail(ValidationError):
pass
class VIESUnavailable(UserError):
pass
class SimilarityWarning(UserWarning):
pass
class EraseError(ValidationError):
pass
class InvalidFormat(ValidationError):
pass

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 259 B

103
modules/party/ir.py Executable file
View File

@@ -0,0 +1,103 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.model import fields
from trytond.pool import Pool, PoolMeta
from trytond.tools import escape_wildcard
from trytond.transaction import Transaction
class Email(metaclass=PoolMeta):
__name__ = 'ir.email'
@classmethod
def _match(cls, name, email):
pool = Pool()
ContactMechanism = pool.get('party.contact_mechanism')
yield from super()._match(name, email)
domain = ['OR']
for field in ['name', 'party.name', 'value']:
for value in [name, email]:
if value and len(value) >= 3:
domain.append(
(field, 'ilike', '%' + escape_wildcard(value) + '%'))
for contact in ContactMechanism.search([
('type', '=', 'email'),
('value', '!=', ''),
domain,
], order=[]):
yield contact.name or contact.party.name, contact.value
class EmailTemplate(metaclass=PoolMeta):
__name__ = 'ir.email.template'
contact_mechanism = fields.Selection(
'get_contact_mechanisms', "Contact Mechanism",
help="Define which email address to use "
"from the party's contact mechanisms.")
@classmethod
def get_contact_mechanisms(cls):
pool = Pool()
ContactMechanism = pool.get('party.contact_mechanism')
return ContactMechanism.usages()
def get(self, record):
with Transaction().set_context(usage=self.contact_mechanism):
return super().get(record)
@classmethod
def email_models(cls):
return super().email_models() + [
'party.party', 'party.contact_mechanism']
@classmethod
def _get_default_exclude(cls, record):
pool = Pool()
Party = pool.get('party.party')
ContactMechanism = pool.get('party.contact_mechanism')
exclude = super()._get_default_exclude(record)
if isinstance(record, Party):
exclude.append('contact_mechanisms')
if isinstance(record, ContactMechanism):
exclude.append('party')
return exclude
@classmethod
def _get_address(cls, record):
pool = Pool()
Party = pool.get('party.party')
ContactMechanism = pool.get('party.contact_mechanism')
address = super()._get_address(record)
usage = Transaction().context.get('usage')
if isinstance(record, ContactMechanism):
if record.type == 'email':
if not usage or getattr(record, usage):
address = (record.name or record.party.name, record.email)
else:
record = record.party
if isinstance(record, Party):
contact = record.contact_mechanism_get('email', usage=usage)
if contact and contact.email:
address = (contact.name or record.name, contact.email)
return address
@classmethod
def _get_language(cls, record):
pool = Pool()
Party = pool.get('party.party')
ContactMechanism = pool.get('party.contact_mechanism')
language = super()._get_language(record)
if isinstance(record, Party):
usage = Transaction().context.get('usage')
contact = record.contact_mechanism_get('email', usage=usage)
if contact and contact.language:
language = contact.language
elif record.lang:
language = record.lang
if isinstance(record, ContactMechanism):
if record.language:
language = record.language
elif record.party.lang:
language = record.party.lang
return language

12
modules/party/ir.xml Executable file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="email_template_view_form">
<field name="model">ir.email.template</field>
<field name="inherit" ref="ir.email_template_view_form"/>
<field name="name">email_template_form</field>
</record>
</data>
</tryton>

315
modules/party/label.fodt Executable file
View File

@@ -0,0 +1,315 @@
<?xml version="1.0" encoding="UTF-8"?>
<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rpt="http://openoffice.org/2005/report" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:css3t="http://www.w3.org/TR/css3-text/" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.text">
<office:meta><meta:generator>LibreOffice/5.2.7.2$Linux_X86_64 LibreOffice_project/20m0$Build-2</meta:generator><meta:creation-date>2008-06-07T15:28:45</meta:creation-date><dc:date>2008-10-22T19:21:28</dc:date><meta:editing-cycles>1</meta:editing-cycles><meta:editing-duration>PT0S</meta:editing-duration><meta:document-statistic meta:character-count="278" meta:image-count="0" meta:non-whitespace-character-count="265" meta:object-count="0" meta:page-count="1" meta:paragraph-count="12" meta:table-count="0" meta:word-count="25"/><meta:user-defined meta:name="Info 1"/><meta:user-defined meta:name="Info 2"/><meta:user-defined meta:name="Info 3"/><meta:user-defined meta:name="Info 4"/></office:meta>
<office:settings>
<config:config-item-set config:name="ooo:view-settings">
<config:config-item config:name="ViewAreaTop" config:type="long">0</config:config-item>
<config:config-item config:name="ViewAreaLeft" config:type="long">0</config:config-item>
<config:config-item config:name="ViewAreaWidth" config:type="long">43760</config:config-item>
<config:config-item config:name="ViewAreaHeight" config:type="long">21691</config:config-item>
<config:config-item config:name="ShowRedlineChanges" config:type="boolean">true</config:config-item>
<config:config-item config:name="InBrowseMode" config:type="boolean">false</config:config-item>
<config:config-item-map-indexed config:name="Views">
<config:config-item-map-entry>
<config:config-item config:name="ViewId" config:type="string">view2</config:config-item>
<config:config-item config:name="ViewLeft" config:type="long">2630</config:config-item>
<config:config-item config:name="ViewTop" config:type="long">2501</config:config-item>
<config:config-item config:name="VisibleLeft" config:type="long">0</config:config-item>
<config:config-item config:name="VisibleTop" config:type="long">0</config:config-item>
<config:config-item config:name="VisibleRight" config:type="long">43759</config:config-item>
<config:config-item config:name="VisibleBottom" config:type="long">21689</config:config-item>
<config:config-item config:name="ZoomType" config:type="short">0</config:config-item>
<config:config-item config:name="ViewLayoutColumns" config:type="short">0</config:config-item>
<config:config-item config:name="ViewLayoutBookMode" config:type="boolean">false</config:config-item>
<config:config-item config:name="ZoomFactor" config:type="short">100</config:config-item>
<config:config-item config:name="IsSelectedFrame" config:type="boolean">false</config:config-item>
</config:config-item-map-entry>
</config:config-item-map-indexed>
</config:config-item-set>
<config:config-item-set config:name="ooo:configuration-settings">
<config:config-item config:name="PrintProspect" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintLeftPages" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintPageBackground" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintControls" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintAnnotationMode" config:type="short">0</config:config-item>
<config:config-item config:name="PrintGraphics" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintRightPages" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintFaxName" config:type="string"/>
<config:config-item config:name="PrintPaperFromSetup" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintTextPlaceholder" config:type="boolean">false</config:config-item>
<config:config-item config:name="ApplyParagraphMarkFormatToNumbering" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintReversed" config:type="boolean">false</config:config-item>
<config:config-item config:name="TabOverMargin" config:type="boolean">false</config:config-item>
<config:config-item config:name="EmbedFonts" config:type="boolean">false</config:config-item>
<config:config-item config:name="SurroundTextWrapSmall" config:type="boolean">false</config:config-item>
<config:config-item config:name="BackgroundParaOverDrawings" config:type="boolean">false</config:config-item>
<config:config-item config:name="ClippedPictures" config:type="boolean">false</config:config-item>
<config:config-item config:name="FloattableNomargins" config:type="boolean">false</config:config-item>
<config:config-item config:name="UnbreakableNumberings" config:type="boolean">false</config:config-item>
<config:config-item config:name="EmbedSystemFonts" config:type="boolean">false</config:config-item>
<config:config-item config:name="TabOverflow" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintTables" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintSingleJobs" config:type="boolean">false</config:config-item>
<config:config-item config:name="SmallCapsPercentage66" config:type="boolean">true</config:config-item>
<config:config-item config:name="CollapseEmptyCellPara" config:type="boolean">true</config:config-item>
<config:config-item config:name="TreatSingleColumnBreakAsPageBreak" config:type="boolean">false</config:config-item>
<config:config-item config:name="MathBaselineAlignment" config:type="boolean">false</config:config-item>
<config:config-item config:name="AddFrameOffsets" config:type="boolean">false</config:config-item>
<config:config-item config:name="IsLabelDocument" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrinterName" config:type="string"/>
<config:config-item config:name="OutlineLevelYieldsNumbering" config:type="boolean">false</config:config-item>
<config:config-item config:name="IgnoreFirstLineIndentInNumbering" config:type="boolean">false</config:config-item>
<config:config-item config:name="UpdateFromTemplate" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrintBlackFonts" config:type="boolean">false</config:config-item>
<config:config-item config:name="TableRowKeep" config:type="boolean">false</config:config-item>
<config:config-item config:name="EmbeddedDatabaseName" config:type="string"/>
<config:config-item config:name="IgnoreTabsAndBlanksForLineCalculation" config:type="boolean">false</config:config-item>
<config:config-item config:name="UseOldPrinterMetrics" config:type="boolean">false</config:config-item>
<config:config-item config:name="InvertBorderSpacing" config:type="boolean">false</config:config-item>
<config:config-item config:name="SaveGlobalDocumentLinks" config:type="boolean">false</config:config-item>
<config:config-item config:name="TabsRelativeToIndent" config:type="boolean">true</config:config-item>
<config:config-item config:name="Rsid" config:type="int">210213</config:config-item>
<config:config-item config:name="PrintProspectRTL" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintEmptyPages" config:type="boolean">true</config:config-item>
<config:config-item config:name="ApplyUserData" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintHiddenText" config:type="boolean">false</config:config-item>
<config:config-item config:name="AddParaTableSpacingAtStart" config:type="boolean">true</config:config-item>
<config:config-item config:name="FieldAutoUpdate" config:type="boolean">true</config:config-item>
<config:config-item config:name="UseOldNumbering" config:type="boolean">false</config:config-item>
<config:config-item config:name="AddParaTableSpacing" config:type="boolean">true</config:config-item>
<config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item>
<config:config-item config:name="SaveVersionOnClose" config:type="boolean">false</config:config-item>
<config:config-item config:name="ChartAutoUpdate" config:type="boolean">true</config:config-item>
<config:config-item config:name="PrinterIndependentLayout" config:type="string">high-resolution</config:config-item>
<config:config-item config:name="IsKernAsianPunctuation" config:type="boolean">false</config:config-item>
<config:config-item config:name="UseFormerObjectPositioning" config:type="boolean">false</config:config-item>
<config:config-item config:name="AddVerticalFrameOffsets" config:type="boolean">false</config:config-item>
<config:config-item config:name="SubtractFlysAnchoredAtFlys" config:type="boolean">true</config:config-item>
<config:config-item config:name="AddParaSpacingToTableCells" config:type="boolean">true</config:config-item>
<config:config-item config:name="AddExternalLeading" config:type="boolean">true</config:config-item>
<config:config-item config:name="CurrentDatabaseDataSource" config:type="string"/>
<config:config-item config:name="AllowPrintJobCancel" config:type="boolean">true</config:config-item>
<config:config-item config:name="ProtectForm" config:type="boolean">false</config:config-item>
<config:config-item config:name="UseFormerLineSpacing" config:type="boolean">false</config:config-item>
<config:config-item config:name="PrintDrawings" config:type="boolean">true</config:config-item>
<config:config-item config:name="UseFormerTextWrapping" config:type="boolean">false</config:config-item>
<config:config-item config:name="UnxForceZeroExtLeading" config:type="boolean">false</config:config-item>
<config:config-item config:name="TabAtLeftIndentForParagraphsInList" config:type="boolean">false</config:config-item>
<config:config-item config:name="RedlineProtectionKey" config:type="base64Binary"/>
<config:config-item config:name="PropLineSpacingShrinksFirstLine" config:type="boolean">false</config:config-item>
<config:config-item config:name="ConsiderTextWrapOnObjPos" config:type="boolean">false</config:config-item>
<config:config-item config:name="RsidRoot" config:type="int">197346</config:config-item>
<config:config-item config:name="StylesNoDefault" config:type="boolean">false</config:config-item>
<config:config-item config:name="LinkUpdateMode" config:type="short">1</config:config-item>
<config:config-item config:name="AlignTabStopPosition" config:type="boolean">true</config:config-item>
<config:config-item config:name="DoNotJustifyLinesWithManualBreak" config:type="boolean">false</config:config-item>
<config:config-item config:name="DoNotResetParaAttrsForNumFont" config:type="boolean">false</config:config-item>
<config:config-item config:name="CurrentDatabaseCommandType" config:type="int">0</config:config-item>
<config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item>
<config:config-item config:name="DoNotCaptureDrawObjsOnPage" config:type="boolean">false</config:config-item>
<config:config-item config:name="CurrentDatabaseCommand" config:type="string"/>
<config:config-item config:name="PrinterSetup" config:type="base64Binary"/>
<config:config-item config:name="ClipAsCharacterAnchoredWriterFlyFrames" config:type="boolean">false</config:config-item>
</config:config-item-set>
</office:settings>
<office:scripts>
<office:script script:language="ooo:Basic">
<ooo:libraries xmlns:ooo="http://openoffice.org/2004/office" xmlns:xlink="http://www.w3.org/1999/xlink"/>
</office:script>
</office:scripts>
<office:font-face-decls>
<style:font-face style:name="StarSymbol" svg:font-family="StarSymbol"/>
<style:font-face style:name="Liberation Serif1" svg:font-family="&apos;Liberation Serif&apos;" style:font-adornments="Bold" style:font-family-generic="roman" style:font-pitch="variable"/>
<style:font-face style:name="Liberation Serif" svg:font-family="&apos;Liberation Serif&apos;" style:font-adornments="Regular" style:font-family-generic="roman" style:font-pitch="variable"/>
<style:font-face style:name="Thorndale AMT" svg:font-family="&apos;Thorndale AMT&apos;" style:font-family-generic="roman" style:font-pitch="variable"/>
<style:font-face style:name="Liberation Sans" svg:font-family="&apos;Liberation Sans&apos;" style:font-adornments="Regular" style:font-family-generic="swiss" style:font-pitch="variable"/>
<style:font-face style:name="Andale Sans UI" svg:font-family="&apos;Andale Sans UI&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
<style:font-face style:name="DejaVu Sans" svg:font-family="&apos;DejaVu Sans&apos;" style:font-family-generic="system" style:font-pitch="variable"/>
</office:font-face-decls>
<office:styles>
<style:default-style style:family="graphic">
<style:graphic-properties svg:stroke-color="#000000" draw:fill-color="#99ccff" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" draw:start-line-spacing-horizontal="0.1114in" draw:start-line-spacing-vertical="0.1114in" draw:end-line-spacing-horizontal="0.1114in" draw:end-line-spacing-vertical="0.1114in" style:flow-with-text="false"/>
<style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" style:writing-mode="lr-tb" style:font-independent-line-spacing="false">
<style:tab-stops/>
</style:paragraph-properties>
<style:text-properties style:use-window-font-color="true" style:font-name="Thorndale AMT" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Andale Sans UI" style:font-size-asian="10.5pt" style:language-asian="zxx" style:country-asian="none" style:font-name-complex="Andale Sans UI" style:font-size-complex="12pt" style:language-complex="zxx" style:country-complex="none"/>
</style:default-style>
<style:default-style style:family="paragraph">
<style:paragraph-properties fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="0.4925in" style:writing-mode="lr-tb"/>
<style:text-properties style:use-window-font-color="true" style:font-name="Thorndale AMT" fo:font-size="12pt" fo:language="en" fo:country="US" style:letter-kerning="true" style:font-name-asian="Andale Sans UI" style:font-size-asian="10.5pt" style:language-asian="zxx" style:country-asian="none" style:font-name-complex="Andale Sans UI" style:font-size-complex="12pt" style:language-complex="zxx" style:country-complex="none" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2"/>
</style:default-style>
<style:default-style style:family="table">
<style:table-properties table:border-model="collapsing"/>
</style:default-style>
<style:default-style style:family="table-row">
<style:table-row-properties fo:keep-together="auto"/>
</style:default-style>
<style:style style:name="Standard" style:family="paragraph" style:class="text">
<style:text-properties style:font-name="Liberation Sans" fo:font-family="&apos;Liberation Sans&apos;" style:font-style-name="Regular" style:font-family-generic="swiss" style:font-pitch="variable" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text">
<style:paragraph-properties fo:margin-top="0.1665in" fo:margin-bottom="0.0835in" loext:contextual-spacing="false" fo:keep-with-next="always"/>
<style:text-properties style:font-name="Liberation Serif" fo:font-family="&apos;Liberation Serif&apos;" style:font-style-name="Regular" style:font-family-generic="roman" style:font-pitch="variable" fo:font-size="16pt" style:font-name-asian="DejaVu Sans" style:font-family-asian="&apos;DejaVu Sans&apos;" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="DejaVu Sans" style:font-family-complex="&apos;DejaVu Sans&apos;" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/>
</style:style>
<style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text">
<style:paragraph-properties fo:margin-top="0in" fo:margin-bottom="0.0835in" loext:contextual-spacing="false"/>
<style:text-properties style:font-name="Liberation Sans" fo:font-family="&apos;Liberation Sans&apos;" style:font-style-name="Regular" style:font-family-generic="swiss" style:font-pitch="variable" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="List" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="list">
<style:text-properties style:font-size-asian="12pt"/>
</style:style>
<style:style style:name="Caption" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties fo:margin-top="0.0835in" fo:margin-bottom="0.0835in" loext:contextual-spacing="false" text:number-lines="false" text:line-number="0"/>
<style:text-properties fo:font-size="12pt" fo:font-style="italic" style:font-size-asian="12pt" style:font-style-asian="italic" style:font-size-complex="12pt" style:font-style-complex="italic"/>
</style:style>
<style:style style:name="Index" style:family="paragraph" style:parent-style-name="Standard" style:class="index">
<style:paragraph-properties text:number-lines="false" text:line-number="0"/>
<style:text-properties style:font-size-asian="12pt"/>
</style:style>
<style:style style:name="Header" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties text:number-lines="false" text:line-number="0">
<style:tab-stops>
<style:tab-stop style:position="3.4626in" style:type="center"/>
<style:tab-stop style:position="6.9252in" style:type="right"/>
</style:tab-stops>
</style:paragraph-properties>
<style:text-properties fo:font-size="9pt" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="Heading_20_2" style:display-name="Heading 2" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="text">
<style:text-properties fo:font-size="14pt" fo:font-style="italic" fo:font-weight="bold" style:font-size-asian="14pt" style:font-style-asian="italic" style:font-weight-asian="bold" style:font-size-complex="14pt" style:font-style-complex="italic" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Footer" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties text:number-lines="false" text:line-number="0">
<style:tab-stops>
<style:tab-stop style:position="3.4626in" style:type="center"/>
<style:tab-stop style:position="6.9252in" style:type="right"/>
</style:tab-stops>
</style:paragraph-properties>
<style:text-properties fo:font-size="9pt" style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="Heading_20_1" style:display-name="Heading 1" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="text">
<style:text-properties fo:font-size="16pt" fo:font-weight="bold" style:font-size-asian="115%" style:font-weight-asian="bold" style:font-size-complex="115%" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Table_20_Contents" style:display-name="Table Contents" style:family="paragraph" style:parent-style-name="Standard" style:class="extra">
<style:paragraph-properties text:number-lines="false" text:line-number="0"/>
<style:text-properties style:font-size-asian="10.5pt"/>
</style:style>
<style:style style:name="Table_20_Heading" style:display-name="Table Heading" style:family="paragraph" style:parent-style-name="Table_20_Contents" style:class="extra" style:master-page-name="">
<style:paragraph-properties fo:text-align="center" style:justify-single-word="false" style:page-number="auto" text:number-lines="false" text:line-number="0"/>
<style:text-properties style:font-name="Liberation Serif1" fo:font-family="&apos;Liberation Serif&apos;" style:font-style-name="Bold" style:font-family-generic="roman" style:font-pitch="variable" fo:font-weight="bold" style:font-size-asian="10.5pt" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Heading_20_3" style:display-name="Heading 3" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:class="text">
<style:text-properties fo:font-size="14pt" fo:font-weight="bold" style:font-size-asian="14pt" style:font-weight-asian="bold" style:font-size-complex="14pt" style:font-weight-complex="bold"/>
</style:style>
<style:style style:name="Text_20_body_20_indent" style:display-name="Text body indent" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="text">
<style:paragraph-properties fo:margin-left="0.1965in" fo:margin-right="0in" fo:text-indent="0in" style:auto-text-indent="false"/>
</style:style>
<style:style style:name="Text" style:family="paragraph" style:parent-style-name="Caption" style:class="extra"/>
<style:style style:name="Placeholder" style:family="text">
<style:text-properties fo:font-variant="small-caps" fo:color="#008080" style:text-underline-style="dotted" style:text-underline-width="auto" style:text-underline-color="font-color"/>
</style:style>
<style:style style:name="Bullet_20_Symbols" style:display-name="Bullet Symbols" style:family="text">
<style:text-properties style:font-name="StarSymbol" fo:font-family="StarSymbol" fo:font-size="9pt" style:font-name-asian="StarSymbol" style:font-family-asian="StarSymbol" style:font-size-asian="9pt" style:font-name-complex="StarSymbol" style:font-family-complex="StarSymbol" style:font-size-complex="9pt"/>
</style:style>
<text:outline-style style:name="Outline">
<text:outline-level-style text:level="1" style:num-format="">
<style:list-level-properties text:min-label-distance="0.15in"/>
</text:outline-level-style>
<text:outline-level-style text:level="2" style:num-format="">
<style:list-level-properties text:min-label-distance="0.15in"/>
</text:outline-level-style>
<text:outline-level-style text:level="3" style:num-format="">
<style:list-level-properties text:min-label-distance="0.15in"/>
</text:outline-level-style>
<text:outline-level-style text:level="4" style:num-format="">
<style:list-level-properties text:min-label-distance="0.15in"/>
</text:outline-level-style>
<text:outline-level-style text:level="5" style:num-format="">
<style:list-level-properties text:min-label-distance="0.15in"/>
</text:outline-level-style>
<text:outline-level-style text:level="6" style:num-format="">
<style:list-level-properties text:min-label-distance="0.15in"/>
</text:outline-level-style>
<text:outline-level-style text:level="7" style:num-format="">
<style:list-level-properties text:min-label-distance="0.15in"/>
</text:outline-level-style>
<text:outline-level-style text:level="8" style:num-format="">
<style:list-level-properties text:min-label-distance="0.15in"/>
</text:outline-level-style>
<text:outline-level-style text:level="9" style:num-format="">
<style:list-level-properties text:min-label-distance="0.15in"/>
</text:outline-level-style>
<text:outline-level-style text:level="10" style:num-format="">
<style:list-level-properties text:min-label-distance="0.15in"/>
</text:outline-level-style>
</text:outline-style>
<text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/>
<text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/>
<text:linenumbering-configuration text:number-lines="false" text:offset="0.1965in" style:num-format="1" text:number-position="left" text:increment="5"/>
</office:styles>
<office:automatic-styles>
<style:style style:name="P1" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0in" fo:margin-bottom="0.2in" loext:contextual-spacing="false"/>
</style:style>
<style:style style:name="P2" style:family="paragraph" style:parent-style-name="Standard">
<style:paragraph-properties fo:margin-top="0in" fo:margin-bottom="0in" loext:contextual-spacing="false"/>
</style:style>
<style:style style:name="P3" style:family="paragraph" style:parent-style-name="Standard" style:master-page-name="">
<style:paragraph-properties fo:margin-top="0in" fo:margin-bottom="0in" loext:contextual-spacing="false" style:page-number="auto"/>
</style:style>
<style:style style:name="P4" style:family="paragraph" style:parent-style-name="Standard" style:master-page-name="">
<style:paragraph-properties fo:margin-top="0.1965in" fo:margin-bottom="0in" loext:contextual-spacing="false" style:page-number="auto"/>
</style:style>
<style:style style:name="Sect1" style:family="section">
<style:section-properties style:editable="false">
<style:columns fo:column-count="1" fo:column-gap="0in"/>
</style:section-properties>
</style:style>
<style:page-layout style:name="pm1">
<style:page-layout-properties fo:page-width="8.2673in" fo:page-height="11.6925in" style:num-format="1" style:print-orientation="portrait" fo:margin-top="0.7874in" fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" fo:margin-right="0.7874in" style:shadow="none" fo:background-color="transparent" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="44" style:layout-grid-base-height="0.2165in" style:layout-grid-ruby-height="0in" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="true" style:layout-grid-display="true" draw:fill="none" draw:fill-color="#99ccff" style:footnote-max-height="0in">
<style:columns fo:column-count="2" fo:column-gap="0.5in">
<style:column style:rel-width="32767*" fo:start-indent="0in" fo:end-indent="0.25in"/>
<style:column style:rel-width="32768*" fo:start-indent="0.25in" fo:end-indent="0in"/>
</style:columns>
<style:footnote-sep style:width="0.0071in" style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" style:line-style="none" style:adjustment="left" style:rel-width="25%" style:color="#000000"/>
</style:page-layout-properties>
<style:header-style/>
<style:footer-style/>
</style:page-layout>
</office:automatic-styles>
<office:master-styles>
<style:master-page style:name="Standard" style:page-layout-name="pm1"/>
</office:master-styles>
<office:body>
<office:text>
<office:forms form:automatic-focus="false" form:apply-design-mode="false"/>
<text:sequence-decls>
<text:sequence-decl text:display-outline-level="0" text:name="Illustration"/>
<text:sequence-decl text:display-outline-level="0" text:name="Table"/>
<text:sequence-decl text:display-outline-level="0" text:name="Text"/>
<text:sequence-decl text:display-outline-level="0" text:name="Drawing"/>
</text:sequence-decls>
<text:section text:style-name="Sect1" text:name="Section1">
<text:p text:style-name="P3"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;party in records&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P4"><text:placeholder text:placeholder-type="text">&lt;party.full_name&gt;</text:placeholder></text:p>
<text:p text:style-name="P1"><text:placeholder text:placeholder-type="text">&lt;if test=&quot;party.addresses&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P1"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;line in party.addresses[0].full_address.split(&apos;\n&apos;)&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P2"><text:placeholder text:placeholder-type="text">&lt;line&gt;</text:placeholder></text:p>
<text:p text:style-name="P2"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
<text:p text:style-name="P2"><text:placeholder text:placeholder-type="text">&lt;for each=&quot;i in range(5 -len(party.addresses[0].full_address.split(&apos;\n&apos;)))&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P2"/>
<text:p text:style-name="P2"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
<text:p text:style-name="P2"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:p>
<text:p text:style-name="P2"><text:placeholder text:placeholder-type="text">&lt;if test=&quot;not party.addresses&quot;&gt;</text:placeholder></text:p>
<text:p text:style-name="P2"/>
<text:p text:style-name="P2"/>
<text:p text:style-name="P2"/>
<text:p text:style-name="P2"/>
<text:p text:style-name="P2"/>
<text:p text:style-name="P2"><text:placeholder text:placeholder-type="text">&lt;/if&gt;</text:placeholder></text:p>
<text:p text:style-name="P2"><text:placeholder text:placeholder-type="text">&lt;/for&gt;</text:placeholder></text:p>
</text:section>
</office:text>
</office:body>
</office:document>

1416
modules/party/locale/bg.po Executable file

File diff suppressed because it is too large Load Diff

1386
modules/party/locale/ca.po Executable file

File diff suppressed because it is too large Load Diff

1403
modules/party/locale/cs.po Executable file

File diff suppressed because it is too large Load Diff

1399
modules/party/locale/de.po Executable file

File diff suppressed because it is too large Load Diff

1388
modules/party/locale/es.po Executable file

File diff suppressed because it is too large Load Diff

1392
modules/party/locale/es_419.po Executable file

File diff suppressed because it is too large Load Diff

1520
modules/party/locale/et.po Executable file

File diff suppressed because it is too large Load Diff

1416
modules/party/locale/fa.po Executable file

File diff suppressed because it is too large Load Diff

1390
modules/party/locale/fi.po Executable file

File diff suppressed because it is too large Load Diff

1389
modules/party/locale/fr.po Executable file

File diff suppressed because it is too large Load Diff

1426
modules/party/locale/hu.po Executable file

File diff suppressed because it is too large Load Diff

1390
modules/party/locale/id.po Executable file

File diff suppressed because it is too large Load Diff

1434
modules/party/locale/it.po Executable file

File diff suppressed because it is too large Load Diff

1449
modules/party/locale/lo.po Executable file

File diff suppressed because it is too large Load Diff

1535
modules/party/locale/lt.po Executable file

File diff suppressed because it is too large Load Diff

1388
modules/party/locale/nl.po Executable file

File diff suppressed because it is too large Load Diff

1430
modules/party/locale/pl.po Executable file

File diff suppressed because it is too large Load Diff

1418
modules/party/locale/pt.po Executable file

File diff suppressed because it is too large Load Diff

1384
modules/party/locale/ro.po Executable file

File diff suppressed because it is too large Load Diff

1420
modules/party/locale/ru.po Executable file

File diff suppressed because it is too large Load Diff

1386
modules/party/locale/sl.po Executable file

File diff suppressed because it is too large Load Diff

1390
modules/party/locale/tr.po Executable file

File diff suppressed because it is too large Load Diff

1405
modules/party/locale/uk.po Executable file

File diff suppressed because it is too large Load Diff

1373
modules/party/locale/zh_CN.po Executable file

File diff suppressed because it is too large Load Diff

55
modules/party/message.xml Executable file
View File

@@ -0,0 +1,55 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data grouped="1">
<record model="ir.message" id="msg_party_code_unique">
<field name="text">The code on party must be unique.</field>
</record>
<record model="ir.message" id="msg_party_set_contact_mechanism">
<field name="text">To change the "%(field)s" for party "%(party)s", you must edit their contact mechanisms.</field>
</record>
<record model="ir.message" id="msg_contact_mechanism_change_party">
<field name="text">You cannot change the party of contact mechanism "%(contact)s".</field>
</record>
<record model="ir.message" id="msg_invalid_phone_number">
<field name="text">The phone number "%(phone)s" for party "%(party)s" is not valid.</field>
</record>
<record model="ir.message" id="msg_email_invalid">
<field name="text">The email address "%(email)s" for party "%(party)s" is not valid.</field>
</record>
<record model="ir.message" id="msg_invalid_code">
<field name="text">The %(type)s "%(code)s" for party "%(party)s" is not valid.</field>
</record>
<record model="ir.message" id="msg_party_identifier_duplicate">
<field name="text">The party "%(party)s" has the same %(type)s "%(code)s".</field>
</record>
<record model="ir.message" id="msg_vies_unavailable">
<field name="text">The VIES service is unavailable, try again later.</field>
</record>
<record model="ir.message" id="msg_different_name">
<field name="text">Parties have different names: "%(source_name)s" vs "%(destination_name)s".</field>
</record>
<record model="ir.message" id="msg_different_tax_identifier">
<field name="text">Parties have different tax identifiers: "%(source_code)s" vs "%(destination_code)s".</field>
</record>
<record model="ir.message" id="msg_erase_active_party">
<field name="text">Party "%(party)s" cannot be erased because they are still active.</field>
</record>
<record model="ir.message" id="msg_address_change_party">
<field name="text">You cannot change the party of address "%(address)s".</field>
</record>
<record model="ir.message" id="msg_invalid_format">
<field name="text">Invalid format "%(format)s" with exception "%(exception)s".</field>
</record>
<record model="ir.message" id="msg_category_name_unique">
<field name="text">The name of party category must be unique by parent.</field>
</record>
<record model="ir.message" id="msg_address_subdivision_country_code_unique">
<field name="text">The country code on subdivision type must be unique.</field>
</record>
<record model="ir.message" id="msg_identifier_type_remove">
<field name="text">To remove the identifier type "%(type)s" from the configuration, you must change it on "%(identifier)s".</field>
</record>
</data>
</tryton>

1205
modules/party/party.py Executable file

File diff suppressed because it is too large Load Diff

221
modules/party/party.xml Executable file
View File

@@ -0,0 +1,221 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="res.group" id="group_party">
<field name="name">Party</field>
</record>
<record model="res.group" id="group_party_admin">
<field name="name">Party Administration</field>
<field name="parent" ref="group_party"/>
</record>
<record model="res.user-res.group" id="user_admin_group_party">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_party"/>
</record>
<record model="res.user-res.group"
id="user_admin_group_party_admin">
<field name="user" ref="res.user_admin"/>
<field name="group" ref="group_party_admin"/>
</record>
<record model="ir.ui.icon" id="party_icon">
<field name="name">tryton-party</field>
<field name="path">icons/tryton-party.svg</field>
</record>
<menuitem
name="Parties"
sequence="10"
id="menu_party"
icon="tryton-party"/>
<menuitem
name="Configuration"
parent="menu_party"
sequence="0"
id="menu_configuration"
icon="tryton-settings"/>
<record model="ir.ui.menu-res.group"
id="menu_party_group_party_admin">
<field name="menu" ref="menu_configuration"/>
<field name="group" ref="group_party_admin"/>
</record>
<record model="ir.ui.menu-res.group" id="menu_party_group_party">
<field name="menu" ref="menu_party"/>
<field name="group" ref="group_party"/>
</record>
<record model="ir.model.access" id="access_party_party">
<field name="model">party.party</field>
<field name="group" ref="group_party"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.ui.view" id="party_view_tree2">
<field name="model">party.party</field>
<field name="type">tree</field>
<field name="name">party_tree2</field>
</record>
<record model="ir.ui.view" id="party_view_tree">
<field name="model">party.party</field>
<field name="type">tree</field>
<field name="name">party_tree</field>
</record>
<record model="ir.ui.view" id="party_view_form">
<field name="model">party.party</field>
<field name="type">form</field>
<field name="name">party_form</field>
</record>
<record model="ir.action.act_window" id="act_party_form">
<field name="name">Parties</field>
<field name="res_model">party.party</field>
</record>
<record model="ir.action.act_window.view" id="act_party_form_view0">
<field name="sequence" eval="10"/>
<field name="view" ref="party_view_tree2"/>
<field name="act_window" ref="act_party_form"/>
</record>
<record model="ir.action.act_window.view" id="act_party_form_view1">
<field name="sequence" eval="20"/>
<field name="view" ref="party_view_tree"/>
<field name="act_window" ref="act_party_form"/>
</record>
<record model="ir.action.act_window.view" id="act_party_form_view2">
<field name="sequence" eval="30"/>
<field name="view" ref="party_view_form"/>
<field name="act_window" ref="act_party_form"/>
</record>
<menuitem
parent="menu_party"
action="act_party_form"
sequence="10"
id="menu_party_form"/>
<record model="ir.action.act_window" id="act_party_by_category">
<field name="name">Parties by Category</field>
<field name="res_model">party.party</field>
<field name="context"
eval="{'categories': [Eval('active_id')]}" pyson="1"/>
<field name="domain"
eval="[('categories', 'child_of', [Eval('active_id')], 'parent')]"
pyson="1"/>
</record>
<record model="ir.action.keyword" id="act_party_by_category_keyword1">
<field name="keyword">tree_open</field>
<field name="model">party.category,-1</field>
<field name="action" ref="act_party_by_category"/>
</record>
<record model="ir.action.report" id="report_label">
<field name="name">Labels</field>
<field name="model">party.party</field>
<field name="report_name">party.label</field>
<field name="report">party/label.fodt</field>
</record>
<record model="ir.action.keyword" id="report_label_party">
<field name="keyword">form_print</field>
<field name="model">party.party,-1</field>
<field name="action" ref="report_label"/>
</record>
<record model="ir.sequence.type" id="sequence_type_party">
<field name="name">Party</field>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_party_group_admin">
<field name="sequence_type" ref="sequence_type_party"/>
<field name="group" ref="res.group_admin"/>
</record>
<record model="ir.sequence.type-res.group"
id="sequence_type_party_group_party_admin">
<field name="sequence_type" ref="sequence_type_party"/>
<field name="group" ref="group_party_admin"/>
</record>
<record model="ir.sequence" id="sequence_party">
<field name="name">Party</field>
<field name="sequence_type" ref="sequence_type_party"/>
</record>
<record model="ir.ui.view" id="identifier_form">
<field name="model">party.identifier</field>
<field name="type">form</field>
<field name="name">identifier_form</field>
</record>
<record model="ir.ui.view" id="identifier_list">
<field name="model">party.identifier</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="name">identifier_list</field>
</record>
<record model="ir.ui.view" id="identifier_list_sequence">
<field name="model">party.identifier</field>
<field name="type">tree</field>
<field name="priority" eval="20"/>
<field name="name">identifier_list_sequence</field>
</record>
<record model="ir.action.wizard" id="wizard_check_vies">
<field name="name">Check VIES</field>
<field name="wiz_name">party.check_vies</field>
<field name="model">party.party</field>
</record>
<record model="ir.action.keyword" id="check_vies_keyword">
<field name="keyword">form_action</field>
<field name="model">party.party,-1</field>
<field name="action" ref="wizard_check_vies"/>
</record>
<record model="ir.ui.view" id="check_vies_result">
<field name="model">party.check_vies.result</field>
<field name="type">form</field>
<field name="name">check_vies_result</field>
</record>
<record model="ir.action.wizard" id="wizard_replace">
<field name="name">Replace</field>
<field name="wiz_name">party.replace</field>
<field name="model">party.party</field>
</record>
<record model="ir.action-res.group"
id="wizard_replace-group_party_admin">
<field name="action" ref="wizard_replace"/>
<field name="group" ref="group_party_admin"/>
</record>
<record model="ir.action.keyword" id="wizard_replace_keyword1">
<field name="keyword">form_action</field>
<field name="model">party.party,-1</field>
<field name="action" ref="wizard_replace"/>
</record>
<record model="ir.ui.view" id="replace_ask_view_form">
<field name="model">party.replace.ask</field>
<field name="type">form</field>
<field name="name">replace_ask_form</field>
</record>
<record model="ir.action.wizard" id="wizard_erase">
<field name="name">Erase</field>
<field name="wiz_name">party.erase</field>
<field name="model">party.party</field>
</record>
<record model="ir.action-res.group" id="wizard_erase-group_party_admin">
<field name="action" ref="wizard_erase"/>
<field name="group" ref="group_party_admin"/>
</record>
<record model="ir.action.keyword" id="wizard_erase_keyword1">
<field name="keyword">form_action</field>
<field name="model">party.party,-1</field>
<field name="action" ref="wizard_erase"/>
</record>
<record model="ir.ui.view" id="erase_ask_view_form">
<field name="model">party.erase.ask</field>
<field name="type">form</field>
<field name="name">erase_ask_form</field>
</record>
</data>
</tryton>

View File

@@ -0,0 +1,6 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from .test_module import PartyCheckEraseMixin
__all__ = ['PartyCheckEraseMixin']

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,63 @@
====================
Party Erase Scenario
====================
Imports::
>>> from proteus import Model, Wizard
>>> from trytond.tests.tools import activate_modules, assertEqual
Activate modules::
>>> config = activate_modules('party')
Create a party::
>>> Party = Model.get('party.party')
>>> Attachment = Model.get('ir.attachment')
>>> party = Party(name='Pam')
>>> _ = party.identifiers.new(code="Identifier")
>>> _ = party.contact_mechanisms.new(type='other', value="mechanism")
>>> party.save()
>>> address, = party.addresses
>>> address.street = "St sample, 15"
>>> address.city = "City"
>>> address.save()
>>> identifier, = party.identifiers
>>> contact_mechanism, = party.contact_mechanisms
>>> attachment = Attachment()
>>> attachment.resource = party
>>> attachment.name = "Attachment"
>>> attachment.save()
Try erase active party::
>>> erase = Wizard('party.erase', models=[party])
>>> assertEqual(erase.form.party, party)
>>> erase.execute('erase')
Traceback (most recent call last):
...
EraseError: ...
Erase inactive party::
>>> party.active = False
>>> party.save()
>>> erase = Wizard('party.erase', models=[party])
>>> assertEqual(erase.form.party, party)
>>> erase.execute('erase')
Check fields have been erased::
>>> party.name
>>> identifier.reload()
>>> identifier.code
'****'
>>> address.reload()
>>> address.street
>>> address.city
>>> contact_mechanism.reload()
>>> contact_mechanism.value
>>> Attachment.find()
[]

View File

@@ -0,0 +1,42 @@
==============================
Party Identifier Notifications
==============================
Imports::
>>> from proteus import Model
>>> from trytond.tests.tools import activate_modules
Activate modules::
>>> config = activate_modules('party')
>>> Party = Model.get('party.party')
Create first party::
>>> party1 = Party(name="Party 1")
>>> identifier = party1.identifiers.new(type='be_vat')
>>> identifier.code = "500923836"
>>> party1.save()
Create second party::
>>> party2 = Party(name="Party 2")
>>> identifier = party2.identifiers.new(type='be_vat')
>>> identifier.code = "500923836"
Check notifications::
>>> len(identifier.notifications())
1
Change identifier::
>>> identifier.type = None
>>> identifier.code = "foo"
Check notifications::
>>> len(identifier.notifications())
0

View File

@@ -0,0 +1,41 @@
===========================
Party Phone Number Scenario
===========================
Imports::
>>> from proteus import Model
>>> from trytond.tests.tools import activate_modules
Activate modules::
>>> config = activate_modules('party')
Create a country::
>>> Country = Model.get('country.country')
>>> spain = Country(name='Spain', code='ES')
>>> spain.save()
Create a party related to the country::
>>> Party = Model.get('party.party')
>>> party = Party(name='Pam')
>>> address, = party.addresses
>>> address.country = spain
The country phone prefix is set when creating a phone of this party::
>>> local_phone = party.contact_mechanisms.new()
>>> local_phone.type = 'phone'
>>> local_phone.value = '666666666'
>>> local_phone.value
'+34 666 66 66 66'
The phone prefix is respected when using international prefix::
>>> international_phone = party.contact_mechanisms.new()
>>> international_phone.type = 'phone'
>>> international_phone.value = '+442083661178'
>>> international_phone.value
'+44 20 8366 1178'

View File

@@ -0,0 +1,52 @@
======================
Party Replace Scenario
======================
Imports::
>>> from proteus import Model, Wizard
>>> from trytond.tests.tools import activate_modules, assertEqual, assertFalse
Activate modules::
>>> config = activate_modules('party')
Create a party::
>>> Party = Model.get('party.party')
>>> party1 = Party(name='Pam')
>>> identifier1 = party1.identifiers.new()
>>> identifier1.type = 'eu_vat'
>>> identifier1.code = 'BE0897290877'
>>> party1.save()
>>> address1, = party1.addresses
>>> identifier1, = party1.identifiers
Create a second party similar party::
>>> party2 = Party(name='Pam')
>>> identifier2 = party2.identifiers.new()
>>> identifier2.type = 'eu_vat'
>>> identifier2.code = 'BE0897290877'
>>> party2.save()
>>> address2, = party2.addresses
>>> identifier2, = party2.identifiers
Replace the second by the first party::
>>> replace = Wizard('party.replace', models=[party2])
>>> assertEqual(replace.form.source, party2)
>>> replace.form.destination = party1
>>> replace.execute('replace')
>>> party2.reload()
>>> bool(party2.active)
False
>>> identifier2.reload()
>>> assertEqual(identifier2.party, party1)
>>> assertFalse(identifier2.active)
>>> address2.reload()
>>> assertEqual(address2.party, party1)
>>> assertFalse(address2.active)

View File

@@ -0,0 +1,594 @@
# 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
from trytond.pool import Pool
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
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 PartyTestCase(PartyCheckEraseMixin, 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
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(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_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')
self.assertFalse(
set(Party.tax_identifier_types())
- set(dict(IDENTIFIER_TYPES).keys()))
@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

View File

@@ -0,0 +1,8 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.tests.test_tryton import load_doc_tests
def load_tests(*args, **kwargs):
return load_doc_tests(__name__, __file__, *args, **kwargs)

14
modules/party/tryton.cfg Executable file
View File

@@ -0,0 +1,14 @@
[tryton]
version=7.2.2
depends:
country
ir
res
xml:
party.xml
category.xml
address.xml
contact_mechanism.xml
configuration.xml
ir.xml
message.xml

View File

@@ -0,0 +1,42 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form col="6">
<label name="party"/>
<field name="party" colspan="5"/>
<label name="party_name"/>
<field name="party_name"/>
<group colspan="2" col="-1" id="checkboxes">
<label name="active"/>
<field name="active"
xexpand="0" width="25"/>
<!-- Add here some checkboxes ! -->
<label name="sequence"/>
<field name="sequence"/>
</group>
<notebook colspan="6">
<page string="General" id="general">
<label name="name"/>
<field name="name"/>
<newline/>
<label name="street" yalign="0"/>
<field name="street" colspan="3" height="80"/>
<newline/>
<label name="postal_code"/>
<field name="postal_code"/>
<label name="city"/>
<field name="city"/>
<newline/>
<label name="country"/>
<field name="country"/>
<label name="subdivision"/>
<field name="subdivision"/>
</page>
<page name="identifiers">
<field name="identifiers" colspan="4" pre_validate="1" view_ids="party.identifier_list_sequence"/>
</page>
<page name="contact_mechanisms">
<field name="contact_mechanisms" colspan="4" pre_validate="1" view_ids="party.contact_mechanism_view_tree_sequence"/>
</page>
</notebook>
</form>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form col="6">
<label name="party"/>
<field name="party" colspan="5"/>
<label name="party_name"/>
<field name="party_name"/>
<group colspan="2" col="-1" id="checkboxes">
<label name="active"/>
<field name="active"
xexpand="0" width="25"/>
<!-- Add here some checkboxes ! -->
<label name="sequence"/>
<field name="sequence"/>
</group>
<newline/>
<label name="name"/>
<field name="name"/>
<newline/>
<label name="street" yalign="0"/>
<field name="street" colspan="3" height="80"/>
<newline/>
<label name="postal_code"/>
<field name="postal_code"/>
<label name="city"/>
<field name="city"/>
<newline/>
<label name="country"/>
<field name="country"/>
<label name="subdivision"/>
<field name="subdivision"/>
</form>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<form col="6">
<label name="country_code"/>
<field name="country_code"/>
<label name="language_code"/>
<field name="language_code"/>
<label name="active"/>
<field name="active"/>
<separator name="format_" colspan="6"/>
<field name="format_" colspan="6"/>
</form>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0"?>
<tree>
<field name="country_code"/>
<field name="language_code"/>
</tree>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form>
<label name="country_code"/>
<field name="country_code"/>
<label name="active"/>
<field name="active"/>
<separator name="types" colspan="4"/>
<field name="types" colspan="4"/>
</form>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="country_code"/>
<field name="types" expand="1"/>
</tree>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="party" expand="1"/>
<field name="party_name" optional="0"/>
<field name="name" optional="0"/>
<field name="street_single_line" expand="2"/>
<field name="postal_code"/>
<field name="city"/>
<field name="country" optional="0"/>
<field name="subdivision" optional="0"/>
</tree>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree sequence="sequence">
<field name="party" expand="1"/>
<field name="party_name" optional="0"/>
<field name="name" optional="0"/>
<field name="street_single_line" expand="2"/>
<field name="postal_code"/>
<field name="city"/>
<field name="country" optional="0"/>
<field name="subdivision" optional="0"/>
</tree>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form>
<label name="name"/>
<field name="name"/>
<label name="active"/>
<field name="active"/>
<label name="parent"/>
<field name="parent"/>
<field name="childs" colspan="4"/>
</form>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="rec_name" expand="1"/>
</tree>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree keyword_open="1">
<field name="name" expand="1"/>
</tree>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form col="1">
<field name="parties_succeed"/>
<field name="parties_failed"/>
</form>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form>
<label name="party_sequence"/>
<field name="party_sequence"/>
<label name="party_lang"/>
<field name="party_lang" widget="selection"/>
<separator name="identifier_types" colspan="4"/>
<field name="identifier_types" colspan="4" height="200"/>
</form>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form>
<label name="party"/>
<field name="party"/>
<group col="-1" colspan="2" id="checkboxes">
<label name="active"/>
<field name="active"
xexpand="0" width="25"/>
<!-- Add here some checkboxes ! -->
<label name="sequence"/>
<field name="sequence"/>
</group>
<label name="type"/>
<field name="type"/>
<group col="2" colspan="2" id="value">
<label name="other_value"/>
<field name="other_value"/>
<label name="website"/>
<field name="website" widget="url"/>
<label name="email"/>
<field name="email" widget="email"/>
<label name="skype"/>
<field name="skype" widget="callto"/>
<label name="sip"/>
<field name="sip" widget="sip"/>
</group>
<label name="name"/>
<field name="name"/>
<label name="language"/>
<field name="language" widget="selection"/>
<separator name="comment" colspan="6"/>
<field name="comment" colspan="6"/>
</form>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="type"/>
<field name="value" expand="1"/>
<field name="name" expand="1" optional="0"/>
<field name="party" expand="2" optional="0"/>
<field name="address" expand="1" optional="0"/>
<field name="language" widget="selection" optional="1"/>
</tree>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree sequence="sequence" editable="1">
<field name="type"/>
<field name="value" expand="1"/>
<field name="name" expand="1" optional="0"/>
<field name="party" expand="2" optional="0"/>
<field name="address" expand="1" optional="0"/>
<field name="language" widget="selection" optional="1"/>
<field name="url" widget="url"/>
</tree>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//field[@name='subject']" position="before">
<label name="contact_mechanism"/>
<field name="contact_mechanism" colspan="3"/>
</xpath>
</data>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form>
<label name="party"/>
<field name="party"/>
</form>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form cursor="type">
<label name="party"/>
<field name="party"/>
<group colspan="2" col="-1" id="checkboxes">
<label name="active"/>
<field name="active"/>
<label name="sequence"/>
<field name="sequence"/>
</group>
<label name="type"/>
<field name="type"/>
<label name="code"/>
<field name="code"/>
<label name="address"/>
<field name="address"/>
</form>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="party" expand="1"/>
<field name="address" expand="1"/>
<field name="type" expand="1"/>
<field name="code" expand="2"/>
</tree>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree sequence="sequence">
<field name="party" expand="1"/>
<field name="address" expand="1"/>
<field name="type" expand="1"/>
<field name="code" expand="2"/>
</tree>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form col="6">
<group col="6" colspan="5" id="header" yalign="0">
<label name="name"/>
<field name="name" xexpand="1"/>
<label name="code"/>
<field name="code"/>
<group col="-1" colspan="2" id="checkboxes" xexpand="0">
<label name="active"/>
<field name="active" xexpand="0" width="25"/>
<!-- Add here some checkboxes ! -->
</group>
<label name="replaced_by"/>
<field name="replaced_by"/>
</group>
<notebook colspan="6">
<page string="General" id="general">
<field name="addresses" mode="form,tree" colspan="4"
view_ids="party.address_view_form_simple,party.address_view_tree_sequence"/>
<group col="2" colspan="4" id="lang">
<label name="lang"/>
<field name="lang" widget="selection" xexpand="0"/>
</group>
<field name="contact_mechanisms" colspan="2"
view_ids="party.contact_mechanism_view_tree_sequence"/>
<field name="categories" colspan="2"
view_ids="party.category_view_list"/>
</page>
<page name="identifiers">
<field name="identifiers" colspan="4" pre_validate="1"
view_ids="party.identifier_list_sequence"/>
</page>
</notebook>
<group id="links" col="-1" colspan="6">
</group>
</form>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="code"/>
<field name="name" expand="1"/>
<field name="lang" optional="1"/>
<field name="tax_identifier" optional="0"/>
</tree>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="code"/>
<field name="name" expand="1"/>
</tree>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0"?>
<form>
<label name="source" string="Party"/>
<field name="source"/>
<label name="destination" string="Replace By"/>
<field name="destination"/>
</form>