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