From 22f4c315a3ee4916d476fceea2a6d5a5248c8a2d Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Sun, 29 Jun 2014 23:31:16 +0200 Subject: [PATCH 1/2] [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 d9bb6083f43..70f5280b861 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 c2a01c7e40a..309296c7955 100644 --- a/addons/account/partner_view.xml +++ b/addons/account/partner_view.xml @@ -12,6 +12,13 @@ + + + + + + + @@ -44,6 +51,7 @@ account.fiscal.position + diff --git a/addons/base_vat/base_vat.py b/addons/base_vat/base_vat.py index 9b3862508b0..b7ac9592dd3 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 87ad2d80be0..5e44ad5b0f4 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 d5d072c4513..f61c518918e 100644 --- a/addons/sale/sale_view.xml +++ b/addons/sale/sale_view.xml @@ -102,7 +102,7 @@ - + diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index f187e6799ac..656f581bc63 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 165030f9f1a..7c0700a555a 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 10f82df2ee8..f086f1440e8 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 633b4cc42c4..1bdd6a73460 100644 --- a/openerp/addons/base/res/res_country_data.xml +++ b/openerp/addons/base/res/res_country_data.xml @@ -1516,5 +1516,18 @@ + + + + + Europe + + diff --git a/openerp/addons/base/res/res_country_view.xml b/openerp/addons/base/res/res_country_view.xml index ad46235c5c6..f1316844dd7 100644 --- a/openerp/addons/base/res/res_country_view.xml +++ b/openerp/addons/base/res/res_country_view.xml @@ -50,6 +50,38 @@ + + res.country.group.tree + res.country.group + + + + + + + + + res.country.group.form + res.country.group + +
+ + + + +
+ + + Country Group + ir.actions.act_window + res.country.group + form + 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. + + + + + diff --git a/openerp/addons/base/security/ir.model.access.csv b/openerp/addons/base/security/ir.model.access.csv index c70733b5140..bbf579486a4 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 From ebd74cedcc91bced28d77935eca134ba330a3847 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Mon, 30 Jun 2014 01:41:28 +0200 Subject: [PATCH 2/2] [FIX] purchase_requisition test of PO's --- .../purchase_requisition/test/purchase_requisition.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/purchase_requisition/test/purchase_requisition.yml b/addons/purchase_requisition/test/purchase_requisition.yml index 5ac152d3d29..355e13e0677 100644 --- a/addons/purchase_requisition/test/purchase_requisition.yml +++ b/addons/purchase_requisition/test/purchase_requisition.yml @@ -69,11 +69,11 @@ purchase_requisition = self.pool.get("purchase.requisition") purchase_ids = self.search(cr, uid, [('requisition_id','=',ref("requisition1"))]) assert purchase_ids, "RFQ is not created." - rfq = self.browse(cr, uid, purchase_ids[0], context=context) - requisition = rfq.requisition_id - supplier = rfq.partner_id - assert supplier.id == ref('base.res_partner_12'), "RFQ Partner is not correspond." - assert len(rfq.order_line) == len(requisition.line_ids), "Lines are not correspond." + for rfq in self.browse(cr, uid, purchase_ids, context=context): + if rfq.partner_id.id == ref('base.res_partner_12'): + break + else: + assert False, "No PO found for res_partner_12." - I confirmed RFQ which has best price. -