From 22f4c315a3ee4916d476fceea2a6d5a5248c8a2d Mon Sep 17 00:00:00 2001
From: Antony Lesuisse <al@openerp.com>
Date: Sun, 29 Jun 2014 23:31:16 +0200
Subject: [PATCH] [IMP] automatic fiscal positions for simple cases

Add group of countries res.country.group
Add get_fiscal_position method a method to compute a fiscal position based on company_id, partner_id, delivery_id
The meaning of res.partner.fiscal_position is now a forced a fiscal position.
The default implementation should handle simple cases, like VAT in UE and sales
tax in the US, but the method can be overriden to handle more complex ficals
rules.
---
 addons/account/partner.py                     | 34 +++++++++++++++++++
 addons/account/partner_view.xml               |  8 +++++
 addons/base_vat/base_vat.py                   |  4 ---
 addons/sale/sale.py                           | 23 ++++++++++---
 addons/sale/sale_view.xml                     |  2 +-
 addons/website_sale/controllers/main.py       |  7 +++-
 addons/website_sale/models/sale_order.py      | 24 +++++++------
 openerp/addons/base/res/res_country.py        |  8 +++++
 openerp/addons/base/res/res_country_data.xml  | 13 +++++++
 openerp/addons/base/res/res_country_view.xml  | 32 +++++++++++++++++
 .../addons/base/security/ir.model.access.csv  |  2 ++
 11 files changed, 135 insertions(+), 22 deletions(-)

diff --git a/addons/account/partner.py b/addons/account/partner.py
index d9bb6083f437..70f5280b8619 100644
--- a/addons/account/partner.py
+++ b/addons/account/partner.py
@@ -27,13 +27,19 @@ from openerp.osv import fields, osv
 class account_fiscal_position(osv.osv):
     _name = 'account.fiscal.position'
     _description = 'Fiscal Position'
+    _order = 'sequence'
     _columns = {
+        'sequence': fields.integer('Sequence'),
         'name': fields.char('Fiscal Position', required=True),
         'active': fields.boolean('Active', help="By unchecking the active field, you may hide a fiscal position without deleting it."),
         'company_id': fields.many2one('res.company', 'Company'),
         'account_ids': fields.one2many('account.fiscal.position.account', 'position_id', 'Account Mapping'),
         'tax_ids': fields.one2many('account.fiscal.position.tax', 'position_id', 'Tax Mapping'),
         'note': fields.text('Notes'),
+        'auto_apply': fields.boolean('Automatic', help="Apply automatically this fiscal position."),
+        'vat_required': fields.boolean('VAT required', help="Apply only if partner has a VAT number."),
+        'country_id': fields.many2one('res.country', 'Countries', help="Apply only if delivery or invoicing country match."),
+        'country_group_id': fields.many2one('res.country.group', 'Country Group', help="Apply only if delivery or invocing country match the group."),
     }
 
     _defaults = {
@@ -66,6 +72,33 @@ class account_fiscal_position(osv.osv):
                 break
         return account_id
 
+    def get_fiscal_position(self, cr, uid, company_id, partner_id, delivery_id=None, context=None):
+        if not partner_id:
+            return False
+        # This can be easily overriden to apply more complex fiscal rules
+        part_obj = self.pool['res.partner']
+        partner = part_obj.browse(cr, uid, partner_id, context=context)
+
+        # partner manually set fiscal position always win
+        if partner.property_account_position:
+            return part.property_account_position.id
+
+        # if no delivery use invocing
+        if delivery_id:
+            delivery = part_obj.browse(cr, uid, delivery_id, context=context)
+        else:
+            delivery = partner
+
+        domain = [
+            ('auto_apply', '=', True),
+            '|', ('vat_required', '=', False), ('vat_required', '=', partner.vat_subjected),
+            '|', ('country_id', '=', None), ('country_id', '=', delivery.country_id.id),
+            '|', ('country_group_id', '=', None), ('country_group_id.country_ids', '=', delivery.country_id.id)
+        ]
+        fiscal_position_ids = self.search(cr, uid, domain, context=context)
+        if fiscal_position_ids:
+            return fiscal_position_ids[0]
+        return False
 
 class account_fiscal_position_tax(osv.osv):
     _name = 'account.fiscal.position.tax'
@@ -206,6 +239,7 @@ class res_partner(osv.osv):
         return self.write(cr, uid, ids, {'last_reconciliation_date': time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)
 
     _columns = {
+        'vat_subjected': fields.boolean('VAT Legal Statement', help="Check this box if the partner is subjected to the VAT. It will be used for the VAT legal statement."),
         'credit': fields.function(_credit_debit_get,
             fnct_search=_credit_search, string='Total Receivable', multi='dc', help="Total amount this customer owes you."),
         'debit': fields.function(_credit_debit_get, fnct_search=_debit_search, string='Total Payable', multi='dc', help="Total amount you have to pay to this supplier."),
diff --git a/addons/account/partner_view.xml b/addons/account/partner_view.xml
index c2a01c7e40a5..309296c7955a 100644
--- a/addons/account/partner_view.xml
+++ b/addons/account/partner_view.xml
@@ -12,6 +12,13 @@
                         <field name="active"/>
                         <field name="company_id" widget="selection" groups="base.group_multi_company"/>
                     </group>
+                    <group>
+                        <field name="auto_apply"/>
+                        <field name="sequence"/>
+                        <field name="vat_required" attrs="{'readonly': [('auto_apply', '=', False)]}"/>
+                        <field name="country_id" attrs="{'readonly': ['|', ('country_group_id','!=',False), ('auto_apply', '=', False)]}" />
+                        <field name="country_group_id" attrs="{'readonly': ['|', ('country_id','!=',False), ('auto_apply', '=', False)]}"/>
+                    </group>
                     <separator string="Taxes Mapping"/>
                     <field name="tax_ids" widget="one2many_list">
                         <tree string="Tax Mapping" editable="bottom">
@@ -44,6 +51,7 @@
             <field name="model">account.fiscal.position</field>
             <field name="arch" type="xml">
                 <tree string="Fiscal Position">
+                    <field name="sequence"/>
                     <field name="name"/>
                     <field name="company_id" groups="base.group_multi_company" widget="selection"/>
                 </tree>
diff --git a/addons/base_vat/base_vat.py b/addons/base_vat/base_vat.py
index 9b3862508b05..b7ac9592dd31 100644
--- a/addons/base_vat/base_vat.py
+++ b/addons/base_vat/base_vat.py
@@ -133,10 +133,6 @@ class res_partner(osv.osv):
     def vat_change(self, cr, uid, ids, value, context=None):
         return {'value': {'vat_subjected': bool(value)}}
 
-    _columns = {
-        'vat_subjected': fields.boolean('VAT Legal Statement', help="Check this box if the partner is subjected to the VAT. It will be used for the VAT legal statement.")
-    }
-
     def _commercial_fields(self, cr, uid, context=None):
         return super(res_partner, self)._commercial_fields(cr, uid, context=context) + ['vat_subjected']
 
diff --git a/addons/sale/sale.py b/addons/sale/sale.py
index 87ad2d80be00..5e44ad5b0f4f 100644
--- a/addons/sale/sale.py
+++ b/addons/sale/sale.py
@@ -320,6 +320,16 @@ class sale_order(osv.osv):
             context_lang.update({'lang': partner_lang})
         return self.pool.get('res.users').browse(cr, uid, uid, context=context_lang).company_id.sale_note
 
+    def onchange_delivery_id(self, cr, uid, ids, company_id, partner_id, delivery_id, fiscal_position, context=None):
+        r = {'value': {}}
+        if not fiscal_position:
+            if not company_id:
+                company_id = self._get_default_company(cr, uid, context=context)
+            fiscal_position = self.pool['account.fiscal.position'].get_fiscal_position(cr, uid, company_id, partner_id, delivery_id, context=context)
+            if fiscal_position:
+                r['value']['fiscal_position'] = fiscal_position
+        return r
+
     def onchange_partner_id(self, cr, uid, ids, part, context=None):
         if not part:
             return {'value': {'partner_invoice_id': False, 'partner_shipping_id': False,  'payment_term': False, 'fiscal_position': False}}
@@ -328,15 +338,15 @@ class sale_order(osv.osv):
         addr = self.pool.get('res.partner').address_get(cr, uid, [part.id], ['delivery', 'invoice', 'contact'])
         pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False
         payment_term = part.property_payment_term and part.property_payment_term.id or False
-        fiscal_position = part.property_account_position and part.property_account_position.id or False
         dedicated_salesman = part.user_id and part.user_id.id or uid
         val = {
             'partner_invoice_id': addr['invoice'],
             'partner_shipping_id': addr['delivery'],
             'payment_term': payment_term,
-            'fiscal_position': fiscal_position,
             'user_id': dedicated_salesman,
         }
+        delivery_onchange = self.onchange_delivery_id(cr, uid, ids, False, part.id, addr['delivery'], False,  context=context)
+        val.update(delivery_onchange['value'])
         if pricelist:
             val['pricelist_id'] = pricelist
         sale_note = self.get_salenote(cr, uid, ids, part.id, context=context)
@@ -345,11 +355,14 @@ class sale_order(osv.osv):
 
     def create(self, cr, uid, vals, context=None):
         if context is None:
-            context = {}        
+            context = {}
         if vals.get('name', '/') == '/':
             vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'sale.order') or '/'
-        if vals.get('partner_id') and any(f not in vals for f in ['partner_invoice_id', 'partner_shipping_id', 'pricelist_id']):
-            defaults = self.onchange_partner_id(cr, uid, [], vals['partner_id'], context)['value']
+        if vals.get('partner_id') and any(f not in vals for f in ['partner_invoice_id', 'partner_shipping_id', 'pricelist_id', 'fiscal_position']):
+            defaults = self.onchange_partner_id(cr, uid, [], vals['partner_id'], context=context)['value']
+            if not vals.get('fiscal_position') and vals.get('partner_shipping_id'):
+                delivery_onchange = self.onchange_delivery_id(cr, uid, [], vals.get('company_id'), None, vals['partner_id'], vals.get('partner_shipping_id'), context=context)
+                defaults.update(delivery_onchange['value'])
             vals = dict(defaults, **vals)
         context.update({'mail_create_nolog': True})
         new_id = super(sale_order, self).create(cr, uid, vals, context=context)
diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml
index d5d072c4513b..f61c518918ee 100644
--- a/addons/sale/sale_view.xml
+++ b/addons/sale/sale_view.xml
@@ -102,7 +102,7 @@
                         <group>
                             <field name="partner_id" on_change="onchange_partner_id(partner_id, context)" domain="[('customer','=',True)]" context="{'search_default_customer':1, 'show_address': 1}" options='{"always_reload": True}'/>
                             <field name="partner_invoice_id" groups="sale.group_delivery_invoice_address" context="{'default_type':'invoice'}"/>
-                            <field name="partner_shipping_id" groups="sale.group_delivery_invoice_address" context="{'default_type':'delivery'}"/>
+                            <field name="partner_shipping_id" on_change="onchange_delivery_id(company_id, partner_id, partner_shipping_id, fiscal_position)" groups="sale.group_delivery_invoice_address" context="{'default_type':'delivery'}"/>
                             <field name="project_id" context="{'partner_id':partner_invoice_id, 'default_pricelist_id':pricelist_id, 'default_name':name, 'default_type': 'contract'}" groups="sale.group_analytic_accounting" domain="[('type','in',['view','normal','contract'])]"/>
                         </group>
                         <group>
diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py
index f187e6799ac2..656f581bc63f 100644
--- a/addons/website_sale/controllers/main.py
+++ b/addons/website_sale/controllers/main.py
@@ -456,6 +456,7 @@ class website_sale(http.Controller):
         if partner_id and request.website.partner_id.id != partner_id:
             orm_partner.write(cr, SUPERUSER_ID, [partner_id], billing_info, context=context)
         else:
+            # create partner
             partner_id = orm_partner.create(cr, SUPERUSER_ID, billing_info, context=context)
 
         # create a new shipping partner
@@ -472,7 +473,9 @@ class website_sale(http.Controller):
             'partner_invoice_id': partner_id,
             'partner_shipping_id': shipping_id or partner_id
         }
-        order_info.update(registry.get('sale.order').onchange_partner_id(cr, SUPERUSER_ID, [], partner_id, context=context)['value'])
+        order_info.update(order_obj.onchange_partner_id(cr, SUPERUSER_ID, [], partner_id, context=context)['value'])
+        order_info.update(order_obj.onchange_delivery_id(cr, SUPERUSER_ID, [], order.company_id.id, partner_id, shipping_id, None, context=context)['value'])
+
         order_info.pop('user_id')
 
         order_obj.write(cr, SUPERUSER_ID, [order.id], order_info, context=context)
@@ -512,6 +515,8 @@ class website_sale(http.Controller):
         self.checkout_form_save(values["checkout"])
         request.session['sale_last_order_id'] = order.id
 
+        request.website.sale_get_order(update_pricelist=True, context=context)
+
         return request.redirect("/shop/payment")
 
     #------------------------------------------------------
diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py
index 165030f9f1ae..7c0700a555a4 100644
--- a/addons/website_sale/models/sale_order.py
+++ b/addons/website_sale/models/sale_order.py
@@ -128,7 +128,7 @@ class website(orm.Model):
     def sale_product_domain(self, cr, uid, ids, context=None):
         return [("sale_ok", "=", True)]
 
-    def sale_get_order(self, cr, uid, ids, force_create=False, code=None, context=None):
+    def sale_get_order(self, cr, uid, ids, force_create=False, code=None, update_pricelist=None, context=None):
         sale_order_obj = self.pool['sale.order']
         sale_order_id = request.session.get('sale_order_id')
         sale_order = None
@@ -157,26 +157,20 @@ class website(orm.Model):
                 request.session['sale_order_id'] = None
                 return None
 
-            def update_pricelist(pricelist_id):
-                values = {'pricelist_id': pricelist_id}
-                values.update(sale_order.onchange_pricelist_id(pricelist_id, None)['value'])
-                sale_order.write(values)
-                for line in sale_order.order_line:
-                    sale_order._cart_update(product_id=line.product_id.id, add_qty=0)
-
             # check for change of pricelist with a coupon
             if code and code != sale_order.pricelist_id.code:
                 pricelist_ids = self.pool['product.pricelist'].search(cr, SUPERUSER_ID, [('code', '=', code)], context=context)
                 if pricelist_ids:
                     pricelist_id = pricelist_ids[0]
                     request.session['sale_order_code_pricelist_id'] = pricelist_id
-                    update_pricelist(pricelist_id)
+                    update_pricelist = True
                 request.session['sale_order_code_pricelist_id'] = False
 
+            pricelist_id = request.session.get('sale_order_code_pricelist_id') or partner.property_product_pricelist.id
+
             # check for change of partner_id ie after signup
             if sale_order.partner_id.id != partner.id and request.website.partner_id.id != partner.id:
                 flag_pricelist = False
-                pricelist_id = request.session.get('sale_order_code_pricelist_id') or partner.property_product_pricelist.id
                 if pricelist_id != sale_order.pricelist_id.id:
                     flag_pricelist = True
                 fiscal_position = sale_order.fiscal_position and sale_order.fiscal_position.id or False
@@ -190,7 +184,15 @@ class website(orm.Model):
                 sale_order_obj.write(cr, SUPERUSER_ID, [sale_order_id], values, context=context)
 
                 if flag_pricelist or values.get('fiscal_position') != fiscal_position:
-                    update_pricelist(pricelist_id)
+                    update_pricelist = True
+
+            # update the pricelist
+            if update_pricelist:
+                values = {'pricelist_id': pricelist_id}
+                values.update(sale_order.onchange_pricelist_id(pricelist_id, None)['value'])
+                sale_order.write(values)
+                for line in sale_order.order_line:
+                    sale_order._cart_update(product_id=line.product_id.id, add_qty=0)
 
             # update browse record
             if (code and code != sale_order.pricelist_id.code) or sale_order.partner_id.id !=  partner.id:
diff --git a/openerp/addons/base/res/res_country.py b/openerp/addons/base/res/res_country.py
index 10f82df2ee85..f086f1440e82 100644
--- a/openerp/addons/base/res/res_country.py
+++ b/openerp/addons/base/res/res_country.py
@@ -84,6 +84,14 @@ addresses belonging to this country.\n\nYou can use the python-style string pate
                 context=context)
 
 
+class CountryGroup(osv.osv):
+    _description="Country Group"
+    _name = 'res.country.group'
+    _columns = {
+        'name': fields.char('Name', required=True),
+        'country_ids': fields.many2many('res.country', string='Countries'),
+    }
+
 class CountryState(osv.osv):
     _description="Country state"
     _name = 'res.country.state'
diff --git a/openerp/addons/base/res/res_country_data.xml b/openerp/addons/base/res/res_country_data.xml
index 633b4cc42c4c..1bdd6a73460d 100644
--- a/openerp/addons/base/res/res_country_data.xml
+++ b/openerp/addons/base/res/res_country_data.xml
@@ -1516,5 +1516,18 @@
             <field name="image" type="base64" file="base/static/img/country_flags/zw.png"></field>
             <field name="currency_id" ref="ZWD"/>
         </record>
+
+
+
+        <record id="europe" model="res.country.group">
+            <field name="name">Europe</field>
+            <field name="country_ids" eval="[(6,0,[
+                ref('at'),ref('be'),ref('bg'),ref('hr'),ref('cy'),
+                ref('cz'),ref('dk'),ref('ee'),ref('fi'),ref('fr'),
+                ref('de'),ref('gr'),ref('hu'),ref('ie'),ref('it'),
+                ref('lv'),ref('lt'),ref('lu'),ref('mt'),ref('nl'),
+                ref('pl'),ref('pt'),ref('ro'),ref('sk'),ref('si'),
+                ref('es'),ref('se'),ref('uk')])]"/>
+        </record>
     </data>
 </openerp>
diff --git a/openerp/addons/base/res/res_country_view.xml b/openerp/addons/base/res/res_country_view.xml
index ad46235c5c64..f1316844dd71 100644
--- a/openerp/addons/base/res/res_country_view.xml
+++ b/openerp/addons/base/res/res_country_view.xml
@@ -50,6 +50,38 @@
 
         <menuitem action="action_country" id="menu_country_partner" parent="menu_localisation" sequence="0" groups="base.group_no_one"/>
 
+        <record id="view_country_group_tree" model="ir.ui.view">
+            <field name="name">res.country.group.tree</field>
+            <field name="model">res.country.group</field>
+            <field name="arch" type="xml">
+                <tree string="Country Group">
+                    <field name="name"/>
+                </tree>
+            </field>
+        </record>
+
+        <record id="view_country_group_form" model="ir.ui.view">
+            <field name="name">res.country.group.form</field>
+            <field name="model">res.country.group</field>
+            <field name="arch" type="xml">
+                <form string="Country Group">
+                    <field name="name"/>
+                    <field name="country_ids" widget="many2many_tags"/>
+                </form>
+            </field>
+        </record>
+
+        <record id="action_country_group" model="ir.actions.act_window">
+            <field name="name">Country Group</field>
+            <field name="type">ir.actions.act_window</field>
+            <field name="res_model">res.country.group</field>
+            <field name="view_type">form</field>
+            <field name="help">Display and manage the list of all countries group. You can create or delete country group to make sure the ones you are working on will be maintained.</field>
+        </record>
+
+
+        <menuitem action="action_country_group" id="menu_country_group" name="Country Group" parent="menu_localisation" sequence="1" groups="base.group_no_one"/>
+
         <!--
           State
         -->
diff --git a/openerp/addons/base/security/ir.model.access.csv b/openerp/addons/base/security/ir.model.access.csv
index c70733b5140a..bbf579486a48 100644
--- a/openerp/addons/base/security/ir.model.access.csv
+++ b/openerp/addons/base/security/ir.model.access.csv
@@ -45,8 +45,10 @@
 "access_res_company_group_user","res_company group_user","model_res_company",,1,0,0,0
 "access_res_country_group_all","res_country group_user_all","model_res_country",,1,0,0,0
 "access_res_country_state_group_all","res_country_state group_user_all","model_res_country_state",,1,0,0,0
+"access_res_country_group_group_all","res_country_group group_user_all","model_res_country_group",,1,0,0,0
 "access_res_country_group_user","res_country group_user","model_res_country","group_partner_manager",1,1,1,1
 "access_res_country_state_group_user","res_country_state group_user","model_res_country_state","group_partner_manager",1,1,1,1
+"access_res_country_group_group_user","res_country_group group_user","model_res_country_group","group_partner_manager",1,1,1,1
 "access_res_currency_group_all","res_currency group_all","model_res_currency",,1,0,0,0
 "access_res_currency_rate_group_all","res_currency_rate group_all","model_res_currency_rate",,1,0,0,0
 "access_res_currency_rate_type_group_all","res_currency_rate_type group_all","model_res_currency_rate_type",,1,0,0,0
-- 
GitLab