Merge lp:~npg-team/openobject-addons/account_salestax_avatax_us into lp:openobject-addons

Proposed by Novapoint Group
Status: Superseded
Proposed branch: lp:~npg-team/openobject-addons/account_salestax_avatax_us
Merge into: lp:openobject-addons
Diff against target: 2228 lines (+2104/-0)
22 files modified
account_salestax_avatax/__init__.py (+33/-0)
account_salestax_avatax/__openerp__.py (+77/-0)
account_salestax_avatax/account.py (+92/-0)
account_salestax_avatax/account_invoice_view.xml (+22/-0)
account_salestax_avatax/account_invoice_workflow.xml (+17/-0)
account_salestax_avatax/account_salestax_avatax.py (+147/-0)
account_salestax_avatax/account_salestax_avatax_data.xml (+90/-0)
account_salestax_avatax/account_salestax_avatax_view.xml (+215/-0)
account_salestax_avatax/invoice.py (+252/-0)
account_salestax_avatax/partner.py (+169/-0)
account_salestax_avatax/partner_view.xml (+63/-0)
account_salestax_avatax/product.py (+57/-0)
account_salestax_avatax/product_view.xml (+108/-0)
account_salestax_avatax/sale.py (+118/-0)
account_salestax_avatax/security/account_salestax_avatax_security.xml (+29/-0)
account_salestax_avatax/security/ir.model.access.csv (+11/-0)
account_salestax_avatax/suds_client.py (+295/-0)
account_salestax_avatax/wizard/__init__.py (+26/-0)
account_salestax_avatax/wizard/account_salestax_avatax_address_validate.py (+135/-0)
account_salestax_avatax/wizard/account_salestax_avatax_address_validate.xml (+53/-0)
account_salestax_avatax/wizard/account_salestax_avatax_ping.py (+58/-0)
account_salestax_avatax/wizard/account_salestax_avatax_ping.xml (+37/-0)
To merge this branch: bzr merge lp:~npg-team/openobject-addons/account_salestax_avatax_us
Reviewer Review Type Date Requested Status
OpenERP Core Team Pending
Review via email: mp+73895@code.launchpad.net

This proposal has been superseded by a proposal from 2011-09-22.

Description of the change

Added account_salestax_avatax module developed by Novapoint Group for sales tax calculation using AvaTax service.

To post a comment you must log in.
2. By Novapoint Group

Improvements in code, security

Unmerged revisions

2. By Novapoint Group

Improvements in code, security

1. By Novapoint Group

Added account_salestax_avatax module for sales tax calculation using AvaTax service

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'account_salestax_avatax'
2=== added file 'account_salestax_avatax/__init__.py'
3--- account_salestax_avatax/__init__.py 1970-01-01 00:00:00 +0000
4+++ account_salestax_avatax/__init__.py 2011-09-22 18:36:28 +0000
5@@ -0,0 +1,33 @@
6+# -*- coding: utf-8 -*-
7+##############################################################################
8+#
9+# OpenERP, Open Source Management Solution
10+# Copyright (C) 2011 NovaPoint Group LLC (<http://www.novapointgroup.com>)
11+# Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
12+#
13+# This program is free software: you can redistribute it and/or modify
14+# it under the terms of the GNU Affero General Public License as
15+# published by the Free Software Foundation, either version 3 of the
16+# License, or (at your option) any later version.
17+#
18+# This program is distributed in the hope that it will be useful,
19+# but WITHOUT ANY WARRANTY; without even the implied warranty of
20+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21+# GNU Affero General Public License for more details.
22+#
23+# You should have received a copy of the GNU Affero General Public License
24+# along with this program. If not, see <http://www.gnu.org/licenses/>.
25+#
26+##############################################################################
27+
28+import product
29+import account_salestax_avatax
30+import partner
31+import suds_client
32+import sale
33+import invoice
34+import account
35+import wizard
36+
37+
38+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
39
40=== added file 'account_salestax_avatax/__openerp__.py'
41--- account_salestax_avatax/__openerp__.py 1970-01-01 00:00:00 +0000
42+++ account_salestax_avatax/__openerp__.py 2011-09-22 18:36:28 +0000
43@@ -0,0 +1,77 @@
44+# -*- coding: utf-8 -*-
45+##############################################################################
46+#
47+# OpenERP, Open Source Management Solution
48+# Copyright (C) 2011 NovaPoint Group LLC (<http://www.novapointgroup.com>)
49+# Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
50+#
51+# This program is free software: you can redistribute it and/or modify
52+# it under the terms of the GNU Affero General Public License as
53+# published by the Free Software Foundation, either version 3 of the
54+# License, or (at your option) any later version.
55+#
56+# This program is distributed in the hope that it will be useful,
57+# but WITHOUT ANY WARRANTY; without even the implied warranty of
58+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
59+# GNU Affero General Public License for more details.
60+#
61+# You should have received a copy of the GNU Affero General Public License
62+# along with this program. If not, see <http://www.gnu.org/licenses/>.
63+#
64+##############################################################################
65+
66+{
67+ "name" : "Use AvaTax web service to calculate tax",
68+ "version" : "1.0",
69+ "author" : 'Novapoint Group LLC',
70+ "description": """ This module supports calculating sales and use taxes, validates addresses
71+using the AvaTax subscription service from AVALARA. www.avalara.com. It is targeted for the US, Canadian, and in the future the international markets
72+supported by AVALARA. Users of the module need to subscribe to a service plan from AVALARA and obtain the proper login credentials prior
73+to using this module. As the US and Canadian tax programs have more
74+than 22,000 unique tax jurisdictions based on a GEO-Location matrix segmented by product category,
75+and tax collection legislation continues to change in the US to include new
76+areas like internet transactions, we believe using a tax service from AvaTax to calculate applicable taxes due simplifies
77+a company's management, time and effort to be in compliance with tax rules.
78+
79+It is intended that the tax calculation service is called whenever an existing tax calculation is calculated within OpenERP.
80+
81+The process to setup the system is as follows:
82+A company would initially install the module.
83+A company would sign-up for the AVATAX service.
84+A company enter their AVATAX credentials in OpenERP.
85+A company then should setup their Chart of Accounts with the appropriate accounts to capture detailed tax information (see your accountant).
86+A company then sets up the AVATAX service to cater to the products and services they provide.
87+A company then configures OpenERP properly for tax processing with AVATAX.
88+A company then tests the AVATAX connection and service.
89+
90+AVATAX also offers a service to manage the submission of taxes due to various tax jurisdictions electronically, and manually.
91+
92+Please review the module documentation prior to installing the module for prerequisites.
93+
94+NOTE: Use of this module is "at your own risk", and does not in any way make NovaPoint Group or OpenERP liable for any issues or construe any
95+obligation that calculations are correct, or that tax calculations are sufficient to meet regulatory or legislative requirements.
96+
97+""",
98+ "category" : "Generic Modules/Accounting",
99+ "website" : "http://www.novapointgroup.com/",
100+ "depends" : ["sale_negotiated_shipping"],
101+ "init_xml" : [],
102+ "demo_xml" : [],
103+ "update_xml" : [
104+ "account_salestax_avatax_data.xml",
105+ "wizard/account_salestax_avatax_ping.xml",
106+ "wizard/account_salestax_avatax_address_validate.xml",
107+ "account_salestax_avatax_view.xml",
108+ "partner_view.xml",
109+ "product_view.xml",
110+ "account_invoice_workflow.xml",
111+ "account_invoice_view.xml",
112+ "security/account_salestax_avatax_security.xml",
113+ "security/ir.model.access.csv",
114+ ],
115+ "test" : [],
116+ "active": False,
117+ "installable": True,
118+}
119+
120+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
121
122=== added file 'account_salestax_avatax/account.py'
123--- account_salestax_avatax/account.py 1970-01-01 00:00:00 +0000
124+++ account_salestax_avatax/account.py 2011-09-22 18:36:28 +0000
125@@ -0,0 +1,92 @@
126+# -*- coding: utf-8 -*-
127+##############################################################################
128+#
129+# OpenERP, Open Source Management Solution
130+# Copyright (C) 2011 NovaPoint Group LLC (<http://www.novapointgroup.com>)
131+# Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
132+#
133+# This program is free software: you can redistribute it and/or modify
134+# it under the terms of the GNU Affero General Public License as
135+# published by the Free Software Foundation, either version 3 of the
136+# License, or (at your option) any later version.
137+#
138+# This program is distributed in the hope that it will be useful,
139+# but WITHOUT ANY WARRANTY; without even the implied warranty of
140+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
141+# GNU Affero General Public License for more details.
142+#
143+# You should have received a copy of the GNU Affero General Public License
144+# along with this program. If not, see <http://www.gnu.org/licenses/>.
145+#
146+##############################################################################
147+import time
148+import string
149+
150+from osv import osv, fields
151+from tools.translate import _
152+
153+from suds_client import AvaTaxService, BaseAddress, Line
154+
155+class account_tax(osv.osv):
156+ _inherit = "account.tax"
157+
158+ def _check_compute_tax(self, cr, uid, avatax_config, doc_date, doc_code, doc_type, partner, ship_from_address_id, shipping_address_id,
159+ lines, shipping_charge, user=None, commit=False, invoice_date=False, reference_code=False, context=None):
160+ address_obj = self.pool.get('res.partner.address')
161+ if not lines:
162+ raise osv.except_osv(_('Error !'), _('AvaTax needs atleast one sale order line defined for tax calculation.'))
163+ if avatax_config.force_address_validation:
164+ if not shipping_address.date_validation:
165+ raise osv.except_osv(_('Address Not Validated !'), _('Please validate the shipping address for the partner %s.'
166+ % (partner.name)))
167+ if not ship_from_address_id:
168+ raise osv.except_osv(_('No Ship from Address Defined !'), _('There is no company address defined.'))
169+ if not shipping_address_id:
170+ raise osv.except_osv(_('No Shipping Address Defined !'), _('There is no shipping address defined for the partner.'))
171+
172+ ship_from_address = address_obj.browse(cr, uid, ship_from_address_id, context=context)
173+ shipping_address = address_obj.browse(cr, uid, shipping_address_id, context=context)
174+ if not ship_from_address.date_validation:
175+ raise osv.except_osv(_('Address Not Validated !'), _('Please validate the company address.'))
176+
177+ if shipping_charge:
178+ lines.append({
179+ 'qty': 1,
180+ 'amount': shipping_charge,
181+ 'itemcode': '',
182+ 'description': '',
183+ 'tax_code': avatax_config.default_shipping_code_id.name
184+ })
185+ avapoint = AvaTaxService(avatax_config.account_number, avatax_config.license_key,
186+ avatax_config.service_url, avatax_config.request_timeout, avatax_config.logging)
187+ avapoint.create_tax_service()
188+ addSvc = avapoint.create_address_service().addressSvc
189+ origin = BaseAddress(addSvc, ship_from_address.street or None,
190+ ship_from_address.street2 or None,
191+ ship_from_address.city, ship_from_address.zip,
192+ ship_from_address.state_id and ship_from_address.state_id.code or None,
193+ ship_from_address.country_id and ship_from_address.country_id.code or None, 0).data
194+ destination = BaseAddress(addSvc, shipping_address.street or None,
195+ shipping_address.street2 or None,
196+ shipping_address.city, shipping_address.zip,
197+ shipping_address.state_id and shipping_address.state_id.code or None,
198+ shipping_address.country_id and shipping_address.country_id.code or None, 1).data
199+ result = avapoint.get_tax(avatax_config.company_code, doc_date, doc_type,
200+ partner.name, doc_code, origin, destination,
201+ lines, partner.exemption_number or None,
202+ partner.exemption_code_id and partner.exemption_code_id.code or None,
203+ user and user.name or None, commit, invoice_date, reference_code)
204+
205+ return result
206+
207+ def cancel_tax(self, cr, uid, avatax_config, doc_code, doc_type, cancel_code):
208+ avapoint = AvaTaxService(avatax_config.account_number, avatax_config.license_key,
209+ avatax_config.service_url, avatax_config.request_timeout,
210+ avatax_config.logging)
211+ avapoint.create_tax_service()
212+ result = avapoint.cancel_tax(avatax_config.company_code, doc_code, doc_type, cancel_code)
213+ return result
214+
215+account_tax()
216+
217+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
218
219=== added file 'account_salestax_avatax/account_invoice_view.xml'
220--- account_salestax_avatax/account_invoice_view.xml 1970-01-01 00:00:00 +0000
221+++ account_salestax_avatax/account_invoice_view.xml 2011-09-22 18:36:28 +0000
222@@ -0,0 +1,22 @@
223+<?xml version="1.0"?>
224+<openerp>
225+ <data>
226+
227+ <!--
228+ Customer Invoice/Credit Memo
229+ -->
230+
231+ <record id="view_account_invoice_form_avatax_inherit" model="ir.ui.view">
232+ <field name="name">account.invoice.form.avatax.inherit</field>
233+ <field name="model">account.invoice</field>
234+ <field name="type">form</field>
235+ <field name="inherit_id" ref="account.invoice_form"/>
236+ <field name="arch" type="xml">
237+ <field name="payment_term" position="after">
238+ <field name="invoice_doc_no" attrs="{'required': [('type','=','out_refund')],'invisible': [('type','!=','out_refund')]}"/>
239+ </field>
240+ </field>
241+ </record>
242+
243+ </data>
244+</openerp>
245\ No newline at end of file
246
247=== added file 'account_salestax_avatax/account_invoice_workflow.xml'
248--- account_salestax_avatax/account_invoice_workflow.xml 1970-01-01 00:00:00 +0000
249+++ account_salestax_avatax/account_invoice_workflow.xml 2011-09-22 18:36:28 +0000
250@@ -0,0 +1,17 @@
251+<?xml version="1.0" encoding="utf-8"?>
252+<openerp>
253+ <data>
254+
255+ <record id="account.act_open" model="workflow.activity">
256+ <field name="wkf_id" ref="account.wkf"/>
257+ <field name="name">open</field>
258+ <field name="action">action_date_assign()
259+action_move_create()
260+action_number()
261+write({'state':'open'})
262+action_commit_tax()</field>
263+ <field name="kind">function</field>
264+ </record>
265+
266+ </data>
267+</openerp>
268\ No newline at end of file
269
270=== added file 'account_salestax_avatax/account_salestax_avatax.py'
271--- account_salestax_avatax/account_salestax_avatax.py 1970-01-01 00:00:00 +0000
272+++ account_salestax_avatax/account_salestax_avatax.py 2011-09-22 18:36:28 +0000
273@@ -0,0 +1,147 @@
274+# -*- coding: utf-8 -*-
275+##############################################################################
276+#
277+# OpenERP, Open Source Management Solution
278+# Copyright (C) 2011 NovaPoint Group LLC (<http://www.novapointgroup.com>)
279+# Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
280+#
281+# This program is free software: you can redistribute it and/or modify
282+# it under the terms of the GNU Affero General Public License as
283+# published by the Free Software Foundation, either version 3 of the
284+# License, or (at your option) any later version.
285+#
286+# This program is distributed in the hope that it will be useful,
287+# but WITHOUT ANY WARRANTY; without even the implied warranty of
288+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
289+# GNU Affero General Public License for more details.
290+#
291+# You should have received a copy of the GNU Affero General Public License
292+# along with this program. If not, see <http://www.gnu.org/licenses/>.
293+#
294+##############################################################################
295+from osv import osv, fields
296+from tools.translate import _
297+import decimal_precision as dp
298+
299+class tax_schedule(osv.osv):
300+ _name = "tax.schedule"
301+ _description = "Tax Schedule"
302+ _columns = {
303+ 'name': fields.char('Name', size=64, required=True),
304+ 'code': fields.char('Code', size=32),
305+ 'jurisdiction_code_ids': fields.one2many('jurisdiction.code', 'tax_schedule_id', 'Jurisdiction Codes'),
306+ 'company_id': fields.many2one('res.company', 'Company', required=True),
307+ 'country_id': fields.many2one('res.country', 'Country', required=True),
308+ }
309+ _defaults = {
310+ 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'tax.schedule', context=c),
311+ }
312+
313+tax_schedule()
314+
315+class jurisdiction_code(osv.osv):
316+ _name = "jurisdiction.code"
317+ _description = "Jurisdiction Code"
318+ _columns = {
319+ 'name': fields.char('Description', size=32, required=True),
320+ 'type': fields.selection([('country', 'Country'), ('composite', 'Composite'), ('state', 'State'),
321+ ('county', 'County'), ('city', 'City'), ('special', 'Special')], 'Type', required=True,
322+ help="Type of tax jurisdiction"),
323+ 'state_id': fields.many2one('res.country.state', 'State', required=True, help="State for which the tax jurisdiction is defined"),
324+ 'code':fields.char('Code', size=32),
325+ 'tax_schedule_id': fields.many2one('tax.schedule', 'Tax Schedule'),
326+ 'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account', required=True, help="Use this tax account for Invoices"),
327+ 'account_paid_id':fields.many2one('account.account', 'Refund Tax Account', required=True, help="Use this tax account for Refunds"),
328+ 'base_code_id': fields.many2one('account.tax.code', 'Account Base Code', help="Use this base code for the Invoices"),
329+ 'tax_code_id': fields.many2one('account.tax.code', 'Account Tax Code', help="Use this tax code for the Invoices"),
330+ 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1"),
331+ 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1"),
332+ 'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this base code for the Refunds"),
333+ 'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this tax code for the Refunds"),
334+ 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1"),
335+ 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1"),
336+ }
337+ _defaults = {
338+ 'ref_tax_sign': 1,
339+ 'ref_base_sign': 1,
340+ 'tax_sign': 1,
341+ 'base_sign': 1,
342+ }
343+
344+jurisdiction_code()
345+
346+class exemption_code(osv.osv):
347+ _name = 'exemption.code'
348+ _description = 'Exemption Code'
349+ _columns = {
350+ 'name': fields.char('Name', size=64),
351+ 'code': fields.char('Code', size=2)
352+ }
353+
354+ def name_get(self, cr, uid, ids, context=None):
355+ if not ids:
356+ return []
357+ reads = self.read(cr, uid, ids, ['name', 'code'], context=context)
358+ res = []
359+ for record in reads:
360+ name = record['name']
361+ if record['code']:
362+ name = '(' + record['code'] + ')' + ' ' + name
363+ res.append((record['id'], name))
364+ return res
365+
366+exemption_code()
367+
368+class account_salestax_avatax(osv.osv):
369+ _name = 'account.salestax.avatax'
370+ _description = 'AvaTax Configuration'
371+ __rec_name = 'account_number'
372+
373+ def _get_avatax_supported_countries(self, cr, uid, context=None):
374+ """ Returns the countries supported by AvaTax Address Validation Service."""
375+
376+ country_pool = self.pool.get('res.country')
377+ return country_pool.search(cr, uid, [('code', 'in', ['US', 'CA'])], context=context)
378+
379+ _columns = {
380+ 'account_number':fields.char('Account Number', size=64, required=True, help="Account Number provided by AvaTax"),
381+ 'license_key': fields.char('License Key', size=64, required=True, help="License Key provided by AvaTax"),
382+ 'service_url': fields.char('Service URL', size=64, required=True, help="The url to connect with"),
383+ 'date_expiration': fields.date('Service Expiration Date', readonly=True, help="The expiration date of the service"),
384+ 'request_timeout': fields.integer('Request Timeout', help="Defines AvaTax request time out length, AvaTax best practices prescribes default setting of 300 seconds"),
385+ 'company_code': fields.char('Company Code', size=64, required=True, help="The company code as defined in the Admin Console of AvaTax"),
386+ 'logging': fields.boolean('Enable Logging', help="Enables detailed AvaTax transaction logging within application"),
387+ 'address_validation': fields.boolean('Disable Address Validation', help="Check to disable address validation"),
388+ 'result_in_uppercase': fields.boolean('Results in Upper Case', help="Check is address validation results desired to be in upper case"),
389+ 'validation_on_save': fields.boolean('Address Validation on Save', help="Check if each address when saved should be validated"),
390+ 'force_address_validation': fields.boolean('Force Address Validation', help="Check if address validation should be done before tax calculation"),
391+ 'disable_tax_calculation': fields.boolean('Disable Tax Calculation', help="Check to disable tax calculation"),
392+ 'default_tax_schedule_id': fields.many2one('tax.schedule', 'Default Tax Schedule', help="Identifies customers using AVATAX. Only customers with AVATAX designation triggers tax calculation from Avatax otherwise it will follow the normal tax calculation that OpenERP provides"),
393+ 'default_shipping_code_id': fields.many2one('product.tax.code', 'Default Shipping Code', help="The default shipping code which will be passed to Avalara"),
394+ 'country_ids': fields.many2many('res.country', 'account_salestax_avatax_country_rel', 'account_salestax_avatax_id', 'country_id', 'Countries', help="Countries where address validation will be used"),
395+ 'active': fields.boolean('Active', help="Uncheck the active field to hide the record"),
396+ 'company_id': fields.many2one('res.company', 'Company', required=True, help="Company which has subscribed to the AvaTax service"),
397+ }
398+ _defaults = {
399+ 'active': True,
400+ 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'account.salestax.avatax', context=c),
401+ 'request_timeout': 300,
402+ 'country_ids': _get_avatax_supported_countries
403+ }
404+
405+ _sql_constraints = [
406+ ('code_company_uniq', 'unique (company_code)', 'The code of the company must be unique!'),
407+ ('account_number_company_uniq', 'unique (account_number, company_id)', 'The account number must be unique per company!'),
408+ ]
409+
410+ def _get_avatax_config_company(self, cr, uid, context=None):
411+ """ Returns the AvaTax configuration for the user company """
412+
413+ user_obj = self.pool.get('res.users')
414+ user = user_obj.browse(cr, uid, uid, context=context)
415+ avatax_config_ids = self.search(cr, uid, [('company_id', '=', user.company_id.id)], context=context)
416+ return avatax_config_ids and self.browse(cr, uid, avatax_config_ids[0], context=context) or False
417+
418+account_salestax_avatax()
419+
420+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
421\ No newline at end of file
422
423=== added file 'account_salestax_avatax/account_salestax_avatax_data.xml'
424--- account_salestax_avatax/account_salestax_avatax_data.xml 1970-01-01 00:00:00 +0000
425+++ account_salestax_avatax/account_salestax_avatax_data.xml 2011-09-22 18:36:28 +0000
426@@ -0,0 +1,90 @@
427+<?xml version="1.0" encoding="utf-8"?>
428+<openerp>
429+ <data noupdate="1">
430+
431+ <!--
432+ Partner Exemption Code
433+ -->
434+
435+ <record id="federal_government_type" model="exemption.code">
436+ <field name="name">Federal Government</field>
437+ <field name="code">A</field>
438+ </record>
439+
440+ <record id="state_government_type" model="exemption.code">
441+ <field name="name">State Government</field>
442+ <field name="code">B</field>
443+ </record>
444+
445+ <record id="tribe_indian_band_type" model="exemption.code">
446+ <field name="name">Tribe / Status Indian / Indian Band</field>
447+ <field name="code">C</field>
448+ </record>
449+
450+ <record id="foreign_diplomat_type" model="exemption.code">
451+ <field name="name">Foreign Diplomat</field>
452+ <field name="code">D</field>
453+ </record>
454+
455+ <record id="charitable_org_type" model="exemption.code">
456+ <field name="name">Charitable or Benevolent Org</field>
457+ <field name="code">E</field>
458+ </record>
459+
460+ <record id="religious_eductional_org_type" model="exemption.code">
461+ <field name="name">Religious or Educational Org</field>
462+ <field name="code">F</field>
463+ </record>
464+
465+ <record id="resale_type" model="exemption.code">
466+ <field name="name">Resale</field>
467+ <field name="code">G</field>
468+ </record>
469+
470+ <record id="commercial_agriculture_production_type" model="exemption.code">
471+ <field name="name">Commercial Agricultural Production</field>
472+ <field name="code">H</field>
473+ </record>
474+
475+ <record id="industrial_manufacturer_type" model="exemption.code">
476+ <field name="name">Industrial Production / Manufacturer</field>
477+ <field name="code">I</field>
478+ </record>
479+
480+ <record id="direct_pay_permit_type" model="exemption.code">
481+ <field name="name">Direct Pay Permit</field>
482+ <field name="code">J</field>
483+ </record>
484+
485+ <record id="direct_mail_type" model="exemption.code">
486+ <field name="name">Direct Mail</field>
487+ <field name="code">K</field>
488+ </record>
489+
490+ <record id="other_type" model="exemption.code">
491+ <field name="name">Other</field>
492+ <field name="code">L</field>
493+ </record>
494+
495+ <record id="local_government_type" model="exemption.code">
496+ <field name="name">Local Government</field>
497+ <field name="code">N</field>
498+ </record>
499+
500+ <record id="commercial_aquaculture_type" model="exemption.code">
501+ <field name="name">Commercial Aquaculture</field>
502+ <field name="code">P</field>
503+ </record>
504+
505+ <record id="commercial_fishery_type" model="exemption.code">
506+ <field name="name">Commercial Fishery</field>
507+ <field name="code">Q</field>
508+ </record>
509+
510+ <record id="non_resident_type" model="exemption.code">
511+ <field name="name">Non-Resident</field>
512+ <field name="code">R</field>
513+ </record>
514+
515+ </data>
516+</openerp>
517\ No newline at end of file
518
519=== added file 'account_salestax_avatax/account_salestax_avatax_view.xml'
520--- account_salestax_avatax/account_salestax_avatax_view.xml 1970-01-01 00:00:00 +0000
521+++ account_salestax_avatax/account_salestax_avatax_view.xml 2011-09-22 18:36:28 +0000
522@@ -0,0 +1,215 @@
523+<?xml version="1.0" encoding="utf-8"?>
524+<openerp>
525+ <data>
526+
527+ <!--
528+ AvaTax API Configuration in OpenERP
529+ -->
530+
531+ <record id="view_account_salestax_avatax_form" model="ir.ui.view">
532+ <field name="name">account.salestax.avatax.form</field>
533+ <field name="model">account.salestax.avatax</field>
534+ <field name="type">form</field>
535+ <field name="arch" type="xml">
536+ <form string="AvaTax API">
537+ <group col="6" colspan="4">
538+ <field name="company_id" groups="base.group_multi_company"/>
539+ <field name="company_code"/>
540+ </group>
541+ <notebook>
542+ <page string="Configuration">
543+ <group colspan="2" col="2">
544+ <separator string="Connection" colspan="2"/>
545+ <field name="account_number"/>
546+ <field name="license_key" password="True"/>
547+ <field name="service_url"/>
548+ <group colspan="2" col="4">
549+ <field name="date_expiration"/>
550+ <button name="%(action_account_salestax_avatax_ping)d" string="Test Connection" type="action" icon="gtk-go-forward"/>
551+ </group>
552+ </group>
553+ <group colspan="2" col="2">
554+ <separator string="Adaptor" colspan="2"/>
555+ <field name="request_timeout"/>
556+ <field name="logging"/>
557+ </group>
558+ <group colspan="2" col="2" expand="1">
559+ <separator string="Address Validation" colspan="2"/>
560+ <field name="address_validation"/>
561+ <field name="result_in_uppercase"/>
562+ <field name="validation_on_save"/>
563+ <separator string="Countries" colspan="2"/>
564+ <field name="country_ids" colspan="2" nolabel="1"/>
565+ </group>
566+ <group colspan="2" col="2">
567+ <separator string="Tax Calculation" colspan="2"/>
568+ <field name="disable_tax_calculation"/>
569+ <field name="force_address_validation"/>
570+ <field name="default_tax_schedule_id" attrs="{'required': [('disable_tax_calculation','=', False)]}"/>
571+ <field name="default_shipping_code_id" attrs="{'required': [('disable_tax_calculation','=', False)]}" domain="[('type', '=', 'freight')]"/>
572+ </group>
573+ </page>
574+ <page string="About AvaTax">
575+ <label string="Use the following for technical support:"/>
576+ <newline/>
577+ <label string="Publisher: Avalara.Inc"/>
578+ <newline/>
579+ <label string="For Technical Support: support@avalara.com"/>
580+ <newline/>
581+ <label string="Support Information: http://www.avalara.com/Contact-Us"/>
582+ <newline/>
583+ <label string="Support Telephone: 877-780-4848"/>
584+ </page>
585+ </notebook>
586+ </form>
587+ </field>
588+ </record>
589+
590+ <record id="view_account_salestax_avatax_tree" model="ir.ui.view">
591+ <field name="name">account.salestax.avatax.tree</field>
592+ <field name="model">account.salestax.avatax</field>
593+ <field name="type">tree</field>
594+ <field name="arch" type="xml">
595+ <tree string="AvaTax API">
596+ <field name="company_id" groups="base.group_multi_company"/>
597+ <field name="company_code"/>
598+ <field name="account_number"/>
599+ <field name="service_url"/>
600+ <field name="date_expiration"/>
601+ </tree>
602+ </field>
603+ </record>
604+
605+ <record id="action_account_salestax_avatax" model="ir.actions.act_window">
606+ <field name="name">AvaTax API</field>
607+ <field name="res_model">account.salestax.avatax</field>
608+ <field name="view_type">form</field>
609+ <field name="view_mode">tree,form</field>
610+ <field name="help">Configuration of AvaTax in OpenERP</field>
611+ </record>
612+
613+ <menuitem id="menu_avatax" name="AvaTax" parent="account.menu_finance_accounting" sequence="30"/>
614+
615+ <menuitem action="action_account_salestax_avatax" id="menu_avatax_api" name="AvaTax API" parent="menu_avatax" sequence="30"/>
616+
617+ <!--
618+ Jurisdiction Code
619+ -->
620+
621+ <record id="view_jurisdiction_code_form" model="ir.ui.view">
622+ <field name="name">jurisdiction.code.form</field>
623+ <field name="model">jurisdiction.code</field>
624+ <field name="type">form</field>
625+ <field name="arch" type="xml">
626+ <form string="Tax Jurisdiction Code">
627+ <group col="6" colspan="4">
628+ <field name="name"/>
629+ <field name="code"/>
630+ <field name="type"/>
631+ <field name="state_id" domain="[('country_id','=',country_id)]"/>
632+ </group>
633+ <separator colspan="4" string="Accounting Information"/>
634+ <field name="account_collected_id" domain="[('type','&lt;&gt;','view'),('type','&lt;&gt;','consolidation')]"/>
635+ <field name="account_paid_id" domain="[('type','&lt;&gt;','view'),('type','&lt;&gt;','consolidation')]"/>
636+ <separator colspan="4" string="Tax Declaration: Invoices"/>
637+ <field name="base_code_id"/>
638+ <field name="base_sign"/>
639+ <field name="tax_code_id"/>
640+ <field name="tax_sign"/>
641+ <separator colspan="4" string="Tax Declaration: Credit Notes"/>
642+ <field name="ref_base_code_id"/>
643+ <field name="ref_base_sign"/>
644+ <field name="ref_tax_code_id"/>
645+ <field name="ref_tax_sign"/>
646+ </form>
647+ </field>
648+ </record>
649+
650+ <record id="view_jurisdiction_code_tree" model="ir.ui.view">
651+ <field name="name">jurisdiction.code.tree</field>
652+ <field name="model">jurisdiction.code</field>
653+ <field name="type">tree</field>
654+ <field name="arch" type="xml">
655+ <tree string="Tax Jurisdiction Code">
656+ <field name="code"/>
657+ <field name="name"/>
658+ <field name="type"/>
659+ <field name="state_id"/>
660+ </tree>
661+ </field>
662+ </record>
663+
664+ <!--
665+ Tax Schedule
666+ -->
667+
668+ <record id="view_tax_schedule_tree" model="ir.ui.view">
669+ <field name="name">tax.schedule.tree</field>
670+ <field name="model">tax.schedule</field>
671+ <field name="type">tree</field>
672+ <field name="arch" type="xml">
673+ <tree string="Tax Schedule">
674+ <field name="code"/>
675+ <field name="name"/>
676+ <field name="country_id"/>
677+ <field name="company_id" groups="base.group_multi_company"/>
678+ </tree>
679+ </field>
680+ </record>
681+
682+ <record id="view_tax_schedule_form" model="ir.ui.view">
683+ <field name="name">tax.schedule.form</field>
684+ <field name="model">tax.schedule</field>
685+ <field name="type">form</field>
686+ <field name="arch" type="xml">
687+ <form string="Tax Schedule">
688+ <group col="6" colspan="4">
689+ <field name="name"/>
690+ <field name="code"/>
691+ <field name="country_id"/>
692+ <field name="company_id" groups="base.group_multi_company"/>
693+ </group>
694+ <field name="jurisdiction_code_ids" nolabel="1">
695+ <form string="Tax Jurisdiction Codes">
696+ <group col="6" colspan="4">
697+ <field name="name"/>
698+ <field name="code"/>
699+ <field name="type"/>
700+ <field name="state_id" domain="[('country_id','=',parent.country_id)]"/>
701+ </group>
702+ <separator colspan="4" string="Accounting Information"/>
703+ <field name="account_collected_id" domain="[('type','&lt;&gt;','view'),('type','&lt;&gt;','consolidation')]"/>
704+ <field name="account_paid_id" domain="[('type','&lt;&gt;','view'),('type','&lt;&gt;','consolidation')]"/>
705+ <separator colspan="4" string="Tax Declaration: Invoices"/>
706+ <field name="base_code_id"/>
707+ <field name="base_sign"/>
708+ <field name="tax_code_id"/>
709+ <field name="tax_sign"/>
710+ <separator colspan="4" string="Tax Declaration: Credit Notes"/>
711+ <field name="ref_base_code_id"/>
712+ <field name="ref_base_sign"/>
713+ <field name="ref_tax_code_id"/>
714+ <field name="ref_tax_sign"/>
715+ </form>
716+ <tree string="Tax Jurisdiction Codes">
717+ <field name="code"/>
718+ <field name="name"/>
719+ <field name="state_id"/>
720+ <field name="type"/>
721+ </tree>
722+ </field>
723+ </form>
724+ </field>
725+ </record>
726+
727+ <record id="action_tax_schedule" model="ir.actions.act_window">
728+ <field name="name">Tax Schedules</field>
729+ <field name="res_model">tax.schedule</field>
730+ <field name="view_type">form</field>
731+ <field name="view_mode">tree,form</field>
732+ </record>
733+
734+ <menuitem action="action_tax_schedule" id="menu_tax_schedule" name="Tax Schedules" parent="menu_avatax" sequence="29"/>
735+
736+ </data>
737+</openerp>
738\ No newline at end of file
739
740=== added file 'account_salestax_avatax/invoice.py'
741--- account_salestax_avatax/invoice.py 1970-01-01 00:00:00 +0000
742+++ account_salestax_avatax/invoice.py 2011-09-22 18:36:28 +0000
743@@ -0,0 +1,252 @@
744+# -*- coding: utf-8 -*-
745+##############################################################################
746+#
747+# OpenERP, Open Source Management Solution
748+# Copyright (C) 2011 NovaPoint Group LLC (<http://www.novapointgroup.com>)
749+# Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
750+#
751+# This program is free software: you can redistribute it and/or modify
752+# it under the terms of the GNU Affero General Public License as
753+# published by the Free Software Foundation, either version 3 of the
754+# License, or (at your option) any later version.
755+#
756+# This program is distributed in the hope that it will be useful,
757+# but WITHOUT ANY WARRANTY; without even the implied warranty of
758+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
759+# GNU Affero General Public License for more details.
760+#
761+# You should have received a copy of the GNU Affero General Public License
762+# along with this program. If not, see <http://www.gnu.org/licenses/>.
763+#
764+##############################################################################
765+import time
766+import string
767+
768+from osv import osv, fields
769+from tools.translate import _
770+import decimal_precision as dp
771+
772+class account_invoice(osv.osv):
773+ _inherit = "account.invoice"
774+
775+ def _avatax_calc(self, cr, uid, ids, name, args, context=None):
776+ res = {}
777+ avatax_config_obj = self.pool.get('account.salestax.avatax')
778+ avatax_config = avatax_config_obj._get_avatax_config_company(cr, uid)
779+
780+ for invoice in self.browse(cr, uid, ids, context=context):
781+ if invoice.type in ['out_invoice', 'out_refund'] and \
782+ avatax_config and not avatax_config.disable_tax_calculation and \
783+ avatax_config.default_tax_schedule_id.id == invoice.partner_id.tax_schedule_id.id:
784+ res[invoice.id] = True
785+ else:
786+ res[invoice.id] = False
787+ return res
788+
789+ _columns = {
790+ 'invoice_doc_no': fields.char('Invoice No', size=32, readonly=True, states={'draft':[('readonly',False)]}, help="Reference of the invoice"),
791+ 'invoice_date': fields.date('Invoice Date', readonly=True),
792+ 'avatax_calc': fields.function(_avatax_calc, method=True, string='Avatax Calculation', type='boolean', store=True)
793+ }
794+
795+ def action_commit_tax(self, cr, uid, ids, context=None):
796+ avatax_config_obj = self.pool.get('account.salestax.avatax')
797+ account_tax_obj = self.pool.get('account.tax')
798+ partner_obj = self.pool.get('res.partner')
799+ for invoice in self.browse(cr, uid, ids, context=context):
800+ if invoice.avatax_calc:
801+ avatax_config = avatax_config_obj._get_avatax_config_company(cr, uid)
802+ sign = invoice.type == 'out_invoice' and 1 or -1
803+ lines = self.create_lines(cr, uid, invoice.invoice_line, sign)
804+ address = partner_obj.address_get(cr, uid, [invoice.company_id.partner_id.id], ['contact'])
805+ account_tax_obj._check_compute_tax(cr, uid, avatax_config, invoice.date_invoice,
806+ invoice.internal_number, not invoice.invoice_doc_no and 'SalesInvoice' or 'ReturnInvoice',
807+ invoice.partner_id, address['contact'],
808+ invoice.address_invoice_id.id, lines, invoice.shipcharge, invoice.user_id,
809+ True, invoice.invoice_date,
810+ invoice.invoice_doc_no, context=context)
811+ return True
812+
813+ def create_lines(self, cr, uid, invoice_lines, sign):
814+ lines = []
815+ for line in invoice_lines:
816+ lines.append({
817+ 'qty': line.quantity,
818+ 'itemcode': line.product_id and line.product_id.default_code or None,
819+ 'description': line.name,
820+ 'amount': sign * line.price_unit * (1-(line.discount or 0.0)/100.0) * line.quantity,
821+ 'tax_code': line.product_id and ((line.product_id.tax_code_id and line.product_id.tax_code_id.name) or
822+ (line.product_id.categ_id.tax_code_id and line.product_id.categ_id.tax_code_id.name)) or None
823+ })
824+ return lines
825+
826+ def refund(self, cr, uid, ids, date=None, period_id=None, description=None, journal_id=None):
827+ refund_ids = super(account_invoice, self).refund(cr, uid, ids, date, period_id, description, journal_id)
828+ invoice = self.browse(cr, uid, ids[0])
829+ if invoice.avatax_calc:
830+ self.write(cr, uid, refund_ids[0], {
831+ 'invoice_doc_no': invoice.internal_number,
832+ 'invoice_date': invoice.date_invoice
833+ })
834+ return refund_ids
835+
836+ def action_cancel(self, cr, uid, ids, *args):
837+ account_tax_obj = self.pool.get('account.tax')
838+ res = super(account_invoice, self).action_cancel(cr, uid, ids, *args)
839+
840+ for invoice in self.browse(cr, uid, ids, *args):
841+ if invoice.avatax_calc and invoice.internal_number:
842+ doc_type = invoice.type == 'out_invoice' and 'SalesInvoice' or 'ReturnInvoice'
843+ account_tax_obj.cancel_tax(cr, uid, avatax_config, invoice.internal_number, doc_type, 'DocVoided')
844+ return res
845+
846+ def check_tax_lines(self, cr, uid, inv, compute_taxes, ait_obj):
847+ super(account_invoice, self).check_tax_lines(cr, uid, inv, compute_taxes, ait_obj)
848+ if inv.avatax_calc:
849+ for tax in inv.tax_line:
850+ key = (tax.tax_code_id.id, tax.base_code_id.id, tax.account_id.id)
851+ if abs(compute_taxes[key]['amount'] - tax.amount) > inv.company_id.currency_id.rounding:
852+ raise osv.except_osv(_('Warning !'), _('Tax amount different !\nClick on compute to update tax base'))
853+
854+account_invoice()
855+
856+class account_invoice_tax(osv.osv):
857+ _inherit = "account.invoice.tax"
858+
859+ def compute(self, cr, uid, invoice_id, context=None):
860+ avatax_config_obj = self.pool.get('account.salestax.avatax')
861+ invoice_obj = self.pool.get('account.invoice')
862+ partner_obj = self.pool.get('res.partner')
863+ account_tax_obj = self.pool.get('account.tax')
864+ jurisdiction_code_obj = self.pool.get('jurisdiction.code')
865+ cur_obj = self.pool.get('res.currency')
866+ state_obj = self.pool.get('res.country.state')
867+ invoice = invoice_obj.browse(cr, uid, invoice_id, context=context)
868+ tax_grouped = {}
869+ if invoice.avatax_calc:
870+ vals = {}
871+ cur = invoice.currency_id
872+ company_currency = invoice.company_id.currency_id.id
873+ lines = invoice_obj.create_lines(cr, uid, invoice.invoice_line, 1)
874+ avatax_config = avatax_config_obj._get_avatax_config_company(cr, uid)
875+ # to check for company address
876+ company_address = partner_obj.address_get(cr, uid, [invoice.company_id.partner_id.id], ['default'])
877+ for tax in account_tax_obj._check_compute_tax(cr, uid, avatax_config,
878+ invoice.date_invoice or time.strftime('%Y-%m-%d'),
879+ invoice.internal_number, 'SalesOrder', invoice.partner_id,
880+ company_address['default'], invoice.address_invoice_id.id,
881+ lines, invoice.shipcharge, invoice.user_id, False,
882+ invoice.date_invoice or time.strftime('%Y-%m-%d'),
883+ context=context).TaxSummary[0]:
884+ val = {}
885+ state_ids = state_obj.search(cr, uid, [('code', '=', tax.Region)], context=context)
886+ state_id = state_ids and state_ids[0] or False
887+ jurisdiction_code_ids = jurisdiction_code_obj.search(cr, uid, [('type', '=', tax['JurisType'].lower()),
888+ ('tax_schedule_id', '=', avatax_config.default_tax_schedule_id.id),
889+ ('state_id', '=', state_id)],
890+ context=context)
891+ if not jurisdiction_code_ids:
892+ raise osv.except_osv(
893+ _('Jurisdiction Code is not defined !'),
894+ _('You must define a jurisdiction code for %s type for %s state in the tax schedule for %s.'
895+ % (tax['JurisType'], tax['Region'], avatax_config.default_tax_schedule_id.name)))
896+ jurisdiction_code = jurisdiction_code_obj.browse(cr, uid, jurisdiction_code_ids[0], context=context)
897+
898+ val['invoice_id'] = invoice.id
899+ val['name'] = tax['TaxName'] or '/'
900+ val['amount'] = tax['Tax']
901+ val['manual'] = False
902+ val['base'] = tax['Base']
903+
904+ if invoice.type == 'out_invoice':
905+ val['base_code_id'] = jurisdiction_code.base_code_id.id
906+ val['tax_code_id'] = jurisdiction_code.tax_code_id.id
907+ val['base_amount'] = cur_obj.compute(cr, uid, invoice.currency_id.id,
908+ company_currency, val['base'] * jurisdiction_code.base_sign,
909+ context={'date': invoice.date_invoice or time.strftime('%Y-%m-%d')}, round=False)
910+ val['tax_amount'] = cur_obj.compute(cr, uid, invoice.currency_id.id,
911+ company_currency, val['amount'] * jurisdiction_code.tax_sign,
912+ context={'date': invoice.date_invoice or time.strftime('%Y-%m-%d')}, round=False)
913+ val['account_id'] = jurisdiction_code.account_collected_id.id
914+ val['base_sign'] = jurisdiction_code.base_sign
915+ else:
916+ val['base_code_id'] = jurisdiction_code.ref_base_code_id.id
917+ val['ref_base_code_id'] = jurisdiction_code.ref_base_code_id.id
918+ val['tax_code_id'] = jurisdiction_code.ref_tax_code_id.id
919+ val['base_amount'] = cur_obj.compute(cr, uid, invoice.currency_id.id,
920+ company_currency, val['base'] * jurisdiction_code.ref_base_sign,
921+ context={'date': invoice.date_invoice or time.strftime('%Y-%m-%d')}, round=False)
922+ val['tax_amount'] = cur_obj.compute(cr, uid, invoice.currency_id.id,
923+ company_currency, val['amount'] * jurisdiction_code.ref_tax_sign,
924+ context={'date': invoice.date_invoice or time.strftime('%Y-%m-%d')}, round=False)
925+ val['account_id'] = jurisdiction_code.account_paid_id.id
926+ val['ref_base_sign'] = jurisdiction_code.ref_base_sign
927+
928+ key = (val['tax_code_id'], val['base_code_id'], val['account_id'])
929+ if not key in tax_grouped:
930+ tax_grouped[key] = val
931+ else:
932+ tax_grouped[key]['amount'] += val['amount']
933+ tax_grouped[key]['base'] += val['base']
934+ tax_grouped[key]['base_amount'] += val['base_amount']
935+ tax_grouped[key]['tax_amount'] += val['tax_amount']
936+
937+ for t in tax_grouped.values():
938+ t['base'] = cur_obj.round(cr, uid, cur, t['base'])
939+ t['amount'] = cur_obj.round(cr, uid, cur, t['amount'])
940+ t['base_amount'] = cur_obj.round(cr, uid, cur, t['base_amount'])
941+ t['tax_amount'] = cur_obj.round(cr, uid, cur, t['tax_amount'])
942+ return tax_grouped
943+ return super(account_invoice_tax, self).compute(cr, uid, invoice_id, context=context)
944+
945+account_invoice_tax()
946+
947+class account_invoice_line(osv.osv):
948+ _inherit = "account.invoice.line"
949+
950+ def move_line_get(self, cr, uid, invoice_id, context=None):
951+ res = []
952+ tax_obj = self.pool.get('account.tax')
953+ cur_obj = self.pool.get('res.currency')
954+ invoice_obj = self.pool.get('account.invoice')
955+ ait_obj = self.pool.get('account.invoice.tax')
956+ invoice = invoice_obj.browse(cr, uid, invoice_id, context=context)
957+ company_currency = invoice.company_id.currency_id.id
958+
959+ if invoice.avatax_calc:
960+ for line in invoice.invoice_line:
961+ mres = self.move_line_get_item(cr, uid, line, context)
962+ if not mres:
963+ continue
964+ res.append(mres)
965+ tax_code_found= False
966+
967+ for tax in ait_obj.compute(cr, uid, invoice_id, context=context).values():
968+ if invoice.type == 'out_invoice':
969+ tax_code_id = tax['base_code_id']
970+ tax_amount = line.price_subtotal * tax['base_sign']
971+ else:
972+ tax_code_id = tax['ref_base_code_id']
973+ tax_amount = line.price_subtotal * tax['ref_base_sign']
974+
975+ if tax_code_found:
976+ if not tax_code_id:
977+ continue
978+ res.append(self.move_line_get_item(cr, uid, line, context))
979+ res[-1]['price'] = 0.0
980+ res[-1]['account_analytic_id'] = False
981+ elif not tax_code_id:
982+ continue
983+ tax_code_found = True
984+
985+ res[-1]['tax_code_id'] = tax_code_id
986+ res[-1]['tax_amount'] = cur_obj.compute(cr, uid, invoice.currency_id.id, company_currency,
987+ tax_amount, context={'date': invoice.date_invoice})
988+
989+ else:
990+ res = super(account_invoice_line, self).move_line_get( cr, uid, invoice_id, context=context)
991+ return res
992+
993+account_invoice_line()
994+
995+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
996\ No newline at end of file
997
998=== added file 'account_salestax_avatax/partner.py'
999--- account_salestax_avatax/partner.py 1970-01-01 00:00:00 +0000
1000+++ account_salestax_avatax/partner.py 2011-09-22 18:36:28 +0000
1001@@ -0,0 +1,169 @@
1002+# -*- coding: utf-8 -*-
1003+##############################################################################
1004+#
1005+# OpenERP, Open Source Management Solution
1006+# Copyright (C) 2011 NovaPoint Group LLC (<http://www.novapointgroup.com>)
1007+# Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
1008+#
1009+# This program is free software: you can redistribute it and/or modify
1010+# it under the terms of the GNU Affero General Public License as
1011+# published by the Free Software Foundation, either version 3 of the
1012+# License, or (at your option) any later version.
1013+#
1014+# This program is distributed in the hope that it will be useful,
1015+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1016+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1017+# GNU Affero General Public License for more details.
1018+#
1019+# You should have received a copy of the GNU Affero General Public License
1020+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1021+#
1022+##############################################################################
1023+import time
1024+
1025+from osv import osv, fields
1026+from tools.translate import _
1027+import decimal_precision as dp
1028+
1029+from suds_client import AvaTaxService, BaseAddress
1030+
1031+class res_partner(osv.osv):
1032+ _inherit = 'res.partner'
1033+ _columns = {
1034+ 'exemption_number': fields.char('Exemption Number', size=64, help="Indicates if the customer is exempt or not"),
1035+ 'exemption_code_id': fields.many2one('exemption.code', 'Exemption Code', help="Indicates the type of exemption the customer may have"),
1036+ 'tax_schedule_id': fields.many2one('tax.schedule', 'Tax Schedule', help="Identifies customers using AVATAX. Only customers with AVATAX designation triggers tax calculation from Avatax otherwise it will follow the normal tax calculation that OpenERP provides")
1037+ }
1038+
1039+res_partner()
1040+
1041+class res_partner_address(osv.osv):
1042+ _inherit = 'res.partner.address'
1043+ _columns = {
1044+ 'date_validation': fields.date('Last Validation Date', readonly=True, help="The date the address was last validated by AvaTax and accepted"),
1045+ 'validation_method': fields.selection([('avatax', 'AVATAX'), ('usps', 'USPS'), ('other', 'Other')], 'Address Validation Method', readonly=True, help="It gets populated when the address is validated by the method"),
1046+ 'latitude': fields.char('Latitude', size=32),
1047+ 'longitude': fields.char('Longitude', size=32),
1048+ 'validated_on_save': fields.boolean('Validated On Save', help="Indicates if the address is already validated on save before calling the wizard")
1049+ }
1050+
1051+ def check_avatax_support(self, cr, uid, avatax_config, country_id, context=None):
1052+ """ Checks if address validation pre-condition meets. """
1053+
1054+ if avatax_config.address_validation:
1055+ raise osv.except_osv(_('Address Validation is Disabled'), _("The AvaTax Address Validation Service is disabled by the administrator. Please make sure it's enabled for the address validation"))
1056+ if country_id and country_id not in [x.id for x in avatax_config.country_ids]:
1057+ raise osv.except_osv(_('Address Validation not Supported for this country'), _("The AvaTax Address Validation Service does not support this country in the configuration, please continue with your normal process."))
1058+ return True
1059+
1060+ def get_state_id(self, cr, uid, code, context=None):
1061+ """ Returns the id of the state from the code. """
1062+
1063+ state_obj = self.pool.get('res.country.state')
1064+ return state_obj.search(cr, uid, [('code', '=', code)], context=context)[0]
1065+
1066+ def get_country_id(self, cr, uid, code, context=None):
1067+ """ Returns the id of the country from the code. """
1068+
1069+ country_obj = self.pool.get('res.country')
1070+ return country_obj.search(cr, uid, [('code', '=', code)], context=context)[0]
1071+
1072+ def get_state_code(self, cr, uid, state_id, context=None):
1073+ """ Returns the code from the id of the state. """
1074+
1075+ state_obj = self.pool.get('res.country.state')
1076+ return state_id and state_obj.browse(cr, uid, state_id, context=context).code
1077+
1078+ def get_country_code(self, cr, uid, country_id, context=None):
1079+ """ Returns the code from the id of the country. """
1080+
1081+ country_obj = self.pool.get('res.country')
1082+ return country_id and country_obj.browse(cr, uid, country_id, context=context).code
1083+
1084+ def _validate_address(self, cr, uid, address, avatax_config=False, context=None):
1085+ """ Returns the valid address from the AvaTax Address Validation Service. """
1086+
1087+ avatax_config_obj= self.pool.get('account.salestax.avatax')
1088+ if context is None:
1089+ context = {}
1090+
1091+ if not avatax_config:
1092+ avatax_config = avatax_config_obj._get_avatax_config_company(cr, uid, context=context)
1093+
1094+ # Create the AvaTax Address service with the configuration parameters set for the instance
1095+ avapoint = AvaTaxService(avatax_config.account_number, avatax_config.license_key,
1096+ avatax_config.service_url, avatax_config.request_timeout, avatax_config.logging)
1097+ addSvc = avapoint.create_address_service().addressSvc
1098+
1099+ # Obtain the state code & country code and create a BaseAddress Object
1100+ state_code = address.get('state_id') and self.get_state_code(cr, uid, address['state_id'], context=context)
1101+ country_code = address.get('country_id') and self.get_country_code(cr, uid, address['country_id'], context=context)
1102+ baseaddress = BaseAddress(addSvc, address.get('street') or None, address.get('street2') or None,
1103+ address.get('city'), address.get('zip'), state_code, country_code, 0).data
1104+ result = avapoint.validate_address(baseaddress, avatax_config.result_in_uppercase and 'Upper' or 'Default')
1105+ valid_address = result.ValidAddresses[0][0]
1106+ return valid_address
1107+
1108+ def update_address(self, cr, uid, vals, ids=None, from_write=False, context=None):
1109+ """ Updates the vals dictionary with the valid address as returned from the AvaTax Address Validation. """
1110+
1111+ address = vals
1112+
1113+ if (vals.get('street') or vals.get('street2') or vals.get('zip') or vals.get('city') or \
1114+ vals.get('country_id') or vals.get('state_id')):
1115+
1116+ address_obj = self.pool.get('res.partner.address')
1117+ avatax_config_obj= self.pool.get('account.salestax.avatax')
1118+ avatax_config = avatax_config_obj._get_avatax_config_company(cr, uid, context=context)
1119+
1120+ if avatax_config and avatax_config.validation_on_save:
1121+ # It implies that there is AvaTax configuration existing for the user company with
1122+ # option 'Address Validation when a address is saved'
1123+ # Check if the other conditions are met
1124+ self.check_avatax_support(cr, uid, avatax_config, address.get('country_id'), context=context)
1125+
1126+ # If this method is called from the 'write' method then we also need to pass
1127+ # the previous address along with the modifications in the vals dictionary
1128+ if from_write:
1129+ fields_to_read = filter(lambda x: x not in vals, ['street', 'street2', 'city', 'state_id', 'zip', 'country_id'])
1130+ address = fields_to_read and address_obj.read(cr, uid, ids, fields_to_read, context=context)[0] or {}
1131+ address['state_id'] = address.get('state_id') and address['state_id'][0]
1132+ address['country_id'] = address.get('country_id') and address['country_id'][0]
1133+ address.update(vals)
1134+
1135+ valid_address = self._validate_address(cr, uid, address, avatax_config, context=context)
1136+ vals.update({
1137+ 'street': valid_address.Line1,
1138+ 'street2': valid_address.Line2,
1139+ 'city': valid_address.City,
1140+ 'state_id': self.get_state_id(cr, uid, valid_address.Region, context=context),
1141+ 'zip': valid_address.PostalCode,
1142+ 'country_id': self.get_country_id(cr, uid, valid_address.Country, context=context),
1143+ 'latitude': valid_address.Latitude,
1144+ 'longitude': valid_address.Longitude,
1145+ 'date_validation': time.strftime('%Y-%m-%d'),
1146+ 'validation_method': 'avatax',
1147+ 'validated_on_save': True
1148+ })
1149+ return vals
1150+
1151+
1152+ def create(self, cr, uid, vals, context=None):
1153+ vals = self.update_address(cr, uid, vals, context=context)
1154+ return super(res_partner_address, self).create(cr, uid, vals, context)
1155+
1156+ def write(self, cr, uid, ids, vals, context=None):
1157+ if context is None:
1158+ context = {}
1159+
1160+ # Follow the normal write process if it's a write operation from the wizard
1161+ if context.get('from_validate_button', False):
1162+ return super(res_partner_address, self).write(cr, uid, ids, vals, context)
1163+
1164+ if context.get('active_id', False):
1165+ vals = self.update_address(cr, uid, vals, ids, True, context=context)
1166+ return super(res_partner_address, self).write(cr, uid, ids, vals, context)
1167+
1168+res_partner_address()
1169+
1170+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1171\ No newline at end of file
1172
1173=== added file 'account_salestax_avatax/partner_view.xml'
1174--- account_salestax_avatax/partner_view.xml 1970-01-01 00:00:00 +0000
1175+++ account_salestax_avatax/partner_view.xml 2011-09-22 18:36:28 +0000
1176@@ -0,0 +1,63 @@
1177+<?xml version="1.0" encoding="utf-8"?>
1178+<openerp>
1179+ <data>
1180+
1181+ <!--
1182+ Partner Tax & Address Validation
1183+ -->
1184+
1185+ <record id="view_partner_details_form_inherit" model="ir.ui.view">
1186+ <field name="name">res.partner.details.form.inherit</field>
1187+ <field name="model">res.partner</field>
1188+ <field name="type">form</field>
1189+ <field name="inherit_id" ref="base.view_partner_form"/>
1190+ <field name="arch" type="xml">
1191+ <xpath expr="/form/notebook/page[@string='Accounting']/field[@name='bank_ids']" position="before">
1192+ <group col="1" colspan="2">
1193+ <separator string="Tax Details" colspan="2" col="2"/>
1194+ <field name="tax_schedule_id"/>
1195+ <field name="exemption_number"/>
1196+ <field name="exemption_code_id"/>
1197+ </group>
1198+ </xpath>
1199+ <xpath expr="/form/notebook/page[@string='General']/field[@name='address']/form" position="inside">
1200+ <group col="4" colspan="2">
1201+ <field name="latitude"/>
1202+ <field name="longitude"/>
1203+ </group>
1204+ <newline/>
1205+ <group colspan="2" col="4">
1206+ <separator string="Validation" colspan="4" col="4"/>
1207+ <field name="date_validation"/>
1208+ <field name="validation_method"/>
1209+ <button name="%(action_account_salestax_avatax_address_validate)d" string="_Validate" type="action" icon="terp-camera_test" colspan="2" context="{'from_validate_button': True}"/>
1210+ </group>
1211+ </xpath>
1212+ </field>
1213+ </record>
1214+
1215+ <!--
1216+ Partner Address
1217+ -->
1218+
1219+ <record id="view_partner_address_details_form_inherit" model="ir.ui.view">
1220+ <field name="name">res.partner.address.details.form.inherit</field>
1221+ <field name="model">res.partner.address</field>
1222+ <field name="type">form</field>
1223+ <field name="inherit_id" ref="base.view_partner_address_form1"/>
1224+ <field name="arch" type="xml">
1225+ <xpath expr="/form[@string='Address']/group[@col='2']/field[@name='state_id']" position="after">
1226+ <field name="latitude"/>
1227+ <field name="longitude"/>
1228+ <group colspan="2" col="4">
1229+ <separator string="Validation" colspan="4" col="4"/>
1230+ <field name="date_validation"/>
1231+ <field name="validation_method"/>
1232+ <button name="%(action_account_salestax_avatax_address_validate)d" string="_Validate" type="action" icon="gtk-go-forward" colspan="2" context="{'from_validate_button': True}"/>
1233+ </group>
1234+ </xpath>
1235+ </field>
1236+ </record>
1237+
1238+ </data>
1239+</openerp>
1240
1241=== added file 'account_salestax_avatax/product.py'
1242--- account_salestax_avatax/product.py 1970-01-01 00:00:00 +0000
1243+++ account_salestax_avatax/product.py 2011-09-22 18:36:28 +0000
1244@@ -0,0 +1,57 @@
1245+# -*- coding: utf-8 -*-
1246+##############################################################################
1247+#
1248+# OpenERP, Open Source Management Solution
1249+# Copyright (C) 2011 NovaPoint Group LLC (<http://www.novapointgroup.com>)
1250+# Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
1251+#
1252+# This program is free software: you can redistribute it and/or modify
1253+# it under the terms of the GNU Affero General Public License as
1254+# published by the Free Software Foundation, either version 3 of the
1255+# License, or (at your option) any later version.
1256+#
1257+# This program is distributed in the hope that it will be useful,
1258+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1259+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1260+# GNU Affero General Public License for more details.
1261+#
1262+# You should have received a copy of the GNU Affero General Public License
1263+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1264+#
1265+##############################################################################
1266+from osv import osv, fields
1267+
1268+class product_tax_code(osv.osv):
1269+ _name = 'product.tax.code'
1270+ _description = 'Tax Code'
1271+ _columns = {
1272+ 'name': fields.char('Code', size=8, required=True),
1273+ 'description': fields.char('Description', size=64),
1274+ 'type': fields.selection([('product', 'Product'), ('freight', 'Freight'), ('service', 'Service'),
1275+ ('digital', 'Digital'), ('other', 'Other')], 'Type', required=True, help="Type of tax code as defined in AvaTax"),
1276+ 'company_id': fields.many2one('res.company', 'Company', required=True),
1277+ }
1278+ _defaults = {
1279+ 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'product.tax.code', context=c),
1280+ }
1281+
1282+product_tax_code()
1283+
1284+
1285+class product_template(osv.osv):
1286+ _inherit = "product.template"
1287+ _columns = {
1288+ 'tax_code_id': fields.many2one('product.tax.code', 'Tax Code', help="AvaTax Tax Code")
1289+ }
1290+
1291+product_template()
1292+
1293+class product_category(osv.osv):
1294+ _inherit = "product.category"
1295+ _columns = {
1296+ 'tax_code_id': fields.many2one('product.tax.code', 'Tax Code', help="AvaTax Tax Code")
1297+ }
1298+
1299+product_category()
1300+
1301+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1302\ No newline at end of file
1303
1304=== added file 'account_salestax_avatax/product_view.xml'
1305--- account_salestax_avatax/product_view.xml 1970-01-01 00:00:00 +0000
1306+++ account_salestax_avatax/product_view.xml 2011-09-22 18:36:28 +0000
1307@@ -0,0 +1,108 @@
1308+<?xml version="1.0" encoding="utf-8"?>
1309+<openerp>
1310+ <data>
1311+
1312+ <!--
1313+ Product Tax Code
1314+ -->
1315+
1316+ <record id="view_product_tax_code_tree" model="ir.ui.view">
1317+ <field name="name">product.tax.code.tree</field>
1318+ <field name="model">product.tax.code</field>
1319+ <field name="type">tree</field>
1320+ <field name="arch" type="xml">
1321+ <tree string="Product Tax Code">
1322+ <field name="name"/>
1323+ <field name="description"/>
1324+ <field name="type"/>
1325+ <field name="company_id" groups="base.group_multi_company"/>
1326+ </tree>
1327+ </field>
1328+ </record>
1329+
1330+ <record id="view_product_tax_code_form" model="ir.ui.view">
1331+ <field name="name">product.tax.code.form</field>
1332+ <field name="model">product.tax.code</field>
1333+ <field name="type">form</field>
1334+ <field name="arch" type="xml">
1335+ <form string="Product Tax Code">
1336+ <field name="name"/>
1337+ <field name="company_id" groups="base.group_multi_company"/>
1338+ <field name="description"/>
1339+ <field name="type"/>
1340+ </form>
1341+ </field>
1342+ </record>
1343+
1344+
1345+ <record id="action_product_tax_code" model="ir.actions.act_window">
1346+ <field name="name">Product Tax Codes</field>
1347+ <field name="res_model">product.tax.code</field>
1348+ <field name="view_type">form</field>
1349+ <field name="view_mode">tree,form</field>
1350+ </record>
1351+
1352+ <menuitem action="action_product_tax_code" id="menu_product_tax_code" name="Product Tax Codes" parent="menu_avatax" sequence="29"/>
1353+
1354+ <!--
1355+ Product
1356+ -->
1357+
1358+ <record id="view_product_normal_form_avatax_inherit" model="ir.ui.view">
1359+ <field name="name">product.normal.form.avatax.inherit</field>
1360+ <field name="model">product.product</field>
1361+ <field name="type">form</field>
1362+ <field name="inherit_id" ref="product.product_normal_form_view"/>
1363+ <field name="priority">30</field>
1364+ <field name="arch" type="xml">
1365+ <xpath expr="/form/notebook/page[@string='Accounting']/group[@name='properties']" position="inside">
1366+ <separator string="AvaTax Properties" colspan="4"/>
1367+ <group colspan="2" col="2">
1368+ <field name="tax_code_id"/>
1369+ </group>
1370+ </xpath>
1371+ </field>
1372+ </record>
1373+
1374+ <!--
1375+ Product Template
1376+ -->
1377+
1378+ <record id="view_product_template_form_avatax_inherit" model="ir.ui.view">
1379+ <field name="name">product.template.form.avatax.inherit</field>
1380+ <field name="model">product.template</field>
1381+ <field name="type">form</field>
1382+ <field name="inherit_id" ref="product.product_template_form_view"/>
1383+ <field name="priority">30</field>
1384+ <field name="arch" type="xml">
1385+ <xpath expr="/form/notebook/page[@string='Accounting']" position="inside">
1386+ <separator string="AvaTax Properties" colspan="4"/>
1387+ <group colspan="2" col="2">
1388+ <field name="tax_code_id"/>
1389+ </group>
1390+ <newline/>
1391+ </xpath>
1392+ </field>
1393+ </record>
1394+
1395+ <!--
1396+ Product Category
1397+ -->
1398+
1399+ <record id="view_product_category_form_avatax_inherit" model="ir.ui.view">
1400+ <field name="name">product.category.form.avatax.inherit</field>
1401+ <field name="model">product.category</field>
1402+ <field name="type">form</field>
1403+ <field name="inherit_id" ref="product.product_category_form_view"/>
1404+ <field name="arch" type="xml">
1405+ <form position="inside">
1406+ <group col="2" colspan="2">
1407+ <separator string="AvaTax Properties" colspan="2"/>
1408+ <field name="tax_code_id"/>
1409+ </group>
1410+ </form>
1411+ </field>
1412+ </record>
1413+
1414+ </data>
1415+</openerp>
1416
1417=== added file 'account_salestax_avatax/sale.py'
1418--- account_salestax_avatax/sale.py 1970-01-01 00:00:00 +0000
1419+++ account_salestax_avatax/sale.py 2011-09-22 18:36:28 +0000
1420@@ -0,0 +1,118 @@
1421+# -*- coding: utf-8 -*-
1422+##############################################################################
1423+#
1424+# OpenERP, Open Source Management Solution
1425+# Copyright (C) 2011 NovaPoint Group LLC (<http://www.novapointgroup.com>)
1426+# Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
1427+#
1428+# This program is free software: you can redistribute it and/or modify
1429+# it under the terms of the GNU Affero General Public License as
1430+# published by the Free Software Foundation, either version 3 of the
1431+# License, or (at your option) any later version.
1432+#
1433+# This program is distributed in the hope that it will be useful,
1434+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1435+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1436+# GNU Affero General Public License for more details.
1437+#
1438+# You should have received a copy of the GNU Affero General Public License
1439+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1440+#
1441+##############################################################################
1442+from osv import osv, fields
1443+from tools.translate import _
1444+import decimal_precision as dp
1445+
1446+class sale_order(osv.osv):
1447+ _inherit = "sale.order"
1448+
1449+ def _amount_all(self, cr, uid, ids, field_name, arg, context=None):
1450+ currency_obj = self.pool.get('res.currency')
1451+ res = {}
1452+ avatax_config_obj = self.pool.get('account.salestax.avatax')
1453+ avatax_config = avatax_config_obj._get_avatax_config_company(cr, uid)
1454+ res = super(sale_order, self)._amount_all(cr, uid, ids, field_name, arg, context=context)
1455+ for order in self.browse(cr, uid, ids, context=context):
1456+ if avatax_config and not avatax_config.disable_tax_calculation and \
1457+ avatax_config.default_tax_schedule_id.id == order.partner_id.tax_schedule_id.id:
1458+
1459+ res[order.id] = {
1460+ 'amount_untaxed': 0.0,
1461+ 'amount_tax': 0.0,
1462+ 'amount_total': 0.0,
1463+ }
1464+ for line in order.order_line:
1465+ res[order.id]['amount_untaxed'] += line.price_subtotal
1466+ res[order.id]['amount_tax'] = currency_obj.round(cr, uid, order.pricelist_id.currency_id, order.tax_amount)
1467+ res[order.id]['amount_total'] = res[order.id]['amount_untaxed'] + res[order.id]['amount_tax']
1468+
1469+ return res
1470+
1471+ def _get_order(self, cr, uid, ids, context=None):
1472+ return super(sale_order, self)._get_order(cr, uid, ids, context=context)
1473+
1474+ _columns = {
1475+ 'amount_untaxed': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Untaxed Amount',
1476+ store = {
1477+ 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
1478+ 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
1479+ },
1480+ multi='sums', help="The amount without tax."),
1481+ 'amount_tax': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Taxes',
1482+ store = {
1483+ 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
1484+ 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
1485+ },
1486+ multi='sums', help="The tax amount."),
1487+ 'amount_total': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Total',
1488+ store = {
1489+ 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
1490+ 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
1491+ },
1492+ multi='sums', help="The total amount."),
1493+ 'tax_amount': fields.float('Tax Code Amount', digits_compute=dp.get_precision('Sale Price')),
1494+ }
1495+
1496+ def create_lines(self, cr, uid, order_lines):
1497+ lines = []
1498+ for line in order_lines:
1499+ lines.append({
1500+ 'qty': line.product_uom_qty,
1501+ 'itemcode': line.product_id and line.product_id.default_code or None,
1502+ 'description': line.name,
1503+ 'amount': line.price_unit * (1-(line.discount or 0.0)/100.0) * line.product_uom_qty,
1504+ 'tax_code': line.product_id and ((line.product_id.tax_code_id and line.product_id.tax_code_id.name) or
1505+ (line.product_id.categ_id.tax_code_id and line.product_id.categ_id.tax_code_id.name)) or None
1506+ })
1507+ return lines
1508+
1509+ def compute_tax(self, cr, uid, ids, context=None):
1510+ avatax_config_obj = self.pool.get('account.salestax.avatax')
1511+ account_tax_obj = self.pool.get('account.tax')
1512+ avatax_config = avatax_config_obj._get_avatax_config_company(cr, uid)
1513+ partner_obj = self.pool.get('res.partner')
1514+
1515+ for order in self.browse(cr, uid, ids):
1516+ if avatax_config and not avatax_config.disable_tax_calculation and \
1517+ avatax_config.default_tax_schedule_id.id == order.partner_id.tax_schedule_id.id:
1518+ address = partner_obj.address_get(cr, uid, [order.company_id.partner_id.id], ['contact'])
1519+ lines = self.create_lines(cr, uid, order.order_line)
1520+ tax_amount = account_tax_obj._check_compute_tax(cr, uid, avatax_config, order.date_confirm or order.date_order,
1521+ order.name, 'SalesOrder', order.partner_id, address['contact'],
1522+ order.partner_shipping_id.id, lines, order.shipcharge, order.user_id,
1523+ context=context).TotalTax
1524+ self.write(cr, uid, [order.id], {'tax_amount': tax_amount, 'order_line': []})
1525+ return True
1526+
1527+ def button_dummy(self, cr, uid, ids, context=None):
1528+ self.compute_tax(cr, uid, ids, context=context)
1529+ return super(sale_order, self).button_dummy(cr, uid, ids, context=context)
1530+
1531+ def action_wait(self, cr, uid, ids, *args):
1532+ res = super(sale_order, self).action_wait(cr, uid, ids)
1533+ self.compute_tax(cr, uid, ids)
1534+ return True
1535+
1536+sale_order()
1537+
1538+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1539
1540=== added directory 'account_salestax_avatax/security'
1541=== added file 'account_salestax_avatax/security/account_salestax_avatax_security.xml'
1542--- account_salestax_avatax/security/account_salestax_avatax_security.xml 1970-01-01 00:00:00 +0000
1543+++ account_salestax_avatax/security/account_salestax_avatax_security.xml 2011-09-22 18:36:28 +0000
1544@@ -0,0 +1,29 @@
1545+<?xml version="1.0" encoding="utf-8"?>
1546+<openerp>
1547+ <data noupdate="1">
1548+
1549+ <!-- Rule for multi-company -->
1550+
1551+ <record id="account_salestax_avatax_comp_rule" model="ir.rule">
1552+ <field name="name" >AvaTax multi-company</field>
1553+ <field name="model_id" ref="model_account_salestax_avatax"/>
1554+ <field name="global" eval="True"/>
1555+ <field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
1556+ </record>
1557+
1558+ <record id="account_salestax_avatax_tax_schedule_rule" model="ir.rule">
1559+ <field name="name" >AvaTax Tax Schedule multi-company</field>
1560+ <field name="model_id" ref="model_tax_schedule"/>
1561+ <field name="global" eval="True"/>
1562+ <field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
1563+ </record>
1564+
1565+ <record id="account_salestax_avatax_tax_codes_rule" model="ir.rule">
1566+ <field name="name" >AvaTax Tax Code multi-company</field>
1567+ <field name="model_id" ref="model_product_tax_code"/>
1568+ <field name="global" eval="True"/>
1569+ <field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
1570+ </record>
1571+
1572+ </data>
1573+</openerp>
1574
1575=== added file 'account_salestax_avatax/security/ir.model.access.csv'
1576--- account_salestax_avatax/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
1577+++ account_salestax_avatax/security/ir.model.access.csv 2011-09-22 18:36:28 +0000
1578@@ -0,0 +1,11 @@
1579+"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
1580+"access_account_salestax_avatax manager","account.salestax.avatax.manager","model_account_salestax_avatax","account.group_account_manager",1,1,1,1
1581+"access_account_salestax_avatax employee","account.salestax.avatax.employee","model_account_salestax_avatax","base.group_user",1,0,0,0
1582+"access_tax_schedule employee","tax.schedule.employee","model_tax_schedule","base.group_user",1,0,0,0
1583+"access_tax_schedule manager","tax.schedule.manager","model_tax_schedule","account.group_account_manager",1,1,1,1
1584+"access_jurisdiction_code employee","jurisdiction.code.employee","model_jurisdiction_code","base.group_user",1,0,0,0
1585+"access_jurisdiction_code manager","jurisdiction.code.manager","model_jurisdiction_code","account.group_account_manager",1,1,1,1
1586+"access_product_tax_code employee","product.tax.code.employee","model_product_tax_code","base.group_user",1,0,0,0
1587+"access_product_tax_code manager","product.tax.code.manager","model_product_tax_code","account.group_account_manager",1,1,1,1
1588+"access_exemption_code manager","exemption.code.manager","model_exemption_code","account.group_account_manager",1,1,1,1
1589+"access_exemption_code employee","exemption.code.employee","model_exemption_code","base.group_user",1,0,0,0
1590
1591=== added file 'account_salestax_avatax/suds_client.py'
1592--- account_salestax_avatax/suds_client.py 1970-01-01 00:00:00 +0000
1593+++ account_salestax_avatax/suds_client.py 2011-09-22 18:36:28 +0000
1594@@ -0,0 +1,295 @@
1595+import suds
1596+import socket
1597+import urllib2
1598+import string
1599+import os
1600+import datetime
1601+
1602+from tools.translate import _
1603+from osv import osv
1604+
1605+class AvaTaxService:
1606+
1607+ def enable_log(self):
1608+ import logging, tempfile
1609+ logger = logging.getLogger('suds.client')
1610+ logger.setLevel(logging.DEBUG)
1611+ handler = logging.FileHandler(os.path.join(tempfile.gettempdir(), "soap-messages.log"))
1612+ logger.propagate = False
1613+ formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
1614+ handler.setFormatter(formatter)
1615+ logger.addHandler(handler)
1616+
1617+ def __init__(self, username, password, url, timeout, enable_log=False):
1618+ self.username = username #This is the company's Development/Production Account number
1619+ self.password = password #Put in the License Key received from AvaTax
1620+ self.url = url
1621+ self.timeout = timeout
1622+ enable_log and self.enable_log()
1623+
1624+ def create_tax_service(self):
1625+ self.taxSvc = self.service('tax')
1626+ return self
1627+
1628+ def create_address_service(self):
1629+ self.addressSvc = self.service('address')
1630+ return self
1631+
1632+ def service(self, name):
1633+ nameCap = string.capitalize(name) # So this will be 'Tax' or 'Address'
1634+ # The Python SUDS library can fetch the WSDL down from the server
1635+ # or use a local file URL. We'll use a local file URL.
1636+# wsdl_url = 'file:///' + os.getcwd().replace('\\', '/') + '/%ssvc.wsdl.xml' % name
1637+ # If you want to fetch the WSDL from the server, use this instead:
1638+ wsdl_url = 'https://avatax.avalara.net/%s/%ssvc.wsdl' % (nameCap, nameCap)
1639+ #wsdl_url = 'file:///home/rima/Downloads/AvaTax.wsdl'
1640+ # So now we'll fetch the WSDL and build the service proxy,
1641+ # handling any errors appropriately.
1642+ try:
1643+ svc = suds.client.Client(wsdl_url)
1644+ except urllib2.URLError, details:
1645+ raise osv.except_osv(_('Server Failed to Response'), _(details))
1646+ else:
1647+ svc.set_options(service='%sSvc' % nameCap)
1648+ svc.set_options(port='%sSvcSoap' % nameCap)
1649+ svc.set_options(location='%s/%s/%sSvc.asmx' % (self.url, nameCap, nameCap))
1650+ svc.set_options(wsse=self.my_security(self.username, self.password))
1651+ svc.set_options(soapheaders=self.my_profile())
1652+ svc.set_options(timeout=self.timeout)
1653+ return svc
1654+
1655+ def my_security(self, username, password):
1656+
1657+ token = suds.wsse.UsernameToken(username, password)
1658+
1659+ # AvaTax leaves the WSSE Nonce and Created elements as
1660+ # optional. As explained in XXX, you should include these if at
1661+ # all possible, to make your connection more secure.
1662+ # Nonce (optional) is a randomly generated, cryptographic token
1663+ # used to prevent theft and replay attacks. We recommend sending
1664+ # it if your SOAP client library supports it.
1665+ token.setnonce()
1666+
1667+ # Created (optional) identifies when the message was created and
1668+ # prevents replay attacks. We recommend sending it if your SOAP
1669+ # client library supports it.
1670+ token.setcreated()
1671+ security = suds.wsse.Security()
1672+ security.tokens.append(token)
1673+ return security
1674+
1675+ def my_profile(self):
1676+
1677+ # Set elements adapter defaults
1678+ ADAPTER = 'Novapoint Group,0.1'
1679+
1680+ # Profile Client.
1681+ CLIENT = 'Novapoint Group,0.1'
1682+
1683+ #Build the Profile element
1684+ profileNameSpace = ('ns1', 'http://avatax.avalara.com/services')
1685+ profile = suds.sax.element.Element('Profile', ns=profileNameSpace)
1686+ profile.append(suds.sax.element.Element('Client', ns=profileNameSpace).setText(CLIENT))
1687+ profile.append(suds.sax.element.Element('Adapter', ns=profileNameSpace).setText(ADAPTER))
1688+ hostname = socket.gethostname()
1689+ profile.append(suds.sax.element.Element('Machine', ns=profileNameSpace).setText(hostname))
1690+ return profile
1691+
1692+ def get_result(self, svc, operation, request):
1693+ try:
1694+ result = operation(request)
1695+ except suds.WebFault, e:
1696+ raise osv.except_osv(_('Error'), _(e.fault.faultstring))
1697+ except urllib2.HTTPError, e:
1698+ raise osv.except_osv(_('Server Failed to Response'), _(e.code))
1699+ except urllib2.URLError, details:
1700+ # We could also print the SOAP request here:
1701+ # print svc.last_sent()
1702+ raise osv.except_osv(_('Failed to reach the server'), _(details.reason))
1703+ else:
1704+ if (result.ResultCode != 'Success'):
1705+ raise osv.except_osv(('Error'), _(AvaTaxError(result.ResultCode, result.Messages)))
1706+ else:
1707+ return result
1708+
1709+ def ping(self):
1710+ return self.get_result(self.taxSvc, self.taxSvc.service.Ping, '')
1711+
1712+ def is_authorized(self):
1713+ return self.get_result(self.taxSvc, self.taxSvc.service.IsAuthorized, 'GetTax,PostTax')
1714+
1715+ def validate_address(self, baseaddress, textcase='Default'):
1716+ # The Validate() operation needs a complex parameter, a
1717+ # ValidateRequest. SUDS gives us a factory method on the service
1718+ # object that creates a proxy class we can use.
1719+ request = self.addressSvc.factory.create('ValidateRequest')
1720+ # SUDS allows us to get defaults set up for us automatically. But
1721+ # to keep this sample code simple, we won't do that here.
1722+ # These are the elements required by the WSDL, with the defaults
1723+ # set by the .NET adapter.
1724+ # See the PHP sample code or .NET SDK Help File for what each of these is for.
1725+ # TextCase, as with many other elements, is defined in the WSDL as
1726+ # an enumeration. Depending on your SOAP client library, language
1727+ # and platform, you may have these available to you. If you can
1728+ # use enumerations rather than strings you should do so, because
1729+ # it offers compile-time checking that can save your development
1730+ # time. We're just using strings elsewhere in this sample code, to
1731+ # keep things simple. But for TextCase we'll use SUDS
1732+ # enumerations, just to show you how this sort of thing would
1733+ # work.
1734+ # This is the SUDS documentation on enumerations:
1735+ # https://fedorahosted.org/suds/wiki/Documentation#ENUMERATIONS
1736+ # So this is how we'll do things if we're just using strings
1737+ #
1738+ # request.TextCase = 'Default'
1739+ #
1740+ textCase = self.addressSvc.factory.create('TextCase')
1741+ request.TextCase = textcase
1742+ request.Coordinates = True
1743+ request.Taxability = False
1744+ request.Date = '1900-01-01'
1745+ request.Address = baseaddress
1746+
1747+ result = self.get_result(self.addressSvc, self.addressSvc.service.Validate, request)
1748+ return result
1749+
1750+ def get_tax(self, company_code, doc_date, doc_type, partner_code, doc_code, origin, destination,
1751+ received_lines, exemption_no=None, customer_usage_type=None, salesman_code=None, commit=False, invoice_date=None, reference_code=None):
1752+ lineslist = []
1753+ request = self.taxSvc.factory.create('GetTaxRequest')
1754+ # We'll first default everything just as the .NET adapter does
1755+ request.Commit = commit
1756+ request.DetailLevel = 'Diagnostic'
1757+ request.Discount = 0.0
1758+ request.ServiceMode = 'Automatic' # PHP defaults this to Automatic; Java likewise
1759+ request.PaymentDate = '1900-01-01'
1760+ request.ExchangeRate = 45
1761+ request.ExchangeRateEffDate = '2011-07-07'
1762+ request.HashCode = 0
1763+ request.ReferenceCode = reference_code
1764+ if invoice_date:
1765+ taxoverride = self.taxSvc.factory.create('TaxOverride')
1766+ taxoverride.TaxOverrideType = 'TaxDate'
1767+ taxoverride.TaxDate = invoice_date
1768+ taxoverride.TaxAmount = 0
1769+ taxoverride.Reason = 'Return Items'
1770+ request.TaxOverride = taxoverride
1771+ # We'll now set the properties as we would normally, including
1772+ # some that have already been defaulted.
1773+ # The GetTax call will exactly match that in the PHP sample
1774+ # code. So you can compare the XML from them line-by-line. We
1775+ # suggest you try to get the same call set up in whatever language
1776+ # and platform you're using, so you can compare your XML likewise.
1777+ # See the PHP sample code for an explanation of each of these.
1778+ request.CompanyCode = company_code
1779+ request.DocDate = doc_date
1780+ request.DocType = doc_type
1781+ request.DocCode = doc_code
1782+ request.CustomerCode = partner_code
1783+ request.ExemptionNo = exemption_no
1784+ request.CustomerUsageType = customer_usage_type
1785+ request.SalespersonCode = salesman_code
1786+ # Now the AddressCode, which we'll reference later. Note that
1787+ # although it's a string and so you could use 'Origin' and
1788+ # 'Destination' here, because addresses could be defined at the
1789+ # line level you're best off just numbering them.
1790+ #
1791+ # Furthermore, any Messages you get back in an error from the
1792+ # service will, in Message.RefersTo, reference the addresses
1793+ # indexed as they appear in Addresses, starting at zero, so if you
1794+ # use AddressCode = '0', '1' and so on, the index in the Message
1795+ # will match the appropriate item.
1796+ # Be very careful to make the addresscodes all match up. If, for
1797+ # example, origin.AddressCode does not match an entry in
1798+ # Addresses, the origin address will be treated as if it were
1799+ # blank.
1800+ # Now we'll set some of the other properties as usual
1801+ addresses = self.taxSvc.factory.create('ArrayOfBaseAddress')
1802+ addresses.BaseAddress = [origin, destination]
1803+ request.Addresses = addresses
1804+ request.OriginCode = '0' # Referencing an address above
1805+ request.DestinationCode = '1' # Referencing an address above
1806+ for line in range(0, len(received_lines)):
1807+ line1 = self.taxSvc.factory.create('Line')
1808+ line1.Qty = received_lines[line].get('qty', 1)
1809+ line1.Discounted = False
1810+ line1.No = '%d' %line
1811+ line1.ItemCode = received_lines[line].get('itemcode', None)
1812+ line1.Description = received_lines[line].get('description', None)
1813+ line1.Amount = received_lines[line].get('amount', 0.0)
1814+ line1.TaxCode = received_lines[line].get('tax_code', None)
1815+ lineslist.append(line1)
1816+ # So now we build request.Lines
1817+ lines = self.taxSvc.factory.create('ArrayOfLine')
1818+ lines.Line = lineslist
1819+ request.Lines = lines
1820+ # And we're ready to make the call
1821+ result = self.get_result(self.taxSvc, self.taxSvc.service.GetTax, request)
1822+ return result
1823+
1824+ def cancel_tax(self, company_code, doc_code, doc_type, cancel_code):
1825+ request = self.taxSvc.factory.create('CancelTaxRequest')
1826+ request.CompanyCode = company_code
1827+ request.DocCode = doc_code
1828+ request.DocType = doc_type
1829+ request.CancelCode = cancel_code
1830+ result = self.get_result(self.taxSvc, self.taxSvc.service.CancelTax, request)
1831+ return result
1832+
1833+class Error(Exception):
1834+ """Base class for exceptions in this module."""
1835+ pass
1836+
1837+class AvaTaxError(Error):
1838+ """Exception raised for errors calling AvaTax.
1839+
1840+ Attributes:
1841+ resultCode -- result.ResultCode
1842+ messages -- result.Messages
1843+ """
1844+
1845+ def __init__(self, resultCode, messages):
1846+ self.resultCode = resultCode
1847+ self.messages = messages
1848+
1849+ def __str__(self):
1850+ str = ''
1851+ for item in self.messages:
1852+ message = item[1][0] # SUDS gives us the message in a list, in a tuple
1853+
1854+ str = "Severity: %s\n\nDetails: %s\n\n RefersTo: %s\n\n Summary: %s" \
1855+ % (message.Severity, message.Details, message.RefersTo, message.Summary)
1856+ return str
1857+
1858+class BaseAddress:
1859+
1860+ def __init__(self, addSvc, Line1=None, Line2=None, City=None, PostalCode=None, Region=None, Country=None, AddressCode=None):
1861+ self.data = addSvc.factory.create('BaseAddress')
1862+ self.data.TaxRegionId = 0
1863+ self.data.Line1 = Line1
1864+ self.data.Line2 = Line2
1865+ self.data.City = City
1866+ self.data.PostalCode = PostalCode
1867+ self.data.Region = Region
1868+ self.data.Country = Country
1869+ self.data.AddressCode = AddressCode
1870+
1871+class Line:
1872+
1873+ def __init__(self, taxSvc, ItemCode, Amount, Qty, Description=None, TaxCode=None):
1874+ self.taxSvc = taxSvc
1875+ # We're not setting No here
1876+ self.data = self.defaultLine()
1877+ self.data.ItemCode = ItemCode
1878+ self.data.Amount = Amount
1879+ self.data.Qty = Qty
1880+ self.data.Description = Description
1881+ self.data.TaxCode = TaxCode
1882+
1883+ def defaultLine(self):
1884+ line = self.taxSvc.factory.create('Line')
1885+ line.Qty = 1
1886+ line.Discounted = False
1887+ return line
1888+
1889+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1890\ No newline at end of file
1891
1892=== added directory 'account_salestax_avatax/wizard'
1893=== added file 'account_salestax_avatax/wizard/__init__.py'
1894--- account_salestax_avatax/wizard/__init__.py 1970-01-01 00:00:00 +0000
1895+++ account_salestax_avatax/wizard/__init__.py 2011-09-22 18:36:28 +0000
1896@@ -0,0 +1,26 @@
1897+# -*- coding: utf-8 -*-
1898+##############################################################################
1899+#
1900+# OpenERP, Open Source Management Solution
1901+# Copyright (C) 2011 NovaPoint Group LLC (<http://www.novapointgroup.com>)
1902+# Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
1903+#
1904+# This program is free software: you can redistribute it and/or modify
1905+# it under the terms of the GNU Affero General Public License as
1906+# published by the Free Software Foundation, either version 3 of the
1907+# License, or (at your option) any later version.
1908+#
1909+# This program is distributed in the hope that it will be useful,
1910+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1911+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1912+# GNU Affero General Public License for more details.
1913+#
1914+# You should have received a copy of the GNU Affero General Public License
1915+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1916+#
1917+##############################################################################
1918+
1919+import account_salestax_avatax_ping
1920+import account_salestax_avatax_address_validate
1921+
1922+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
1923\ No newline at end of file
1924
1925=== added file 'account_salestax_avatax/wizard/account_salestax_avatax_address_validate.py'
1926--- account_salestax_avatax/wizard/account_salestax_avatax_address_validate.py 1970-01-01 00:00:00 +0000
1927+++ account_salestax_avatax/wizard/account_salestax_avatax_address_validate.py 2011-09-22 18:36:28 +0000
1928@@ -0,0 +1,135 @@
1929+# -*- coding: utf-8 -*-
1930+##############################################################################
1931+#
1932+# OpenERP, Open Source Management Solution
1933+# Copyright (C) 2011 NovaPoint Group LLC (<http://www.novapointgroup.com>)
1934+# Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
1935+#
1936+# This program is free software: you can redistribute it and/or modify
1937+# it under the terms of the GNU Affero General Public License as
1938+# published by the Free Software Foundation, either version 3 of the
1939+# License, or (at your option) any later version.
1940+#
1941+# This program is distributed in the hope that it will be useful,
1942+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1943+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1944+# GNU Affero General Public License for more details.
1945+#
1946+# You should have received a copy of the GNU Affero General Public License
1947+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1948+#
1949+##############################################################################
1950+import time
1951+
1952+from osv import osv, fields
1953+from tools.translate import _
1954+
1955+class account_salestax_avatax_address_validate(osv.osv_memory):
1956+ _name = 'account.salestax.avatax.address.validate'
1957+ _description = 'Address Validation using AvaTax'
1958+ _columns = {
1959+ 'original_street': fields.char('Street', size=64, readonly=True),
1960+ 'original_street2': fields.char('Street2', size=64, readonly=True),
1961+ 'original_city': fields.char('City', size=64, readonly=True),
1962+ 'original_zip': fields.char('Zip', size=64, readonly=True),
1963+ 'original_state': fields.char('State', size=64, readonly=True),
1964+ 'original_country': fields.char('Country', size=64, readonly=True),
1965+ 'street': fields.char('Street', size=64),
1966+ 'street2': fields.char('Street2', size=64),
1967+ 'city': fields.char('City', size=64),
1968+ 'zip': fields.char('Zip', size=64),
1969+ 'state': fields.char('State', size=64),
1970+ 'country': fields.char('Country', size=64),
1971+ 'latitude': fields.char('Laitude', size=64),
1972+ 'longitude': fields.char('Longitude', size=64)
1973+ }
1974+
1975+ def view_init(self, cr, uid, fields, context=None):
1976+ """ Checks for precondition before wizard executes. """
1977+ address_obj = self.pool.get('res.partner.address')
1978+ avatax_config_obj= self.pool.get('account.salestax.avatax')
1979+
1980+ if context is None:
1981+ context = {}
1982+
1983+ # Check if there is avatax tax service active for the user company.
1984+ # Prevent validating the address if the address validation is disabled by the administrator.
1985+
1986+ if context.get('active_id', False) and context.get('active_model', False) == 'res.partner.address':
1987+ avatax_config = avatax_config_obj._get_avatax_config_company(cr, uid, context=context)
1988+ if not avatax_config:
1989+ raise osv.except_osv(_('Service Not Setup'), _("The AvaTax Tax Service is not active. To sign-up for the AvaTax Tax Service please visit www.novapointgroup.com."))
1990+ address = address_obj.browse(cr, uid, context['active_id'], context=context)
1991+ if address.validated_on_save and avatax_config.validation_on_save:
1992+ raise osv.except_osv(_('Address Already Validated'), _("Address Validation on Save is already active in the AvaTax Configuration."))
1993+ address_obj.check_avatax_support(cr, uid, avatax_config, address.country_id and address.country_id.id or False, context=context)
1994+ return True
1995+
1996+ def default_get(self, cr, uid, fields, context=None):
1997+ """ Returns the default values for the fields. """
1998+
1999+ res = super(account_salestax_avatax_address_validate, self).default_get(cr, uid, fields, context)
2000+
2001+ if context.get('active_id', False) and context.get('active_model', False) == 'res.partner.address':
2002+ address_obj = self.pool.get('res.partner.address')
2003+ address = address_obj.read(cr, uid, context['active_id'], ['street', 'street2', 'city', 'state_id', 'zip', 'country_id'], context=context)
2004+ address['state_id'] = address.get('state_id') and address['state_id'][0]
2005+ address['country_id'] = address.get('country_id') and address['country_id'][0]
2006+
2007+ # Get the valid result from the AvaTax Address Validation Service
2008+ valid_address = address_obj._validate_address(cr, uid, address, context=context)
2009+
2010+ if 'original_street' in fields:
2011+ res.update({'original_street': address['street']})
2012+ if 'original_street2' in fields:
2013+ res.update({'original_street2': address['street2']})
2014+ if 'original_city' in fields:
2015+ res.update({'original_city': address['city']})
2016+ if 'original_state' in fields:
2017+ res.update({'original_state': address_obj.get_state_code(cr, uid, address['state_id'], context=context)})
2018+ if 'original_zip' in fields:
2019+ res.update({'original_zip': address['zip']})
2020+ if 'original_country' in fields:
2021+ res.update({'original_country': address_obj.get_country_code(cr, uid, address['country_id'], context=context)})
2022+ if 'street' in fields:
2023+ res.update({'street': valid_address.Line1})
2024+ if 'street2' in fields:
2025+ res.update({'street2': valid_address.Line2})
2026+ if 'city' in fields:
2027+ res.update({'city': valid_address.City})
2028+ if 'state' in fields:
2029+ res.update({'state': valid_address.Region})
2030+ if 'zip' in fields:
2031+ res.update({'zip': valid_address.PostalCode})
2032+ if 'country' in fields:
2033+ res.update({'country': valid_address.Country})
2034+ if 'latitude' in fields:
2035+ res.update({'latitude': valid_address.Latitude})
2036+ if 'longitude' in fields:
2037+ res.update({'longitude': valid_address.Longitude})
2038+ return res
2039+
2040+ def accept_valid_address(self, cr, uid, ids, context=None):
2041+ """ Updates the existing address with the valid address returned by the service. """
2042+
2043+ valid_address = self.read(cr, uid, ids, context=context)[0]
2044+ if context.get('active_id', False):
2045+ address_obj = self.pool.get('res.partner.address')
2046+ address_result = {
2047+ 'street': valid_address['street'],
2048+ 'street2': valid_address['street2'],
2049+ 'city': valid_address['city'],
2050+ 'state_id': address_obj.get_state_id(cr, uid, valid_address['state'], context=context),
2051+ 'zip': valid_address['zip'],
2052+ 'country_id': address_obj.get_country_id(cr, uid, valid_address['country'], context=context),
2053+ 'latitude': valid_address['latitude'],
2054+ 'longitude': valid_address['longitude'],
2055+ 'date_validation': time.strftime('%Y-%m-%d'),
2056+ 'validation_method': 'avatax'
2057+ }
2058+ address_obj.write(cr, uid, [context['active_id']], address_result, context=context)
2059+ return {'type': 'ir.actions.act_window_close'}
2060+
2061+account_salestax_avatax_address_validate()
2062+
2063+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
2064
2065=== added file 'account_salestax_avatax/wizard/account_salestax_avatax_address_validate.xml'
2066--- account_salestax_avatax/wizard/account_salestax_avatax_address_validate.xml 1970-01-01 00:00:00 +0000
2067+++ account_salestax_avatax/wizard/account_salestax_avatax_address_validate.xml 2011-09-22 18:36:28 +0000
2068@@ -0,0 +1,53 @@
2069+<?xml version="1.0" encoding="utf-8"?>
2070+<openerp>
2071+ <data>
2072+
2073+ <!-- Partner Address Validate -->
2074+
2075+ <record id="view_account_salestax_avatax_address_validate" model="ir.ui.view">
2076+ <field name="name">Address Validatation</field>
2077+ <field name="model">account.salestax.avatax.address.validate</field>
2078+ <field name="type">form</field>
2079+ <field name="arch" type="xml">
2080+ <form string="Address Validation">
2081+ <group colspan="4" col="8">
2082+ <group colspan="1">
2083+ <separator string="Original Address" colspan="4"/>
2084+ <field name="original_street" colspan="4" width="220"/>
2085+ <field name="original_street2" colspan="4" width="220"/>
2086+ <field name="original_city" colspan="4" width="220"/>
2087+ <field name="original_state" colspan="4" width="220"/>
2088+ <field name="original_zip" colspan="4" width="220"/>
2089+ <field name="original_country" colspan="4" width="220"/>
2090+ </group>
2091+ <separator orientation="vertical" rowspan="10"/>
2092+ <group colspan="6">
2093+ <separator string="Validated Address" colspan="4"/>
2094+ <field name="street" colspan="4" width="220"/>
2095+ <field name="street2" colspan="4"/>
2096+ <field name="city" colspan="4"/>
2097+ <field name="state" colspan="4"/>
2098+ <field name="zip" colspan="4"/>
2099+ <field name="country" colspan="4"/>
2100+ </group>
2101+ <group colspan="8" col="8">
2102+ <button special="cancel" string="Cancel" icon="gtk-cancel"/>
2103+ <button name="accept_valid_address" type="object" icon="gtk-ok" string="_Accept"/>
2104+ </group>
2105+ </group>
2106+ </form>
2107+ </field>
2108+ </record>
2109+
2110+ <record id="action_account_salestax_avatax_address_validate" model="ir.actions.act_window">
2111+ <field name="name">Address Validation</field>
2112+ <field name="type">ir.actions.act_window</field>
2113+ <field name="res_model">account.salestax.avatax.address.validate</field>
2114+ <field name="view_type">form</field>
2115+ <field name="view_mode">form</field>
2116+ <field name="view_id" ref="view_account_salestax_avatax_address_validate"/>
2117+ <field name="target">new</field>
2118+ </record>
2119+
2120+ </data>
2121+</openerp>
2122\ No newline at end of file
2123
2124=== added file 'account_salestax_avatax/wizard/account_salestax_avatax_ping.py'
2125--- account_salestax_avatax/wizard/account_salestax_avatax_ping.py 1970-01-01 00:00:00 +0000
2126+++ account_salestax_avatax/wizard/account_salestax_avatax_ping.py 2011-09-22 18:36:28 +0000
2127@@ -0,0 +1,58 @@
2128+# -*- coding: utf-8 -*-
2129+##############################################################################
2130+#
2131+# OpenERP, Open Source Management Solution
2132+# Copyright (C) 2011 NovaPoint Group LLC (<http://www.novapointgroup.com>)
2133+# Copyright (C) 2004-2010 OpenERP SA (<http://www.openerp.com>)
2134+#
2135+# This program is free software: you can redistribute it and/or modify
2136+# it under the terms of the GNU Affero General Public License as
2137+# published by the Free Software Foundation, either version 3 of the
2138+# License, or (at your option) any later version.
2139+#
2140+# This program is distributed in the hope that it will be useful,
2141+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2142+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2143+# GNU Affero General Public License for more details.
2144+#
2145+# You should have received a copy of the GNU Affero General Public License
2146+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2147+#
2148+##############################################################################
2149+
2150+from osv import osv, fields
2151+from tools.translate import _
2152+
2153+from account_salestax_avatax.suds_client import AvaTaxService
2154+
2155+class account_salestax_avatax_ping(osv.osv_memory):
2156+ _name = 'account.salestax.avatax.ping'
2157+ _description = 'Ping Service'
2158+ _columns = {
2159+ 'name': fields.char('Name', size=64)
2160+ }
2161+
2162+ def default_get(self, cr, uid, fields_list, context=None):
2163+ res = super(account_salestax_avatax_ping, self).default_get(cr, uid, fields_list, context)
2164+ return self.ping(cr, uid, context=context)
2165+
2166+ def ping(self, cr, uid, context=None):
2167+ """ Call the Avatax's Ping Service to test the connection. """
2168+
2169+ if context is None:
2170+ context = {}
2171+
2172+ if context.get('active_id', False):
2173+ avatax_pool = self.pool.get('account.salestax.avatax')
2174+ avatax_config = avatax_pool.browse(cr, uid, context['active_id'], context=context)
2175+ avapoint = AvaTaxService(avatax_config.account_number, avatax_config.license_key,
2176+ avatax_config.service_url, avatax_config.request_timeout, avatax_config.logging)
2177+ taxSvc = avapoint.create_tax_service().taxSvc # Create 'tax' service for Ping and is_authorized calls
2178+ avapoint.ping()
2179+ result = avapoint.is_authorized()
2180+ avatax_pool.write(cr, uid, avatax_config.id, {'date_expiration': result.Expires})
2181+ return {'type': 'ir.actions.act_window_close'}
2182+
2183+account_salestax_avatax_ping()
2184+
2185+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
2186
2187=== added file 'account_salestax_avatax/wizard/account_salestax_avatax_ping.xml'
2188--- account_salestax_avatax/wizard/account_salestax_avatax_ping.xml 1970-01-01 00:00:00 +0000
2189+++ account_salestax_avatax/wizard/account_salestax_avatax_ping.xml 2011-09-22 18:36:28 +0000
2190@@ -0,0 +1,37 @@
2191+<?xml version="1.0" encoding="utf-8"?>
2192+<openerp>
2193+ <data>
2194+
2195+ <!-- Ping AvaTax Tax Service -->
2196+
2197+ <record id="view_account_salestax_avatax_ping" model="ir.ui.view">
2198+ <field name="name">Test Connection</field>
2199+ <field name="model">account.salestax.avatax.ping</field>
2200+ <field name="type">form</field>
2201+ <field name="arch" type="xml">
2202+ <form string="Test Connection">
2203+ <group height="70" width="130">
2204+ <label align="0.5" string="Test Connection Successful!" colspan="4"/>
2205+ <newline/>
2206+ <group colspan="2" col="4">
2207+ <label string=""/>
2208+ <button icon="gtk-ok" special="cancel" string="_OK"/>
2209+ </group>
2210+ </group>
2211+ </form>
2212+ </field>
2213+ </record>
2214+
2215+ <record id="action_account_salestax_avatax_ping" model="ir.actions.act_window">
2216+ <field name="name">Test Connection</field>
2217+ <field name="type">ir.actions.act_window</field>
2218+ <field name="res_model">account.salestax.avatax.ping</field>
2219+ <field name="view_type">form</field>
2220+ <field name="view_mode">form</field>
2221+ <field name="view_id" ref="view_account_salestax_avatax_ping"/>
2222+ <field name="context">{'record_id': active_id}</field>
2223+ <field name="target">new</field>
2224+ </record>
2225+
2226+ </data>
2227+</openerp>
2228\ No newline at end of file

Subscribers

People subscribed via source and target branches

to all changes: