Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
vjFaLk committed Jan 18, 2018
2 parents 92bfa89 + 90e4ee9 commit 6f199cd
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 87 deletions.
2 changes: 1 addition & 1 deletion erpnext_taxjar/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

__version__ = '2.0.5'
__version__ = '3.0.0'

243 changes: 165 additions & 78 deletions erpnext_taxjar/api.py
Original file line number Diff line number Diff line change
@@ -1,76 +1,23 @@
import frappe
import taxjar
import traceback

def get_client():
api_key = frappe.get_doc("TaxJar Settings", "TaxJar Settings").get_password("api_key")
client = taxjar.Client(api_key=api_key)
return client


def set_sales_tax(doc, method):
if not doc.items:
return

# Allow skipping calculation of tax for dev environment
# if taxjar_calculate_tax isn't defined in site_config we assume
# we DO want to calculate tax all the time.
if not frappe.local.conf.get("taxjar_calculate_tax", 1):
return

if frappe.db.get_value("Customer", doc.customer, "exempt_from_sales_tax"):
for tax in doc.taxes:
if tax.description == "Sales Tax":
tax.tax_amount = 0
break
doc.run_method("calculate_taxes_and_totals")
return

tax_account_head = frappe.db.get_single_value("TaxJar Settings", "tax_account_head")

client = get_client()
tax_dict = get_tax_data(doc)

if not tax_dict:
taxes_list = []
for tax in doc.taxes:
if tax.account_head != tax_account_head:
taxes_list.append(tax)
break
setattr(doc, "taxes", taxes_list)
return

try:
taxdata = client.tax_for_order(tax_dict)
except:
return

if "Sales Tax" in [tax.description for tax in doc.taxes]:
for tax in doc.taxes:
if tax.description == "Sales Tax":
tax.tax_amount = taxdata.amount_to_collect
break
elif taxdata.amount_to_collect > 0:
doc.append("taxes", {
"charge_type": "Actual",
"description": "Sales Tax",
"account_head": tax_account_head,
"tax_amount": taxdata.amount_to_collect
})
import pycountry
import taxjar

doc.run_method("calculate_taxes_and_totals")
import frappe
from erpnext import get_default_company
from frappe import _
from frappe.contacts.doctype.address.address import get_company_address


def create_transaction(doc, method):

# Allow skipping creation of transaction for dev environment
# if taxjar_create_transactions isn't defined in site_config we assume
# we DO NOT want to create transactions all the time, except on production.
if not frappe.local.conf.get("taxjar_create_transactions", 0):
return

client = get_client()
sales_tax = 0

for tax in doc.taxes:
if tax.account_head == "Sales Tax - JA":
sales_tax = tax.tax_amount
Expand All @@ -79,6 +26,7 @@ def create_transaction(doc, method):
return

tax_dict = get_tax_data(doc)

if not tax_dict:
return

Expand All @@ -87,50 +35,189 @@ def create_transaction(doc, method):
tax_dict['sales_tax'] = sales_tax
tax_dict['amount'] = doc.total + tax_dict['shipping']

client = get_client()

try:
client.create_order(tax_dict)
except taxjar.exceptions.TaxJarResponseError as err:
frappe.throw(_(sanitize_error_response(err)))
except Exception as ex:
print(traceback.format_exc(ex))


def delete_transaction(doc, method):
client = get_client()
client.delete_order(doc.name)


def get_tax_data(doc):
from shipment_management.utils import get_state_code
taxjar_settings = frappe.get_single("TaxJar Settings")

client = get_client()
if not (taxjar_settings.api_key or taxjar_settings.tax_account_head):
return

def get_client():
api_key = frappe.get_doc("TaxJar Settings", "TaxJar Settings").get_password("api_key")
client = taxjar.Client(api_key=api_key)
return client

company_address = frappe.get_list("Dynamic Link", filters = {"link_doctype" : "Company"}, fields=["parent"])[0]
company_address = frappe.get_doc("Address", company_address.parent)
if company_address and not doc.shipping_address_name:
shipping_address = company_address
elif company_address and doc.shipping_address_name:
shipping_address = frappe.get_doc("Address", doc.shipping_address_name)
else:

def get_shipping_address(doc):
company_address = get_company_address(get_default_company()).company_address
company_address = frappe.get_doc("Address", company_address)
shipping_address = None

if company_address:
if doc.shipping_address_name:
shipping_address = frappe.get_doc("Address", doc.shipping_address_name)
else:
shipping_address = company_address

return shipping_address


def get_tax_data(doc):
shipping_address = get_shipping_address(doc)

if not shipping_address:
return

if not shipping_address.country == "United States":
taxjar_settings = frappe.get_single("TaxJar Settings")

if not (taxjar_settings.api_key or taxjar_settings.tax_account_head):
return

shipping = 0

for tax in doc.taxes:
if tax.account_head == "Freight and Forwarding Charges - JA":
shipping = tax.tax_amount

shipping_state = validate_state(shipping_address)
country_code = frappe.db.get_value("Country", shipping_address.country, "code")
country_code = country_code.upper()

tax_dict = {
'to_country': 'US',
'to_country': country_code,
'to_zip': shipping_address.pincode,
'to_city': shipping_address.city,
'to_state': get_state_code(shipping_address),
'to_state': shipping_state,
'shipping': shipping,
'amount': doc.net_total
}

return tax_dict


def sanitize_error_response(response):
response = response.full_response.get("detail")

sanitized_responses = {
"to_zip": "Zipcode",
"to_city": "City",
"to_state": "State",
"to_country": "Country",
"sales_tax": "Sales Tax"
}

for k, v in sanitized_responses.items():
response = response.replace(k, v)

return response


def set_sales_tax(doc, method):
if not doc.items:
return

# Allow skipping calculation of tax for dev environment
# if taxjar_calculate_tax isn't defined in site_config we assume
# we DO want to calculate tax all the time.
if not frappe.local.conf.get("taxjar_calculate_tax", 1):
return

if frappe.db.get_value("Customer", doc.customer, "exempt_from_sales_tax"):
for tax in doc.taxes:
if tax.description == "Sales Tax":
tax.tax_amount = 0
break

doc.run_method("calculate_taxes_and_totals")
return

tax_account_head = frappe.db.get_single_value("TaxJar Settings", "tax_account_head")
tax_dict = get_tax_data(doc)

tax_data = validate_tax_request(tax_dict)
if not tax_data.amount_to_collect:
taxes_list = []

for tax in doc.taxes:
if tax.account_head != tax_account_head:
taxes_list.append(tax)

setattr(doc, "taxes", taxes_list)
return

if tax_data is not None and tax_data.amount_to_collect > 0:
# Loop through tax rows for existing Sales Tax entry
# If none are found, add a row with the tax amount
for tax in doc.taxes:
if tax.account_head == tax_account_head:
tax.tax_amount = tax_data.amount_to_collect

doc.run_method("calculate_taxes_and_totals")
break
else:
doc.append("taxes", {
"charge_type": "Actual",
"description": "Sales Tax",
"account_head": tax_account_head,
"tax_amount": tax_data.amount_to_collect
})

doc.run_method("calculate_taxes_and_totals")


def validate_address(doc, address):
# Validate address using PyCountry
tax_dict = get_tax_data(doc)

if tax_dict:
# Validate address using TaxJar
validate_tax_request(tax_dict)


def validate_tax_request(tax_dict):
client = get_client()

try:
tax_data = client.tax_for_order(tax_dict)
except taxjar.exceptions.TaxJarResponseError as err:
frappe.throw(_(sanitize_error_response(err)))
else:
return tax_data


def validate_state(address):
if not address:
return

if not address.get("state"):
return

address_state = address.get("state").upper()

# Search the given state in PyCountry's database
try:
lookup_state = pycountry.subdivisions.lookup(address_state)
except LookupError:
# If search fails, try again if the given state is an ISO code
if len(address_state) in range(1, 4):
country_code = frappe.db.get_value("Country", address.get("country"), "code")

states = pycountry.subdivisions.get(country_code=country_code.upper())
states = [state.code.split('-')[1] for state in states] # PyCountry returns state code as {country_code}-{state-code} (e.g. US-FL)

if address_state in states:
return address_state
else:
error_message = """{} is not a valid state! Check for typos or enter the ISO code for your state."""

frappe.throw(_(error_message.format(address_state)))
else:
return lookup_state.code.split('-')[1]
17 changes: 10 additions & 7 deletions erpnext_taxjar/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,22 @@


doc_events = {
"Quotation" : {
"Quotation": {
"validate": "erpnext_taxjar.api.set_sales_tax"
},
"Sales Order" : {
"validate" : "erpnext_taxjar.api.set_sales_tax"
"Sales Order": {
"validate": "erpnext_taxjar.api.set_sales_tax"
},
"Sales Invoice" : {
"validate" : "erpnext_taxjar.api.set_sales_tax",
"on_submit" : "erpnext_taxjar.api.create_transaction",
"on_cancel" : "erpnext_taxjar.api.delete_transaction"
"Sales Invoice": {
"validate": "erpnext_taxjar.api.set_sales_tax",
"on_submit": "erpnext_taxjar.api.create_transaction",
"on_cancel": "erpnext_taxjar.api.delete_transaction"
}
}

awc_address_validation = [
"erpnext_taxjar.api.validate_address"
]

# include js, css files in header of desk.html
# app_include_css = "/assets/erpnext_taxjar/css/erpnext_taxjar.css"
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
frappe
erpnext
taxjar
taxjar
pycountry

0 comments on commit 6f199cd

Please sign in to comment.