mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
Initial commit of account_us_wa_salestax for 11.0
This commit is contained in:
1
account_us_wa_salestax/__init__.py
Normal file
1
account_us_wa_salestax/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
13
account_us_wa_salestax/__manifest__.py
Normal file
13
account_us_wa_salestax/__manifest__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
{'name': 'US WA State SalesTax API',
|
||||
'version': '10.0.1.0.0',
|
||||
'category': 'Tools',
|
||||
'depends': ['account',
|
||||
],
|
||||
'author': 'Hibou Corp.',
|
||||
'license': 'AGPL-3',
|
||||
'website': 'https://hibou.io/',
|
||||
'data': ['views/account_fiscal_position_view.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
}
|
||||
2
account_us_wa_salestax/models/__init__.py
Normal file
2
account_us_wa_salestax/models/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import account_fiscal_position
|
||||
from . import wa_tax_request
|
||||
78
account_us_wa_salestax/models/account_fiscal_position.py
Normal file
78
account_us_wa_salestax/models/account_fiscal_position.py
Normal file
@@ -0,0 +1,78 @@
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
from .wa_tax_request import WATaxRequest
|
||||
|
||||
|
||||
class AccountFiscalPosition(models.Model):
|
||||
_inherit = 'account.fiscal.position'
|
||||
|
||||
is_us_wa = fields.Boolean(string='Use WA State API')
|
||||
wa_base_tax_id = fields.Many2one('account.tax', string='WA Base/Error Tax')
|
||||
|
||||
@api.multi
|
||||
def map_tax(self, taxes, product=None, partner=None):
|
||||
|
||||
if not taxes or not self.is_us_wa or partner is None:
|
||||
return super(AccountFiscalPosition, self).map_tax(taxes)
|
||||
|
||||
AccountTax = self.env['account.tax'].sudo()
|
||||
result = AccountTax.browse()
|
||||
|
||||
for tax in taxes:
|
||||
# step 1: If we were to save the location code on the partner we might not have to do this
|
||||
request = WATaxRequest()
|
||||
res = request.get_rate(partner)
|
||||
wa_tax = None
|
||||
if not request.is_success(res):
|
||||
# Cache.
|
||||
wa_tax = AccountTax.search([
|
||||
('wa_location_zips', 'like', '%' + partner.zip + '%'),
|
||||
('amount_type', '=', 'percent'),
|
||||
('type_tax_use', '=', 'sale')], limit=1)
|
||||
if not wa_tax:
|
||||
result |= self.wa_base_tax_id
|
||||
continue
|
||||
|
||||
# step 2: Find or create tax
|
||||
if not wa_tax:
|
||||
wa_tax = AccountTax.search([
|
||||
('wa_location_code', '=', res['location_code']),
|
||||
('amount', '=', res['rate']),
|
||||
('amount_type', '=', 'percent'),
|
||||
('type_tax_use', '=', 'sale')], limit=1)
|
||||
if not wa_tax:
|
||||
wa_tax = AccountTax.create({
|
||||
'name': '%s - WA Tax %s %%' % (res['location_code'], res['rate']),
|
||||
'wa_location_code': res['location_code'],
|
||||
'amount': res['rate'],
|
||||
'amount_type': 'percent',
|
||||
'type_tax_use': 'sale',
|
||||
'account_id': self.wa_base_tax_id.account_id.id,
|
||||
'refund_account_id': self.wa_base_tax_id.refund_account_id.id
|
||||
})
|
||||
|
||||
if not wa_tax.wa_location_zips:
|
||||
wa_tax.wa_location_zips = partner.zip
|
||||
elif not wa_tax.wa_location_zips.find(partner.zip) >= 0:
|
||||
zips = wa_tax.wa_location_zips.split(',')
|
||||
zips.append(partner.zip)
|
||||
wa_tax.wa_location_zips = zips.append(',')
|
||||
|
||||
# step 3: Find or create mapping
|
||||
tax_line = self.tax_ids.filtered(lambda x: x.tax_src_id.id == tax.id and x.tax_dest_id.id == wa_tax.id)
|
||||
if not tax_line:
|
||||
tax_line = self.env['account.fiscal.position.tax'].sudo().create({
|
||||
'position_id': self.id,
|
||||
'tax_src_id': tax.id,
|
||||
'tax_dest_id': wa_tax.id,
|
||||
})
|
||||
|
||||
result |= tax_line.tax_dest_id
|
||||
return result
|
||||
|
||||
class AccountTax(models.Model):
|
||||
_inherit = 'account.tax'
|
||||
|
||||
wa_location_code = fields.Integer('WA Location Code')
|
||||
wa_location_zips = fields.Char('WA Location ZIPs', default='')
|
||||
74
account_us_wa_salestax/models/wa_tax_request.py
Normal file
74
account_us_wa_salestax/models/wa_tax_request.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from urllib.request import urlopen, quote
|
||||
from urllib.error import HTTPError
|
||||
from ssl import _create_unverified_context
|
||||
from logging import getLogger
|
||||
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
_logger = getLogger(__name__)
|
||||
|
||||
|
||||
class WATaxRequest(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def get_rate(self, partner):
|
||||
# https://webgis.dor.wa.gov/webapi/addressrates.aspx/?output=text\&addr=test\&city=Marysville\&zip=98270
|
||||
if not all((partner.street, partner.city, partner.zip)):
|
||||
raise ValidationError('WATaxRequest impossible without Street, City and ZIP.')
|
||||
url = 'https://webgis.dor.wa.gov/webapi/addressrates.aspx?output=text&addr=' + quote(partner.street) + \
|
||||
'&city=' + quote(partner.city) + '&zip=' + quote(partner.zip)
|
||||
_logger.info(url)
|
||||
try:
|
||||
response = urlopen(url, context=_create_unverified_context())
|
||||
response_body = response.read()
|
||||
_logger.info(response_body)
|
||||
except HTTPError as e:
|
||||
_logger.warn('Error on request: ' + str(e))
|
||||
response_body = ''
|
||||
|
||||
return self._parse_rate(response_body)
|
||||
|
||||
def is_success(self, result):
|
||||
'''
|
||||
ADDRESS = 0,
|
||||
LATLON = 0,
|
||||
PLUS4 = 1,
|
||||
ADDRESS_STANARDIZED = 2,
|
||||
PLUS4_STANARDIZED = 3,
|
||||
ADDRESS_CHANGED = 4,
|
||||
ZIPCODE = 5,
|
||||
ADDRESS_NOT_FOUND = 6,
|
||||
LATLON_NOT_FOUND = 7,
|
||||
POI = 8,
|
||||
ERROR = 9
|
||||
internal parse_error = 100
|
||||
'''
|
||||
if 'result_code' not in result or result['result_code'] >= 9 or result['result_code'] < 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _parse_rate(self, response_body):
|
||||
# 'LocationCode=1704 Rate=0.100 ResultCode=0'
|
||||
# {
|
||||
# 'result_code': 0,
|
||||
# 'location_code': '1704',
|
||||
# 'rate': '10.00',
|
||||
# }
|
||||
|
||||
res = {'result_code': 100}
|
||||
if len(response_body) > 200:
|
||||
# this likely means that they returned an HTML page
|
||||
return res
|
||||
|
||||
body_parts = response_body.decode().split(' ')
|
||||
for part in body_parts:
|
||||
if part.find('ResultCode=') >= 0:
|
||||
res['result_code'] = int(part[len('ResultCode='):])
|
||||
elif part.find('Rate=') >= 0:
|
||||
res['rate'] = '%.2f' % (float(part[len('Rate='):]) * 100.0)
|
||||
elif part.find('LocationCode=') >= 0:
|
||||
res['location_code'] = part[len('LocationCode='):]
|
||||
elif part.find('debughint=') >= 0:
|
||||
res['debug_hint'] = part[len('debughint='):]
|
||||
return res
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="account_position_us_wa_inherit_from_view" model="ir.ui.view">
|
||||
<field name="name">account.fiscal.position.form.inherit</field>
|
||||
<field name="model">account.fiscal.position</field>
|
||||
<field name="inherit_id" ref="account.view_account_position_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='active']" position="after">
|
||||
<field name="is_us_wa"/>
|
||||
<field name="wa_base_tax_id" attrs="{'invisible': [('is_us_wa', '=', False)]}" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_tax_form" model="ir.ui.view">
|
||||
<field name="name">account.tax.form.inherit</field>
|
||||
<field name="model">account.tax</field>
|
||||
<field name="inherit_id" ref="account.view_tax_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='description']" position="after">
|
||||
<field name="wa_location_code" />
|
||||
<field name="wa_location_zips" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user