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>