From 0d13244808fd1bb4dd60d2bb33aecb30f74d5df6 Mon Sep 17 00:00:00 2001 From: Dariusz Aniszewski Date: Thu, 11 Jun 2015 23:19:46 +0200 Subject: [PATCH] models refactor. working example of issuing invoice. tests --- python_ifirma/core.py | 144 ++++++++++++++++++++ python_ifirma/helpers.py | 18 +++ python_ifirma/ifirma.py | 346 ----------------------------------------------- python_ifirma/tests.py | 139 ++++++++++++++----- 4 files changed, 264 insertions(+), 383 deletions(-) create mode 100644 python_ifirma/core.py create mode 100644 python_ifirma/helpers.py delete mode 100644 python_ifirma/ifirma.py diff --git a/python_ifirma/core.py b/python_ifirma/core.py new file mode 100644 index 0000000..f441797 --- /dev/null +++ b/python_ifirma/core.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- +import json +import datetime +from time import strftime + +from python_ifirma.helpers import Helpers + +import requests + + +class VAT: + VAT_0 = 0.00 + VAT_5 = 0.05 + VAT_8 = 0.08 + VAT_23 = 0.23 + + +class Address: + def __init__(self, city, zip_code, street=None, country=None): + super().__init__() + self.city = city + self.zip_code = zip_code + self.street = street + self.country = country + + +class Client: + def __init__(self, name, tax_id, address, email=None, phone_number=None): + self.name = name + self.tax_id = tax_id + self.address = address + self.email = email + self.phone_number = phone_number + + def get_dict(self): + return { + "Nazwa": self.name, + "NIP": self.tax_id, + "KodPocztowy": self.address.zip_code, + "Ulica": self.address.street, + "Miejscowosc": self.address.city, + "Kraj": self.address.country, + "Email": self.email, + "Telefon": self.phone_number, + } + + +class Position: + def __init__(self, vat_rate, quantity, base_price, full_name, unit, pkwiu=None, discount_percent=None): + self.vat_rate = vat_rate + self.quantity = quantity + self.base_price = base_price + self.full_name = full_name + self.unit = unit + self.pkwiu = pkwiu + self.discount_percent = discount_percent + + def get_dict(self): + return { + "StawkaVat": self.vat_rate, + "Ilosc": self.quantity, + "CenaJednostkowa": self.base_price, + "NazwaPelna": self.full_name, + "Jednostka": self.unit, + "TypStawkiVat": "PRC", + "Rabat": self.discount_percent + } + + +class Invoice: + def __init__(self, client, positions): + self.client = client + self.positions = positions + self.issue_date = datetime.date.today() + + def __get_issue_date(self): + return strftime("%Y-%m-%d", self.issue_date.timetuple()) + + def __get_total_price(self): + return sum( + [ + position.quantity * position.base_price * ( + (1 - position.discount_percent / 100) if position.discount_percent else 1 + ) for position in self.positions + ] + ) + + def get_request_data(self): + return { + "Zaplacono": self.__get_total_price(), + "ZaplaconoNaDokumencie": self.__get_total_price(), + "LiczOd": "BRT", + "DataWystawienia": self.__get_issue_date(), + "DataSprzedazy": self.__get_issue_date(), + "FormatDatySprzedazy": "MSC", + "SposobZaplaty": "ELE", + "RodzajPodpisuOdbiorcy": "BPO", + "WidocznyNumerGios": False, + "Numer": None, + "Pozycje": [position.get_dict() for position in self.positions], + "Kontrahent": self.client.get_dict(), + } + + +class iFirmaAPI(): + __username = None + __invoice_key_name = "faktura" + __invoice_key_value = None + + __user_key_name = "abonent" + __user_key_value = None + + def __init__(self, _username, _invoice_key_value, _user_key_value=None): + self.__username = _username + self.__invoice_key_value = Helpers.unhex_key_value(_invoice_key_value) + self.__user_key_value = Helpers.unhex_key_value(_user_key_value) + + def __create_invoice_and_return_id(self, invoice, url): + request_content = json.dumps(invoice.get_request_data(), separators=(',', ':')) + request_hash_text = "{}{}{}{}".format( + url, + self.__username, + self.__invoice_key_name, + request_content, + ) + headers = { + "Accept": "application/json", + "Content-type": "application/json; charset=UTF-8", + "Authentication": "IAPIS user={}, hmac-sha1={}".format( + self.__username, + Helpers.get_hmac_of_text(self.__invoice_key_value, request_hash_text) + ) + } + response = requests.post(url, data=request_content, headers=headers) + response_dict = json.loads(response.content.decode("utf-8"), 'utf-8') + if response_dict["response"].get("Identyfikator"): + invoice_id = response_dict["response"]["Identyfikator"] + return invoice_id + else: + return None + + def generate_invoice(self, invoice): + url = "https://www.ifirma.pl/iapi/fakturakraj.json" + return self.__create_invoice_and_return_id(invoice, url) diff --git a/python_ifirma/helpers.py b/python_ifirma/helpers.py new file mode 100644 index 0000000..038f14c --- /dev/null +++ b/python_ifirma/helpers.py @@ -0,0 +1,18 @@ +import binascii +import hashlib +import hmac + +import six + + +class Helpers: + @staticmethod + def unhex_key_value(text): + try: + return binascii.unhexlify(text) + except binascii.Error: + raise TypeError + + @staticmethod + def get_hmac_of_text(key, text): + return hmac.new(key, six.b(text), hashlib.sha1).hexdigest() \ No newline at end of file diff --git a/python_ifirma/ifirma.py b/python_ifirma/ifirma.py deleted file mode 100644 index dec17fb..0000000 --- a/python_ifirma/ifirma.py +++ /dev/null @@ -1,346 +0,0 @@ -# -*- coding: utf-8 -*- -import binascii -from enum import Enum -import hashlib -import hmac -import json -import datetime - -import requests -import six - - -class VAT(Enum): - VAT_0 = 0.00 - VAT_5 = 0.05 - VAT_8 = 0.08 - VAT_23 = 0.23 - PERCENTAGE = "PRC" - EXEMPT = "ZW" - - -class Client: - __name = None - __id = None - __eu_prefix = None - __street = None - __zip_code = None - __country = None - __city = None - __email = None - __phone_number = None - __is_private = True - - def __init__(self, name, street, zip_code, country, city, _id=None, eu_prefix=None, email=None, phone_number=None, - is_private=True): - self.__name = name - self.__street = street - self.__zip_code = zip_code - self.__country = country - self.__city = city - self.__id = _id - self.__eu_prefix = eu_prefix - self.__email = email - self.__phone_number = phone_number - self.__is_private = is_private - - def get_dict(self): - return { - "Nazwa": self.__name, - "Identyfikator": self.__id, - "PrefiksUE": self.__eu_prefix, - "Ulica": self.__street, - "KodPocztowy": self.__zip_code, - "Kraj": self.__country, - "Miejscowosc": self.__city, - "Email": self.__email, - "Telefon": self.__phone_number, - "OsobaFizyczna": self.__is_private, - } - - def get_json(self): - return json.dumps(self.get_dict()) - - -class Position: - __vat = None - __quantity = None - __base_price = None - __full_name = None - __unit = None - __pkwiu = None - __vat_type = None - __discount = None - - def __init__(self, vat, quantity, base_price, full_name, unit, pkwiu="", vat_type=VAT.PERCENTAGE, discount=0): - self.__vat = vat - self.__quantity = quantity - self.__base_price = base_price - self.__full_name = full_name - self.__unit = unit - self.__pkwiu = pkwiu - self.__vat_type = vat_type - self.__discount = discount - - def get_dict(self): - return { - "StawkaVat": self.__vat.value, - "Ilosc": self.__quantity, - "CenaJednostkowa": self.__base_price, - "NazwaPelna": self.__full_name, - "Jednostka": self.__unit, - "PKWiU": self.__pkwiu, - "TypStawkiVat": self.__vat_type.value, - "Rabat": self.__discount - } - - -class Invoice: - __client = None - __positions = [] - - def set_client(self, client): - self.__client = client - - def add_position(self, position): - self.__positions.append(position) - - def clear_positions(self): - self.__positions.clear() - - def get_dict(self): - return { - "LiczOd": "BRT", - "NumerKontaBankowego": None, - "TypFakturyKrajowej": "SPRZ", - "DataWystawienia": today, - "MiejsceWystawienia": "Warszawa", - "TerminPlatnosci": None, - "SposobZaplaty": "PRZ", - "NazwaSeriiNumeracji": "default", - "NazwaSzablonu": "logo", - "RodzajPodpisuOdbiorcy": "BPO", - "PodpisOdbiorcy": "", - "PodpisWystawcy": "", - "Uwagi": "", - "WidocznyNumerGios": True, - "Numer": None, - "Pozycje": [Position(VAT.VAT_23, 1, 1000, "Nazwa pozycji", "szt", discount=10).get_dict()], - "Kontrahent": Client("Imię Nazwisko", "Ulica", "01-234", "Polska", "Warszawa").get_dict() - } - - -class iFirmaAPI(): - username = None - invoice_key_name = "faktura" - invoice_key_value = None - - user_key_name = "abonent" - user_key_value = None - - @classmethod - def __sanitize_key_value(cls, text): - try: - return binascii.unhexlify(text) - except TypeError: - return text - - @classmethod - def __get_hmac_of_text(cls, key, text): - return hmac.new(key, six.b(text), hashlib.sha1).hexdigest() - - def __init__(self, _username, _invoice_key_value, _user_key_value=None): - self.username = _username - self.invoice_key_value = self.__sanitize_key_value(_invoice_key_value) - self.user_key_value = self.__sanitize_key_value(_user_key_value) - - def __create_invoice_and_return_id(self, data, url): - request_content = json.dumps(data, separators=(',', ':')) - request_hash_text = "{}{}{}{}".format( - url, - self.username, - self.invoice_key_name, - request_content, - ) - headers = { - "Accept": "application/json", - "Content-type": "application/json; charset=UTF-8", - "Authentication": "IAPIS user={}, hmac-sha1={}".format( - self.username, - self.__get_hmac_of_text(self.invoice_key_value, request_hash_text) - ) - } - response = requests.post(url, data=request_content, headers=headers) - response_dict = json.loads(response.content.decode("utf-8"), 'utf-8') - if response_dict["response"].get("Identyfikator"): - invoice_id = response_dict["response"]["Identyfikator"] - return invoice_id - else: - code = response_dict["response"]["Kod"] - info = response_dict["response"]["Informacja"] - if code == 201 and info.find(u"musi być zgodna z miesiącem i rokiem księgowym") > 0: - self.change_billing_month_to_next() - return self.__create_invoice_and_return_id(data, url) - return None - - def generate_invoice(self, data): - url = "https://www.ifirma.pl/iapi/fakturakraj.json" - return self.__create_invoice_and_return_id(data, url) - - def generate_proforma_invoice(self, data): - url = "https://www.ifirma.pl/iapi/fakturaproformakraj.json" - - return self.__create_invoice_and_return_id(data, url) - - def __download_pdf(self, file_path, url): - request_hash_text = "{}{}{}".format( - url, - self.username, - self.invoice_key_name, - ) - headers = { - "Accept": "application/pdf", - "Content-type": "application/pdf; charset=UTF-8", - "Authentication": "IAPIS user={}, hmac-sha1={}".format( - self.username, - self.__get_hmac_of_text(self.invoice_key_value, request_hash_text) - ) - } - resp = requests.get(url, headers=headers) - content = resp.content - print(content) - file = open(file_path, "wb") - file.write(content) - file.close() - - def download_invoice_pdf(self, invoice_id, file_path): - url = "https://www.ifirma.pl/iapi/fakturakraj/{}.pdf".format(invoice_id) - self.__download_pdf(file_path, url) - - def download_proforma_invoice_pdf(self, invoice_id, file_path): - url = "https://www.ifirma.pl/iapi/fakturaproformakraj/{}.pdf".format(invoice_id) - self.__download_pdf(file_path, url) - - def create_invoice_from_proforma(self, proforma_id): - url = "https://www.ifirma.pl/iapi/fakturaproformakraj/add/{}.json".format(proforma_id) - request_hash_text = "{}{}{}".format( - url, - self.username, - self.invoice_key_name, - ) - headers = { - "Accept": "application/json", - "Content-type": "application/json; charset=UTF-8", - "Authentication": "IAPIS user={}, hmac-sha1={}".format( - self.username, - self.__get_hmac_of_text(self.invoice_key_value, request_hash_text) - ) - } - resp = requests.get(url, headers=headers) - response_dict = json.loads(resp.content.decode("utf-8")) - invoice_id = response_dict["response"]["Identyfikator"] - return invoice_id - - def change_billing_month_to_next(self): - data = { - "MiesiacKsiegowy": "NAST", - "PrzeniesDaneZPoprzedniegoRoku": False, - } - url = "https://www.ifirma.pl/iapi/abonent/miesiacksiegowy.json" - request_content = json.dumps(data, separators=(',', ':')) - - request_hash_text = "{}{}{}{}".format( - url, - self.username, - self.user_key_name, - request_content - ) - - headers = { - "Accept": "application/json", - "Content-type": "application/json; charset=UTF-8", - "Authentication": "IAPIS user={}, hmac-sha1={}".format( - self.username, - self.__get_hmac_of_text(self.user_key_value, request_hash_text), - ) - } - resp = requests.put(url, data=request_content, headers=headers) - - def get_invoice_details(self, invoice_id): - url = "https://www.ifirma.pl/iapi/fakturakraj/{}.json".format(invoice_id) - request_hash_text = "{}{}{}".format( - url, - self.username, - self.invoice_key_name, - ) - headers = { - "Accept": "application/json", - "Content-type": "application/json; charset=UTF-8", - "Authentication": "IAPIS user={}, hmac-sha1={}".format( - self.username, - self.__get_hmac_of_text(self.invoice_key_value, request_hash_text), - ) - } - resp = requests.get(url, headers=headers) - return json.loads(resp.content) - - def get_pro_forma_details(self, proforma_id): - url = "https://www.ifirma.pl/iapi/fakturaproformakraj/{}.json".format(proforma_id) - request_hash_text = "{}{}{}".format( - url, - self.username, - self.invoice_key_name, - ) - headers = { - "Accept": "application/json", - "Content-type": "application/json; charset=UTF-8", - "Authentication": "IAPIS user={}, hmac-sha1={}".format( - self.username, - self.__get_hmac_of_text(self.invoice_key_value, request_hash_text), - ) - } - resp = requests.get(url, headers=headers) - return json.loads(resp.content) - - -def main(): - api = iFirmaAPI("$DEMO239002", "B4E1FC1D017C5556", _user_key_value="114D8F7A22BC87F8") - - - def change_billing_month(): - api.change_billing_month_to_next() - - def issue_proforma_and_then_invoice_and_download(): - today = "{}".format(datetime.date.today()) - data = { - "LiczOd": "BRT", - "NumerKontaBankowego": None, - "TypFakturyKrajowej": "SPRZ", - "DataWystawienia": today, - "MiejsceWystawienia": "Warszawa", - "TerminPlatnosci": None, - "SposobZaplaty": "PRZ", - "NazwaSeriiNumeracji": "default", - "NazwaSzablonu": "logo", - "RodzajPodpisuOdbiorcy": "BPO", - "PodpisOdbiorcy": "", - "PodpisWystawcy": "", - "Uwagi": "", - "WidocznyNumerGios": True, - "Numer": None, - "Pozycje": [Position(VAT.VAT_23, 1, 1000, "Nazwa pozycji", "szt", discount=10).get_dict()], - "Kontrahent": Client("Imię Nazwisko", "Ulica", "01-234", "Polska", "Warszawa").get_dict() - } - proforma_invoice_id = api.generate_proforma_invoice(data) - api.download_proforma_invoice_pdf(proforma_invoice_id, "inv.pdf") - invoice_id = api.create_invoice_from_proforma(proforma_invoice_id) - api.download_invoice_pdf(invoice_id, "inv2.pdf") - - # change_billing_month() - issue_proforma_and_then_invoice_and_download() - - # api.create_invoice_from_proforma(346723) - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/python_ifirma/tests.py b/python_ifirma/tests.py index 8d1b683..020b7dc 100644 --- a/python_ifirma/tests.py +++ b/python_ifirma/tests.py @@ -1,50 +1,115 @@ from unittest.case import TestCase -from python_ifirma.ifirma import Client +from python_ifirma.core import Client, iFirmaAPI, Invoice, Position, VAT, Address +from python_ifirma.helpers import Helpers + + +TEST_IFIRMA_USER = "$DEMO254271" +TEST_IFIRMA_INVOICE_KEY = "7B10B300C2C029E2" +TEST_IFIRMA_USER_KEY = "12EFF84EFE80A28A" + + +class TestHelpers(TestCase): + def test_unhex_good_value(self): + self.assertEqual(b'\x11\x11\x11', Helpers.unhex_key_value("111111")) + + def test_unhex_non_hex_value(self): + with self.assertRaises(TypeError): + Helpers.unhex_key_value("qwerty") + + def test_hmac(self): + # example from iFirma API documentation + self.assertEqual("cec153ee6350475f117a307111e2bd7d83034925", Helpers.get_hmac_of_text( + Helpers.unhex_key_value("111111"), '222222')) + + +class TestAddress(TestCase): + def test_create_address(self): + a = Address("City", "00-000") + self.assertEqual("City", a.city) + self.assertEqual("00-000", a.zip_code) + self.assertIsNone(a.street) + self.assertIsNone(a.country) + + def test_create_address_with_street(self): + a = Address("City", "00-000", street="street") + self.assertEqual("City", a.city) + self.assertEqual("00-000", a.zip_code) + self.assertEqual("street", a.street) + self.assertIsNone(a.country) + + def test_create_address_with_country(self): + a = Address("City", "00-000", country="country") + self.assertEqual("City", a.city) + self.assertEqual("00-000", a.zip_code) + self.assertIsNone(a.street) + self.assertEqual("country", a.country) + + def test_create_address_with_street_and_country(self): + a = Address("City", "00-000", street="street", country="country") + self.assertEqual("City", a.city) + self.assertEqual("00-000", a.zip_code) + self.assertEqual("street", a.street) + self.assertEqual("country", a.country) class TestClient(TestCase): def setUp(self): - pass + self.address = Address("City", "00-000", street="street", country="country") + + def test_create_client(self): + c = Client("Name", "1231231212", self.address) + self.assertEqual("Name", c.name) + self.assertEqual("1231231212", c.tax_id) + self.assertEqual(self.address, c.address) + self.assertIsNone(c.email) + self.assertIsNone(c.phone_number) + + def test_create_client_with_email(self): + c = Client("Name", "1231231212", self.address, email="email@server.com") + self.assertEqual("Name", c.name) + self.assertEqual("1231231212", c.tax_id) + self.assertEqual(self.address, c.address) + self.assertEqual("email@server.com", c.email) + self.assertIsNone(c.phone_number) + + def test_create_client_with_phone_number(self): + c = Client("Name", "1231231212", self.address, phone_number="111-222-333") + self.assertEqual("Name", c.name) + self.assertEqual("1231231212", c.tax_id) + self.assertEqual(self.address, c.address) + self.assertIsNone(c.email) + self.assertEqual("111-222-333", c.phone_number) + + def test_create_client_with_email_and_phone_number(self): + c = Client("Name", "1231231212", self.address, email="email@server.com", phone_number="111-222-333") + self.assertEqual("Name", c.name) + self.assertEqual("1231231212", c.tax_id) + self.assertEqual(self.address, c.address) + self.assertEqual("email@server.com", c.email) + self.assertEqual("111-222-333", c.phone_number) + + +class TestCreateInvoice(TestCase): + def setUp(self): + print("SERUP") + self.ifirma_client = iFirmaAPI(TEST_IFIRMA_USER, TEST_IFIRMA_INVOICE_KEY, TEST_IFIRMA_USER_KEY) - def __create_basic_client(self): self.client = Client( - "name", - "street", - "00-001", - "Polska", - "Warszawa" + "Dariusz", + "1231231212", + Address("Warszawa", "03-185"), + email="email@server.com", ) - def __create_power_client(self): - self.client = Client( - "name", - "street", - "00-001", - "Polska", - "Warszawa", - email="email@server.com" - ) + self.position = Position(VAT.VAT_23, 1, 1000, "nazwa", "szt") + def test_generate_invoice(self): + invoice = Invoice(self.client, [self.position]) + self.assertIsNotNone(self.ifirma_client.generate_invoice(invoice)) - def testClient(self): - self.__create_basic_client() - get_dict = self.client.get_dict() - self.assertIn("Nazwa", get_dict) - self.assertIn("Identyfikator", get_dict) - self.assertIn("PrefiksUE", get_dict) - self.assertIn("Ulica", get_dict) - self.assertIn("KodPocztowy", get_dict) - self.assertIn("Kraj", get_dict) - self.assertIn("Miejscowosc", get_dict) - self.assertIn("Email", get_dict) - self.assertIn("Telefon", get_dict) - self.assertIn("OsobaFizyczna", get_dict) + def test_generate_invoice_with_position_with_bad_vat(self): + bad_position = Position(0.22, 1, 1000, "nazwa", "szt") - def testEmailUnSet(self): - self.__create_basic_client() - self.assertIsNone(self.client.get_dict()["Email"]) - - def testEmailSet(self): - self.__create_power_client() - self.assertIsNotNone(self.client.get_dict()["Email"]) \ No newline at end of file + invoice = Invoice(self.client, [bad_position]) + self.assertIsNone(self.ifirma_client.generate_invoice(invoice)) \ No newline at end of file