diff --git a/partner_firstname/README.rst b/partner_firstname/README.rst index 5602d35ee..7c9bf10e9 100644 --- a/partner_firstname/README.rst +++ b/partner_firstname/README.rst @@ -39,11 +39,28 @@ Contributors ------------ * Nicolas Bessi +* Yannick Vaucher +* Vincent Renaville +* Guewen Baconnier +* Holger Brunn * Jonathan Nemry * Olivier Laurent +* Sandy Carter +* Alexis de Lattre +* Lorenzo Battistini * Hans Henrik Gabelgaard * Jairo Llopis * Adrien Peiffer +* Ronald Portier +* Sylvain Van Hoof +* Pedro Baeza + +Translations +------------ + +* Danish: Hans Henrik Gabelgaard +* Italian: Leonardo Donelli +* Spanish: Antonio Espinosa Maintainer ---------- diff --git a/partner_firstname/__init__.py b/partner_firstname/__init__.py index 4cdd79b3f..8a9bba4af 100644 --- a/partner_firstname/__init__.py +++ b/partner_firstname/__init__.py @@ -1,21 +1,4 @@ # -*- coding: utf-8 -*- - -# Author: Nicolas Bessi. Copyright Camptocamp SA -# Copyright (C) -# 2014: Agile Business Group () -# 2015: Grupo ESOC -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - +# © 2013 Nicolas Bessi (Camptocamp SA) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from . import models diff --git a/partner_firstname/__openerp__.py b/partner_firstname/__openerp__.py index 57ad3366f..751f5eafe 100644 --- a/partner_firstname/__openerp__.py +++ b/partner_firstname/__openerp__.py @@ -1,31 +1,21 @@ # -*- coding: utf-8 -*- - -# Author: Nicolas Bessi. Copyright Camptocamp SA -# Copyright (C) -# 2014: Agile Business Group () -# 2015: Grupo ESOC -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +# © 2013 Nicolas Bessi (Camptocamp SA) +# © 2014 Agile Business Group () +# © 2015 Grupo ESOC () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). { 'name': 'Partner first name and last name', 'summary': "Split first name and last name for non company partners", - 'version': '8.0.2.0.0', - 'author': "Camptocamp,Odoo Community Association (OCA)", + 'version': '9.0.1.0.0', + 'author': "Camptocamp, " + "Grupo ESOC Ingeniería de Servicios, " + "Odoo Community Association (OCA)", + 'license': "AGPL-3", 'maintainer': 'Camptocamp, Acsone', 'category': 'Extra Tools', - 'website': 'http://www.camptocamp.com, http://www.acsone.eu', + 'website': + 'http://www.camptocamp.com, http://www.acsone.eu, http://grupoesoc.es', 'depends': ['base'], 'data': [ 'views/res_partner.xml', @@ -35,6 +25,6 @@ 'demo': [], 'test': [], 'auto_install': False, - 'installable': False, + 'installable': True, 'images': [] } diff --git a/partner_firstname/exceptions.py b/partner_firstname/exceptions.py index 99d5b630e..d2fdea566 100644 --- a/partner_firstname/exceptions.py +++ b/partner_firstname/exceptions.py @@ -1,21 +1,6 @@ -# -*- encoding: utf-8 -*- - -# Odoo, Open Source Management Solution -# Copyright (C) 2014-2015 Grupo ESOC -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - +# -*- coding: utf-8 -*- +# © 2014-2015 Grupo ESOC () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from openerp import _, exceptions diff --git a/partner_firstname/models/__init__.py b/partner_firstname/models/__init__.py new file mode 100644 index 000000000..95688f355 --- /dev/null +++ b/partner_firstname/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# © 2013 Nicolas Bessi (Camptocamp SA) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import res_partner +from . import res_user diff --git a/partner_firstname/models.py b/partner_firstname/models/res_partner.py similarity index 59% rename from partner_firstname/models.py rename to partner_firstname/models/res_partner.py index 47f6f7e0f..82acceb71 100644 --- a/partner_firstname/models.py +++ b/partner_firstname/models/res_partner.py @@ -1,26 +1,11 @@ # -*- coding: utf-8 -*- - -# Author: Nicolas Bessi. Copyright Camptocamp SA -# Copyright (C) -# 2014: Agile Business Group () -# 2015: Grupo ESOC -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - +# © 2013 Nicolas Bessi (Camptocamp SA) +# © 2014 Agile Business Group () +# © 2015 Grupo ESOC () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import logging from openerp import api, fields, models -from . import exceptions +from .. import exceptions _logger = logging.getLogger(__name__) @@ -38,6 +23,56 @@ class ResPartner(models.Model): required=False, store=True) + @api.model + def create(self, vals): + """Add inverted names at creation if unavailable.""" + context = dict(self.env.context) + name = vals.get("name", context.get("default_name")) + + if name is not None: + # Calculate the splitted fields + inverted = self._get_inverse_name( + self._get_whitespace_cleaned_name(name), + vals.get("is_company", + self.default_get(["is_company"])["is_company"])) + + for key, value in inverted.iteritems(): + if not vals.get(key) or context.get("copy"): + vals[key] = value + + # Remove the combined fields + if "name" in vals: + del vals["name"] + if "default_name" in context: + del context["default_name"] + + return super(ResPartner, self.with_context(context)).create(vals) + + @api.multi + def copy(self, default=None): + """Ensure partners are copied right. + + Odoo adds ``(copy)`` to the end of :attr:`~.name`, but that would get + ignored in :meth:`~.create` because it also copies explicitly firstname + and lastname fields. + """ + return super(ResPartner, self.with_context(copy=True)).copy(default) + + @api.model + def default_get(self, fields_list): + """Invert name when getting default values.""" + result = super(ResPartner, self).default_get(fields_list) + + inverted = self._get_inverse_name( + self._get_whitespace_cleaned_name(result.get("name", "")), + result.get("is_company", False)) + + for field in inverted.keys(): + if field in fields_list: + result[field] = inverted.get(field) + + return result + @api.model def _get_computed_name(self, lastname, firstname): """Compute the 'name' field according to splitted data. @@ -55,13 +90,11 @@ class ResPartner(models.Model): def _inverse_name_after_cleaning_whitespace(self): """Clean whitespace in :attr:`~.name` and split it. - Removes leading, trailing and duplicated whitespace. - The splitting logic is stored separately in :meth:`~._inverse_name`, so submodules can extend that method and get whitespace cleaning for free. """ # Remove unneeded whitespace - clean = u" ".join(self.name.split(None)) if self.name else self.name + clean = self._get_whitespace_cleaned_name(self.name) # Clean name avoiding infinite recursion if self.name != clean: @@ -71,9 +104,17 @@ class ResPartner(models.Model): else: self._inverse_name() + @api.model + def _get_whitespace_cleaned_name(self, name): + """Remove redundant whitespace from :param:`name`. + + Removes leading, trailing and duplicated whitespace. + """ + return u" ".join(name.split(None)) if name else name + @api.model def _get_inverse_name(self, name, is_company=False): - """Try to revert the effect of :meth:`._compute_name`. + """Compute the inverted name. - If the partner is a company, save it in the lastname. - Otherwise, make a guess. @@ -90,21 +131,23 @@ class ResPartner(models.Model): parts = [name or False, False] # Guess name splitting else: - parts = name.split(" ", 1) + parts = name.strip().split(" ", 1) while len(parts) < 2: parts.append(False) - return parts + return {"lastname": parts[0], "firstname": parts[1]} @api.one def _inverse_name(self): + """Try to revert the effect of :meth:`._compute_name`.""" parts = self._get_inverse_name(self.name, self.is_company) - self.lastname, self.firstname = parts + self.lastname, self.firstname = parts["lastname"], parts["firstname"] @api.one @api.constrains("firstname", "lastname") def _check_name(self): """Ensure at least one name is set.""" - if not (self.firstname or self.lastname): + if ((self.type == 'contact' or self.is_company) and + not (self.firstname or self.lastname)): raise exceptions.EmptyNamesError(self) @api.one @@ -145,3 +188,11 @@ class ResPartner(models.Model): # Force calculations there records._inverse_name() _logger.info("%d partners updated installing module.", len(records)) + + # Disabling SQL constraint givint a more explicit error using a Python + # contstraint + _sql_constraints = [( + 'check_name', + "CHECK( 1=1 )", + 'Contacts require a name.' + )] diff --git a/partner_firstname/models/res_user.py b/partner_firstname/models/res_user.py new file mode 100644 index 000000000..5b0f7c8fc --- /dev/null +++ b/partner_firstname/models/res_user.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# © 2013 Nicolas Bessi (Camptocamp SA) +# © 2014 Agile Business Group () +# © 2015 Grupo ESOC () +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +import logging +from openerp import api, models + + +_logger = logging.getLogger(__name__) + + +class ResUser(models.Model): + _inherit = 'res.users' + + @api.model + def default_get(self, fields_list): + """Invert name when getting default values.""" + result = super(ResUser, self).default_get(fields_list) + + partner_model = self.env['res.partner'] + inverted = partner_model._get_inverse_name( + partner_model._get_whitespace_cleaned_name(result.get("name", "")), + result.get("is_company", False)) + + for field in inverted.keys(): + if field in fields_list: + result[field] = inverted.get(field) + + return result diff --git a/partner_firstname/tests/__init__.py b/partner_firstname/tests/__init__.py index 7dfc1f9cc..2692c082c 100644 --- a/partner_firstname/tests/__init__.py +++ b/partner_firstname/tests/__init__.py @@ -1,31 +1,12 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# Authors: Nemry Jonathan -# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu) -# All Rights Reserved -# -# WARNING: This program as such is intended to be used by professional -# programmers who take the whole responsibility of assessing all potential -# consequences resulting from its eventual inadequacies and bugs. -# End users who are looking for a ready-to-use solution with commercial -# guarantees and support are strongly advised to contact a Free Software -# Service Company. -# -# This program is Free Software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -# -############################################################################## +# © 2014 Nemry Jonathan (Acsone SA/NV) (http://www.acsone.eu) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from . import test_empty, test_name, test_onchange +from . import ( + test_create, + test_defaults, + test_delete, + test_empty, + test_name, + test_onchange +) diff --git a/partner_firstname/tests/base.py b/partner_firstname/tests/base.py index 85e204435..15a2c795d 100644 --- a/partner_firstname/tests/base.py +++ b/partner_firstname/tests/base.py @@ -1,29 +1,6 @@ # -*- coding: utf-8 -*- - -# Authors: Nemry Jonathan -# Copyright (c) 2014 Acsone SA/NV (http://www.acsone.eu) -# All Rights Reserved -# -# WARNING: This program as such is intended to be used by professional -# programmers who take the whole responsibility of assessing all potential -# consequences resulting from its eventual inadequacies and bugs. -# End users who are looking for a ready-to-use solution with commercial -# guarantees and support are strongly advised to contact a Free Software -# Service Company. -# -# This program is Free Software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# © 2014 Nemry Jonathan (Acsone SA/NV) (http://www.acsone.eu) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from openerp.tests.common import TransactionCase from .. import exceptions as ex @@ -71,7 +48,8 @@ class BaseCase(TransactionCase, MailInstalled): def test_copy(self): """Copy the partner and compare the result.""" self.expect(self.lastname, u"%s (copy)" % self.firstname) - self.changed = self.original.with_context(lang="en_US").copy() + self.changed = (self.original.with_context(copy=True, lang="en_US") + .copy()) def test_one_name(self): """Test what happens when only one name is given.""" diff --git a/partner_firstname/tests/test_create.py b/partner_firstname/tests/test_create.py new file mode 100644 index 000000000..8f3e06a7f --- /dev/null +++ b/partner_firstname/tests/test_create.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +# © 2015 Grupo ESOC Ingeniería de Servicios, S.L. - Jairo Llopis. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +"""Test default values for models.""" + +from openerp.tests.common import TransactionCase +from .base import MailInstalled + + +class PersonCase(TransactionCase): + """Test ``res.partner`` when it is a person.""" + context = {"default_is_company": False} + model = "res.partner" + + def setUp(self): + super(PersonCase, self).setUp() + self.good_values = { + "firstname": u"Núñez", + "lastname": u"Fernán", + } + self.good_values["name"] = "%s %s" % (self.good_values["lastname"], + self.good_values["firstname"]) + if "default_is_company" in self.context: + self.good_values["is_company"] = self.context["default_is_company"] + self.values = self.good_values.copy() + + def tearDown(self): + self.record = (self.env[self.model] + .with_context(self.context) + .create(self.values)) + + for key, value in self.good_values.iteritems(): + self.assertEqual( + self.record[key], + value, + "Checking key %s" % key) + + super(PersonCase, self).tearDown() + + def test_no_name(self): + """Name is calculated.""" + del self.values["name"] + + def test_wrong_name_value(self): + """Wrong name value is ignored, name is calculated.""" + self.values["name"] = u"BÄD" + + def test_wrong_name_context(self): + """Wrong name context is ignored, name is calculated.""" + del self.values["name"] + self.context["default_name"] = u"BÄD" + + def test_wrong_name_value_and_context(self): + """Wrong name value and context is ignored, name is calculated.""" + self.values["name"] = u"BÄD1" + self.context["default_name"] = u"BÄD2" + + +class CompanyCase(PersonCase): + """Test ``res.partner`` when it is a company.""" + context = {"default_is_company": True} + + def setUp(self): + super(CompanyCase, self).setUp() + self.good_values.update(lastname=self.values["name"], firstname=False) + self.values = self.good_values.copy() + + +class UserCase(PersonCase, MailInstalled): + """Test ``res.users``.""" + model = "res.users" + context = {"default_login": "user@example.com"} + + def tearDown(self): + # Cannot create users if ``mail`` is installed + if self.mail_installed(): + # Skip tests + super(PersonCase, self).tearDown() + else: + # Run tests + super(UserCase, self).tearDown() diff --git a/partner_firstname/tests/test_defaults.py b/partner_firstname/tests/test_defaults.py new file mode 100644 index 000000000..ab6c3a969 --- /dev/null +++ b/partner_firstname/tests/test_defaults.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# © 2015 Grupo ESOC Ingeniería de Servicios, S.L. - Jairo Llopis. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +"""Test default values for models.""" + +from openerp.tests.common import TransactionCase +from .base import MailInstalled + + +class PersonCase(TransactionCase): + """Test ``res.partner`` when it is a person.""" + context = {"default_is_company": False} + model = "res.partner" + + def setUp(self): + super(PersonCase, self).setUp() + self.values = { + "firstname": u"Núñez", + "lastname": u"Fernán", + } + self.values["name"] = "%s %s" % (self.values["lastname"], + self.values["firstname"]) + if "default_is_company" in self.context: + self.values["is_company"] = self.context["default_is_company"] + + def tearDown(self): + for key, value in self.values.iteritems(): + self.assertEqual( + self.defaults.get(key), + value, + "Checking key %s" % key) + + return super(PersonCase, self).tearDown() + + def test_default_get(self): + """Getting default values for fields includes new fields.""" + self.defaults = (self.env[self.model] + .with_context(self.context, + default_name=self.values["name"]) + .default_get(self.values.keys())) + + +class CompanyCase(PersonCase): + """Test ``res.partner`` when it is a company.""" + context = {"default_is_company": True} + + def tearDown(self): + self.values.update(lastname=self.values["name"], firstname=False) + return super(CompanyCase, self).tearDown() + + +class UserCase(PersonCase, MailInstalled): + """Test ``res.users``.""" + model = "res.users" + context = {"default_login": "user@example.com"} + + def tearDown(self): + # Cannot create users if ``mail`` is installed + if self.mail_installed(): + # Skip tests + super(PersonCase, self).tearDown() + else: + # Run tests + super(UserCase, self).tearDown() diff --git a/partner_firstname/tests/test_delete.py b/partner_firstname/tests/test_delete.py new file mode 100644 index 000000000..0729502f1 --- /dev/null +++ b/partner_firstname/tests/test_delete.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# © 2015 Grupo ESOC +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from openerp.tests.common import TransactionCase +from .base import MailInstalled + + +class CompanyCase(TransactionCase): + model = "res.partner" + context = {"default_is_company": True} + + def test_computing_after_unlink(self): + """Test what happens if recomputed after unlinking. + + This test might seem useless, but really this happens when module + ``partner_relations`` is installed. + + See https://github.com/OCA/partner-contact/issues/154. + """ + data = {"name": u"Söme name"} + record = self.env[self.model].with_context(**self.context).create(data) + record.unlink() + record.recompute() + + +class PersonCase(CompanyCase): + context = {"default_is_company": False} + + +class UserCase(CompanyCase, MailInstalled): + model = "res.users" + context = {"default_login": "user@example.com"} + + def test_computing_after_unlink(self): + # Cannot create users if ``mail`` is installed + if not self.mail_installed(): + super(UserCase, self).test_computing_after_unlink() diff --git a/partner_firstname/tests/test_empty.py b/partner_firstname/tests/test_empty.py index 280ed9e92..4728ca20c 100644 --- a/partner_firstname/tests/test_empty.py +++ b/partner_firstname/tests/test_empty.py @@ -1,26 +1,11 @@ -# -*- encoding: utf-8 -*- - -# Odoo, Open Source Management Solution -# Copyright (C) 2014-2015 Grupo ESOC -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - +# -*- coding: utf-8 -*- +# © 2014-2015 Grupo ESOC +# © 2016 Yannick Vaucher (Camptocamp) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). """Test situations where names are empty. To have more accurate results, remove the ``mail`` module before testing. """ - from openerp.tests.common import TransactionCase from .base import MailInstalled from .. import exceptions as ex @@ -34,8 +19,9 @@ class CompanyCase(TransactionCase): def tearDown(self): try: data = {"name": self.name} + model = self.env[self.model].with_context(**self.context) with self.assertRaises(ex.EmptyNamesError): - self.env[self.model].with_context(**self.context).create(data) + model.create(data) finally: super(CompanyCase, self).tearDown() @@ -50,7 +36,7 @@ class CompanyCase(TransactionCase): class PersonCase(CompanyCase): """Test ``res.partner`` when it is a person.""" - context = {"default_is_company": False} + context = {"default_is_company": False, "default_type": 'contact'} class UserCase(CompanyCase, MailInstalled): @@ -66,3 +52,23 @@ class UserCase(CompanyCase, MailInstalled): else: # Run tests super(UserCase, self).tearDown() + + +class AddressCase(TransactionCase): + """Test ``res.partner`` when it is a address.""" + + def test_new_empty_invoice_address(self): + """Create an invoice patner without name.""" + self.original = self.env["res.partner"].create({ + "is_company": False, + "type": 'invoice', + "lastname": "", + "firstname": ""}) + + def test_new_empty_shipping_address(self): + """Create an shipping patner without name.""" + self.original = self.env["res.partner"].create({ + "is_company": False, + "type": 'delivery', + "lastname": "", + "firstname": ""}) diff --git a/partner_firstname/tests/test_onchange.py b/partner_firstname/tests/test_onchange.py index 6d2ec278f..573f30522 100644 --- a/partner_firstname/tests/test_onchange.py +++ b/partner_firstname/tests/test_onchange.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +# © 2015 Grupo ESOC +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + """These tests try to mimic the behavior of the UI form. The form operates in onchange mode, with its limitations. diff --git a/partner_firstname/views/res_partner.xml b/partner_firstname/views/res_partner.xml index 0838628eb..85c269613 100644 --- a/partner_firstname/views/res_partner.xml +++ b/partner_firstname/views/res_partner.xml @@ -15,7 +15,7 @@ } - + - - - - - - - - +
- -