From ae71b26f2576c42df61db577fd1cc50ed738759e Mon Sep 17 00:00:00 2001 From: Luciano Rossi Date: Mon, 22 Apr 2019 16:08:17 -0300 Subject: [PATCH] Update tests. Add scenario manual pos. --- MANIFEST.in | 11 +- README | 46 ++++ README.md | 42 --- setup.py | 2 +- tests/scenario_invoice.rst | 441 +++++++++++++++++++++++++++++++ tests/test_account_invoice_ar.py | 12 +- tests/tools.py | 40 ++- 7 files changed, 531 insertions(+), 63 deletions(-) create mode 100644 README delete mode 100644 README.md create mode 100644 tests/scenario_invoice.rst diff --git a/MANIFEST.in b/MANIFEST.in index 6c649d8..c3ed57a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,9 +1,10 @@ include INSTALL -include README.md -include TODO -include COPYRIGHT +include README include CHANGELOG +include COPYRIGHT include LICENSE +include tryton.cfg include *.xml -include *.odt -include *.csv +include *.fodt +include view/*.xml +include locale/*.po diff --git a/README b/README new file mode 100644 index 0000000..968bed2 --- /dev/null +++ b/README @@ -0,0 +1,46 @@ +account_invoice_ar +================== + +The Tryton `account_invoice_ar` module add account invoice +localization for Argentina (AFIP). + +Installing +---------- + +See INSTALL + +Support +------- + +If you encounter any problems with this module, please don't hesitate to ask +questions on the module bug tracker: + + https://github.com/tryton-ar/account_invoice_ar/issues + +For more information please contact the programmers at tryton-ar + + website: https://groups.google.com/forum/#!forum/tryton-ar + +If you encounter any problems with Tryton, please don't hesitate to ask +questions on the Tryton bug tracker, mailing list, wiki or IRC channel: + + http://bugs.tryton.org/ + http://groups.tryton.org/ + http://wiki.tryton.org/ + irc://irc.freenode.net/tryton + irc://irc.freenode.net/tryton-es + +License +------- + +See LICENSE + +Copyright +--------- + +See COPYRIGHT + + +For more information please visit the Tryton web site: + + http://www.tryton.org/ diff --git a/README.md b/README.md deleted file mode 100644 index 15b6c4c..0000000 --- a/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# account_invoice_ar - -The Tryton `account_invoice_ar` module add account invoice localization for Argentina (AFIP). - -See *tryton.cfg* - -This module is based on [pyafipws.openerp](http://code.google.com/p/pyafipws.openerp/) - -## Installing - -See INSTALL - -##Support - -If you encounter any problems with this module, please don't hesitate to ask -questions on the module bug tracker: - -https://github.com/tryton-ar/account_invoice_ar/issues - -For more information please contact the programmers at tryton-ar - * website: https://groups.google.com/forum/#!forum/tryton-ar - -If you encounter any problems with Tryton, please don't hesitate to ask -questions on the Tryton bug tracker, mailing list, wiki or IRC channel: - - * http://bugs.tryton.org/ - * http://groups.tryton.org/ - * http://wiki.tryton.org/ - * irc://irc.freenode.net/tryton - * irc://irc.freenode.net/tryton-es - -##License - -See LICENSE - -## Copyright - -See COPYRIGHT - -For more information please visit the Tryton web site: - - http://www.tryton.org/ diff --git a/setup.py b/setup.py index 289f1b5..f2706f2 100644 --- a/setup.py +++ b/setup.py @@ -65,7 +65,7 @@ def get_require_version(name): version=version, description=('Tryton module to add account invoice (electronic/manual) ' 'localizacion for Argentina (AFIP)'), - long_description=read('README.md'), + long_description=read('README'), author='tryton-ar', url='https://github.com/tryton-ar/account_invoice_ar', download_url=download_url, diff --git a/tests/scenario_invoice.rst b/tests/scenario_invoice.rst new file mode 100644 index 0000000..b4cbc30 --- /dev/null +++ b/tests/scenario_invoice.rst @@ -0,0 +1,441 @@ +================ +Invoice Scenario +================ + +Imports:: + >>> import datetime + >>> from dateutil.relativedelta import relativedelta + >>> from decimal import Decimal + >>> from operator import attrgetter + >>> from proteus import Model, Wizard + >>> from trytond.tests.tools import activate_modules + >>> from trytond.modules.company.tests.tools import create_company, \ + ... get_company + >>> from trytond.modules.currency.tests.tools import get_currency + >>> from trytond.modules.account.tests.tools import create_fiscalyear, \ + ... create_chart, get_accounts, create_tax, create_tax_code + >>> from trytond.modules.account_invoice.tests.tools import \ + ... set_fiscalyear_invoice_sequences + >>> from trytond.modules.account_invoice_ar.tests.tools import \ + ... create_pos, get_invoice_types, get_pos, create_tax_groups + >>> today = datetime.date.today() + +Install account_invoice:: + + >>> config = activate_modules('account_invoice_ar') + +Create company:: + + >>> currency = get_currency('ARS') + >>> _ = create_company(currency=currency) + >>> company = get_company() + >>> tax_identifier = company.party.identifiers.new() + >>> tax_identifier.type = 'ar_cuit' + >>> tax_identifier.code = '11111111113' + >>> company.party.iva_condition = 'responsable_inscripto' + >>> company.party.save() + +Create fiscal year:: + + >>> fiscalyear = set_fiscalyear_invoice_sequences( + ... create_fiscalyear(company)) + >>> fiscalyear.click('create_period') + >>> period = fiscalyear.periods[0] + >>> period_ids = [p.id for p in fiscalyear.periods] + +Create chart of accounts:: + + >>> _ = create_chart(company) + >>> accounts = get_accounts(company) + >>> receivable = accounts['receivable'] + >>> revenue = accounts['revenue'] + >>> expense = accounts['expense'] + >>> account_tax = accounts['tax'] + >>> account_cash = accounts['cash'] + +Create point of sale:: + + >>> _ = create_pos(company) + >>> pos = get_pos() + >>> invoice_types = get_invoice_types() + +Create tax groups:: + + >>> tax_groups = create_tax_groups() + +Create tax:: + + >>> TaxCode = Model.get('account.tax.code') + >>> tax = create_tax(Decimal('.10')) + >>> tax.group = tax_groups['iva'] + >>> tax.save() + >>> invoice_base_code = create_tax_code(tax, 'base', 'invoice') + >>> invoice_base_code.save() + >>> invoice_tax_code = create_tax_code(tax, 'tax', 'invoice') + >>> invoice_tax_code.save() + >>> credit_note_base_code = create_tax_code(tax, 'base', 'credit') + >>> credit_note_base_code.save() + >>> credit_note_tax_code = create_tax_code(tax, 'tax', 'credit') + >>> credit_note_tax_code.save() + +Create payment method:: + + >>> Journal = Model.get('account.journal') + >>> PaymentMethod = Model.get('account.invoice.payment.method') + >>> Sequence = Model.get('ir.sequence') + >>> journal_cash, = Journal.find([('type', '=', 'cash')]) + >>> payment_method = PaymentMethod() + >>> payment_method.name = 'Cash' + >>> payment_method.journal = journal_cash + >>> payment_method.credit_account = account_cash + >>> payment_method.debit_account = account_cash + >>> payment_method.save() + +Create Write Off method:: + + >>> WriteOff = Model.get('account.move.reconcile.write_off') + >>> sequence_journal, = Sequence.find([('code', '=', 'account.journal')]) + >>> journal_writeoff = Journal(name='Write-Off', type='write-off', + ... sequence=sequence_journal) + >>> journal_writeoff.save() + >>> writeoff_method = WriteOff() + >>> writeoff_method.name = 'Rate loss' + >>> writeoff_method.journal = journal_writeoff + >>> writeoff_method.credit_account = expense + >>> writeoff_method.debit_account = expense + >>> writeoff_method.save() + +Create party:: + + >>> Party = Model.get('party.party') + >>> party = Party(name='Party') + >>> party.iva_condition='responsable_inscripto' + >>> party.vat_number='33333333339' + >>> party.save() + +Create party consumidor final:: + + >>> Party = Model.get('party.party') + >>> party_cf = Party(name='Party') + >>> party_cf.iva_condition='consumidor_final' + >>> party_cf.save() + +Create account category:: + + >>> ProductCategory = Model.get('product.category') + >>> account_category = ProductCategory(name="Account Category") + >>> account_category.accounting = True + >>> account_category.account_expense = expense + >>> account_category.account_revenue = revenue + >>> account_category.customer_taxes.append(tax) + >>> account_category.save() + +Create product:: + + >>> ProductUom = Model.get('product.uom') + >>> unit, = ProductUom.find([('name', '=', 'Unit')]) + >>> ProductTemplate = Model.get('product.template') + >>> template = ProductTemplate() + >>> template.name = 'product' + >>> template.default_uom = unit + >>> template.type = 'service' + >>> template.list_price = Decimal('40') + >>> template.account_category = account_category + >>> template.save() + >>> product, = template.products + +Create payment term:: + + >>> PaymentTerm = Model.get('account.invoice.payment_term') + >>> payment_term = PaymentTerm(name='Term') + >>> line = payment_term.lines.new(type='percent', ratio=Decimal('.5')) + >>> delta, = line.relativedeltas + >>> delta.days = 20 + >>> line = payment_term.lines.new(type='remainder') + >>> delta = line.relativedeltas.new(days=40) + >>> payment_term.save() + +Create invoice:: + + >>> Invoice = Model.get('account.invoice') + >>> InvoiceLine = Model.get('account.invoice.line') + >>> invoice = Invoice() + >>> invoice.party = party + >>> invoice.pos = pos + >>> invoice.payment_term = payment_term + >>> line = InvoiceLine() + >>> invoice.lines.append(line) + >>> line.product = product + >>> line.quantity = 5 + >>> line.unit_price = Decimal('40') + >>> line = InvoiceLine() + >>> invoice.lines.append(line) + >>> line.account = revenue + >>> line.description = 'Test' + >>> line.quantity = 1 + >>> line.unit_price = Decimal(20) + >>> invoice.untaxed_amount + Decimal('220.00') + >>> invoice.tax_amount + Decimal('20.00') + >>> invoice.total_amount + Decimal('240.00') + >>> invoice.invoice_type == invoice_types['1'] + True + >>> invoice.save() + +Test change tax:: + + >>> tax_line, = invoice.taxes + >>> tax_line.tax == tax + True + >>> tax_line.tax = None + >>> tax_line.tax = tax + +Post invoice:: + + >>> invoice.click('post') + >>> invoice.state + 'posted' + >>> invoice.tax_identifier.code + '11111111113' + >>> invoice.untaxed_amount + Decimal('220.00') + >>> invoice.tax_amount + Decimal('20.00') + >>> invoice.total_amount + Decimal('240.00') + >>> receivable.reload() + >>> receivable.debit + Decimal('240.00') + >>> receivable.credit + Decimal('0.00') + >>> revenue.reload() + >>> revenue.debit + Decimal('0.00') + >>> revenue.credit + Decimal('220.00') + >>> account_tax.reload() + >>> account_tax.debit + Decimal('0.00') + >>> account_tax.credit + Decimal('20.00') + >>> with config.set_context(periods=period_ids): + ... invoice_base_code = TaxCode(invoice_base_code.id) + ... invoice_base_code.amount + Decimal('200.00') + >>> with config.set_context(periods=period_ids): + ... invoice_tax_code = TaxCode(invoice_tax_code.id) + ... invoice_tax_code.amount + Decimal('20.00') + >>> with config.set_context(periods=period_ids): + ... credit_note_base_code = TaxCode(credit_note_base_code.id) + ... credit_note_base_code.amount + Decimal('0.00') + >>> with config.set_context(periods=period_ids): + ... credit_note_tax_code = TaxCode(credit_note_tax_code.id) + ... credit_note_tax_code.amount + Decimal('0.00') + +Credit invoice with refund:: + + >>> credit = Wizard('account.invoice.credit', [invoice]) + >>> credit.form.with_refund = True + >>> credit.execute('credit') + >>> credit_note, = Invoice.find([ + ... ('type', '=', 'out'), ('id', '!=', invoice.id)]) + >>> credit_note.state + 'paid' + >>> credit_note.untaxed_amount == -invoice.untaxed_amount + True + >>> credit_note.tax_amount == -invoice.tax_amount + True + >>> credit_note.total_amount == -invoice.total_amount + True + >>> credit_note.origins == invoice.rec_name + True + >>> credit_note.pos == pos + True + >>> credit_note.invoice_type == invoice_types['3'] + True + >>> invoice.reload() + >>> invoice.state + 'paid' + >>> invoice.reconciled == today + True + >>> receivable.reload() + >>> receivable.debit + Decimal('240.00') + >>> receivable.credit + Decimal('240.00') + >>> revenue.reload() + >>> revenue.debit + Decimal('220.00') + >>> revenue.credit + Decimal('220.00') + >>> account_tax.reload() + >>> account_tax.debit + Decimal('20.00') + >>> account_tax.credit + Decimal('20.00') + >>> with config.set_context(periods=period_ids): + ... invoice_base_code = TaxCode(invoice_base_code.id) + ... invoice_base_code.amount + Decimal('200.00') + >>> with config.set_context(periods=period_ids): + ... invoice_tax_code = TaxCode(invoice_tax_code.id) + ... invoice_tax_code.amount + Decimal('20.00') + >>> with config.set_context(periods=period_ids): + ... credit_note_base_code = TaxCode(credit_note_base_code.id) + ... credit_note_base_code.amount + Decimal('200.00') + >>> with config.set_context(periods=period_ids): + ... credit_note_tax_code = TaxCode(credit_note_tax_code.id) + ... credit_note_tax_code.amount + Decimal('20.00') + +Pay invoice:: + + >>> invoice, = invoice.duplicate() + >>> invoice.click('post') + + >>> pay = Wizard('account.invoice.pay', [invoice]) + >>> pay.form.amount + Decimal('240.00') + >>> pay.form.amount = Decimal('120.00') + >>> pay.form.payment_method = payment_method + >>> pay.execute('choice') + >>> pay.state + 'end' + + >>> pay = Wizard('account.invoice.pay', [invoice]) + >>> pay.form.amount + Decimal('120.00') + >>> pay.form.amount = Decimal('20.00') + >>> pay.form.payment_method = payment_method + >>> pay.execute('choice') + >>> pay.form.type = 'partial' + >>> pay.form.amount + Decimal('20.00') + >>> len(pay.form.lines_to_pay) + 1 + >>> len(pay.form.payment_lines) + 0 + >>> len(pay.form.lines) + 1 + >>> pay.form.amount_writeoff + Decimal('100.00') + >>> pay.execute('pay') + + >>> pay = Wizard('account.invoice.pay', [invoice]) + >>> pay.form.amount + Decimal('-20.00') + >>> pay.form.amount = Decimal('99.00') + >>> pay.form.payment_method = payment_method + >>> pay.execute('choice') + >>> pay.form.type = 'writeoff' + >>> pay.form.writeoff = writeoff_method + >>> pay.form.amount + Decimal('99.00') + >>> len(pay.form.lines_to_pay) + 1 + >>> len(pay.form.payment_lines) + 1 + >>> len(pay.form.lines) + 1 + >>> pay.form.amount_writeoff + Decimal('1.00') + >>> pay.execute('pay') + + >>> invoice.state + 'paid' + +Create empty invoice:: + + >>> invoice = Invoice() + >>> invoice.party = party + >>> invoice.pos = pos + >>> invoice.payment_term = payment_term + >>> invoice.click('post') + >>> invoice.state + 'paid' + +Create some complex invoice and test its taxes base rounding:: + + >>> invoice = Invoice() + >>> invoice.party = party + >>> invoice.pos = pos + >>> invoice.payment_term = payment_term + >>> invoice.invoice_date = today + >>> line = invoice.lines.new() + >>> line.product = product + >>> line.quantity = 1 + >>> line.unit_price = Decimal('0.0035') + >>> line = invoice.lines.new() + >>> line.product = product + >>> line.quantity = 1 + >>> line.unit_price = Decimal('0.0035') + >>> invoice.save() + >>> invoice.untaxed_amount + Decimal('0.00') + >>> invoice.taxes[0].base == invoice.untaxed_amount + True + >>> found_invoice, = Invoice.find([('untaxed_amount', '=', Decimal(0))]) + >>> found_invoice.id == invoice.id + True + >>> found_invoice, = Invoice.find([('total_amount', '=', Decimal(0))]) + >>> found_invoice.id == invoice.id + True + +Clear company tax_identifier:: + + >>> tax_identifier, = company.party.identifiers + >>> tax_identifier.type = None + >>> tax_identifier.save() + +Create a paid invoice:: + + >>> invoice = Invoice() + >>> invoice.party = party + >>> invoice.pos = pos + >>> invoice.payment_term = payment_term + >>> line = invoice.lines.new() + >>> line.product = product + >>> line.quantity = 5 + >>> line.unit_price = Decimal('40') + >>> invoice.click('post') + >>> pay = Wizard('account.invoice.pay', [invoice]) + >>> pay.form.payment_method = payment_method + >>> pay.execute('choice') + >>> pay.state + 'end' + >>> invoice.tax_identifier + >>> invoice.state + 'paid' + +The invoice is posted when the reconciliation is deleted:: + + >>> invoice.payment_lines[0].reconciliation.delete() + >>> invoice.reload() + >>> invoice.state + 'posted' + >>> invoice.tax_identifier + +Credit invoice with non line lines:: + + >>> invoice = Invoice() + >>> invoice.party = party + >>> invoice.pos = pos + >>> invoice.payment_term = payment_term + >>> line = invoice.lines.new() + >>> line.product = product + >>> line.quantity = 5 + >>> line.unit_price = Decimal('40') + >>> line = invoice.lines.new() + >>> line.type = 'comment' + >>> line.description = 'Comment' + >>> invoice.click('post') + >>> credit = Wizard('account.invoice.credit', [invoice]) + >>> credit.form.with_refund = True + >>> credit.execute('credit') diff --git a/tests/test_account_invoice_ar.py b/tests/test_account_invoice_ar.py index b6c17b8..689cd59 100644 --- a/tests/test_account_invoice_ar.py +++ b/tests/test_account_invoice_ar.py @@ -1,12 +1,14 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import unittest +import doctest from trytond.tests.test_tryton import ModuleTestCase from trytond.tests.test_tryton import suite as test_suite +from trytond.tests.test_tryton import doctest_teardown +from trytond.tests.test_tryton import doctest_checker - -class TestCase(ModuleTestCase): +class AccountInvoiceArTestCase(ModuleTestCase): 'Test module' module = 'account_invoice_ar' @@ -14,5 +16,9 @@ class TestCase(ModuleTestCase): def suite(): suite = test_suite() suite.addTests(unittest.TestLoader().loadTestsFromTestCase( - TestCase)) + AccountInvoiceArTestCase)) + suite.addTests(doctest.DocFileSuite('scenario_invoice.rst', + tearDown=doctest_teardown, encoding='utf-8', + checker=doctest_checker, + optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)) return suite diff --git a/tests/tools.py b/tests/tools.py index 8dd1b64..6be9dbf 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -1,15 +1,15 @@ # This file is part of the account_invoice_ar module for Tryton. # The COPYRIGHT file at the top level of this repository contains # the full copyright notices and license terms. - from proteus import Model from trytond.modules.company.tests.tools import get_company -__all__ = ['create_pos', 'get_pos', 'get_invoice_types'] +__all__ = ['create_pos', 'get_pos', 'get_invoice_types', + 'create_tax_groups', 'set_company_afip'] -def create_pos(company=None, config=None): +def create_pos(company=None, type='manual', number=1, config=None): "Create a Point of Sale" Pos = Model.get('account.pos', config=config) Sequence = Model.get('ir.sequence', config=config) @@ -19,8 +19,8 @@ def create_pos(company=None, config=None): pos = Pos( company=company.id, - number=1, - pos_type='manual', + number=number, + pos_type=type, ) for attr, name in ( @@ -34,7 +34,7 @@ def create_pos(company=None, config=None): ('12', '12-Nota de Debito C'), ('13', '13-Nota de Credito C')): sequence = Sequence( - name='%s %s' % (name, 'manual'), + name='%s %s' % (name, type), code='account.invoice', company=company) sequence.save() @@ -46,14 +46,18 @@ def create_pos(company=None, config=None): return pos -def get_pos(company=None, config=None): +def get_pos(company=None, type='manual', number=1, config=None): "Return the only pos" Pos = Model.get('account.pos', config=config) if not company: company = get_company() - pos, = Pos.find([('company', '=', company.id)]) + pos, = Pos.find([ + ('company', '=', company.id), + ('pos_type', '=', type), + ('number', '=', number), + ]) return pos @@ -68,10 +72,6 @@ def get_invoice_types(company=None, pos=None, config=None): if not pos: pos = get_pos(company) - accounts = Account.find([ - ('kind', 'in', ['receivable', 'payable', 'revenue', 'expense']), - ('company', '=', company.id), - ]) invoice_types = PosSequence.find([ ('pos', '=', pos.id), ]) @@ -93,3 +93,19 @@ def create_tax_groups(company=None, config=None): group.save() groups[type] = group return groups + + +def set_company_afip(company=None, config=None): + "Set AFIP certificates" + if not company: + company = get_company() + crt_file = 'reingart.crt' + key_file = 'reingart.key' + with open(crt_file, 'rb') as f: + read_data = f.read() + company.pyafipws_certificate = read_data.encode('utf8') + with open(key_file, 'rb') as f: + read_data = f.read() + company.pyafipws_private_key = read_data.encode('utf8') + company.pyafipws_mode_cert = 'homologacion' + company.save()