Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Taxjar Integration update #27143

Merged
merged 9 commits into from
Aug 31, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 65 additions & 25 deletions erpnext/erpnext_integrations/taxjar_integration.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import traceback

import taxjar

import frappe
import taxjar
from erpnext import get_default_company
from frappe import _
from frappe.contacts.doctype.address.address import get_company_address
from frappe.utils import cint

TAX_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "tax_account_head")
SHIP_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "shipping_account_head")
Expand All @@ -14,6 +13,10 @@
SUPPORTED_COUNTRY_CODES = ["AT", "AU", "BE", "BG", "CA", "CY", "CZ", "DE", "DK", "EE", "ES", "FI",
"FR", "GB", "GR", "HR", "HU", "IE", "IT", "LT", "LU", "LV", "MT", "NL", "PL", "PT", "RO",
"SE", "SI", "SK", "US"]
SUPPORTED_STATE_CODES = ['AL', 'AK', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'DC', 'FL', 'GA', 'HI', 'ID', 'IL',
'IN', 'IA', 'KS', 'KY', 'LA', 'ME', 'MD', 'MA', 'MI', 'MN', 'MS', 'MO', 'MT', 'NE',
'NV', 'NH', 'NJ', 'NM', 'NY', 'NC', 'ND', 'OH', 'OK', 'OR', 'PA', 'RI', 'SC', 'SD',
'TN', 'TX', 'UT', 'VT', 'VA', 'WA', 'WV', 'WI', 'WY']


def get_client():
Expand All @@ -27,7 +30,11 @@ def get_client():
api_url = taxjar.SANDBOX_API_URL

if api_key and api_url:
return taxjar.Client(api_key=api_key, api_url=api_url)
client = taxjar.Client(api_key=api_key, api_url=api_url)
client.set_api_config('headers', {
'x-api-version': '2020-08-07'
})
return client


def create_transaction(doc, method):
Expand Down Expand Up @@ -57,7 +64,10 @@ def create_transaction(doc, method):
tax_dict['amount'] = doc.total + tax_dict['shipping']

try:
client.create_order(tax_dict)
if tax_dict['amount']<0:
client.create_refund(tax_dict)
else:
client.create_order(tax_dict)
except taxjar.exceptions.TaxJarResponseError as err:
frappe.throw(_(sanitize_error_response(err)))
except Exception as ex:
Expand Down Expand Up @@ -89,14 +99,16 @@ def get_tax_data(doc):
to_country_code = frappe.db.get_value("Country", to_address.country, "code")
to_country_code = to_country_code.upper()

if to_country_code not in SUPPORTED_COUNTRY_CODES:
return

shipping = sum([tax.tax_amount for tax in doc.taxes if tax.account_head == SHIP_ACCOUNT_HEAD])

if to_shipping_state is not None:
to_shipping_state = get_iso_3166_2_state_code(to_address)
line_items = [get_line_item_dict(item) for item in doc.items]

if from_shipping_state not in SUPPORTED_STATE_CODES:
from_shipping_state = get_state_code(from_address, 'Company')

if to_shipping_state not in SUPPORTED_STATE_CODES:
to_shipping_state = get_state_code(to_address, 'Shipping')

tax_dict = {
'from_country': from_country_code,
'from_zip': from_address.pincode,
Expand All @@ -109,11 +121,29 @@ def get_tax_data(doc):
'to_street': to_address.address_line1,
'to_state': to_shipping_state,
'shipping': shipping,
'amount': doc.net_total
'amount': doc.net_total,
'plugin': 'erpnext',
'line_items': line_items
}
return tax_dict

return tax_dict

def get_state_code(address, location):
if address is not None:
state_code = get_iso_3166_2_state_code(address)
if state_code not in SUPPORTED_STATE_CODES:
frappe.throw(_("Please enter a valid State in the {0} Address").format(location))
else:
frappe.throw(_("Please enter a valid State in the {0} Address").format(location))

return state_code

def get_line_item_dict(item):
return dict(
id = item.get('idx'),
quantity = item.get('qty'),
unit_price = item.get('rate'),
product_tax_code = item.get('product_tax_category')
)

def set_sales_tax(doc, method):
if not TAXJAR_CALCULATE_TAX:
Expand All @@ -122,17 +152,7 @@ def set_sales_tax(doc, method):
if not doc.items:
return

# if the party is exempt from sales tax, then set all tax account heads to zero
sales_tax_exempted = hasattr(doc, "exempt_from_sales_tax") and doc.exempt_from_sales_tax \
or frappe.db.has_column("Customer", "exempt_from_sales_tax") and frappe.db.get_value("Customer", doc.customer, "exempt_from_sales_tax")

if sales_tax_exempted:
for tax in doc.taxes:
if tax.account_head == TAX_ACCOUNT_HEAD:
tax.tax_amount = 0
break

doc.run_method("calculate_taxes_and_totals")
if check_sales_tax_exemption(doc):
return

tax_dict = get_tax_data(doc)
Expand All @@ -143,7 +163,6 @@ def set_sales_tax(doc, method):
return

tax_data = validate_tax_request(tax_dict)

if tax_data is not None:
if not tax_data.amount_to_collect:
setattr(doc, "taxes", [tax for tax in doc.taxes if tax.account_head != TAX_ACCOUNT_HEAD])
Expand All @@ -163,9 +182,28 @@ def set_sales_tax(doc, method):
"account_head": TAX_ACCOUNT_HEAD,
"tax_amount": tax_data.amount_to_collect
})
# Assigning values to tax_collectable and taxable_amount fields in sales item table
for item in tax_data.breakdown.line_items:
doc.get('items')[cint(item.id)-1].tax_collectable = item.tax_collectable
doc.get('items')[cint(item.id)-1].taxable_amount = item.taxable_amount

doc.run_method("calculate_taxes_and_totals")

def check_sales_tax_exemption(doc):
# if the party is exempt from sales tax, then set all tax account heads to zero
sales_tax_exempted = hasattr(doc, "exempt_from_sales_tax") and doc.exempt_from_sales_tax \
or frappe.db.has_column("Customer", "exempt_from_sales_tax") \
and frappe.db.get_value("Customer", doc.customer, "exempt_from_sales_tax")

if sales_tax_exempted:
for tax in doc.taxes:
if tax.account_head == TAX_ACCOUNT_HEAD:
tax.tax_amount = 0
break
doc.run_method("calculate_taxes_and_totals")
return True
else:
return False

def validate_tax_request(tax_dict):
"""Return the sales tax that should be collected for a given order."""
Expand Down Expand Up @@ -200,6 +238,8 @@ def get_shipping_address_details(doc):

if doc.shipping_address_name:
shipping_address = frappe.get_doc("Address", doc.shipping_address_name)
elif doc.customer_address:
shipping_address = frappe.get_doc("Address", doc.customer_address_name)
else:
shipping_address = get_company_address_details(doc)

Expand Down
1 change: 1 addition & 0 deletions erpnext/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -295,4 +295,5 @@ erpnext.patches.v13_0.add_custom_field_for_south_africa #2
erpnext.patches.v13_0.update_recipient_email_digest
erpnext.patches.v13_0.shopify_deprecation_warning
erpnext.patches.v13_0.einvoicing_deprecation_warning
erpnext.patches.v13_0.custom_fields_for_taxjar_integration
erpnext.patches.v14_0.delete_einvoicing_doctypes
29 changes: 29 additions & 0 deletions erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from __future__ import unicode_literals
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from erpnext.regional.united_states.setup import add_permissions

def execute():
company = frappe.get_all('Company', filters = {'country': 'United States'}, fields=['name'])
if not company:
return

frappe.reload_doc("regional", "doctype", "product_tax_category")

custom_fields = {
'Sales Invoice Item': [
dict(fieldname='product_tax_category', fieldtype='Link', insert_after='description', options='Product Tax Category',
label='Product Tax Category', fetch_from='item_code.product_tax_category'),
dict(fieldname='tax_collectable', fieldtype='Currency', insert_after='net_amount',
label='Tax Collectable', read_only=1),
dict(fieldname='taxable_amount', fieldtype='Currency', insert_after='tax_collectable',
label='Taxable Amount', read_only=1)
],
'Item': [
dict(fieldname='product_tax_category', fieldtype='Link', insert_after='item_group', options='Product Tax Category',
label='Product Tax Category')
]
}
create_custom_fields(custom_fields, update=True)
add_permissions()
frappe.enqueue('erpnext.regional.united_states.setup.add_product_tax_categories', now=True)
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt

frappe.ui.form.on('Product Tax Category', {
// refresh: function(frm) {

// }
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"actions": [],
"autoname": "field:product_tax_code",
"creation": "2021-08-23 12:33:37.910225",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"product_tax_code",
"column_break_2",
"category_name",
"section_break_4",
"description"
],
"fields": [
{
"fieldname": "product_tax_code",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Product Tax Code",
"reqd": 1,
"unique": 1
},
{
"fieldname": "description",
"fieldtype": "Small Text",
"label": "Description"
},
{
"fieldname": "category_name",
"fieldtype": "Data",
"label": "Category Name",
"length": 255
},
{
"fieldname": "column_break_2",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_4",
"fieldtype": "Section Break"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-08-24 09:10:25.313642",
"modified_by": "Administrator",
"module": "Regional",
"name": "Product Tax Category",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "category_name",
"track_changes": 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt

# import frappe
from frappe.model.document import Document

class ProductTaxCategory(Document):
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt

# import frappe
import unittest

class TestProductTaxCategory(unittest.TestCase):
pass
Loading