Merge lp:~npg-team/openobject-addons/account_salestax_avatax_us into lp:openobject-addons
- account_salestax_avatax_us
- Merge into trunk
Proposed by
Novapoint Group
Status: | Rejected |
---|---|
Rejected by: | Fabien (Open ERP) |
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
OpenERP Core Team | Pending | ||
Review via email: mp+76639@code.launchpad.net |
This proposal supersedes a proposal from 2011-09-02.
Commit message
Description of the change
Improvements in account_
To post a comment you must log in.
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:37:20 +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:37:20 +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:37:20 +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:37:20 +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:37:20 +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:37:20 +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:37:20 +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:37:20 +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','<>','view'),('type','<>','consolidation')]"/> |
635 | + <field name="account_paid_id" domain="[('type','<>','view'),('type','<>','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','<>','view'),('type','<>','consolidation')]"/> |
704 | + <field name="account_paid_id" domain="[('type','<>','view'),('type','<>','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:37:20 +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:37:20 +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:37:20 +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:37:20 +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:37:20 +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:37:20 +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:37:20 +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:37:20 +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:37:20 +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:37:20 +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:37:20 +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:37:20 +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:37:20 +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:37:20 +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 |