diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index f463de6..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,62 +0,0 @@
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-env/
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-*.egg-info/
-.installed.cfg
-*.egg
-
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*,cover
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-target/
-
-
-.idea
-tests/fixtures/dist/*.pdf
-.DS_Store
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 14b80ab..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-language: python
-
-python:
- - "2.6"
- - "2.7"
- - "3.3"
- - "3.4"
-
-env:
- - REPORTLAB=2.6
- - REPORTLAB=2.7
- - REPORTLAB=3.0
- - REPORTLAB=3.1.44
- - REPORTLAB=3.2
-
-matrix:
- exclude:
- - env: REPORTLAB=2.6
- python: "3.3"
- - env: REPORTLAB=2.6
- python: "3.4"
- - env: REPORTLAB=2.7
- python: "3.3"
- - env: REPORTLAB=2.7
- python: "3.4"
- - env: REPORTLAB=3.0
- python: "2.6"
- - env: REPORTLAB=3.1.44
- python: "2.6"
- - env: REPORTLAB=3.2
- python: "2.6"
-
-install:
- - pip install reportlab==$REPORTLAB
- - python setup.py install
-
-script: python setup.py test
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 7c5ddfd..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2015 zhangshine, (c) 2021 Kumi Systems e.U.
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
\ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 9c8317c..0000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,2 +0,0 @@
-include LICENSE
-include README.rst
\ No newline at end of file
diff --git a/README.rst b/README.rst
deleted file mode 100644
index 446b26a..0000000
--- a/README.rst
+++ /dev/null
@@ -1,69 +0,0 @@
-=========
-PyInvoice
-=========
-
-Invoice/Receipt Generator.
-
-Dependency
-----------
-* Reportlab
-* Only tested with Python >3.6 – may work with any Python >2.6 with a corresponding Reportlab version, but no promises. Use Python3!
-
-Install
--------
-
-.. code-block:: bash
-
- pip install git+https://kumig.it/kumisystems/PyInvoice
-
-Usage
------
-
-.. code-block:: python
-
- from datetime import datetime, date
- from pyinvoice.models import InvoiceInfo, ServiceProviderInfo, ClientInfo, Item, Transaction
- from pyinvoice.templates import SimpleInvoice
-
- doc = SimpleInvoice('invoice.pdf')
-
- # Paid stamp, optional
- doc.is_paid = True
-
- doc.invoice_info = InvoiceInfo(1023, datetime.now(), datetime.now()) # Invoice info, optional
-
- # Service Provider Info, optional
- doc.service_provider_info = ServiceProviderInfo(
- name='PyInvoice',
- street='My Street',
- city='My City',
- state='My State',
- country='My Country',
- post_code='222222',
- vat_tax_number='Vat/Tax number'
- )
-
- # Client info, optional
- doc.client_info = ClientInfo(email='client@example.com')
-
- # Add Item
- doc.add_item(Item('Item', 'Item desc', 1, '1.1'))
- doc.add_item(Item('Item', 'Item desc', 2, '2.2'))
- doc.add_item(Item('Item', 'Item desc', 3, '3.3'))
-
- # Tax rate, optional
- doc.set_item_tax_rate(20) # 20%
-
- # Transactions detail, optional
- doc.add_transaction(Transaction('Paypal', 111, datetime.now(), 1))
- doc.add_transaction(Transaction('Stripe', 222, date.today(), 2))
-
- # Optional
- doc.set_bottom_tip("Email: example@example.com
Don't hesitate to contact us for any questions.")
-
- doc.finish()
-
-
-License
--------
-MIT
diff --git a/dist/invoice.png b/dist/invoice.png
new file mode 100644
index 0000000..c1e904d
Binary files /dev/null and b/dist/invoice.png differ
diff --git a/dist/simple.pdf b/dist/simple.pdf
new file mode 100644
index 0000000..34aad3d
--- /dev/null
+++ b/dist/simple.pdf
@@ -0,0 +1,53 @@
+%PDF-1.4
+%“Œ‹ž ReportLab Generated PDF document http://www.reportlab.com
+1 0 obj
+<< /F1 2 0 R /F2 3 0 R >>
+endobj
+2 0 obj
+<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >>
+endobj
+3 0 obj
+<< /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font >>
+endobj
+4 0 obj
+<< /Contents 8 0 R /MediaBox [ 0 0 612 792 ] /Parent 7 0 R /Resources << /Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> /Rotate 0 /Trans << >>
+ /Type /Page >>
+endobj
+5 0 obj
+<< /Outlines 9 0 R /PageMode /UseNone /Pages 7 0 R /Type /Catalog >>
+endobj
+6 0 obj
+<< /Author (CiCiApp.com) /CreationDate (D:20150614215600-08'00') /Creator (PyInvoice \(https://ciciapp.com/pyinvoice\)) /Keywords () /Producer (ReportLab PDF Library - www.reportlab.com) /Subject (Invoice)
+ /Title (Invoice) >>
+endobj
+7 0 obj
+<< /Count 1 /Kids [ 4 0 R ] /Type /Pages >>
+endobj
+8 0 obj
+<< /Filter [ /ASCII85Decode /FlateDecode ] /Length 1627 >>
+stream
+Gau`T9lo&I&A;jKs'[RO3ggS=D\>&f[?F9>D9&X+SoS6b\n/mUCl*2lJ):D1OH`.pML%fK$5r14p?aK:]-%35Ip)a=@Kl8e*%p,(?l\=h>`s[s]N0WZZshY883pEbQZ"mgeCqmP[7V9%%'GJb0&bf`2&EtU[PE*d7.-&!(E2!GMO0+t4n.F@a4[_Ahn?es2)Y2nCFdUUKaU.cIfiKh)6LijsW0Nf`9P14*];W?.hq,(<::O>O'djb\`^jQBk]OUFCl4"J=I5-fS8"^#1Z>OCW4"FZ8Ve"+:b:;iC1_fPQ8JBg.spWXQ&;r*s5JHlY7A..VQi\u#9u]ib$@j<]o$+!_;4/k:/h6eFALA+G/_-PT6!G?H[(Q%AdQ@g23op/_au>649=V.j,;HTB+Xn!4QAHAntYqQ5`lo/HcG+8&iAqm?P(7?UQQ__):($\IINg7-kaFCcZks'-FOuYKX"?nDg.hSFd:cQ(SsMIbp>kKrQXk1Od])R6TOsqpPYY-]bk)2cOki-1!2fr%$+f47'u%B!JM)9A>Eu/\d6gnKPqmhC'3lTJ@h)q5Ag\5U7("NerdeP6itu-7i]ES*]sE4e]3bX?R.dAQsrYqrT&\[^e3H1haB+mJ2:gB/L-]P3*u(hJS7,`orGYhe$GU'M%%gL5f3R'm)R7SXOpC3ntcjqeVH4k:V28B0.RgG=b6o]gUJArM'+`"a/9c)!sKO_U[m\!Gg](ruIu^/e":FI5G3eg-CC3LQ5Ca`%#]Y3Z'.SCFYLhm^(&XP>$[^EYC5mGVZGLYj].Mg?2isendstream
+endobj
+9 0 obj
+<< /Count 0 /Type /Outlines >>
+endobj
+xref
+0 10
+0000000000 65535 f
+0000000075 00000 n
+0000000119 00000 n
+0000000229 00000 n
+0000000344 00000 n
+0000000541 00000 n
+0000000628 00000 n
+0000000876 00000 n
+0000000938 00000 n
+0000002661 00000 n
+trailer
+<< /ID
+ % ReportLab generated PDF document -- digest (http://www.reportlab.com)
+ [(q\364!\275!\347\0005\320<\200\276\267\317\011E) (q\364!\275!\347\0005\320<\200\276\267\317\011E)]
+ /Info 6 0 R /Root 5 0 R /Size 10 >>
+startxref
+2710
+%%EOF
diff --git a/pyinvoice/__init__.py b/pyinvoice/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/pyinvoice/components.py b/pyinvoice/components.py
deleted file mode 100644
index 0ddff3a..0000000
--- a/pyinvoice/components.py
+++ /dev/null
@@ -1,58 +0,0 @@
-from reportlab.lib.units import inch
-from reportlab.platypus import Paragraph, Table, TableStyle
-from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
-from reportlab.lib import colors
-
-
-class CodeSnippet(Paragraph):
- style = ParagraphStyle(
- name='CodeSnippet',
- parent=getSampleStyleSheet()['Code'],
- backColor=colors.lightgrey, leftIndent=0,
- borderPadding=(5, 5, 5, 5)
- )
-
- def __init__(self, code):
- Paragraph.__init__(self, code, self.style)
-
-
-class SimpleTable(Table):
- def __init__(self, data, horizontal_align=None):
- Table.__init__(self, data, hAlign=horizontal_align)
-
-
-class TableWithHeader(Table):
- def __init__(self, data, horizontal_align=None, style=None):
- Table.__init__(self, data, hAlign=horizontal_align)
-
- default_style = [
- ('INNERGRID', (0, 0), (-1, -1), .25, colors.black),
- ('BOX', (0, 0), (-1, -1), .25, colors.black),
- ('BACKGROUND', (0, 0), (-1, -len(data)), colors.lightgrey),
- ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
- ('VALIGN', (0, 0), (-1, -1), 'MIDDLE')
- ]
-
- if style and isinstance(style, list):
- default_style.extend(style)
-
- self.setStyle(TableStyle(default_style))
-
-
-class PaidStamp(object):
- def __init__(self, x, y):
- self.x = x
- self.y = y
-
- def __call__(self, canvas, doc):
- # "PAID"
- canvas.saveState()
- canvas.setFontSize(50)
- canvas.setFillColor(colors.red)
- canvas.setStrokeColor(colors.red)
- canvas.rotate(45)
- canvas.drawString(self.x, self.y, 'PAID')
- canvas.setLineWidth(4)
- canvas.setLineJoin(1) # Round join
- canvas.rect(self.x - .25 * inch, self.y - .25 * inch, width=2*inch, height=inch)
- canvas.restoreState()
\ No newline at end of file
diff --git a/pyinvoice/models.py b/pyinvoice/models.py
deleted file mode 100644
index 7a4d171..0000000
--- a/pyinvoice/models.py
+++ /dev/null
@@ -1,148 +0,0 @@
-from __future__ import unicode_literals
-from decimal import Decimal
-
-
-class PDFInfo(object):
- """
- PDF Properties
- """
- def __init__(self, title=None, author=None, subject=None):
- """
- PDF Properties
- :param title: PDF title
- :type title: str or unicode
- :param author: PDF author
- :type author: str or unicode
- :param subject: PDF subject
- :type subject: str or unicode
- """
- self.title = title
- self.author = author
- self.subject = subject
- self.creator = 'PyInvoice (https://ciciapp.com/pyinvoice)'
-
-
-class InvoiceInfo(object):
- """
- Invoice information
- """
- def __init__(self, invoice_id=None, invoice_datetime=None, due_datetime=None):
- """
- Invoice info
- :param invoice_id: Invoice id
- :type invoice_id: int or str or unicode or None
- :param invoice_datetime: Invoice create datetime
- :type invoice_datetime: str or unicode or datetime or date
- :param due_datetime: Invoice due datetime
- :type due_datetime: str or unicode or datetime or date
- """
- self.invoice_id = invoice_id
- self.invoice_datetime = invoice_datetime
- self.due_datetime = due_datetime
-
-
-class AddressInfo(object):
- def __init__(self, name=None, street=None, city=None, state=None, country=None, post_code=None):
- """
- :type name: str or unicode or None
- :type street: str or unicode or None
- :type city: str or unicode or None
- :type state: str or unicode or None
- :type country: str or unicode or None
- :type post_code: str or unicode or int or None
- """
- self.name = name
- self.street = street
- self.city = city
- self.state = state
- self.country = country
- self.post_code = post_code
-
-
-class ServiceProviderInfo(AddressInfo):
- """
- Service provider/Merchant information
- """
- def __init__(self, name=None, street=None, city=None, state=None, country=None, post_code=None,
- vat_tax_number=None):
- """
- :type name: str or unicode or None
- :type street: str or unicode or None
- :type city: str or unicode or None
- :type state: str or unicode or None
- :type country: str or unicode or None
- :type post_code: str or unicode or None
- :type vat_tax_number: str or unicode or int or None
- """
- super(ServiceProviderInfo, self).__init__(name, street, city, state, country, post_code)
- self.vat_tax_number = vat_tax_number
-
-
-class ClientInfo(AddressInfo):
- """
- Client/Custom information
- """
- def __init__(self, name=None, street=None, city=None, state=None, country=None, post_code=None,
- email=None, client_id=None):
- """
- :type name: str or unicode or None
- :type street: str or unicode or None
- :type city: str or unicode or None
- :type state: str or unicode or None
- :type country: str or unicode or None
- :type post_code: str or unicode or None
- :type email: str or unicode or None
- :type client_id: str or unicode or int or None
- """
- super(ClientInfo, self).__init__(name, street, city, state, country, post_code)
- self.email = email
- self.client_id = client_id
-
-
-class Item(object):
- """
- Product/Item information
- """
- def __init__(self, name, description, units, unit_price):
- """
- Item modal init
- :param name: Item name
- :type name: str or unicode or int
- :param description: Item detail
- :type description: str or unicode or int
- :param units: Amount
- :type units: int or str or unicode
- :param unit_price: Unit price
- :type unit_price: Decimal or str or unicode or int or float
- :return:
- """
- self.name = name
- self.description = description
- self.units = units
- self.unit_price = unit_price
-
- @property
- def amount(self):
- return int(self.units) * Decimal(str(self.unit_price))
-
-
-class Transaction(object):
- """
- Transaction information
- """
- def __init__(self, gateway, transaction_id, transaction_datetime, amount):
- """
- :param gateway: Payment gateway like Paypal, Stripe etc.
- :type gateway: str or unicode
- :param transaction_id:
- :type transaction_id: int or str or unicode
- :param transaction_datetime:
- :type transaction_datetime: date or datetime or str or unicode
- :param amount: $$
- :type amount: int or float or str or unicode
- :return:
- """
- self.gateway = gateway
- self.transaction_id = transaction_id
- self.transaction_datetime = transaction_datetime
- self.amount = amount
\ No newline at end of file
diff --git a/pyinvoice/templates.py b/pyinvoice/templates.py
deleted file mode 100644
index fd4b241..0000000
--- a/pyinvoice/templates.py
+++ /dev/null
@@ -1,329 +0,0 @@
-from __future__ import unicode_literals
-from datetime import datetime, date
-from decimal import Decimal
-
-from reportlab.lib import colors
-from reportlab.lib.enums import TA_CENTER, TA_RIGHT
-from reportlab.lib.pagesizes import letter
-from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
-from reportlab.lib.units import inch
-from reportlab.platypus import SimpleDocTemplate, Paragraph, Table, Spacer, Image
-
-from pyinvoice.components import SimpleTable, TableWithHeader, PaidStamp
-from pyinvoice.models import PDFInfo, Item, Transaction, InvoiceInfo, ServiceProviderInfo, ClientInfo
-
-
-class SimpleInvoice(SimpleDocTemplate):
- default_pdf_info = PDFInfo(title='Invoice', author='CiCiApp.com', subject='Invoice')
- precision = None
-
- def __init__(self, invoice_path, pdf_info=None, precision='0.01'):
- if not pdf_info:
- pdf_info = self.default_pdf_info
-
- SimpleDocTemplate.__init__(
- self,
- invoice_path,
- pagesize=letter,
- rightMargin=inch,
- leftMargin=inch,
- topMargin=inch,
- bottomMargin=inch,
- **pdf_info.__dict__
- )
-
- self.precision = precision
-
- self._defined_styles = getSampleStyleSheet()
- self._defined_styles.add(
- ParagraphStyle('RightHeading1', parent=self._defined_styles.get('Heading1'), alignment=TA_RIGHT)
- )
- self._defined_styles.add(
- ParagraphStyle('TableParagraph', parent=self._defined_styles.get('Normal'), alignment=TA_CENTER)
- )
-
- self.invoice_info = None
- self.service_provider_info = None
- self.client_info = None
- self.is_paid = False
- self._items = []
- self._item_tax_rate = None
- self._transactions = []
- self._story = []
- self._bottom_tip = None
- self._bottom_tip_align = None
- self._logo = None
- self._build_kwargs = {}
-
- @property
- def items(self):
- return self._items[:]
-
- def add_item(self, item):
- if isinstance(item, Item):
- self._items.append(item)
-
- def set_item_tax_rate(self, rate):
- self._item_tax_rate = rate
-
- @property
- def transactions(self):
- return self._transactions[:]
-
- def add_transaction(self, t):
- if isinstance(t, Transaction):
- self._transactions.append(t)
-
- def set_bottom_tip(self, text, align=TA_CENTER):
- self._bottom_tip = text
- self._bottom_tip_align = align
-
- @staticmethod
- def _format_value(value):
- if isinstance(value, datetime):
- value = value.strftime('%Y-%m-%d %H:%M')
- elif isinstance(value, date):
- value = value.strftime('%Y-%m-%d')
- return value
-
- def _attribute_to_table_data(self, instance, attribute_verbose_name_list):
- data = []
-
- for property_name, verbose_name in attribute_verbose_name_list:
- attr = getattr(instance, property_name)
- if attr:
- attr = self._format_value(attr)
- data.append(['{0}:'.format(verbose_name), attr])
-
- return data
-
- def _invoice_info_data(self):
- if isinstance(self.invoice_info, InvoiceInfo):
- props = [('invoice_id', 'Invoice id'), ('invoice_datetime', 'Invoice date'),
- ('due_datetime', 'Invoice due date')]
-
- return self._attribute_to_table_data(self.invoice_info, props)
-
- return []
-
- def _build_invoice_info(self):
- invoice_info_data = self._invoice_info_data()
- if invoice_info_data:
- self._story.append(Paragraph('Invoice', self._defined_styles.get('RightHeading1')))
- self._story.append(SimpleTable(invoice_info_data, horizontal_align='RIGHT'))
-
- def _service_provider_data(self):
- if isinstance(self.service_provider_info, ServiceProviderInfo):
- props = [('name', 'Name'), ('street', 'Street'), ('city', 'City'), ('state', 'State'),
- ('country', 'Country'), ('post_code', 'Post code'), ('vat_tax_number', 'Vat/Tax number')]
-
- return self._attribute_to_table_data(self.service_provider_info, props)
-
- return []
-
- def _build_service_provider_info(self):
- # Merchant
- service_provider_info_data = self._service_provider_data()
- if service_provider_info_data:
- self._story.append(Paragraph('Service Provider', self._defined_styles.get('RightHeading1')))
- self._story.append(SimpleTable(service_provider_info_data, horizontal_align='RIGHT'))
-
- def _client_info_data(self):
- if not isinstance(self.client_info, ClientInfo):
- return []
-
- props = [('name', 'Name'), ('street', 'Street'), ('city', 'City'), ('state', 'State'),
- ('country', 'Country'), ('post_code', 'Post code'), ('email', 'Email'), ('client_id', 'Client id')]
- return self._attribute_to_table_data(self.client_info, props)
-
- def _build_client_info(self):
- # ClientInfo
- client_info_data = self._client_info_data()
- if client_info_data:
- self._story.append(Paragraph('Client', self._defined_styles.get('Heading1')))
- self._story.append(SimpleTable(client_info_data, horizontal_align='LEFT'))
-
- def _build_service_provider_and_client_info(self):
- if isinstance(self.service_provider_info, ServiceProviderInfo) and isinstance(self.client_info, ClientInfo):
- # Merge Table
- table_data = [
- [
- Paragraph('Service Provider', self._defined_styles.get('Heading1')), '',
- '',
- Paragraph('Client', self._defined_styles.get('Heading1')), ''
- ]
- ]
- table_style = [
- ('SPAN', (0, 0), (1, 0)),
- ('SPAN', (3, 0), (4, 0)),
- ('LINEBELOW', (0, 0), (1, 0), 1, colors.gray),
- ('LINEBELOW', (3, 0), (4, 0), 1, colors.gray),
- ('LEFTPADDING', (0, 0), (-1, -1), 0),
- ]
- client_info_data = self._client_info_data()
- service_provider_data = self._service_provider_data()
- diff = abs(len(client_info_data) - len(service_provider_data))
- if diff > 0:
- if len(client_info_data) < len(service_provider_data):
- client_info_data.extend([["", ""]]*diff)
- else:
- service_provider_data.extend([["", ""]*diff])
- for d in zip(service_provider_data, client_info_data):
- d[0].append('')
- d[0].extend(d[1])
- table_data.append(d[0])
- self._story.append(
- Table(table_data, style=table_style)
- )
- else:
- self._build_service_provider_info()
- self._build_client_info()
-
- def _item_raw_data_and_subtotal(self):
- item_data = []
- item_subtotal = 0
-
- for item in self._items:
- if not isinstance(item, Item):
- continue
-
- item_data.append(
- (
- item.name,
- Paragraph(item.description, self._defined_styles.get('TableParagraph')),
- item.units,
- '{0:.2f}'.format(float(item.unit_price)),
- '{0:.2f}'.format(item.amount)
- )
- )
- item_subtotal += item.amount
-
- return item_data, item_subtotal
-
- def _item_data_and_style(self):
- # Items
- item_data, item_subtotal = self._item_raw_data_and_subtotal()
- style = []
-
- if not item_data:
- return item_data, style
-
- self._story.append(
- Paragraph('Detail', self._defined_styles.get('Heading1'))
- )
-
- item_data_title = ('Name', 'Description', 'Units', 'Unit Price', 'Amount')
- item_data.insert(0, item_data_title) # Insert title
-
- # Summary field
- sum_start_y_index = len(item_data)
- sum_end_x_index = -1 - 1
- sum_start_x_index = len(item_data_title) - abs(sum_end_x_index)
-
- # ##### Subtotal #####
- rounditem_subtotal = self.getroundeddecimal(item_subtotal, self.precision)
- item_data.append(
- ('Subtotal', '', '', '', '{0:.2f}'.format(rounditem_subtotal))
- )
-
- style.append(('SPAN', (0, sum_start_y_index), (sum_start_x_index, sum_start_y_index)))
- style.append(('ALIGN', (0, sum_start_y_index), (sum_end_x_index, -1), 'RIGHT'))
-
- # Tax total
- if self._item_tax_rate is not None:
- tax_total = item_subtotal * (Decimal(str(self._item_tax_rate)) / Decimal('100'))
- roundtax_total = self.getroundeddecimal(tax_total, self.precision)
- item_data.append(
- ('Vat/Tax ({0}%)'.format(self._item_tax_rate), '', '', '', '{0:.2f}'.format(roundtax_total))
- )
- sum_start_y_index += 1
- style.append(('SPAN', (0, sum_start_y_index), (sum_start_x_index, sum_start_y_index)))
- style.append(('ALIGN', (0, sum_start_y_index), (sum_end_x_index, -1), 'RIGHT'))
- else:
- tax_total = None
-
- # Total
- total = item_subtotal + (tax_total if tax_total else Decimal('0'))
- roundtotal = self.getroundeddecimal(total, self.precision)
- item_data.append(('Total', '', '', '', '{0:.2f}'.format(roundtotal)))
- sum_start_y_index += 1
- style.append(('SPAN', (0, sum_start_y_index), (sum_start_x_index, sum_start_y_index)))
- style.append(('ALIGN', (0, sum_start_y_index), (sum_end_x_index, -1), 'RIGHT'))
-
- return item_data, style
-
- def getroundeddecimal(self, nrtoround, precision):
- d = Decimal(nrtoround)
- aftercomma = Decimal(precision) # or anything that has the exponent depth you want
- rvalue = Decimal(d.quantize(aftercomma, rounding='ROUND_HALF_UP'))
- return rvalue
-
- def _build_items(self):
- item_data, style = self._item_data_and_style()
- if item_data:
- self._story.append(TableWithHeader(item_data, horizontal_align='LEFT', style=style))
-
- def _transactions_data(self):
- transaction_table_data = [
- (
- t.transaction_id,
- Paragraph(t.gateway, self._defined_styles.get('TableParagraph')),
- self._format_value(t.transaction_datetime),
- '{0:.2f}'.format(t.amount),
- ) for t in self._transactions if isinstance(t, Transaction)
- ]
-
- if transaction_table_data:
- transaction_table_data.insert(0, ('Transaction id', 'Gateway', 'Transaction date', 'Amount'))
-
- return transaction_table_data
-
- def _build_transactions(self):
- # Transaction
- transaction_table_data = self._transactions_data()
-
- if transaction_table_data:
- self._story.append(Paragraph('Transaction', self._defined_styles.get('Heading1')))
- self._story.append(TableWithHeader(transaction_table_data, horizontal_align='LEFT'))
-
- def _build_bottom_tip(self):
- if self._bottom_tip:
- self._story.append(Spacer(5, 5))
- self._story.append(
- Paragraph(
- self._bottom_tip,
- ParagraphStyle(
- 'BottomTip',
- parent=self._defined_styles.get('Normal'),
- alignment=self._bottom_tip_align
- )
- )
- )
-
- def logo(self, logo):
- if isinstance(logo, Image):
- self._logo = logo
- else:
- self._logo = Image(logo)
-
- def _build_logo(self):
- if self._logo:
- self._logo.hAlign = "LEFT"
- self._logo.vAlign = "TOP"
-
- self._story.append(self._logo)
-
- def _build_paid_stamp(self):
- if self.is_paid:
- self._build_kwargs['onFirstPage'] = PaidStamp(3 * inch, -2 * inch)
-
- def finish(self):
- self._build_logo()
- self._build_invoice_info()
- self._build_service_provider_and_client_info()
- self._build_items()
- self._build_transactions()
- self._build_bottom_tip()
- self._build_paid_stamp()
-
- self.build(self._story, **self._build_kwargs)
\ No newline at end of file
diff --git a/renovate.json b/renovate.json
deleted file mode 100644
index 5db72dd..0000000
--- a/renovate.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "$schema": "https://docs.renovatebot.com/renovate-schema.json",
- "extends": [
- "config:recommended"
- ]
-}
diff --git a/requirements-dev.txt b/requirements-dev.txt
deleted file mode 100644
index 5bf8348..0000000
--- a/requirements-dev.txt
+++ /dev/null
@@ -1 +0,0 @@
-reportlab
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 00ed041..0000000
--- a/setup.py
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/usr/bin/env python
-import os
-
-try:
- from setuptools import setup
-except ImportError:
- from distutils.core import setup
-
-
-with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme:
- README = readme.read()
-
-# allow setup.py to be run from any path
-os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
-
-
-setup(
- name='PyInvoice',
- version='0.1.8',
- packages=['pyinvoice', 'tests'],
- include_package_data=True,
- license='MIT License',
- description='Invoice/Receipt generator',
- long_description=README,
- url='https://kumig.it/kumisystems/PyInvoice',
- author='Kumi Systems e.U.',
- author_email='support@kumi.systems',
- install_requires=['reportlab'],
- test_suite='tests',
- classifiers=[
- 'Intended Audience :: Developers',
- 'License :: OSI Approved :: MIT License',
- 'Operating System :: OS Independent',
- 'Programming Language :: Python',
- 'Programming Language :: Python :: 2',
- 'Programming Language :: Python :: 2.6',
- 'Programming Language :: Python :: 2.7',
- 'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.2',
- 'Programming Language :: Python :: 3.3',
- 'Programming Language :: Python :: Implementation :: CPython',
- 'Topic :: Software Development :: Libraries :: Python Modules',
- ],
-)
diff --git a/tests/__init__.py b/tests/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/fixtures/dist/empty.txt b/tests/fixtures/dist/empty.txt
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/test_templates.py b/tests/test_templates.py
deleted file mode 100644
index 2e69eb9..0000000
--- a/tests/test_templates.py
+++ /dev/null
@@ -1,287 +0,0 @@
-from decimal import Decimal
-import os
-import unittest
-from datetime import datetime, date
-
-from pyinvoice.models import InvoiceInfo, ServiceProviderInfo, ClientInfo, Item, Transaction
-from pyinvoice.templates import SimpleInvoice
-
-
-class TestSimpleInvoice(unittest.TestCase):
- def setUp(self):
- self.file_base_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'fixtures/dist')
-
- def test_simple(self):
- invoice_path = os.path.join(self.file_base_dir, 'simple.pdf')
-
- if os.path.exists(invoice_path):
- os.remove(invoice_path)
-
- doc = SimpleInvoice(invoice_path)
-
- doc.is_paid = True
-
- doc.invoice_info = InvoiceInfo(1023, datetime.now(), datetime.now())
-
- doc.service_provider_info = ServiceProviderInfo(
- name='PyInvoice',
- street='My Street',
- city='My City',
- state='My State',
- country='My Country',
- post_code='222222',
- vat_tax_number='Vat/Tax number'
- )
-
- doc.client_info = ClientInfo(email='client@example.com')
-
- doc.add_item(Item('Item', 'Item desc', 1, '1.1'))
- doc.add_item(Item('Item', 'Item desc', 2, '2.2'))
- doc.add_item(Item('Item', 'Item desc', 3, '3.3'))
-
- items = doc.items
- self.assertEqual(len(items), 3)
-
- doc.set_item_tax_rate(20) # 20%
-
- doc.add_transaction(Transaction('Paypal', 111, datetime.now(), 1))
- doc.add_transaction(Transaction('Stripe', 222, date.today(), 2))
-
- transactions = doc.transactions
- self.assertEqual(len(transactions), 2)
-
- doc.set_bottom_tip("Email: example@example.com
Don't hesitate to contact us for any questions.")
-
- doc.finish()
-
- self.assertTrue(os.path.exists(invoice_path))
-
- def test_only_items(self):
- invoice_path = os.path.join(self.file_base_dir, 'only_items.pdf')
- if os.path.exists(invoice_path):
- os.remove(invoice_path)
-
- invoice = SimpleInvoice(invoice_path)
-
- # Before add items
- item_data, item_subtotal = invoice._item_raw_data_and_subtotal()
- self.assertEqual(len(item_data), 0)
- self.assertEqual(item_subtotal, Decimal('0'))
- item_data, style = invoice._item_data_and_style()
- self.assertEqual(len(item_data), 0)
- self.assertEqual(style, [])
-
- # Add items
- invoice.add_item(Item('Item1', 'Item desc', 1, 1.1))
- invoice.add_item(Item('Item2', 'Item desc', 2, u'2.2'))
- invoice.add_item(Item(u'Item3', 'Item desc', 3, '3.3'))
-
- # After add items
- items = invoice.items
- self.assertEqual(len(items), 3)
- self.assertEqual(items[0].name, 'Item1')
- self.assertEqual(items[0].amount, Decimal('1.1'))
- self.assertEqual(items[1].amount, Decimal('4.4'))
- self.assertEqual(items[2].name, u'Item3')
- self.assertEqual(items[2].amount, Decimal('9.9'))
-
- item_data, item_subtotal = invoice._item_raw_data_and_subtotal()
- self.assertEqual(item_subtotal, Decimal('15.4'))
- self.assertEqual(len(item_data), 3)
-
- item_data, style = invoice._item_data_and_style()
- self.assertEqual(len(item_data), 6) # header, subtotal, total
- self.assertEqual(item_data[-2][-1], Decimal('15.4')) # subtotal
- self.assertEqual(item_data[-1][-1], Decimal('15.4')) # total
-
- # test style
- # ## Subtotal
- self.assertEqual(style[-4], ('SPAN', (0, 4), (3, 4)))
- self.assertEqual(style[-3], ('ALIGN', (0, 4), (-2, -1), 'RIGHT'))
- # ## Total
- self.assertEqual(style[-2], ('SPAN', (0, 5), (3, 5)))
- self.assertEqual(style[-1], ('ALIGN', (0, 5), (-2, -1), 'RIGHT'))
-
- invoice.finish()
-
- self.assertTrue(os.path.exists(invoice_path))
-
- def test_only_items_with_tax_rate(self):
- invoice_path = os.path.join(self.file_base_dir, 'only_items_with_tax.pdf')
- if os.path.exists(invoice_path):
- os.remove(invoice_path)
-
- invoice = SimpleInvoice(invoice_path)
-
- # Before add items
- item_data, item_subtotal = invoice._item_raw_data_and_subtotal()
- self.assertEqual(len(item_data), 0)
- self.assertEqual(item_subtotal, Decimal('0'))
- item_data, style = invoice._item_data_and_style()
- self.assertEqual(len(item_data), 0)
- self.assertEqual(style, [])
-
- # Add items
- invoice.add_item(Item('Item1', 'Item desc', 1, 1.1))
- invoice.add_item(Item('Item2', 'Item desc', 2, u'2.2'))
- invoice.add_item(Item(u'Item3', 'Item desc', 3, '3.3'))
- # set tax rate
- invoice.set_item_tax_rate(19)
-
- # After add items
- items = invoice.items
- self.assertEqual(len(items), 3)
- self.assertEqual(items[0].name, 'Item1')
- self.assertEqual(items[0].amount, Decimal('1.1'))
- self.assertEqual(items[1].amount, Decimal('4.4'))
- self.assertEqual(items[2].name, u'Item3')
- self.assertEqual(items[2].amount, Decimal('9.9'))
-
- item_data, item_subtotal = invoice._item_raw_data_and_subtotal()
- self.assertEqual(item_subtotal, Decimal('15.4'))
- self.assertEqual(len(item_data), 3)
-
- item_data, style = invoice._item_data_and_style()
- self.assertEqual(len(item_data), 7) # header, subtotal, tax, total
- self.assertEqual(item_data[-3][-1], Decimal('15.4')) # subtotal
- self.assertEqual(item_data[-2][-1], Decimal('2.93')) # tax
- self.assertEqual(item_data[-1][-1], Decimal('18.33')) # total
-
- invoice.finish()
-
- self.assertTrue(os.path.exists(invoice_path))
-
- def test_invoice_info(self):
- invoice_path = os.path.join(self.file_base_dir, 'invoice_info.pdf')
- if os.path.exists(invoice_path):
- os.remove(invoice_path)
-
- invoice = SimpleInvoice(invoice_path)
-
- # Before add invoice info
- invoice_info_data = invoice._invoice_info_data()
- self.assertEqual(invoice_info_data, [])
-
- invoice.invoice_info = InvoiceInfo(12)
-
- # After add invoice info
- invoice_info_data = invoice._invoice_info_data()
- self.assertEqual(len(invoice_info_data), 1)
- self.assertEqual(invoice_info_data[0][0], 'Invoice id:')
- self.assertEqual(invoice_info_data[0][1], 12)
-
- invoice.invoice_info = InvoiceInfo(12, invoice_datetime=datetime(2015, 6, 1))
- invoice_info_data = invoice._invoice_info_data()
- self.assertEqual(len(invoice_info_data), 2)
- self.assertEqual(invoice_info_data[1][0], 'Invoice date:')
- self.assertEqual(invoice_info_data[1][1], '2015-06-01 00:00')
-
- invoice.finish()
-
- self.assertTrue(os.path.exists(invoice_path))
-
- def test_service_provider_info(self):
- invoice_path = os.path.join(self.file_base_dir, 'service_provider_info.pdf')
- if os.path.exists(invoice_path):
- os.remove(invoice_path)
-
- invoice = SimpleInvoice(invoice_path)
-
- # Before add service provider info
- info_data = invoice._service_provider_data()
- self.assertEqual(info_data, [])
-
- # Empty info
- invoice.service_provider_info = ServiceProviderInfo()
- info_data = invoice._service_provider_data()
- self.assertEqual(info_data, [])
-
- invoice.service_provider_info = ServiceProviderInfo(
- name='CiCiApp',
- street='Street xxx',
- city='City ccc',
- state='State sss',
- country='Country rrr',
- post_code='Post code ppp',
- vat_tax_number=666
- )
-
- # After add service provider info
- info_data = invoice._service_provider_data()
- self.assertEqual(len(info_data), 7)
- self.assertEqual(info_data[0][0], 'Name:')
- self.assertEqual(info_data[0][1], 'CiCiApp')
- self.assertEqual(info_data[4][0], 'Country:')
- self.assertEqual(info_data[4][1], 'Country rrr')
- self.assertEqual(info_data[6][0], 'Vat/Tax number:')
- self.assertEqual(info_data[6][1], 666)
-
- invoice.finish()
-
- self.assertTrue(os.path.exists(invoice_path))
-
- def test_client_info(self):
- invoice_path = os.path.join(self.file_base_dir, 'client_info.pdf')
- if os.path.exists(invoice_path):
- os.remove(invoice_path)
-
- invoice = SimpleInvoice(invoice_path)
-
- # Before add client info
- info_data = invoice._client_info_data()
- self.assertEqual(info_data, [])
-
- # Empty info
- invoice.client_info = ClientInfo()
- info_data = invoice._client_info_data()
- self.assertEqual(info_data, [])
-
- invoice.client_info = ClientInfo(
- name='Client ccc',
- street='Street sss',
- city='City ccc',
- state='State sss',
- country='Country ccc',
- post_code='Post code ppp',
- email='Email@example.com',
- client_id=3214
- )
-
- # After add client info
- info_data = invoice._client_info_data()
- self.assertEqual(len(info_data), 8)
- self.assertEqual(info_data[0][0], 'Name:')
- self.assertEqual(info_data[0][1], 'Client ccc')
- self.assertEqual(info_data[6][0], 'Email:')
- self.assertEqual(info_data[6][1], 'Email@example.com')
- self.assertEqual(info_data[7][0], 'Client id:')
- self.assertEqual(info_data[7][1], 3214)
-
- invoice.finish()
-
- self.assertTrue(os.path.exists(invoice_path))
-
- def test_transaction(self):
- invoice_path = os.path.join(self.file_base_dir, 'transaction.pdf')
- if os.path.exists(invoice_path):
- os.remove(invoice_path)
-
- invoice = SimpleInvoice(invoice_path)
-
- transaction_data = invoice._transactions_data()
- self.assertEqual(transaction_data, [])
-
- invoice.add_transaction(Transaction('A', 1, date.today(), 9.9))
- invoice.add_transaction(Transaction('B', 3, date(2015, 6, 1), 3.3))
-
- transaction_data = invoice._transactions_data()
- self.assertEqual(len(transaction_data), 3)
- self.assertEqual(transaction_data[0][0], 'Transaction id')
- self.assertEqual(transaction_data[1][3], 9.9)
- self.assertEqual(transaction_data[2][0], 3)
- self.assertEqual(transaction_data[2][2], '2015-06-01')
- self.assertEqual(transaction_data[2][3], 3.3)
-
- invoice.finish()
-
- self.assertTrue(os.path.exists(invoice_path))