diff --git a/base_location/README.rst b/base_location/README.rst index 160dbf7c0..33b6fe83c 100644 --- a/base_location/README.rst +++ b/base_location/README.rst @@ -1,33 +1,54 @@ -.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg - :target: https://www.gnu.org/licenses/agpl-3.0-standalone.html - :alt: License: AGPL-3 +==================================== +Location management (aka Better ZIP) +==================================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpartner--contact-lightgray.png?logo=github + :target: https://github.com/OCA/partner-contact/tree/12.0/base_location + :alt: OCA/partner-contact +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/partner-contact-12-0/partner-contact-12-0-base_location + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/134/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module introduces a zip model that allows you to manage locations in a better way. + +The zips will allow the users to complete automatically all address-related fields by just filling the zip. -======================= -Enhanced ZIP management -======================= - -This module introduces a better zip/npa management system. +Also allows different search filters. -It enables zip, city, state and country auto-completion on partners and companies. +**Table of contents** -Also allows different search filters. +.. contents:: + :local: Configuration ============= -#. Activate the developer mode in *Settings*. -#. Go to *Settings / Technical / Locations Management / Locations*. -#. Create a new Location. +#. Go to *Contacts / Configuration / Localization / Cities*. +#. Create a new City. + +#. Go to *Contacts / Configuration / Localization / Zips*. +#. Create a new Zip and relate it to the city (you can also create the Zip from the City). or, with module 'Contacts Directory' installed: #. Go to *Contacts / Configuration / Localization / Countries*. #. Locate the desired country. -#. Press on the button 'Locations'. - -or, -#. Go to *Contacts / Configuration / Localization / Fed. States* -#. Locate the desired state. -#. Enter the desired Locations. +#. Press on the button 'Cities' / 'Zips'. Usage ===== @@ -36,52 +57,59 @@ Usage #. Fill the field *Location completion* #. Information about country, state, city and zip will be filled automatically - - -.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas - :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/134/11.0 - Bug Tracker =========== -Bugs are tracked on `GitHub Issues -`_. In case of trouble, please -check there if your issue has already been reported. If you spotted it first, -help us smash it by providing detailed and welcomed feedback. +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. Credits ======= -Images ------- +Authors +~~~~~~~ -* Icon park: `Icon http://icon-park.com/icon/location-map-pin-orange3/` +* Camptocamp +* ACYSOS S.L. +* Alejandro Santana +* Tecnativa +* AdaptiveCity Contributors ------------- +~~~~~~~~~~~~ * Nicolas Bessi (Camptocamp) * Ignacio Ibeas (Acysos S.L.) -* Pedro M. Baeza +* Pedro M. Baeza * Alejandro Santana * Sandy Carter * Yannick Vaucher * Francesco Apruzzese * Dave Lasley +* Aitor Bouzas + +Other credits +~~~~~~~~~~~~~ + +* Icon park: `Icon http://icon-park.com/icon/location-map-pin-orange3/` +Maintainers +~~~~~~~~~~~ -Maintainer ----------- +This module is maintained by the OCA. .. image:: https://odoo-community.org/logo.png :alt: Odoo Community Association :target: https://odoo-community.org -This module is maintained by the OCA. - OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -To contribute to this module, please visit https://odoo-community.org. +This module is part of the `OCA/partner-contact `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/base_location/__init__.py b/base_location/__init__.py index e2aed6e67..69f7babdf 100644 --- a/base_location/__init__.py +++ b/base_location/__init__.py @@ -1,4 +1,3 @@ -# Copyright 2016 Nicolas Bessi, Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import models diff --git a/base_location/__openerp__.py b/base_location/__openerp__.py index 1d4810eba..532856707 100644 --- a/base_location/__openerp__.py +++ b/base_location/__openerp__.py @@ -4,26 +4,30 @@ { 'name': 'Location management (aka Better ZIP)', - 'version': '11.0.1.0.1', + 'version': '12.0.1.0.0', 'depends': [ - 'base_address_city' + 'base_address_city', + 'contacts', ], 'author': "Camptocamp," "ACYSOS S.L.," "Alejandro Santana," "Tecnativa," + "AdaptiveCity," "Odoo Community Association (OCA)", 'license': "AGPL-3", 'summary': '''Enhanced zip/npa management system''', - 'website': 'http://www.camptocamp.com', - 'data': ['views/better_zip_view.xml', - 'views/state_view.xml', - 'views/res_country_view.xml', - 'views/company_view.xml', - 'views/partner_view.xml', - 'security/ir.model.access.csv'], + 'website': 'https://github.com/OCA/partner-contact', + 'data': [ + 'security/ir.model.access.csv', + 'views/res_city_zip_view.xml', + 'views/res_city_view.xml', + 'views/res_country_view.xml', + 'views/res_company_view.xml', + 'views/res_partner_view.xml', + ], 'demo': [ - 'demo/better_zip.xml', + 'demo/res_city_zip.xml', ], 'installable': True, 'auto_install': False, diff --git a/base_location/demo/better_zip.xml b/base_location/demo/better_zip.xml deleted file mode 100644 index 0c7f3b9ee..000000000 --- a/base_location/demo/better_zip.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - 1000 - Brussels - - - diff --git a/base_location/demo/res_city_zip.xml b/base_location/demo/res_city_zip.xml new file mode 100644 index 000000000..e940e49f1 --- /dev/null +++ b/base_location/demo/res_city_zip.xml @@ -0,0 +1,13 @@ + + + + + Brussels + + + + + 1000 + + + diff --git a/base_location/models/__init__.py b/base_location/models/__init__.py index 3f7a893a5..1fb23520f 100644 --- a/base_location/models/__init__.py +++ b/base_location/models/__init__.py @@ -1,7 +1,6 @@ -# Copyright 2016 Nicolas Bessi, Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from . import better_zip -from . import partner -from . import state -from . import company +from . import res_city_zip +from . import res_partner +from . import res_company +from . import res_city diff --git a/base_location/models/better_zip.py b/base_location/models/better_zip.py deleted file mode 100644 index 991ffa6c8..000000000 --- a/base_location/models/better_zip.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright 2016 Nicolas Bessi, Camptocamp SA -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import api, fields, models, _ -from odoo.exceptions import ValidationError - - -class BetterZip(models.Model): - '''City/locations completion object''' - - _name = "res.better.zip" - _description = __doc__ - _order = "name asc" - - name = fields.Char('ZIP') - code = fields.Char( - 'City Code', - size=64, - help="The official code for the city" - ) - city = fields.Char('City', required=True) - city_id = fields.Many2one( - 'res.city', - 'City', - ) - state_id = fields.Many2one( - 'res.country.state', - 'State', - ) - country_id = fields.Many2one('res.country', 'Country') - enforce_cities = fields.Boolean( - related='country_id.enforce_cities', - readonly=True, - ) - latitude = fields.Float() - longitude = fields.Float() - - @api.multi - @api.depends('name', 'city', 'state_id', 'country_id') - def name_get(self): - result = [] - for rec in self: - name = [] - if rec.name: - name.append('%(name)s' % {'name': rec.name}) - name.append('%(name)s' % {'name': rec.city}) - if rec.state_id: - name.append('%(name)s' % {'name': rec.state_id.name}) - if rec.country_id: - name.append('%(name)s' % {'name': rec.country_id.name}) - result.append((rec.id, ", ".join(name))) - return result - - @api.model - def name_search(self, name='', args=None, operator='ilike', limit=100): - args = list(args or []) - args += ['|', ('city', operator, name), - '|', ('name', operator, name), ('code', operator, name)] - recs = self.search(args, limit=limit) - return recs.name_get() - - @api.onchange('country_id') - def _onchange_country_id(self): - if self.state_id.country_id != self.country_id: - self.state_id = False - if self.city_id.country_id != self.country_id: - self.city_id = False - if self.country_id: - domain = [('country_id', '=', self.country_id.id)] - else: - domain = [] - return { - 'domain': { - 'state_id': domain, - 'city_id': domain, - } - } - - @api.onchange('city_id') - def _onchange_city_id(self): - if self.city_id: - self.city = self.city_id.name - self.country_id = self.city_id.country_id - self.state_id = self.city_id.state_id - - @api.onchange('state_id') - def _onchange_state_id(self): - if self.state_id: - self.country_id = self.state_id.country_id - - @api.constrains('state_id', 'country_id', 'city_id') - def constrains_country(self): - for rec in self: - if rec.state_id and rec.state_id.country_id != \ - rec.country_id: - raise ValidationError(_( - "The country of the state differs from the country in " - "location %s") % rec.name) - if rec.city_id and rec.city_id.country_id \ - != rec.country_id: - raise ValidationError(_( - "The country of the city differs from the country in " - "location %s") % rec.name) - if rec.city_id and rec.city_id.state_id \ - != rec.state_id: - raise ValidationError(_( - "The state of the city differs from the state in " - "location %s") % rec.name) diff --git a/base_location/models/res_city.py b/base_location/models/res_city.py new file mode 100644 index 000000000..629268b5d --- /dev/null +++ b/base_location/models/res_city.py @@ -0,0 +1,19 @@ +# Copyright 2018 Aitor Bouzas +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class City(models.Model): + _inherit = 'res.city' + + zip_ids = fields.One2many('res.city.zip', 'city_id', + string="Zips in this city") + + _sql_constraints = [ + ('name_state_country_uniq', + 'UNIQUE(name, state_id, country_id)', + 'You already have a city with that name in the same state.' + 'The city must have a unique name within ' + 'it\'s state and it\'s country'), + ] diff --git a/base_location/models/res_city_zip.py b/base_location/models/res_city_zip.py new file mode 100644 index 000000000..12c679a96 --- /dev/null +++ b/base_location/models/res_city_zip.py @@ -0,0 +1,40 @@ +# Copyright 2016 Nicolas Bessi, Camptocamp SA +# Copyright 2018 Aitor Bouzas +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class ResCityZip(models.Model): + """City/locations completion object""" + + _name = "res.city.zip" + _description = __doc__ + _order = "name asc" + _rec_name = "display_name" + + name = fields.Char('ZIP', required=True) + city_id = fields.Many2one( + 'res.city', + 'City', + required=True, + ) + display_name = fields.Char(compute='_compute_new_display_name', + store=True, index=True) + + _sql_constraints = [ + ('name_city_uniq', 'UNIQUE(name, city_id)', + 'You already have a zip with that code in the same city. ' + 'The zip code must be unique within it\'s city'), + ] + + @api.multi + @api.depends('name', 'city_id') + def _compute_new_display_name(self): + for rec in self: + name = [rec.name, rec.city_id.name] + if rec.city_id.state_id: + name.append(rec.city_id.state_id.name) + if rec.city_id.country_id: + name.append(rec.city_id.country_id.name) + rec.display_name = ", ".join(name) diff --git a/base_location/models/company.py b/base_location/models/res_company.py similarity index 62% rename from base_location/models/company.py rename to base_location/models/res_company.py index 1016a1d23..f78642aa9 100644 --- a/base_location/models/company.py +++ b/base_location/models/res_company.py @@ -8,38 +8,39 @@ from odoo import models, fields, api class ResCompany(models.Model): _inherit = 'res.company' + # In order to keep the same logic used in Odoo, fields must be computed + # and inversed, not related. This way we can ensure that it works + # correctly on changes and that inconsistencies cannot happen. + # When you make the fields related, the constrains added in res.partner + # will fail, because when you change the city_id in the company, you are + # effectively changing it in the partner. The constrains on the partner + # are evaluated before the inverse methods update the other fields (city, + # etc..) so we need them to ensure consistency. + # As a conclusion, address fields are very related to each other. + # Either you make them all related to the partner in company, or you + # don't for all of them. Mixing both approaches produces inconsistencies. + city_id = fields.Many2one( 'res.city', compute='_compute_address', inverse='_inverse_city_id', - string="City" + string="City ID" ) zip_id = fields.Many2one( - 'res.better.zip', + 'res.city.zip', string='ZIP Location', compute='_compute_address', inverse='_inverse_zip_id', oldname="better_zip_id", help='Use the city name or the zip code to search the location', ) - # In order to keep the same logic used in odoo, fields must be computed - # and inversed, not related. This way, we can ensure that it works - # correctly on changes and inconsistencies cannot happen. - # When you make the fields related, the constrains added in res.partner - # will fail. because when you change the city_id in the company, you are - # effectively changing it in the partner. The constrains on the partner - # are evaluated before the inverse methods update the other fields (city, - # etc..). And we need constrains in the partner to ensure consistency. - # So, as a conclusion, address fields are very related to each other. - # Either you make them all related to the partner in company, or you - # don't for all of them. But mixing methods produces inconsistencies. country_enforce_cities = fields.Boolean( related='country_id.enforce_cities' ) def _get_company_address_fields(self, partner): - res = super(ResCompany, self)._get_company_address_fields(partner) + res = super()._get_company_address_fields(partner) res['city_id'] = partner.city_id res['zip_id'] = partner.zip_id return res @@ -55,16 +56,15 @@ class ResCompany(models.Model): @api.onchange('zip_id') def _onchange_zip_id(self): if self.zip_id: - self.zip = self.zip_id.name - self.city_id = self.zip_id.city_id - self.city = self.zip_id.city - self.country_id = self.zip_id.country_id - if self.country_id.enforce_cities: - self.state_id = self.city_id.state_id - else: - self.state_id = self.zip_id.state_id + self.update({ + 'zip': self.zip_id.name, + 'city_id': self.zip_id.city_id, + 'city': self.zip_id.city_id.name, + 'country_id': self.zip_id.city_id.country_id, + 'state_id': self.zip_id.city_id.state_id, + }) @api.onchange('state_id') - def onchange_state_id(self): + def _onchange_state_id(self): if self.state_id.country_id: self.country_id = self.state_id.country_id.id diff --git a/base_location/models/partner.py b/base_location/models/res_partner.py similarity index 55% rename from base_location/models/partner.py rename to base_location/models/res_partner.py index befddd1ff..273dc3027 100644 --- a/base_location/models/partner.py +++ b/base_location/models/res_partner.py @@ -8,16 +8,19 @@ from odoo.exceptions import ValidationError class ResPartner(models.Model): _inherit = 'res.partner' - zip_id = fields.Many2one('res.better.zip', 'ZIP Location') + + zip_id = fields.Many2one('res.city.zip', 'ZIP Location') @api.onchange('city_id') def _onchange_city_id(self): if not self.zip_id: - super(ResPartner, self)._onchange_city_id() + super()._onchange_city_id() if self.zip_id and self.city_id != self.zip_id.city_id: - self.zip_id = False - self.zip = False - self.city = False + self.update({ + 'zip_id': False, + 'zip': False, + 'city': False, + }) if self.city_id: return { 'domain': { @@ -26,38 +29,37 @@ class ResPartner(models.Model): } return {'domain': {'zip_id': []}} - @api.onchange('state_id') - def _onchange_state_id(self): - if self.zip_id and self.state_id != self.zip_id.state_id: - self.zip_id = False - self.zip = False - self.city = False - @api.onchange('country_id') def _onchange_country_id(self): - res = super(ResPartner, self)._onchange_country_id() - if self.zip_id and self.zip_id.country_id != self.country_id: + res = super()._onchange_country_id() + if self.zip_id and self.zip_id.city_id.country_id != self.country_id: self.zip_id = False return res @api.onchange('zip_id') def _onchange_zip_id(self): if self.zip_id: - self.country_id = self.zip_id.country_id - if self.country_id.enforce_cities: - self.city_id = self.zip_id.city_id - self.zip = self.zip_id.name - self.state_id = self.zip_id.state_id - self.city = self.zip_id.city + vals = { + 'city_id': self.zip_id.city_id, + 'zip': self.zip_id.name, + 'city': self.zip_id.city_id.name, + } + if self.zip_id.city_id.country_id: + vals.update({'country_id': self.zip_id.city_id.country_id}) + if self.zip_id.city_id.state_id: + vals.update({'state_id': self.zip_id.city_id.state_id}) + self.update(vals) @api.constrains('zip_id', 'country_id', 'city_id', 'state_id') def _check_zip(self): - for rec in self.filtered('zip_id'): - if rec.zip_id.state_id != rec.state_id: + for rec in self: + if not rec.zip_id: + continue + if rec.zip_id.city_id.state_id != rec.state_id: raise ValidationError(_( "The state of the partner %s differs from that in " "location %s") % (rec.name, rec.zip_id.name)) - if rec.zip_id.country_id != rec.country_id: + if rec.zip_id.city_id.country_id != rec.country_id: raise ValidationError(_( "The country of the partner %s differs from that in " "location %s") % (rec.name, rec.zip_id.name)) @@ -67,6 +69,14 @@ class ResPartner(models.Model): "location %s") % (rec.name, rec.zip_id.name)) @api.onchange('state_id') - def onchange_state_id(self): + def _onchange_state_id(self): + vals = {} if self.state_id.country_id: - self.country_id = self.state_id.country_id.id + vals.update({'country_id': self.state_id.country_id}) + if self.zip_id and self.state_id != self.zip_id.city_id.state_id: + vals.update({ + 'zip_id': False, + 'zip': False, + 'city': False, + }) + self.update(vals) diff --git a/base_location/models/state.py b/base_location/models/state.py deleted file mode 100644 index 5161a34a6..000000000 --- a/base_location/models/state.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright 2016 Nicolas Bessi, Camptocamp SA -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import models, fields - - -class ResCountryState(models.Model): - - _inherit = 'res.country.state' - - better_zip_ids = fields.One2many('res.better.zip', 'state_id', 'Cities') diff --git a/base_location/readme/CONFIGURE.rst b/base_location/readme/CONFIGURE.rst new file mode 100644 index 000000000..32d87344b --- /dev/null +++ b/base_location/readme/CONFIGURE.rst @@ -0,0 +1,10 @@ +#. Go to *Contacts / Configuration / Localization / Cities*. +#. Create a new City. + +#. Go to *Contacts / Configuration / Localization / Zips*. +#. Create a new Zip and relate it to the city (you can also create the Zip from the City). + +or, with module 'Contacts Directory' installed: +#. Go to *Contacts / Configuration / Localization / Countries*. +#. Locate the desired country. +#. Press on the button 'Cities' / 'Zips'. diff --git a/base_location/readme/CONTRIBUTORS.rst b/base_location/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..b90283688 --- /dev/null +++ b/base_location/readme/CONTRIBUTORS.rst @@ -0,0 +1,9 @@ +* Nicolas Bessi (Camptocamp) +* Ignacio Ibeas (Acysos S.L.) +* Pedro M. Baeza +* Alejandro Santana +* Sandy Carter +* Yannick Vaucher +* Francesco Apruzzese +* Dave Lasley +* Aitor Bouzas diff --git a/base_location/readme/CREDITS.rst b/base_location/readme/CREDITS.rst new file mode 100644 index 000000000..e84ac4aed --- /dev/null +++ b/base_location/readme/CREDITS.rst @@ -0,0 +1 @@ +* Icon park: `Icon http://icon-park.com/icon/location-map-pin-orange3/` diff --git a/base_location/readme/DESCRIPTION.rst b/base_location/readme/DESCRIPTION.rst new file mode 100644 index 000000000..c4afc713f --- /dev/null +++ b/base_location/readme/DESCRIPTION.rst @@ -0,0 +1,5 @@ +This module introduces a zip model that allows you to manage locations in a better way. + +The zips will allow the users to complete automatically all address-related fields by just filling the zip. + +Also allows different search filters. diff --git a/base_location/readme/USAGE.rst b/base_location/readme/USAGE.rst new file mode 100644 index 000000000..bc8bfc983 --- /dev/null +++ b/base_location/readme/USAGE.rst @@ -0,0 +1,3 @@ +#. Access a partner record +#. Fill the field *Location completion* +#. Information about country, state, city and zip will be filled automatically diff --git a/base_location/security/ir.model.access.csv b/base_location/security/ir.model.access.csv index 5ef6c8b46..e0699ce7b 100644 --- a/base_location/security/ir.model.access.csv +++ b/base_location/security/ir.model.access.csv @@ -1,3 +1,2 @@ "id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" -"ir_model_access_betterzip0","res_better_zip group_user_all","model_res_better_zip",base.group_user,1,0,0,0 -"ir_model_access_betterzip1","res_better_zip group_user","model_res_better_zip","base.group_partner_manager",1,1,1,1 +"ir_model_access_cityzip0","res_city_zip group_user","model_res_city_zip","base.group_partner_manager",1,1,1,1 diff --git a/base_location/static/description/index.html b/base_location/static/description/index.html new file mode 100644 index 000000000..2ed60fbde --- /dev/null +++ b/base_location/static/description/index.html @@ -0,0 +1,463 @@ + + + + + + +Location management (aka Better ZIP) + + + +
+

Location management (aka Better ZIP)

+ + +

Beta License: AGPL-3 OCA/partner-contact Translate me on Weblate Try me on Runbot

+

This module introduces a zip model that allows you to manage locations in a better way.

+

The zips will allow the users to complete automatically all address-related fields by just filling the zip.

+

Also allows different search filters.

+

Table of contents

+ +
+

Configuration

+
    +
  1. Go to Contacts / Configuration / Localization / Cities.
  2. +
  3. Create a new City.
  4. +
  5. Go to Contacts / Configuration / Localization / Zips.
  6. +
  7. Create a new Zip and relate it to the city (you can also create the Zip from the City).
  8. +
+

or, with module ‘Contacts Directory’ installed: +#. Go to Contacts / Configuration / Localization / Countries. +#. Locate the desired country. +#. Press on the button ‘Cities’ / ‘Zips’.

+
+
+

Usage

+
    +
  1. Access a partner record
  2. +
  3. Fill the field Location completion
  4. +
  5. Information about country, state, city and zip will be filled automatically
  6. +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Camptocamp
  • +
  • ACYSOS S.L.
  • +
  • Alejandro Santana
  • +
  • Tecnativa
  • +
  • AdaptiveCity
  • +
+
+
+

Contributors

+ +
+
+

Other credits

+
    +
  • Icon park: Icon http://icon-park.com/icon/location-map-pin-orange3/
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/partner-contact project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/base_location/tests/__init__.py b/base_location/tests/__init__.py index 372da9775..9816762b0 100644 --- a/base_location/tests/__init__.py +++ b/base_location/tests/__init__.py @@ -1,4 +1,3 @@ -# Copyright 2015 Yannick Vaucher, Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import test_base_location diff --git a/base_location/tests/test_base_location.py b/base_location/tests/test_base_location.py index b86817bcb..eef4aad61 100644 --- a/base_location/tests/test_base_location.py +++ b/base_location/tests/test_base_location.py @@ -1,94 +1,119 @@ # Copyright 2015 Yannick Vaucher, Camptocamp SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo.tests.common import TransactionCase from odoo.exceptions import ValidationError +from odoo.tests import tagged, common +from odoo.tools.misc import mute_logger +import psycopg2 + + +@tagged('post_install', '-at_install') +class TestBaseLocation(common.SavepointCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + country_obj = cls.env['res.country.state'] + city_obj = cls.env['res.city'] + zip_obj = cls.env['res.city.zip'] + cls.partner_obj = cls.env['res.partner'] + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + cls.state_vd = country_obj.create({ + 'name': 'Vaud', + 'code': 'VD', + 'country_id': cls.env.ref('base.ch').id, + }) + cls.env.ref('base.es').write({ + 'enforce_cities': True + }) + cls.company = cls.env.ref('base.main_company') - -class TestBaseLocation(TransactionCase): - - def test_onchange_better_zip_state_id(self): - """ Test onchange on res.better.zip """ - usa_MA = self.env.ref('base.state_us_34') - better_zip1 = self.env['res.better.zip'].new(self.values_better_zip1()) - better_zip1.state_id = usa_MA - better_zip1._onchange_state_id() - self.assertEqual(better_zip1.country_id, usa_MA.country_id) - - def test_onchange_better_zip_city_id(self): - better_zip2 = self.env['res.better.zip'].new(self.values_better_zip2()) - better_zip2.city_id = self.city_madrid - better_zip2._onchange_city_id() - self.assertEqual(better_zip2.city, self.city_madrid.name) - - def test_onchange_better_zip_country_id(self): - better_zip1 = self.env['res.better.zip'].new(self.values_better_zip1()) - better_zip1.country_id = self.env.ref('base.es') - better_zip1._onchange_country_id() - self.assertFalse(better_zip1.state_id) - - def test_onchange_better_zip_none(self): - better_zip1 = self.env['res.better.zip'].new(self.values_better_zip1()) - better_zip1.country_id = False - better_zip1._onchange_country_id() - self.assertFalse(better_zip1.state_id) + cls.state_bcn = country_obj.create({ + 'name': 'Barcelona', + 'code': '08', + 'country_id': cls.env.ref('base.es').id, + }) + cls.state_madrid = country_obj.create({ + 'name': 'Madrid', + 'code': '28', + 'country_id': cls.env.ref('base.es').id, + }) + cls.city_bcn = city_obj.create({ + 'name': 'Barcelona', + 'state_id': cls.state_bcn.id, + 'country_id': cls.env.ref('base.es').id, + }) + cls.city_madrid = city_obj.create({ + 'name': 'Madrid', + 'state_id': cls.state_madrid.id, + 'country_id': cls.env.ref('base.es').id, + }) + cls.city_lausanne = city_obj.create({ + 'name': 'Lausanne', + 'state_id': cls.state_vd.id, + 'country_id': cls.env.ref('base.ch').id, + }) + cls.lausanne = zip_obj.create({ + 'name': '666', + 'city_id': cls.city_lausanne.id, + }) + cls.barcelona = zip_obj.create({ + 'name': '444', + 'city_id': cls.city_bcn.id, + }) def test_onchange_partner_city_completion(self): - partner1 = self.env['res.partner'].new({ + """Test that partner data is filled accodingly""" + partner1 = self.partner_obj.new({ 'name': 'Camptocamp', }) - better_zip2 = self.env['res.better.zip'].create( - self.values_better_zip2()) - better_zip2.country_id.enforce_cities = True - partner1.zip_id = better_zip2 + self.barcelona.city_id.country_id.enforce_cities = True + partner1.zip_id = self.barcelona partner1._onchange_zip_id() - self.assertEqual(partner1.zip, better_zip2.name) - self.assertEqual(partner1.city, better_zip2.city) - self.assertEqual(partner1.state_id, better_zip2.state_id) - self.assertEqual(partner1.country_id, better_zip2.country_id) + self.assertEqual(partner1.zip, self.barcelona.name) + self.assertEqual(partner1.city, self.barcelona.city_id.name) + self.assertEqual(partner1.state_id, self.barcelona.city_id.state_id) + self.assertEqual(partner1.country_id, + self.barcelona.city_id.country_id) def test_onchange_company_city_completion(self): + """Test that company data is filled accodingly""" company = self.env['res.company'].new({'name': 'Test'}) - better_zip1 = self.env['res.better.zip'].create( - self.values_better_zip1()) - company.zip_id = better_zip1 + company.zip_id = self.lausanne company._onchange_zip_id() - self.assertEqual(company.zip, better_zip1.name) - self.assertEqual(company.city, better_zip1.city) - self.assertEqual(company.state_id, better_zip1.state_id) - self.assertEqual(company.country_id, better_zip1.country_id) + self.assertEqual(company.zip, self.lausanne.name) + self.assertEqual(company.city, self.lausanne.city_id.name) + self.assertEqual(company.state_id, self.lausanne.city_id.state_id) + self.assertEqual(company.country_id, self.lausanne.city_id.country_id) def test_company_address_fields(self): - better_zip1 = self.env['res.better.zip'].create( - self.values_better_zip1() - ) + """Test if the partner address fields changes when + changing the ones from the company""" company = self.env['res.company'].create({ 'name': 'Test', }) self.assertTrue(company.partner_id) company.partner_id.write({ - 'zip_id': better_zip1.id, - 'state_id': better_zip1.state_id.id, - 'country_id': better_zip1.country_id.id, - 'city_id': better_zip1.city_id.id, - 'city': better_zip1.city, - 'zip': better_zip1.name, + 'zip_id': self.lausanne.id, + 'state_id': self.lausanne.city_id.state_id.id, + 'country_id': self.lausanne.city_id.country_id.id, + 'city_id': self.lausanne.city_id.id, + 'city': self.lausanne.city_id.name, + 'zip': self.lausanne.name, }) company._compute_address() self.assertEqual(company.zip_id, company.partner_id.zip_id) self.assertEqual(company.city_id, company.partner_id.city_id) def test_company_address_fields_inverse(self): - better_zip2 = self.env['res.better.zip'].create( - self.values_better_zip2() - ) + """Test inverse fields from res.company""" company = self.env['res.company'].new({ 'name': 'Test', - 'partner_id': self.env['res.partner'].new({}).id + 'partner_id': self.partner_obj.new({}).id # Partner must be initiated in order to be filled }) company.update({ - 'zip_id': better_zip2.id, + 'zip_id': self.barcelona.id, }) company._inverse_city_id() company._inverse_zip_id() @@ -96,112 +121,76 @@ class TestBaseLocation(TransactionCase): self.assertEqual(company.city_id, company.partner_id.city_id) def test_onchange_company_city_id_completion(self): + """Test city auto-completion when changing zip in a company""" company = self.env['res.company'].new({'name': 'Test'}) - better_zip2 = self.env['res.better.zip'].create( - self.values_better_zip2()) - company.zip_id = better_zip2 + company.zip_id = self.barcelona company._onchange_zip_id() - self.assertEqual(company.city_id, better_zip2.city_id) - - def test_constrains_better_zip_01(self): - better_zip1 = self.env['res.better.zip'].create( - self.values_better_zip1()) - better_zip2 = self.env['res.better.zip'].create( - self.values_better_zip2()) - - better_zip1.city_id = self.city_lausanne - with self.assertRaises(ValidationError): - better_zip2.city_id = better_zip1.city_id - - def test_constrains_better_zip_02(self): - better_zip1 = self.env['res.better.zip'].create( - self.values_better_zip1()) - better_zip2 = self.env['res.better.zip'].create( - self.values_better_zip2()) - - with self.assertRaises(ValidationError): - better_zip2.country_id = better_zip1.country_id - - def test_constrains_better_zip_03(self): - better_zip1 = self.env['res.better.zip'].create( - self.values_better_zip1()) - better_zip2 = self.env['res.better.zip'].create( - self.values_better_zip2()) - - with self.assertRaises(ValidationError): - better_zip2.state_id = better_zip1.state_id - - def test_constrains_better_zip_04(self): - better_zip2 = self.env['res.better.zip'].create( - self.values_better_zip2()) - - with self.assertRaises(ValidationError): - better_zip2.city_id = self.city_madrid + self.assertEqual(company.city_id, self.barcelona.city_id) def test_constrains_partner_01(self): - better_zip2 = self.env['res.better.zip'].create( - self.values_better_zip2()) + """Test partner 1 constraints""" with self.assertRaises(ValidationError): - self.env['res.partner'].create({ + self.partner_obj.create({ 'name': 'P1', - 'zip_id': better_zip2.id, + 'zip_id': self.barcelona.id, }) - def test_constrains_partner_02(self): - better_zip2 = self.env['res.better.zip'].create( - self.values_better_zip2()) - partner = self.env['res.partner'].create({ + def test_constrains_partner_country(self): + """Test partner country constraints""" + partner = self.partner_obj.create({ 'name': 'P1', - 'zip_id': better_zip2.id, - 'country_id': better_zip2.country_id.id, - 'state_id': better_zip2.state_id.id, - 'city_id': better_zip2.city_id.id, + 'zip_id': self.barcelona.id, + 'country_id': self.barcelona.city_id.country_id.id, + 'state_id': self.barcelona.city_id.state_id.id, + 'city_id': self.barcelona.city_id.id, }) with self.assertRaises(ValidationError): partner.country_id = self.ref('base.ch') - with self.assertRaises(ValidationError): - partner.state_id = self.state_vd.id, + def test_constrains_partner_state(self): + """Test partner state constraints""" + partner = self.partner_obj.create({ + 'name': 'P1', + 'zip_id': self.barcelona.id, + 'country_id': self.barcelona.city_id.country_id.id, + 'state_id': self.barcelona.city_id.state_id.id, + 'city_id': self.barcelona.city_id.id, + }) with self.assertRaises(ValidationError): - partner.city_id = self.city_lausanne + partner.state_id = self.state_vd.id - def values_better_zip1(self): - return { - 'name': 1000, - 'city': 'Lausanne', - 'state_id': self.state_vd.id, - 'country_id': self.ref('base.ch'), - } + def test_constrains_partner_city(self): + """Test partner city constraints""" + partner = self.partner_obj.create({ + 'name': 'P1', + 'zip_id': self.barcelona.id, + 'country_id': self.barcelona.city_id.country_id.id, + 'state_id': self.barcelona.city_id.state_id.id, + 'city_id': self.barcelona.city_id.id, + }) - def values_better_zip2(self): - return { - 'city_id': self.city_bcn.id, - 'city': self.city_bcn.name, - 'state_id': self.state_bcn.id, - 'country_id': self.ref('base.es'), - } + with self.assertRaises(ValidationError): + partner.city_id = self.city_lausanne def test_partner_onchange_country(self): - country_es = self.browse_ref('base.es') + """Test partner onchange country_id""" + country_es = self.env.ref('base.es') country_es.enforce_cities = True - better_zip1 = self.env['res.better.zip'].create( - self.values_better_zip1()) - partner = self.env['res.partner'].new({ + partner = self.partner_obj.new({ 'name': 'TEST', - 'zip_id': better_zip1.id + 'zip_id': self.lausanne.id }) partner.country_id = country_es partner._onchange_country_id() self.assertFalse(partner.zip_id) def test_partner_onchange_city(self): - better_zip1 = self.env['res.better.zip'].create( - self.values_better_zip1()) - partner = self.env['res.partner'].new({ + """Test partner onchange city_id""" + partner = self.partner_obj.new({ 'name': 'TEST', - 'zip_id': better_zip1.id + 'zip_id': self.lausanne.id }) self.city_bcn.country_id.enforce_cities = False partner.city_id = self.city_bcn @@ -212,119 +201,78 @@ class TestBaseLocation(TransactionCase): self.assertFalse(res['domain']['zip_id']) def test_partner_onchange_state(self): - better_zip1 = self.env['res.better.zip'].create( - self.values_better_zip1()) - partner = self.env['res.partner'].new({ + """Test partner onchange state_id""" + partner = self.partner_obj.new({ 'name': 'TEST', - 'zip_id': better_zip1.id + 'zip_id': self.lausanne.id }) partner.state_id = self.state_bcn partner._onchange_state_id() self.assertFalse(partner.zip_id) + self.assertEqual(partner.country_id, partner.state_id.country_id) + + def test_company_onchange_state(self): + """Test company onchange state_id""" + self.company.state_id = self.state_bcn + self.company._onchange_state_id() + self.assertEqual(self.company.country_id, + self.company.state_id.country_id) def test_display_name(self): - better_zip1 = self.env['res.better.zip'].create( - self.values_better_zip1()) + """Test if the display_name is stored and computed properly""" self.assertEqual( - better_zip1.display_name, '1000, Lausanne, Vaud, '+self.browse_ref( + self.lausanne.display_name, + '666, Lausanne, Vaud, ' + self.browse_ref( 'base.ch' ).name ) - def test_name_search___can_find_using_city_name_or_zip_or_code(self): - barcelona_data = { - 'city_id': self.city_bcn.id, - 'city': self.city_bcn.name, - 'name': '444', - 'code': 'BA', - 'state_id': self.state_bcn.id, - 'country_id': self.ref('base.es'), - } + def test_name_search(self): + """Test that zips can be searched through both the name of the + city or the zip code""" madrid_data = { 'city_id': self.city_madrid.id, - 'city': self.city_madrid.name, 'name': '555', - 'code': 'MD', - 'state_id': self.state_madrid.id, - 'country_id': self.ref('base.es'), - } - lausanne_data = { - 'city_id': self.city_lausanne.id, - 'city': self.city_lausanne.name, - 'name': '666', - 'code': 'LA', - 'state_id': self.state_vd.id, - 'country_id': self.ref('base.ch'), } - barcelona = self.env['res.better.zip'].create(barcelona_data) - madrid = self.env['res.better.zip'].create(madrid_data) - lausanne = self.env['res.better.zip'].create(lausanne_data) + madrid = self.env['res.city.zip'].create(madrid_data) - found_recs = self.env['res.better.zip'].name_search(name='444') + found_recs = self.env['res.city.zip'].name_search(name='444') self.assertEqual(len(found_recs), 1) - self.assertEqual(found_recs[0][0], barcelona.id) - found_recs = self.env['res.better.zip'].name_search(name='Barcelona') + self.assertEqual(found_recs[0][0], self.barcelona.id) + found_recs = self.env['res.city.zip'].name_search(name='Barcelona') self.assertEqual(len(found_recs), 1) - self.assertEqual(found_recs[0][0], barcelona.id) - found_recs = self.env['res.better.zip'].name_search(name='BA') - self.assertEqual(len(found_recs), 1) - self.assertEqual(found_recs[0][0], barcelona.id) + self.assertEqual(found_recs[0][0], self.barcelona.id) - found_recs = self.env['res.better.zip'].name_search(name='555') - self.assertEqual(len(found_recs), 1) - self.assertEqual(found_recs[0][0], madrid.id) - found_recs = self.env['res.better.zip'].name_search(name='Madrid') + found_recs = self.env['res.city.zip'].name_search(name='555') self.assertEqual(len(found_recs), 1) self.assertEqual(found_recs[0][0], madrid.id) - found_recs = self.env['res.better.zip'].name_search(name='MD') + found_recs = self.env['res.city.zip'].name_search(name='Madrid') self.assertEqual(len(found_recs), 1) self.assertEqual(found_recs[0][0], madrid.id) - found_recs = self.env['res.better.zip'].name_search(name='666') + found_recs = self.env['res.city.zip'].name_search(name='666') self.assertEqual(len(found_recs), 1) - self.assertEqual(found_recs[0][0], lausanne.id) - found_recs = self.env['res.better.zip'].name_search(name='Lausanne') + self.assertEqual(found_recs[0][0], self.lausanne.id) + found_recs = self.env['res.city.zip'].name_search(name='Lausanne') self.assertEqual(len(found_recs), 1) - self.assertEqual(found_recs[0][0], lausanne.id) - found_recs = self.env['res.better.zip'].name_search(name='LA') - self.assertEqual(len(found_recs), 1) - self.assertEqual(found_recs[0][0], lausanne.id) - - def setUp(self): - super(TestBaseLocation, self).setUp() - self.state_vd = self.env['res.country.state'].create({ - 'name': 'Vaud', - 'code': 'VD', - 'country_id': self.ref('base.ch'), - }) - self.env['res.country'].browse(self.ref('base.es')).write({ - 'enforce_cities': True - }) - self.company = self.env.ref('base.main_company') + self.assertEqual(found_recs[0][0], self.lausanne.id) + + def test_zip_ql_constraints(self): + """Test UNIQUE name within it's area for zips""" + with self.assertRaises( + psycopg2.IntegrityError), mute_logger('odoo.sql_db'): + self.env['res.city.zip'].create({ + 'name': '666', + 'city_id': self.city_lausanne.id, + }) - self.state_bcn = self.env['res.country.state'].create({ - 'name': 'Barcelona', - 'code': '08', - 'country_id': self.ref('base.es'), - }) - self.state_madrid = self.env['res.country.state'].create({ - 'name': 'Madrid', - 'code': '28', - 'country_id': self.ref('base.es'), - }) - self.city_bcn = self.env['res.city'].create({ - 'name': 'Barcelona', - 'state_id': self.state_bcn.id, - 'country_id': self.ref('base.es'), - }) - self.city_madrid = self.env['res.city'].create({ - 'name': 'Madrid', - 'state_id': self.state_madrid.id, - 'country_id': self.ref('base.es'), - }) - self.city_lausanne = self.env['res.city'].create({ - 'name': 'Lausanne', - 'state_id': self.state_vd.id, - 'country_id': self.ref('base.ch'), - }) + def test_city_sql_contraint(self): + """Test UNIQUE name within it's area for cities""" + with self.assertRaises( + psycopg2.IntegrityError), mute_logger('odoo.sql_db'): + self.env['res.city'].create({ + 'name': 'Barcelona', + 'state_id': self.state_bcn.id, + 'country_id': self.ref('base.es'), + }) diff --git a/base_location/views/better_zip_view.xml b/base_location/views/better_zip_view.xml deleted file mode 100644 index 207c88e92..000000000 --- a/base_location/views/better_zip_view.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - res.better.zip.form - res.better.zip - -
- - - - - - - - - - - - - -
-
-
- - - res.better.zip.tree - res.better.zip - - - - - - - - - - - - - res.better.zip.select - res.better.zip - - - - - - - - - - - - - - - - - Locations - res.better.zip - form - tree,form - - - - - - - - -
diff --git a/base_location/views/res_city_view.xml b/base_location/views/res_city_view.xml new file mode 100644 index 000000000..5ad6fb9aa --- /dev/null +++ b/base_location/views/res_city_view.xml @@ -0,0 +1,63 @@ + + + + + res.city + + + + + + + 1 + + + + + + + + + res.city + +
+ + + + + + + + + + +
+
+
+ + + Cities + ir.actions.act_window + res.city + form + tree,form + + + Display and manage the list of all cities that can be assigned to + your partner records. Note that an option can be set on each country separately + to enforce any address of it to have a city in this list. + + + + + +
diff --git a/base_location/views/res_city_zip_view.xml b/base_location/views/res_city_zip_view.xml new file mode 100644 index 000000000..3eeb5f744 --- /dev/null +++ b/base_location/views/res_city_zip_view.xml @@ -0,0 +1,56 @@ + + + + + res.city.zip.form + res.city.zip + +
+ + + + +
+
+
+ + + res.city.zip.tree + res.city.zip + + + + + + + + + + res.city.zip.select + res.city.zip + + + + + + + + + + Locations + res.city.zip + form + tree,form + + + + + + +
diff --git a/base_location/views/company_view.xml b/base_location/views/res_company_view.xml similarity index 100% rename from base_location/views/company_view.xml rename to base_location/views/res_company_view.xml diff --git a/base_location/views/res_country_view.xml b/base_location/views/res_country_view.xml index cbf0f3ff3..0292f52ab 100644 --- a/base_location/views/res_country_view.xml +++ b/base_location/views/res_country_view.xml @@ -22,7 +22,7 @@ icon="fa-globe" type="action" context="{'default_country_id': active_id, 'search_default_country_id': active_id}" - string="Locations"> + string="Zips"> diff --git a/base_location/views/partner_view.xml b/base_location/views/res_partner_view.xml similarity index 99% rename from base_location/views/partner_view.xml rename to base_location/views/res_partner_view.xml index 1e8a016e5..e10fbd474 100644 --- a/base_location/views/partner_view.xml +++ b/base_location/views/res_partner_view.xml @@ -23,4 +23,3 @@ - diff --git a/base_location/views/state_view.xml b/base_location/views/state_view.xml deleted file mode 100644 index cbd4f72aa..000000000 --- a/base_location/views/state_view.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - view_country_state_form2 - res.country.state - - - - - - - - - - - - - - - -