diff --git a/account_us_ca_salestax/__init__.py b/account_us_ca_salestax/__init__.py
new file mode 100644
index 00000000..0650744f
--- /dev/null
+++ b/account_us_ca_salestax/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/account_us_ca_salestax/__manifest__.py b/account_us_ca_salestax/__manifest__.py
new file mode 100644
index 00000000..2ce76a86
--- /dev/null
+++ b/account_us_ca_salestax/__manifest__.py
@@ -0,0 +1,13 @@
+{'name': 'US CA California State SalesTax API',
+ 'version': '12.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,
+ }
diff --git a/account_us_ca_salestax/models/__init__.py b/account_us_ca_salestax/models/__init__.py
new file mode 100644
index 00000000..2637a061
--- /dev/null
+++ b/account_us_ca_salestax/models/__init__.py
@@ -0,0 +1 @@
+from . import account_fiscal_position
diff --git a/account_us_ca_salestax/models/account_fiscal_position.py b/account_us_ca_salestax/models/account_fiscal_position.py
new file mode 100644
index 00000000..11e32d67
--- /dev/null
+++ b/account_us_ca_salestax/models/account_fiscal_position.py
@@ -0,0 +1,76 @@
+from odoo import api, fields, models
+from .ca_tax_request import CATaxRequest
+
+
+class AccountFiscalPosition(models.Model):
+ _inherit = 'account.fiscal.position'
+
+ is_us_ca = fields.Boolean(string='Use CA State API')
+ ca_base_tax_id = fields.Many2one('account.tax', string='CA Base/Error Tax', company_dependent=True)
+
+ @api.multi
+ def map_tax(self, taxes, product=None, partner=None):
+
+ if not taxes or not self.is_us_ca or partner is None:
+ return super(AccountFiscalPosition, self).map_tax(taxes)
+
+ AccountTax = self.env['account.tax'].sudo()
+ result = AccountTax.browse()
+
+ for tax in taxes:
+ ca_tax = None
+ request = CATaxRequest()
+ try:
+ res = request.get_rate(partner)
+ ca_tax = AccountTax.search([
+ ('company_id', '=', self.ca_base_tax_id.company_id.id),
+ ('ca_county', '=', res['county']),
+ ('amount', '=', res['rate']),
+ ('amount_type', '=', 'percent'),
+ ('type_tax_use', '=', 'sale')], limit=1)
+ if not ca_tax:
+ ca_tax = AccountTax.create({
+ 'name': '%s - Tax %0.2f%%' % (res['county'], res['rate']),
+ 'ca_county': res['county'],
+ 'amount': res['rate'],
+ 'amount_type': 'percent',
+ 'type_tax_use': 'sale',
+ 'account_id': self.ca_base_tax_id.account_id.id,
+ 'refund_account_id': self.ca_base_tax_id.refund_account_id.id,
+ 'company_id': self.ca_base_tax_id.company_id.id,
+ 'ca_location_zips': partner.zip,
+ })
+ except:
+ ca_tax = AccountTax.search([
+ ('ca_location_zips', 'like', '%' + partner.zip + '%'),
+ ('amount_type', '=', 'percent'),
+ ('type_tax_use', '=', 'sale')], limit=1)
+ if not ca_tax:
+ result |= self.ca_base_tax_id
+ continue
+
+ if not ca_tax.ca_location_zips:
+ ca_tax.write({'ca_location_zips': partner.zip})
+ elif not ca_tax.ca_location_zips.find(str(partner.zip)) >= 0:
+ zips = ca_tax.ca_location_zips.split(',')
+ zips.append(str(partner.zip))
+ ca_tax.write({'ca_location_zips': ','.join(zips)})
+
+ # 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 == ca_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': ca_tax.id,
+ })
+
+ result |= tax_line.tax_dest_id
+ return result
+
+
+class AccountTax(models.Model):
+ _inherit = 'account.tax'
+
+ ca_county = fields.Char('CA County')
+ ca_location_zips = fields.Char('CA Location ZIPs', default='')
diff --git a/account_us_ca_salestax/models/ca_rates.wsdl b/account_us_ca_salestax/models/ca_rates.wsdl
new file mode 100644
index 00000000..3ba42502
--- /dev/null
+++ b/account_us_ca_salestax/models/ca_rates.wsdl
@@ -0,0 +1,227 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/account_us_ca_salestax/models/ca_tax_request.py b/account_us_ca_salestax/models/ca_tax_request.py
new file mode 100644
index 00000000..71a313c7
--- /dev/null
+++ b/account_us_ca_salestax/models/ca_tax_request.py
@@ -0,0 +1,29 @@
+import os
+import logging
+from suds.client import Client
+
+_logger = logging.getLogger(__name__)
+
+
+class CATaxRequest:
+ def __init__(self):
+ wsdl_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), './ca_rates.wsdl')
+ self.client = Client('file:///%s' % wsdl_path.lstrip('/'))
+
+ def get_rate(self, partner):
+ request = self.client.factory.create('CARateRequest')
+ request.StreetAddress = partner.street
+ request.State = partner.state_id.code
+ request.City = partner.city
+ zip_ = partner.zip
+ if zip_ and len(zip_) > 5:
+ zip_ = zip_[:5]
+ request.ZipCode = zip_
+
+ _logger.debug('CA Request: ' + str(request))
+ response = self.client.service.GetRate(request)
+ _logger.debug('CA Response: ' + str(response))
+ return {
+ 'rate': response.CARateResponses[0][0].Responses[0][0].Rate * 100.0,
+ 'county': response.CARateResponses[0][0].Responses[0][0].Details.County,
+ }
diff --git a/account_us_ca_salestax/views/account_fiscal_position_view.xml b/account_us_ca_salestax/views/account_fiscal_position_view.xml
new file mode 100644
index 00000000..30972681
--- /dev/null
+++ b/account_us_ca_salestax/views/account_fiscal_position_view.xml
@@ -0,0 +1,28 @@
+
+
+
+
+ account.fiscal.position.form.inherit
+ account.fiscal.position
+
+
+
+
+
+
+
+
+
+
+ account.tax.form.inherit
+ account.tax
+
+
+
+
+
+
+
+
+
+