Skip to content

Commit

Permalink
feat: validate EU VAT ID (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
barredterra authored Apr 19, 2022
1 parent 8af3a6b commit f2077d0
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 3 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ App to hold regional code for Germany, built on top of ERPNext.

![Section with Register Information](docs/register_information.png)

- Validation of EU VAT IDs

<video src="docs/validate_vat_id.webm" autoplay loop>Validate EU VAT ID</video>

## Installation

### On Frappe Cloud
Expand Down
Binary file added docs/validate_vat_id.webm
Binary file not shown.
20 changes: 20 additions & 0 deletions erpnext_germany/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import frappe


@frappe.whitelist()
def validate_vat_id(vat_id: str) -> bool:
"""Use the EU VAT checker to validate a VAT ID."""
from .utils.eu_vat import is_valid_eu_vat_id

result = frappe.cache().hget("eu_vat_validation", vat_id, shared=True)
if result is not None:
return result

try:
result = is_valid_eu_vat_id(vat_id)
frappe.cache().hset("eu_vat_validation", vat_id, result, shared=True)
except Exception:
frappe.response["status_code"] = 501
result = None

return result
8 changes: 5 additions & 3 deletions erpnext_germany/hooks.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from . import __version__ as app_version
from .constants import REGISTER_COURTS


app_name = "erpnext_germany"
app_title = "ERPNext Germany"
app_publisher = "ALYF GmbH"
Expand All @@ -17,7 +16,7 @@

# include js, css files in header of desk.html
# app_include_css = "/assets/erpnext_germany/css/erpnext_germany.css"
# app_include_js = "/assets/erpnext_germany/js/erpnext_germany.js"
app_include_js = "/assets/erpnext_germany/js/validate_vat_id.js"

# include js, css files in header of web template
# web_include_css = "/assets/erpnext_germany/css/erpnext_germany.css"
Expand All @@ -34,7 +33,10 @@
# page_js = {"page" : "public/js/file.js"}

# include js in doctype views
# doctype_js = {"doctype" : "public/js/doctype.js"}
doctype_js = {
"Customer": "public/js/customer.js",
"Supplier": "public/js/supplier.js",
}
# doctype_list_js = {"doctype" : "public/js/doctype_list.js"}
# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"}
# doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"}
Expand Down
5 changes: 5 additions & 0 deletions erpnext_germany/public/js/customer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
frappe.ui.form.on("Customer", {
setup: function (frm) {
erpnext_germany.utils.setup_vat_id_validation_button(frm);
}
});
5 changes: 5 additions & 0 deletions erpnext_germany/public/js/supplier.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
frappe.ui.form.on("Supplier", {
setup: function (frm) {
erpnext_germany.utils.setup_vat_id_validation_button(frm);
}
});
51 changes: 51 additions & 0 deletions erpnext_germany/public/js/validate_vat_id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@

frappe.provide("erpnext_germany.utils");

erpnext_germany.utils.setup_vat_id_validation_button = function (frm) {
const $wrapper = $(frm.fields_dict.tax_id.input_area);
const $link_button = $(
`<span class="link-btn"
style="top: 2px; right: 2px;"
>
<a
class="btn btn-xs btn-default"
title="${__("Validate EU VAT ID")}"
style="padding: 3px;"
>
${frappe.utils.icon("search", "sm")}
</a>
</span>`
);

$($wrapper).append($link_button);
$link_button.toggle(true);
$link_button.on("click", "a", () => {
const vat_id = frm.doc.tax_id;
erpnext_germany.utils.check_vat_id(vat_id)
.then((is_valid) => {
if (is_valid === true) {
frappe.show_alert({
message: __("Tax ID is a valid EU VAT ID"),
indicator: "green",
});
} else if (is_valid === false) {
frappe.show_alert({
message: __("Tax ID is not a valid EU VAT ID"),
indicator: "red",
});
} else {
frappe.show_alert({
message: __("Tax ID could not be validated"),
indicator: "grey",
});
}
});
});
};

erpnext_germany.utils.check_vat_id = function (vat_id) {
return frappe.xcall(
"erpnext_germany.api.validate_vat_id",
{ vat_id: vat_id },
);
};
4 changes: 4 additions & 0 deletions erpnext_germany/translations/de.csv
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
Register Type,Registerart
Register Number,Registernummer
Register Court,Registergericht
Validate EU VAT ID,Ust-IdNr. validieren
Tax ID is a valid EU VAT ID,Steuernummer ist gültige Ust-IdNr.
Tax ID is not a valid EU VAT ID,Steuernummer ist keine gültige Ust-IdNr.
Tax ID could not be validated,Steuernummer konnte nicht validiert werden.
12 changes: 12 additions & 0 deletions erpnext_germany/utils/eu_vat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import zeep


def is_valid_eu_vat_id(vat_id) -> bool:
"""Use the EU VAT checker to validate a VAT ID."""
country_code = vat_id[:2].upper()
vat_number = vat_id[2:].replace(" ", "")

client = zeep.Client("https://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl")
result = client.service.checkVat(vatNumber=vat_number, countryCode=country_code)

return result.valid
21 changes: 21 additions & 0 deletions erpnext_germany/utils/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from unittest import TestCase
from .eu_vat import is_valid_vat_id


class TestUtils(TestCase):
def test_validate_vat_id(self):
valid_ids = [
"DE329035522", # ALYF
"DE210157578", # SAP
]
invalid_ids = [
"ABC123",
"Test Test",
"DE1234567890",
]

for vat_id in valid_ids:
self.assertTrue(is_valid_vat_id(vat_id))

for vat_id in invalid_ids:
self.assertFalse(is_valid_vat_id(vat_id))

0 comments on commit f2077d0

Please sign in to comment.