You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
181 lines
7.8 KiB
Python
181 lines
7.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import hashlib
|
|
import logging
|
|
import re
|
|
import requests
|
|
|
|
from zeep import Client
|
|
from zeep.exceptions import Fault
|
|
|
|
from odoo import modules, fields, _
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class TaxCloudRequest(object):
|
|
""" Low-level object intended to interface Odoo recordsets with TaxCloud,
|
|
through appropriate SOAP requests """
|
|
|
|
def __init__(self, api_id, api_key):
|
|
wsdl_path = modules.get_module_path('account_taxcloud') + '/api/taxcloud.wsdl'
|
|
self.client = Client('file:///%s' % wsdl_path)
|
|
self.factory = self.client.type_factory('ns0')
|
|
self.api_login_id = api_id
|
|
self.api_key = api_key
|
|
|
|
def verify_address(self, partner):
|
|
# Ensure that the partner address is as accurate as possible (with zip4 field for example)
|
|
zip_match = re.match(r"^\D*(\d{5})\D*(\d{4})?", partner.zip or '')
|
|
zips = list(zip_match.groups()) if zip_match else []
|
|
address_to_verify = {
|
|
'apiLoginID': self.api_login_id,
|
|
'apiKey': self.api_key,
|
|
'Address1': partner.street or '',
|
|
'Address2': partner.street2 or '',
|
|
'City': partner.city,
|
|
"State": partner.state_id.code,
|
|
"Zip5": zips.pop(0) if zips else '',
|
|
"Zip4": zips.pop(0) if zips else '',
|
|
}
|
|
res = requests.post("https://api.taxcloud.com/1.0/TaxCloud/VerifyAddress", data=address_to_verify).json()
|
|
if int(res.get('ErrNumber', False)):
|
|
# If VerifyAddress fails, use Lookup with the initial address
|
|
_logger.info('Could not verify address for partner #%s using taxcloud; using unverified address instead', partner.id)
|
|
res.update(address_to_verify)
|
|
return res
|
|
|
|
def set_location_origin_detail(self, shipper):
|
|
address = self.verify_address(shipper)
|
|
self.origin = self.factory.Address()
|
|
self.origin.Address1 = address['Address1'] or ''
|
|
self.origin.Address2 = address['Address2'] or ''
|
|
self.origin.City = address['City']
|
|
self.origin.State = address['State']
|
|
self.origin.Zip5 = address['Zip5']
|
|
self.origin.Zip4 = address['Zip4']
|
|
|
|
def set_location_destination_detail(self, recipient_partner):
|
|
address = self.verify_address(recipient_partner)
|
|
self.destination = self.factory.Address()
|
|
self.destination.Address1 = address['Address1'] or ''
|
|
self.destination.Address2 = address['Address2'] or ''
|
|
self.destination.City = address['City']
|
|
self.destination.State = address['State']
|
|
self.destination.Zip5 = address['Zip5']
|
|
self.destination.Zip4 = address['Zip4']
|
|
|
|
def set_items_detail(self, product_id, tic_code):
|
|
self.cart_items = self.factory.ArrayOfCartItem()
|
|
self.cart_item = self.factory.CartItem()
|
|
self.cart_item.Index = 1
|
|
self.cart_item.ItemID = product_id
|
|
if tic_code:
|
|
self.cart_item.TIC = tic_code
|
|
# Send fixed price 100$ and Qty 1 to calculate percentage based on amount returned.
|
|
self.cart_item.Price = 100
|
|
self.cart_item.Qty = 1
|
|
self.cart_items.CartItem = [self.cart_item]
|
|
|
|
def set_invoice_items_detail(self, invoice):
|
|
self.customer_id = invoice.partner_id.id
|
|
self.taxcloud_date = invoice.get_taxcloud_reporting_date()
|
|
self.cart_id = invoice.id
|
|
self.cart_items = self.factory.ArrayOfCartItem()
|
|
self.cart_items.CartItem = self._process_lines(invoice.invoice_line_ids)
|
|
|
|
def _process_lines(self, lines):
|
|
cart_items = []
|
|
for index, line in enumerate(lines.filtered(lambda l: l.display_type not in ('line_note', 'line_section'))):
|
|
qty = line._get_qty()
|
|
if line._get_taxcloud_price() >= 0.0 and qty >= 0.0:
|
|
product_id = line.product_id.id
|
|
tic_code = line.product_id.tic_category_id.code or \
|
|
line.product_id.categ_id.tic_category_id.code or \
|
|
line.company_id.tic_category_id.code or \
|
|
line.env.company.tic_category_id.code
|
|
price_unit = line._get_taxcloud_price() * (1 - (line.discount or 0.0) / 100.0)
|
|
|
|
cart_item = self.factory.CartItem()
|
|
cart_item.Index = index
|
|
cart_item.ItemID = product_id
|
|
if tic_code:
|
|
cart_item.TIC = tic_code
|
|
cart_item.Price = price_unit
|
|
cart_item.Qty = qty
|
|
cart_items.append(cart_item)
|
|
return cart_items
|
|
|
|
def get_all_taxes_values(self):
|
|
customer_id = hasattr(self, 'customer_id') and self.customer_id or 'NoCustomerID'
|
|
cart_id = hasattr(self, 'cart_id') and self.cart_id or 'NoCartID'
|
|
_logger.info('fetching tax values for cart %s (customer: %s)', cart_id, customer_id)
|
|
formatted_response = {}
|
|
if not self.api_login_id or not self.api_key:
|
|
formatted_response['error_message'] = _("Please configure taxcloud credentials on the current company "
|
|
"or use a different fiscal position")
|
|
return formatted_response
|
|
|
|
try:
|
|
response = self.client.service.LookupForDate(
|
|
self.api_login_id,
|
|
self.api_key,
|
|
customer_id,
|
|
cart_id,
|
|
self.cart_items,
|
|
self.origin,
|
|
self.destination,
|
|
False, # deliveredBySeller
|
|
None, # exemptCert
|
|
self.taxcloud_date, # useDate
|
|
)
|
|
formatted_response['response'] = response
|
|
if response.ResponseType == 'OK':
|
|
formatted_response['values'] = {}
|
|
for item in response.CartItemsResponse.CartItemResponse:
|
|
index = item.CartItemIndex
|
|
tax_amount = item.TaxAmount
|
|
formatted_response['values'][index] = tax_amount
|
|
elif response.ResponseType == 'Error':
|
|
formatted_response['error_message'] = response.Messages.ResponseMessage[0].Message
|
|
except Fault as fault:
|
|
formatted_response['error_message'] = fault.message
|
|
except IOError:
|
|
formatted_response['error_message'] = "TaxCloud Server Not Found"
|
|
return formatted_response
|
|
|
|
# Get TIC category on synchronize.
|
|
def get_tic_category(self):
|
|
formatted_response = {}
|
|
try:
|
|
self.response = self.client.service.GetTICs(self.api_login_id, self.api_key)
|
|
if self.response.ResponseType == 'OK':
|
|
formatted_response['data'] = self.response.TICs.TIC
|
|
elif self.response.ResponseType == 'Error':
|
|
formatted_response['error_message'] = self.response.Messages.ResponseMessage[0].Message
|
|
except Fault as fault:
|
|
formatted_response['error_message'] = fault.message
|
|
except IOError:
|
|
formatted_response['error_message'] = "TaxCloud Server Not Found"
|
|
|
|
return formatted_response
|
|
|
|
@property
|
|
def hash(self):
|
|
# The hash is used as key to cache request responses, to avoid using too much space in the
|
|
# cache.
|
|
# The current date is appended to refresh the value every day.
|
|
return hashlib.sha1(
|
|
(
|
|
(self.api_login_id or '')
|
|
+ (self.api_key or '')
|
|
+ str(hasattr(self, "customer_id") and self.customer_id or "NoCustomerID")
|
|
+ str(hasattr(self, "cart_id") and self.cart_id or "NoCartID")
|
|
+ str(self.cart_items)
|
|
+ str(self.origin)
|
|
+ str(self.destination)
|
|
+ fields.Date.to_string(fields.Date.today())
|
|
).encode("utf-8")
|
|
).hexdigest()
|