From 8dd09e1e9753bcba719a611fb5cd77cdac1afff9 Mon Sep 17 00:00:00 2001 From: zhangshine Date: Wed, 10 Jun 2015 22:55:53 +0800 Subject: [PATCH] test item --- pyinvoice/models.py | 4 +- pyinvoice/templates.py | 83 +++++++++++++++++++++---------------- tests/test_templates.py | 92 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 140 insertions(+), 39 deletions(-) diff --git a/pyinvoice/models.py b/pyinvoice/models.py index 7850781..0ed6630 100644 --- a/pyinvoice/models.py +++ b/pyinvoice/models.py @@ -113,7 +113,7 @@ class Item(object): :param units: Amount :type units: int or str or unicode :param unit_price: Unit price - :type unit_price: Decimal or str or unicode + :type unit_price: Decimal or str or unicode or int or float :return: """ self.name = name @@ -123,7 +123,7 @@ class Item(object): @property def amount(self): - return int(self.units) * Decimal(self.unit_price) + return int(self.units) * Decimal(str(self.unit_price)) class Transaction(object): diff --git a/pyinvoice/templates.py b/pyinvoice/templates.py index 3fa937f..5a148b6 100644 --- a/pyinvoice/templates.py +++ b/pyinvoice/templates.py @@ -8,6 +8,7 @@ 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 +from reportlab.platypus.doctemplate import _doNothing from pyinvoice.components import SimpleTable, TableWithHeader, PaidStamp from pyinvoice.models import PDFInfo, Item, Transaction, InvoiceInfo, ServiceProviderInfo, ClientInfo @@ -170,8 +171,7 @@ class SimpleInvoice(SimpleDocTemplate): self._build_service_provider_info() self._build_client_info() - def _build_items(self): - # Items + def _item_raw_data_and_subtotal(self): item_data = [] item_subtotal = 0 @@ -190,47 +190,60 @@ class SimpleInvoice(SimpleDocTemplate): ) item_subtotal += item.amount - if item_data: - self._story.append( - Paragraph('Detail', self._defined_styles.get('Heading1')) - ) + return item_data, item_subtotal - item_data_title = ('Name', 'Description', 'Units', 'Unit Price', 'Amount') - item_data.insert(0, item_data_title) # Insert title + def _item_data_and_style(self): + # Items + item_data, item_subtotal = self._item_raw_data_and_subtotal() + style = [] - # 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) - style = [] + if not item_data: + return item_data, style - # ##### Subtotal ##### + 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 ##### + item_data.append( + ('Subtotal', '', '', '', item_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')) item_data.append( - ('Subtotal', '', '', '', item_subtotal) + ('Vat/Tax ({0}%)'.format(self._item_tax_rate), '', '', '', tax_total) ) - - 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')) - item_data.append( - ('Vat/Tax ({0}%)'.format(self._item_tax_rate), '', '', '', tax_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') - item_data.append(('Total', '', '', '', 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')) + item_data.append(('Total', '', '', '', 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')) + + return item_data, style + + 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 _build_transactions(self): @@ -274,4 +287,4 @@ class SimpleInvoice(SimpleDocTemplate): self._build_transactions() self._build_bottom_tip() - self.build(self._story, onFirstPage=PaidStamp(7 * inch, 5.8 * inch) if self.is_paid else None) \ No newline at end of file + self.build(self._story, onFirstPage=PaidStamp(7 * inch, 5.8 * inch) if self.is_paid else _doNothing) \ No newline at end of file diff --git a/tests/test_templates.py b/tests/test_templates.py index b3e8b2e..d381c76 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -1,3 +1,4 @@ +from decimal import Decimal import os import unittest from datetime import datetime, date @@ -8,10 +9,10 @@ from pyinvoice.templates import SimpleInvoice class TestSimpleInvoice(unittest.TestCase): def setUp(self): - self.file_base_dir = os.path.dirname(os.path.realpath(__file__)) + 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, 'fixtures/dist/simple.pdf') + invoice_path = os.path.join(self.file_base_dir, 'simple.pdf') if os.path.exists(invoice_path): os.remove(invoice_path) @@ -53,4 +54,91 @@ class TestSimpleInvoice(unittest.TestCase): 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 + + 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.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.926')) # tax + self.assertEqual(item_data[-1][-1], Decimal('18.326')) # total + + invoice.finish() + self.assertTrue(os.path.exists(invoice_path)) \ No newline at end of file