[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.
This commit is contained in:
Antony Lesuisse 2014-06-29 23:31:16 +02:00
parent 5209fbc7ed
commit 22f4c315a3
11 changed files with 135 additions and 22 deletions

View File

@ -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."),

View File

@ -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>

View File

@ -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']

View File

@ -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)

View File

@ -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>

View File

@ -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")
#------------------------------------------------------

View File

@ -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:

View File

@ -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'

View File

@ -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>

View File

@ -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
-->

View File

@ -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

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
45 access_res_company_group_user res_company group_user model_res_company 1 0 0 0
46 access_res_country_group_all res_country group_user_all model_res_country 1 0 0 0
47 access_res_country_state_group_all res_country_state group_user_all model_res_country_state 1 0 0 0
48 access_res_country_group_group_all res_country_group group_user_all model_res_country_group 1 0 0 0
49 access_res_country_group_user res_country group_user model_res_country group_partner_manager 1 1 1 1
50 access_res_country_state_group_user res_country_state group_user model_res_country_state group_partner_manager 1 1 1 1
51 access_res_country_group_group_user res_country_group group_user model_res_country_group group_partner_manager 1 1 1 1
52 access_res_currency_group_all res_currency group_all model_res_currency 1 0 0 0
53 access_res_currency_rate_group_all res_currency_rate group_all model_res_currency_rate 1 0 0 0
54 access_res_currency_rate_type_group_all res_currency_rate_type group_all model_res_currency_rate_type 1 0 0 0