diff --git a/addons/account/models/partner.py b/addons/account/models/partner.py index f81719fdfc88fb6459a0eead8edb21b0689d87ce..192dd6c57fac02beee77d2844de55624c64eb61a 100644 --- a/addons/account/models/partner.py +++ b/addons/account/models/partner.py @@ -2,10 +2,10 @@ from operator import itemgetter import time -from openerp.exceptions import UserError from openerp import api, fields, models, _ from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT +from openerp.exceptions import ValidationError class AccountFiscalPosition(models.Model): @@ -23,16 +23,26 @@ class AccountFiscalPosition(models.Model): note = fields.Text('Notes') auto_apply = fields.Boolean(string='Detect Automatically', help="Apply automatically this fiscal position.") vat_required = fields.Boolean(string='VAT required', help="Apply only if partner has a VAT number.") - country_id = fields.Many2one('res.country', string='Countries', + country_id = fields.Many2one('res.country', string='Country', help="Apply only if delivery or invoicing country match.") country_group_id = fields.Many2one('res.country.group', string='Country Group', help="Apply only if delivery or invocing country match the group.") + state_ids = fields.Many2many('res.country.state', string='Federal States') + zip_from = fields.Integer(string='Zip Range From', default=0) + zip_to = fields.Integer(string='Zip Range To', default=0) + # To be used in hiding the 'Federal States' field('attrs' in view side) when selected 'Country' has 0 states. + states_count = fields.Integer(compute='_compute_states_count') @api.one - @api.constrains('country_id', 'country_group_id') - def _check_country(self): - if self.country_id and self.country_group_id: - raise UserError(_('You can not select a country and a group of countries.')) + def _compute_states_count(self): + self.states_count = len(self.country_id.state_ids) + + @api.one + @api.constrains('zip_from', 'zip_to') + def _check_zip(self): + if self.zip_from > self.zip_to: + raise ValidationError(_('Invalid "Zip Range", please configure it properly.')) + return True @api.v7 def map_tax(self, cr, uid, fposition_id, taxes, context=None): @@ -95,6 +105,49 @@ class AccountFiscalPosition(models.Model): accounts[key] = ref_dict[acc] return accounts + @api.onchange('country_id') + def _onchange_country_id(self): + if self.country_id: + self.zip_from = self.zip_to = self.country_group_id = False + self.state_ids = [(5,)] + self.states_count = len(self.country_id.state_ids) + + @api.onchange('country_group_id') + def _onchange_country_group_id(self): + if self.country_group_id: + self.zip_from = self.zip_to = self.country_id = False + self.state_ids = [(5,)] + + @api.model + def _get_fpos_by_region(self, country_id=False, state_id=False, zipcode=False, vat_required=False): + if not country_id: + return False + domains = [[('auto_apply', '=', True), ('vat_required', '=', vat_required)]] + if vat_required: + # Possibly allow fallback to non-VAT positions, if no VAT-required position matches + domains += [[('auto_apply', '=', True), ('vat_required', '=', False)]] + if zipcode and zipcode.isdigit(): + zipcode = int(zipcode) + domain_zip = [('zip_from', '<=', zipcode), ('zip_to', '>=', zipcode)] + else: + zipcode, domain_zip = 0, [('zip_from', '=', 0), ('zip_to', '=', 0)] + state_domain = [('state_ids', '=', False)] + if state_id: + state_domain = [('state_ids', '=', state_id)] + for domain in domains: + # Build domain to search records with exact matching criteria + fpos_id = self.search(domain + [('country_id', '=', country_id)] + state_domain + domain_zip, limit=1).id + # return records that fit the most the criteria, and fallback on less specific fiscal positions if any can be found + if not fpos_id and zipcode: + fpos_id = self.search(domain + [('country_id', '=', country_id)] + state_domain + [('zip_from', '=', 0), ('zip_to', '=', 0)], limit=1).id + if not fpos_id and state_id: + fpos_id = self.search(domain + [('country_id', '=', country_id)] + [('state_ids', '=', False)] + domain_zip, limit=1).id + if not fpos_id and state_id and zipcode: + fpos_id = self.search(domain + [('country_id', '=', country_id)] + [('state_ids', '=', False)] + [('zip_from', '=', 0), ('zip_to', '=', 0)], limit=1).id + if fpos_id: + return fpos_id + return False + @api.model def get_fiscal_position(self, partner_id, delivery_id=None): if not partner_id: @@ -113,6 +166,10 @@ class AccountFiscalPosition(models.Model): if delivery.property_account_position_id or partner.property_account_position_id: return delivery.property_account_position_id.id or partner.property_account_position_id.id + fiscal_position_id = self._get_fpos_by_region(delivery.country_id.id, delivery.state_id.id, delivery.zip, bool(partner.vat)) + if fiscal_position_id: + return fiscal_position_id + domains = [[('auto_apply', '=', True), ('vat_required', '=', bool(partner.vat))]] if partner.vat: # Possibly allow fallback to non-VAT positions, if no VAT-required position matches @@ -120,10 +177,6 @@ class AccountFiscalPosition(models.Model): for domain in domains: if delivery.country_id.id: - fiscal_position = self.search(domain + [('country_id', '=', delivery.country_id.id)], limit=1) - if fiscal_position: - return fiscal_position.id - fiscal_position = self.search(domain + [('country_group_id.country_ids', '=', delivery.country_id.id)], limit=1) if fiscal_position: return fiscal_position.id @@ -175,6 +228,18 @@ class ResPartner(models.Model): _inherit = 'res.partner' _description = 'Partner' + @api.multi + def onchange_state(self, state_id): + res = super(ResPartner, self).onchange_state(state_id=state_id) + res['value']['property_account_position_id'] = self.env['account.fiscal.position']._get_fpos_by_region( + country_id=self.env.context.get('country_id'), state_id=state_id, zipcode=self.env.context.get('zip')) + return res + + @api.onchange('country_id', 'zip') + def _onchange_country_id(self): + self.property_account_position_id = self.env['account.fiscal.position']._get_fpos_by_region( + country_id=self.country_id.id, state_id=self.state_id.id, zipcode=self.zip) + @api.multi def _credit_debit_get(self): tables, where_clause, where_params = self.env['account.move.line']._query_get() diff --git a/addons/account/views/partner_view.xml b/addons/account/views/partner_view.xml index 48f094e5bec66659fc21508eb1b651d4cfe5a071..34f5ffdd129e19570944d51c5f692de95609a57b 100644 --- a/addons/account/views/partner_view.xml +++ b/addons/account/views/partner_view.xml @@ -16,9 +16,21 @@ </group> <group> <field name="auto_apply"/> + <field name="states_count" invisible="1"/> <field name="vat_required" attrs="{'invisible': [('auto_apply', '!=', True)]}"/> - <field name="country_id" attrs="{'invisible': [('auto_apply', '!=', True)]}"/> <field name="country_group_id" attrs="{'invisible': [('auto_apply', '!=', True)]}"/> + <field name="country_id" attrs="{'invisible': [('auto_apply', '!=', True)]}"/> + <field name="state_ids" widget="many2many_tags" domain="[('country_id', '=', country_id)]" + attrs="{'invisible': ['|', '|', ('auto_apply', '!=', True), ('country_id', '=', False), ('states_count', '=', 0)]}"/> + <label for="zip_from" string="Zip Range" + attrs="{'invisible': ['|', ('auto_apply', '!=', True), ('country_id', '=', False)]}"/> + <div attrs="{'invisible': ['|', ('auto_apply', '!=', True), ('country_id', '=', False)]}"> + <span> From </span> + <field name="zip_from" class="oe_inline"/> + <div class="oe_edit_only"/> + <span> To </span> + <field name="zip_to" class="oe_inline"/> + </div> </group> </group> <notebook> diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index b5f7bc96cdd2a7e5bd0db56901340a7d2ac5f4dd..98b48744f5a825c09d66b3194c7c7ed172ca04a1 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -579,6 +579,9 @@ class website_sale(http.Controller): partner_id = order.partner_id.id # save partner informations + if billing_info.get('country_id'): + billing_info['property_account_position_id'] = request.registry['account.fiscal.position']._get_fpos_by_region( + cr, SUPERUSER_ID, billing_info['country_id'], billing_info.get('state_id') or False, billing_info.get('zip'), billing_info.get('vat') and True or False) if partner_id and request.website.partner_id.id != partner_id: orm_partner.write(cr, SUPERUSER_ID, [partner_id], billing_info, context=context) else: diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index a330785aef7a64e38c87087cd2c6b7ff23f3ec7a..789ad4e3179dbf80586bde34b2b2c1cd7d796573 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -378,7 +378,7 @@ class res_partner(osv.Model, format_address): if state_id: state = self.env['res.country.state'].browse(state_id) return {'value': {'country_id': state.country_id.id}} - return {} + return {'value': {}} @api.multi def on_change_company_type(self, company_type): diff --git a/openerp/addons/base/res/res_partner_view.xml b/openerp/addons/base/res/res_partner_view.xml index 21fd350f5448ea3dcf478d0704c40694c83292be..1061ab4e9b2eed3e2e247208970fdf22c40a1ca0 100644 --- a/openerp/addons/base/res/res_partner_view.xml +++ b/openerp/addons/base/res/res_partner_view.xml @@ -178,7 +178,7 @@ <field name="city" placeholder="City" class="o_address_city" attrs="{'readonly': [('type', '=', 'contact'),('parent_id', '!=', False)]}"/> <field name="state_id" class="o_address_state" placeholder="State" options='{"no_open": True}' on_change="onchange_state(state_id)" - attrs="{'readonly': [('type', '=', 'contact'),('parent_id', '!=', False)]}"/> + attrs="{'readonly': [('type', '=', 'contact'),('parent_id', '!=', False)]}" context="{'country_id': country_id, 'zip': zip}"/> <field name="zip" placeholder="ZIP" class="o_address_zip" attrs="{'readonly': [('type', '=', 'contact'),('parent_id', '!=', False)]}"/> <field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True, "no_create": True}' @@ -290,7 +290,7 @@ <field name="street" placeholder="Street..." class="o_address_street"/> <field name="street2" placeholder="Street 2..." class="o_address_street"/> <field name="city" placeholder="City" class="o_address_city"/> - <field name="state_id" class="o_address_state" placeholder="State" options='{"no_open": True}' on_change="onchange_state(state_id)"/> + <field name="state_id" class="o_address_state" placeholder="State" options='{"no_open": True}' on_change="onchange_state(state_id)" context="{'country_id': country_id, 'zip': zip}"/> <field name="zip" placeholder="ZIP" class="o_address_zip"/> <field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True, "no_create": True}'/> </div>