├── README.rst
├── .gitattributes
├── cache
└── .gitignore
├── MANIFEST.in
├── view
├── pos_sequence_tree.xml
├── transaction_tree.xml
├── invoice_export_license_tree.xml
├── pos_tree.xml
├── invoice_export_license_form.xml
├── pos_sequence_form.xml
├── currency_form.xml
├── bank_account_form.xml
├── invoice_tree.xml
├── recover_invoice_start_form.xml
├── recover_invoice_data_form.xml
├── party_form.xml
├── transaction_form.xml
├── pos_form.xml
├── credit_start_form.xml
└── invoice_form.xml
├── scripts
├── __init__.py
└── update_currencies.py
├── tests
├── __init__.py
├── test_module.py
├── test_scenario.py
├── scenario_invoice_noperiod.rst
├── tools.py
├── scenario_recover_invoice_electronic.rst
├── scenario_invoice_supplier.rst
├── scenario_invoice_pos_electronic_fce.rst
├── scenario_invoice.rst
├── scenario_invoice_pos_electronic.rst
└── scenario_invoice_pos_electronic_wsfex.rst
├── tryton.cfg
├── bank.xml
├── currency.xml
├── bank.py
├── tox.ini
├── .gitlab-ci.yml
├── COPYRIGHT
├── CHANGELOG
├── doc
└── index.rst
├── .gitignore
├── __init__.py
├── party.py
├── .drone.yml
├── party.xml
├── pos.xml
├── invoice.xml
├── currency.py
├── setup.py
├── message.xml
├── pos.py
└── LICENSE
/README.rst:
--------------------------------------------------------------------------------
1 | doc/index.rst
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.odt diff=odt
2 |
--------------------------------------------------------------------------------
/cache/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except this file
4 | !.gitignore
5 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include CHANGELOG
2 | include COPYRIGHT
3 | include LICENSE
4 | include README.rst
5 | graft doc
6 |
--------------------------------------------------------------------------------
/view/pos_sequence_tree.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/view/transaction_tree.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/view/invoice_export_license_tree.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/scripts/__init__.py:
--------------------------------------------------------------------------------
1 | # This file is part of Tryton. The COPYRIGHT file at the top level of
2 | # this repository contains the full copyright notices and license terms.
3 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | # This file is part of Tryton. The COPYRIGHT file at the top level of
2 | # this repository contains the full copyright notices and license terms.
3 |
--------------------------------------------------------------------------------
/view/pos_tree.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/view/invoice_export_license_form.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/view/pos_sequence_form.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/view/currency_form.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/view/bank_account_form.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/view/invoice_tree.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/tryton.cfg:
--------------------------------------------------------------------------------
1 | [tryton]
2 | version=7.1.0
3 | depends:
4 | account_ar
5 | account_invoice
6 | bank_ar
7 | party_ar
8 | xml:
9 | bank.xml
10 | currency.xml
11 | invoice.xml
12 | party.xml
13 | pos.xml
14 | message.xml
15 |
--------------------------------------------------------------------------------
/view/recover_invoice_start_form.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/bank.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | bank.account
7 |
8 | bank_account_form
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/currency.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | currency.currency
7 |
8 | currency_form
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/view/recover_invoice_data_form.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/bank.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # This file is part of the account_invoice_ar module for Tryton.
3 | # The COPYRIGHT file at the top level of this repository contains
4 | # the full copyright notices and license terms.
5 |
6 | from trytond.model import fields
7 | from trytond.pool import PoolMeta
8 |
9 |
10 | class BankAccount(metaclass=PoolMeta):
11 | __name__ = 'bank.account'
12 |
13 | pyafipws_cbu = fields.Boolean('CBU del Emisor')
14 |
--------------------------------------------------------------------------------
/tests/test_module.py:
--------------------------------------------------------------------------------
1 | # This file is part of Tryton. The COPYRIGHT file at the top level of
2 | # this repository contains the full copyright notices and license terms.
3 |
4 | from trytond.modules.company.tests import CompanyTestMixin
5 | from trytond.tests.test_tryton import ModuleTestCase
6 |
7 |
8 | class InvoiceArTestCase(CompanyTestMixin, ModuleTestCase):
9 | 'Test account_invoice_ar module'
10 | module = 'account_invoice_ar'
11 |
12 |
13 | del ModuleTestCase
14 |
--------------------------------------------------------------------------------
/view/party_form.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/view/transaction_form.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = {py38,py39,py310,py311,py312}-{sqlite,postgresql}
3 |
4 | [testenv]
5 | usedevelop = true
6 | extras = test
7 | commands =
8 | coverage run --omit=*/tests/* -m xmlrunner discover -s tests {posargs}
9 | commands_post =
10 | coverage report
11 | coverage xml
12 | deps =
13 | coverage
14 | unittest-xml-reporting
15 | postgresql: psycopg2 >= 2.7.0
16 | passenv = *
17 | setenv =
18 | sqlite: TRYTOND_DATABASE_URI={env:SQLITE_URI:sqlite://}
19 | postgresql: TRYTOND_DATABASE_URI={env:POSTGRESQL_URI:postgresql://}
20 | sqlite: DB_NAME={env:DB_NAME::memory:}
21 | postgresql: DB_NAME={env:DB_NAME:test}
22 |
--------------------------------------------------------------------------------
/view/pos_form.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | before_script:
2 | - apt-get update -yqq
3 | - apt-get install swig -yqq
4 | - apt-get install libssl-dev -yqq
5 | - apt-get install python3-dev -yqq
6 | - sed -i 's#ssh://git@gitlab.com#https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com#g' setup.py
7 |
8 | stages:
9 | - test
10 | - build
11 | - deploy
12 |
13 | test:py37-postgresql:
14 | stage: test
15 | image: python:3.7
16 | tags:
17 | - postgres
18 | services:
19 | - postgres
20 | variables:
21 | CFLAGS: "-O0"
22 | DB_CACHE: "~/cache"
23 | TOX_TESTENV_PASSENV: "CFLAGS DB_CACHE"
24 | POSTGRES_HOST_AUTH_METHOD: "trust"
25 | POSTGRESQL_URI: "postgresql://postgres:@postgres:5432/"
26 | script:
27 | - pip install tox
28 | - tox -e py37-postgresql
29 |
--------------------------------------------------------------------------------
/tests/test_scenario.py:
--------------------------------------------------------------------------------
1 | # This file is part of Tryton. The COPYRIGHT file at the top level of
2 | # this repository contains the full copyright notices and license terms.
3 |
4 | import doctest
5 | import glob
6 | import os
7 |
8 | from trytond.tests.test_tryton import doctest_checker, doctest_teardown
9 |
10 |
11 | def load_tests(loader, tests, pattern):
12 | cwd = os.getcwd()
13 | try:
14 | os.chdir(os.path.dirname(__file__))
15 | for scenario in glob.glob('*.rst'):
16 | tests.addTests(doctest.DocFileSuite(
17 | scenario, tearDown=doctest_teardown, encoding='utf-8',
18 | checker=doctest_checker,
19 | optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
20 | finally:
21 | os.chdir(cwd)
22 | return tests
23 |
--------------------------------------------------------------------------------
/COPYRIGHT:
--------------------------------------------------------------------------------
1 | Copyright (C) 2017-2025 Adrián Bernardi - Silix
2 | Copyright (C) 2013-2024 Luciano Rossi
3 | Copyright (C) 2013-2020 Gcoop Cooperativa de Trabajo LTDA
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU General Public License as published by
7 | the Free Software Foundation, either version 3 of the License, or
8 | (at your option) any later version.
9 |
10 | This program is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | GNU General Public License for more details.
14 |
15 | You should have received a copy of the GNU General Public License
16 | along with this program. If not, see .
17 |
--------------------------------------------------------------------------------
/view/credit_start_form.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
12 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/CHANGELOG:
--------------------------------------------------------------------------------
1 | Version 7.0.0 - 2023-08-01
2 | * Bug fixes (see git logs for details)
3 |
4 | Version 6.6.0 - 2023-09-26
5 | * Bug fixes (see git logs for details)
6 |
7 | Version 6.0.0 - 2021-09-10
8 | * Bug fixes (see git logs for details)
9 |
10 | Version 5.6.1 - 2020-09-19
11 | * Implements new PyAfipWsWrapper
12 |
13 | Version 5.6.0 - 2020-09-08
14 | * Bug fixes (see git logs for details)
15 |
16 | Version 5.2.0 - 2020-07-22
17 | * Bug fixes (see git logs for details)
18 |
19 | Version 5.0.0 - 2018-12-26
20 | * Bug fixes (see git logs for details)
21 |
22 | Version 4.8.1 - 2018-12-26
23 | * Bug fixes (see git logs for details)
24 |
25 | Version 4.8.0 - 2018-09-26
26 | * Bug fixes (see git logs for details)
27 |
28 | Version 4.6.0 - 2018-12-26
29 | * Bug fixes (see git logs for details)
30 |
31 | Version 4.4.0 - 2017-09-19
32 | * Bug fixes (see git logs for details)
33 |
34 | Version 4.2.1 - 2017-07-26
35 | * Bug fixes (see git logs for details)
36 |
37 | Version 3.8.2 - 2016-02-01
38 | * Remove party integration. Move to party_ar module.
39 |
40 | Version 2.8.0 - 2013-05-23
41 | * Initial release
42 |
--------------------------------------------------------------------------------
/doc/index.rst:
--------------------------------------------------------------------------------
1 | account_invoice_ar
2 | ==================
3 |
4 | The Tryton `account_invoice_ar` module add account invoice
5 | localization for Argentina (AFIP).
6 |
7 | Installing
8 | ----------
9 |
10 | See INSTALL
11 |
12 | Support
13 | -------
14 |
15 | If you encounter any problems with this module, please don't hesitate to ask
16 | questions on the module bug tracker:
17 |
18 | https://github.com/tryton-ar/account_invoice_ar/issues
19 |
20 | For more information please contact the programmers at tryton-ar
21 |
22 | website: https://groups.google.com/forum/#!forum/tryton-ar
23 |
24 | If you encounter any problems with Tryton, please don't hesitate to ask
25 | questions on the Tryton bug tracker, mailing list, wiki or IRC channel:
26 |
27 | http://bugs.tryton.org/
28 | http://groups.tryton.org/
29 | http://wiki.tryton.org/
30 | irc://irc.freenode.net/tryton
31 | irc://irc.freenode.net/tryton-es
32 |
33 | License
34 | -------
35 |
36 | See LICENSE
37 |
38 | Copyright
39 | ---------
40 |
41 | See COPYRIGHT
42 |
43 |
44 | For more information please visit the Tryton web site:
45 |
46 | http://www.tryton.org/
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 | .hypothesis/
47 |
48 | # Translations
49 | *.mo
50 | *.pot
51 |
52 | # Django stuff:
53 | *.log
54 |
55 | # Sphinx documentation
56 | docs/_build/
57 |
58 | # PyBuilder
59 | target/
60 |
61 | #Ipython Notebook
62 | .ipynb_checkpoints
63 |
64 | .env
65 | .python-version
66 | TEST-*
67 | coverage.xml
68 | .mypy_cache
69 | cache/
70 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | # This file is part of the account_invoice_ar module for Tryton.
2 | # The COPYRIGHT file at the top level of this repository contains
3 | # the full copyright notices and license terms.
4 |
5 | from trytond.pool import Pool
6 | from . import invoice
7 | from . import pos
8 | from . import bank
9 | from . import party
10 | from . import currency
11 |
12 | __all__ = ['register']
13 |
14 |
15 | def register():
16 | Pool.register(
17 | pos.Pos,
18 | pos.PosSequence,
19 | invoice.Invoice,
20 | invoice.InvoiceLine,
21 | invoice.InvoiceExportLicense,
22 | invoice.CreditInvoiceStart,
23 | invoice.InvoiceCmpAsoc,
24 | invoice.AfipWSTransaction,
25 | invoice.RecoverInvoiceStart,
26 | invoice.RecoverInvoiceData,
27 | bank.BankAccount,
28 | party.Party,
29 | currency.Currency,
30 | module='account_invoice_ar', type_='model')
31 | Pool.register(
32 | invoice.CreditInvoice,
33 | invoice.RecoverInvoice,
34 | module='account_invoice_ar', type_='wizard')
35 | Pool.register(
36 | invoice.InvoiceReport,
37 | module='account_invoice_ar', type_='report')
38 |
--------------------------------------------------------------------------------
/party.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # This file is part of the account_invoice_ar module for Tryton.
3 | # The COPYRIGHT file at the top level of this repository contains
4 | # the full copyright notices and license terms.
5 | from decimal import Decimal
6 |
7 | from trytond.model import fields
8 | from trytond.pool import Pool, PoolMeta
9 | from trytond.pyson import Eval, Or
10 | from trytond.transaction import Transaction
11 |
12 |
13 | class Party(metaclass=PoolMeta):
14 | __name__ = 'party.party'
15 |
16 | pyafipws_fce = fields.Boolean('MiPyme FCE',
17 | states={'readonly': ~Eval('active', True)})
18 | pyafipws_fce_amount = fields.Numeric('MiPyme FCE Amount',
19 | digits=(16, Eval('pyafipws_fce_amount_digits', 2)),
20 | states={
21 | 'readonly': Or(
22 | ~Eval('pyafipws_fce', False),
23 | ~Eval('active', True)),
24 | })
25 | pyafipws_fce_amount_digits = fields.Function(fields.Integer(
26 | 'Currency Digits'), 'get_pyafipws_fce_amount_digits')
27 |
28 | @staticmethod
29 | def default_pyafipws_fce_amount():
30 | return Decimal('0')
31 |
32 | def get_pyafipws_fce_amount_digits(self, name):
33 | pool = Pool()
34 | Company = pool.get('company.company')
35 | company_id = Transaction().context.get('company')
36 | if company_id:
37 | company = Company(company_id)
38 | return company.currency.digits
39 |
--------------------------------------------------------------------------------
/.drone.yml:
--------------------------------------------------------------------------------
1 | clone:
2 | hg:
3 | image: plugins/hg
4 | environment:
5 | - HG_SHARE_POOL=/root/.cache/hg
6 | volumes:
7 | - cache:/root/.cache
8 |
9 | pipeline:
10 | tox:
11 | image: ${IMAGE}
12 | environment:
13 | - CFLAGS=-O0
14 | - DB_CACHE=/cache
15 | - TOX_TESTENV_PASSENV=CFLAGS DB_CACHE
16 | - POSTGRESQL_URI=postgresql://postgres@postgresql:5432/
17 | commands:
18 | - echo "[extensions]" >> /root/.hgrc
19 | - echo "hgext.share =" >> /root/.hgrc
20 | - echo "[share]" >> /root/.hgrc
21 | - echo "pool = /root/.cache/hg" >> /root/.hgrc
22 | - pip install tox
23 | - tox -e "${TOXENV}-${DATABASE}"
24 | volumes:
25 | - cache:/root/.cache
26 |
27 | services:
28 | postgresql:
29 | image: postgres
30 | environment:
31 | - POSTGRES_HOST_AUTH_METHOD=trust
32 | command: "-c fsync=off -c synchronous_commit=off -c full_page_writes=off"
33 | when:
34 | matrix:
35 | DATABASE: postgresql
36 |
37 | matrix:
38 | include:
39 | - IMAGE: python:3.5
40 | TOXENV: py35
41 | DATABASE: sqlite
42 | - IMAGE: python:3.5
43 | TOXENV: py35
44 | DATABASE: postgresql
45 | - IMAGE: python:3.6
46 | TOXENV: py36
47 | DATABASE: sqlite
48 | - IMAGE: python:3.6
49 | TOXENV: py36
50 | DATABASE: postgresql
51 | - IMAGE: python:3.7
52 | TOXENV: py37
53 | DATABASE: sqlite
54 | - IMAGE: python:3.7
55 | TOXENV: py37
56 | DATABASE: postgresql
57 | - IMAGE: python:3.8
58 | TOXENV: py38
59 | DATABASE: sqlite
60 | - IMAGE: python:3.8
61 | TOXENV: py38
62 | DATABASE: postgresql
63 |
--------------------------------------------------------------------------------
/scripts/update_currencies.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # This file is part of Tryton. The COPYRIGHT file at the top level of
3 | # this repository contains the full copyright notices and license terms.
4 | import os
5 | import sys
6 | from argparse import ArgumentParser
7 |
8 | try:
9 | import argcomplete
10 | except ImportError:
11 | argcomplete = None
12 |
13 | try:
14 | from proteus import Model, config
15 | except ImportError:
16 | prog = os.path.basename(sys.argv[0])
17 | sys.exit("proteus must be installed to use %s" % prog)
18 |
19 |
20 | def update_currencies():
21 | print("Update currencies", file=sys.stderr)
22 | Currency = Model.get('currency.currency')
23 |
24 | afip_codes = {
25 | 'ARS': 'PES',
26 | 'USD': 'DOL',
27 | 'EUR': '060',
28 | 'GBP': '021',
29 | 'BRL': '012',
30 | 'UYU': '011',
31 | }
32 |
33 | records = []
34 | for code, afip_code in afip_codes.items():
35 | print(code, file=sys.stderr)
36 | try:
37 | currency, = Currency.find([('code', '=', code)])
38 | currency.afip_code = afip_code
39 | records.append(currency)
40 | except Exception:
41 | pass
42 | Currency.save(records)
43 |
44 |
45 | def main(database, config_file=None):
46 | config.set_trytond(database, config_file=config_file)
47 | with config.get_config().set_context(active_test=False):
48 | update_currencies()
49 |
50 |
51 | def run():
52 | parser = ArgumentParser()
53 | parser.add_argument('-d', '--database', dest='database', required=True)
54 | parser.add_argument('-c', '--config', dest='config_file',
55 | help='the trytond config file')
56 | if argcomplete:
57 | argcomplete.autocomplete(parser)
58 |
59 | args = parser.parse_args()
60 | main(args.database, args.config_file)
61 |
62 |
63 | if __name__ == '__main__':
64 | run()
65 |
--------------------------------------------------------------------------------
/party.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | party.party
7 |
8 | party_form
9 |
10 |
12 |
14 |
15 |
16 |
17 |
19 |
21 |
22 |
23 |
24 |
25 |
26 |
28 |
30 |
31 |
32 |
33 |
35 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/pos.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 | account.pos
10 | form
11 | pos_form
12 |
13 |
14 | account.pos
15 | tree
16 | pos_tree
17 |
18 |
19 |
20 | Points of Sale
21 | account.pos
22 |
23 |
25 |
26 |
27 |
28 |
29 |
31 |
32 |
33 |
34 |
35 |
36 |
38 |
39 |
40 | User in companies
41 |
43 |
44 |
45 |
46 |
49 |
50 |
51 |
52 |
53 | account.pos.sequence
54 | form
55 | pos_sequence_form
56 |
57 |
58 | account.pos.sequence
59 | tree
60 | pos_sequence_tree
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/invoice.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | account.invoice
7 |
8 | invoice_form
9 |
10 |
11 | account.invoice
12 |
13 | invoice_tree
14 |
15 |
16 | account.invoice.credit.start
17 |
18 | credit_start_form
19 |
20 |
21 |
22 | account.invoice.export.license
23 | tree
24 | invoice_export_license_tree
25 |
26 |
27 | account.invoice.export.license
28 | form
29 | invoice_export_license_form
30 |
31 |
32 |
33 | account_invoice_ar.afip_transaction
34 | tree
35 | transaction_tree
36 |
37 |
38 | account_invoice_ar.afip_transaction
39 | form
40 | transaction_form
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | Invoice
49 | account.invoice
50 | account.invoice
51 | account_invoice_ar/invoice.fodt
52 |
53 |
54 | form_print
55 | account.invoice,-1
56 |
57 |
58 |
59 |
60 |
61 |
62 | account.invoice.recover.start
63 | form
64 | recover_invoice_start_form
65 |
66 |
67 | account.invoice.recover.data
68 | form
69 | recover_invoice_data_form
70 |
71 |
72 |
73 | Recover Invoice
74 | account.invoice.recover
75 | account.invoice
76 |
77 |
78 | form_action
79 | account.invoice,-1
80 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/view/invoice_form.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
36 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
74 |
75 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/currency.py:
--------------------------------------------------------------------------------
1 | # This file is part of the account_invoice_ar module for Tryton.
2 | # The COPYRIGHT file at the top level of this repository contains
3 | # the full copyright notices and license terms.
4 |
5 | import logging
6 | from decimal import Decimal
7 | import datetime
8 | from pyafipws.wsfev1 import WSFEv1
9 | from pyafipws.wsfexv1 import WSFEXv1
10 |
11 | from trytond.model import fields
12 | from trytond.pool import Pool, PoolMeta
13 | from trytond.transaction import Transaction
14 | from trytond.exceptions import UserError
15 | from trytond.i18n import gettext
16 |
17 | logger = logging.getLogger(__name__)
18 |
19 |
20 | class Currency(metaclass=PoolMeta):
21 | __name__ = 'currency.currency'
22 |
23 | afip_code = fields.Char('AFIP Code', size=3,
24 | help="The 3 digits AFIP currency code.")
25 |
26 | @classmethod
27 | def compute(cls, from_currency, amount, to_currency, round=True):
28 | pool = Pool()
29 | Company = pool.get('company.company')
30 |
31 | currency_rate = Transaction().context.get('currency_rate')
32 | if not currency_rate:
33 | return super().compute(from_currency, amount, to_currency, round)
34 |
35 | if to_currency == from_currency:
36 | if round:
37 | return to_currency.round(amount)
38 | else:
39 | return amount
40 |
41 | company = Company(Transaction().context['company'])
42 | if from_currency == company.currency:
43 | from_currency_rate = currency_rate
44 | currency_rate = Decimal('1.0')
45 | else:
46 | from_currency_rate = Decimal('1.0')
47 |
48 | if round:
49 | return to_currency.round(
50 | amount * currency_rate / from_currency_rate)
51 | else:
52 | return amount * currency_rate / from_currency_rate
53 |
54 |
55 | class Rate(metaclass=PoolMeta):
56 | __name__ = 'currency.currency.rate'
57 |
58 | def get_afip_rate(self, service='wsfex'):
59 | '''
60 | get rate from afip webservice.
61 | '''
62 | pool = Pool()
63 | Company = pool.get('company.company')
64 | company_id = Transaction().context.get('company')
65 | if not company_id:
66 | logger.error('The company is not defined')
67 | raise UserError(gettext(
68 | 'account_invoice_ar.msg_company_not_defined'))
69 | company = Company(company_id)
70 | # authenticate against AFIP:
71 | ta = company.pyafipws_authenticate(service=service)
72 |
73 | if service == 'wsfe':
74 | ws = WSFEv1()
75 | if company.pyafipws_mode_cert == 'homologacion':
76 | WSDL = 'https://wswhomo.afip.gov.ar/wsfev1/service.asmx?WSDL'
77 | elif company.pyafipws_mode_cert == 'produccion':
78 | WSDL = (
79 | 'https://servicios1.afip.gov.ar/wsfev1/service.asmx?WSDL')
80 | elif service == 'wsfex':
81 | ws = WSFEXv1()
82 | if company.pyafipws_mode_cert == 'homologacion':
83 | WSDL = 'https://wswhomo.afip.gov.ar/wsfexv1/service.asmx?WSDL'
84 | elif company.pyafipws_mode_cert == 'produccion':
85 | WSDL = (
86 | 'https://servicios1.afip.gov.ar/wsfexv1/service.asmx?WSDL')
87 | else:
88 | logger.critical('AFIP ws is not yet supported! %s', service)
89 | raise UserError(gettext(
90 | 'account_invoice_ar.msg_webservice_not_supported',
91 | service=service))
92 |
93 | cache = Company.get_cache_dir()
94 | ws.LanzarExcepciones = True
95 | try:
96 | ws.Conectar(wsdl=WSDL, cache=cache, cacert=True)
97 | except Exception as e:
98 | msg = ws.Excepcion + ' ' + str(e)
99 | logger.error('WSAA connecting to afip: %s' % msg)
100 | raise UserError(gettext(
101 | 'account_invoice_ar.msg_wsaa_error', msg=msg))
102 | ws.SetTicketAcceso(ta)
103 | ws.Cuit = company.party.vat_number
104 |
105 | if not self.currency.afip_code:
106 | logger.error('AFIP code is empty %s', self.currency.code)
107 | raise UserError(gettext(
108 | 'account_invoice_ar.msg_afip_code_empty'))
109 |
110 | self.rate = Decimal(ws.GetParamCtz('DOL'))
111 | self.date = datetime.datetime.strptime(ws.FchCotiz, '%Y%m%d').date()
112 |
--------------------------------------------------------------------------------
/tests/scenario_invoice_noperiod.rst:
--------------------------------------------------------------------------------
1 | ================
2 | Invoice Scenario
3 | ================
4 |
5 | Imports::
6 | >>> import datetime as dt
7 | >>> from dateutil.relativedelta import relativedelta
8 | >>> from decimal import Decimal
9 | >>> from proteus import Model, Wizard
10 | >>> from trytond.tests.tools import activate_modules
11 | >>> from trytond.modules.currency.tests.tools import get_currency
12 | >>> from trytond.modules.company.tests.tools import create_company, \
13 | ... get_company
14 | >>> from trytond.modules.account.tests.tools import create_fiscalyear, \
15 | ... create_chart
16 | >>> from trytond.modules.account_ar.tests.tools import get_accounts
17 | >>> from trytond.modules.account_invoice.tests.tools import \
18 | ... set_fiscalyear_invoice_sequences
19 | >>> from trytond.modules.account_invoice_ar.tests.tools import \
20 | ... create_pos, get_pos, get_invoice_types, get_tax
21 | >>> today = dt.date.today()
22 | >>> year = dt.date(2012, 1, 1)
23 |
24 | Install account_invoice_ar::
25 |
26 | >>> config = activate_modules('account_invoice_ar')
27 |
28 | Create company::
29 |
30 | >>> currency = get_currency('ARS')
31 | >>> currency.afip_code = 'PES'
32 | >>> currency.save()
33 | >>> _ = create_company(currency=currency)
34 | >>> company = get_company()
35 | >>> tax_identifier = company.party.identifiers.new()
36 | >>> tax_identifier.type = 'ar_vat'
37 | >>> tax_identifier.code = '30710158254' # gcoop CUIT
38 | >>> company.party.iva_condition = 'responsable_inscripto'
39 | >>> company.party.save()
40 |
41 | Create fiscal year::
42 |
43 | >>> fiscalyear = set_fiscalyear_invoice_sequences(
44 | ... create_fiscalyear(company, year))
45 | >>> fiscalyear.click('create_period')
46 | >>> period = fiscalyear.periods[0]
47 | >>> period_ids = [p.id for p in fiscalyear.periods]
48 |
49 | Create chart of accounts::
50 |
51 | >>> _ = create_chart(company, chart='account_ar.root_ar')
52 | >>> accounts = get_accounts(company)
53 |
54 | Create point of sale::
55 |
56 | >>> _ = create_pos(company)
57 | >>> pos = get_pos()
58 | >>> invoice_types = get_invoice_types()
59 |
60 | Create taxes::
61 |
62 | >>> sale_tax = get_tax('IVA Ventas 21%')
63 | >>> purchase_tax = get_tax('IVA Compras 21%')
64 |
65 |
66 | Create party::
67 |
68 | >>> Party = Model.get('party.party')
69 | >>> party = Party(name='Party')
70 | >>> party.iva_condition='responsable_inscripto'
71 | >>> party.vat_number='33333333339'
72 | >>> party.account_payable = accounts['payable']
73 | >>> party.account_receivable = accounts['receivable']
74 | >>> party.save()
75 |
76 |
77 | Create account category::
78 |
79 | >>> ProductCategory = Model.get('product.category')
80 | >>> account_category = ProductCategory(name="Account Category")
81 | >>> account_category.accounting = True
82 | >>> account_category.account_expense = accounts['expense']
83 | >>> account_category.account_revenue = accounts['revenue']
84 | >>> account_category.customer_taxes.append(sale_tax)
85 | >>> account_category.supplier_taxes.append(purchase_tax)
86 | >>> account_category.save()
87 |
88 | Create product::
89 |
90 | >>> ProductUom = Model.get('product.uom')
91 | >>> unit, = ProductUom.find([('name', '=', 'Unit')])
92 | >>> ProductTemplate = Model.get('product.template')
93 | >>> template = ProductTemplate()
94 | >>> template.name = 'product'
95 | >>> template.default_uom = unit
96 | >>> template.type = 'service'
97 | >>> template.list_price = Decimal('40')
98 | >>> template.account_category = account_category
99 | >>> template.save()
100 | >>> product, = template.products
101 |
102 |
103 | Create invoice::
104 |
105 | >>> Invoice = Model.get('account.invoice')
106 | >>> InvoiceLine = Model.get('account.invoice.line')
107 | >>> invoice = Invoice()
108 | >>> invoice.party = party
109 | >>> invoice.pos = pos
110 | >>> line = InvoiceLine()
111 | >>> invoice.lines.append(line)
112 | >>> line.product = product
113 | >>> line.description = 'Test'
114 | >>> line.quantity = 1
115 | >>> line.unit_price = Decimal(20)
116 | >>> invoice.invoice_type == invoice_types['1']
117 | True
118 | >>> invoice.save()
119 |
120 | Post invoice without period::
121 |
122 | >>> invoice.click('post') # doctest: +IGNORE_EXCEPTION_DETAIL
123 | Traceback (most recent call last):
124 | ...
125 | trytond.modules.account.exceptions.PeriodNotFoundError: ...
126 | >>> invoice.state
127 | 'draft'
128 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # This file is part of the account_invoice_ar module for Tryton.
3 | # The COPYRIGHT file at the top level of this repository contains
4 | # the full copyright notices and license terms.
5 |
6 | import io
7 | import os
8 | import re
9 | from configparser import ConfigParser
10 | from setuptools import find_packages, setup
11 |
12 | MODULE = 'account_invoice_ar'
13 | PREFIX = 'trytonar'
14 | MODULE2PREFIX = {
15 | 'account_ar': 'trytonar',
16 | 'bank_ar': 'trytonar',
17 | 'party_ar': 'trytonar',
18 | }
19 |
20 |
21 | def read(fname):
22 | content = io.open(
23 | os.path.join(os.path.dirname(__file__), fname),
24 | 'r', encoding='utf-8').read()
25 | content = re.sub(
26 | r'(?m)^\.\. toctree::\r?\n((^$|^\s.*$)\r?\n)*', '', content)
27 | return content
28 |
29 |
30 | def get_require_version(name):
31 | if name in LINKS:
32 | return '' # '%s @ %s' % (name, LINKS[name])
33 | require = '%s >= %s.%s, < %s.%s'
34 | require %= (name, major_version, minor_version,
35 | major_version, minor_version + 1)
36 | return require
37 |
38 |
39 | config = ConfigParser()
40 | config.read_file(open(os.path.join(os.path.dirname(__file__), 'tryton.cfg')))
41 | info = dict(config.items('tryton'))
42 | for key in ('depends', 'extras_depend', 'xml'):
43 | if key in info:
44 | info[key] = info[key].strip().splitlines()
45 | version = info.get('version', '0.0.1')
46 | major_version, minor_version, _ = version.split('.', 2)
47 | major_version = int(major_version)
48 | minor_version = int(minor_version)
49 |
50 | url = 'https://github.com/tryton-ar/%s' % MODULE
51 | download_url = 'https://github.com/tryton-ar/%s/tree/%s.%s' % (
52 | MODULE, major_version, minor_version)
53 |
54 | LINKS = {
55 | 'trytonar_account_ar': ('git+https://github.com/tryton-ar/'
56 | 'account_ar.git@%s.%s#egg=trytonar_account_ar-%s.%s' %
57 | (major_version, minor_version, major_version, minor_version)),
58 | 'trytonar_bank_ar': ('git+https://github.com/tryton-ar/'
59 | 'bank_ar.git@%s.%s#egg=trytonar_bank_ar-%s.%s' %
60 | (major_version, minor_version, major_version, minor_version)),
61 | 'trytonar_party_ar': ('git+https://github.com/tryton-ar/'
62 | 'party_ar.git@%s.%s#egg=trytonar_party_ar-%s.%s' %
63 | (major_version, minor_version, major_version, minor_version)),
64 | }
65 |
66 | requires = []
67 | for dep in info.get('depends', []):
68 | if not re.match(r'(ir|res)(\W|$)', dep):
69 | module_name = '%s_%s' % (MODULE2PREFIX.get(dep, 'trytond'), dep)
70 | requires.append(get_require_version(module_name))
71 | requires.append(get_require_version('trytond'))
72 |
73 | tests_require = [get_require_version('proteus'), 'pytz']
74 | for dep in info.get('extras_depend', []):
75 | module_name = '%s_%s' % (MODULE2PREFIX.get(dep, 'trytond'), dep)
76 | tests_require.append(get_require_version(module_name))
77 |
78 | setup(name='%s_%s' % (PREFIX, MODULE),
79 | version=version,
80 | description=('Tryton module to add account invoice (electronic/manual) '
81 | 'localization for Argentina (AFIP)'),
82 | long_description=read('README.rst'),
83 | author='tryton-ar',
84 | url=url,
85 | download_url=download_url,
86 | project_urls={
87 | "Bug Tracker": 'https://github.com/tryton-ar/%s/issues' % MODULE,
88 | "Documentation": 'https://docs.tryton.org/',
89 | "Forum": 'https://www.tryton.org/forum',
90 | "Source Code": url,
91 | },
92 | keywords='tryton, invoice, account, argentina, afip',
93 | package_dir={'trytond.modules.%s' % MODULE: '.'},
94 | packages=(
95 | ['trytond.modules.%s' % MODULE]
96 | + ['trytond.modules.%s.%s' % (MODULE, p) for p in find_packages()]
97 | ),
98 | package_data={
99 | 'trytond.modules.%s' % MODULE: (info.get('xml', []) + [
100 | 'tryton.cfg', 'view/*.xml', 'locale/*.po', '*.fodt',
101 | 'tests/*.rst']),
102 | },
103 | classifiers=[
104 | 'Development Status :: 5 - Production/Stable',
105 | 'Environment :: Plugins',
106 | 'Framework :: Tryton',
107 | 'Intended Audience :: Developers',
108 | 'Intended Audience :: Financial and Insurance Industry',
109 | 'Intended Audience :: Legal Industry',
110 | 'License :: OSI Approved :: '
111 | 'GNU General Public License v3 or later (GPLv3+)',
112 | 'Natural Language :: English',
113 | 'Natural Language :: Spanish',
114 | 'Operating System :: OS Independent',
115 | 'Programming Language :: Python :: 3',
116 | 'Programming Language :: Python :: 3.8',
117 | 'Programming Language :: Python :: 3.9',
118 | 'Programming Language :: Python :: 3.10',
119 | 'Programming Language :: Python :: 3.11',
120 | 'Programming Language :: Python :: 3.12',
121 | 'Programming Language :: Python :: Implementation :: CPython',
122 | 'Topic :: Office/Business',
123 | 'Topic :: Office/Business :: Financial :: Accounting',
124 | ],
125 | license='GPL-3',
126 | python_requires='>=3.8',
127 | install_requires=requires,
128 | extras_require={
129 | 'test': tests_require,
130 | },
131 | zip_safe=False,
132 | entry_points="""
133 | [trytond.modules]
134 | %s = trytond.modules.%s
135 | [console_scripts]
136 | trytond_update_currencies_afip = trytond.modules.%s.scripts.update_currencies:run
137 | """ % (MODULE, MODULE, MODULE),
138 | )
139 |
--------------------------------------------------------------------------------
/message.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debe establecer los valores "Fecha desde" y "Fecha hasta" en el Diario, correspondientes al servicio que se está facturando
6 |
7 |
8 | El número de la factura (%(cbte_nro)d), no coincide con el que espera la AFIP (%(cbte_nro_next)d). Modifique la secuencia del diario
9 |
10 |
11 | No fue posible obtener el CAE de la factura "%(invoice)s" para el cliente "%(party)s". Mensaje: "%(msg)s"
12 |
13 |
14 | Este diario (%s) no tiene establecido los datos necesaios para facturar electrónicamente
15 |
16 |
17 | No existe una secuencia para facturas del tipo: %(invoice_type)s
18 |
19 |
20 | Existe mas de una secuencia para facturas del tipo: %(invoice_type)s
21 |
22 |
23 | The iva condition on company "%(company)s" is missing.
24 |
25 |
26 | The iva condition on party "%(party)s" is missing.
27 |
28 |
29 | El campo "Tipo de factura" es requerido.
30 |
31 |
32 | Debe configurar el código AFIP de la moneda.
33 |
34 |
35 | Debe configurar la cotización de la moneda.
36 |
37 |
38 | Cotización no válida para AFIP. Deber ser: %(afip_ctz)s
39 |
40 |
41 | Debe establecer el valor de Incoterms si desea realizar un tipo de "Factura E".
42 |
43 |
44 | El numero de factura ya ha sido ingresado en el sistema.
45 |
46 |
47 | El impuesto (%s) debe tener un grupo asignado (iibb, municipal, iva).
48 |
49 |
50 | El campo "Referencia" es requerido.
51 |
52 |
53 | Facturas rechazadas (ID: %(invoices)s):
54 | %(msg)s
55 |
56 |
57 | AFIP web service is unknown
58 |
59 |
60 | AFIP webservice %(service)s is not yet supported!
61 |
62 |
63 | The company is not defined
64 |
65 |
66 | There was a problem to connect webservice WSAA: (%(msg)s)
67 |
68 |
69 | Error CAESolicitarX: (%s)
70 |
71 |
72 | The value "%(ref_value)s" is not a number.
73 |
74 |
75 | La empresa no tiene configurado el identificador impositivo
76 |
77 |
78 | The "concept" is required if pos type is electronic
79 |
80 |
81 | Si el tipo de comprobante es MiPyme (FCE) es obligatorio informar CBU.
82 |
83 |
84 | Falta cargar el Comprobante asociado o un Período de fechas
85 |
86 |
87 | El Comprobante asociado es incorrecto
88 |
89 |
90 | Falta fecha factura
91 |
92 |
93 | Existen diferencias entre el comprobante de AFIP y el del sistema
94 |
95 |
96 | Es requisito que el tercero "%(party)s" tenga CUIT Pais.
97 |
98 |
99 | Invoice "%(invoice)s" is missing a VAT tax.
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/tests/tools.py:
--------------------------------------------------------------------------------
1 | # This file is part of the account_invoice_ar module for Tryton.
2 | # The COPYRIGHT file at the top level of this repository contains
3 | # the full copyright notices and license terms.
4 | import os
5 | import sys
6 | from pyafipws.wsaa import WSAA
7 | from pyafipws.wsfev1 import WSFEv1
8 | from pyafipws.wsfexv1 import WSFEXv1
9 | from proteus import Model
10 |
11 | from trytond.modules.company.tests.tools import get_company
12 | from trytond.modules.party_ar.tests.tools import set_afip_certs
13 | from trytond.modules.party_ar.afip import PyAfipWsWrapper
14 |
15 | __all__ = ['create_pos', 'get_pos', 'get_invoice_types',
16 | 'get_tax', 'get_tax_group', 'get_wsfev1', 'get_wsfexv1']
17 |
18 |
19 | def create_pos(company=None, type='manual', number=1, ws=None, config=None):
20 | "Create a Point of Sale"
21 | Pos = Model.get('account.pos', config=config)
22 | SequenceType = Model.get('ir.sequence.type', config=config)
23 | Sequence = Model.get('ir.sequence', config=config)
24 |
25 | if not company:
26 | company = get_company()
27 |
28 | pos = Pos(
29 | company=company.id,
30 | number=number,
31 | pos_type=type,
32 | pyafipws_electronic_invoice_service=ws,
33 | )
34 | sequence_type, = SequenceType.find([
35 | ('name', '=', 'Invoice'),
36 | ], limit=1)
37 |
38 | for attr, name in (
39 | ('1', '01-Factura A'),
40 | ('2', '02-Nota de Debito A'),
41 | ('3', '03-Nota de Credito A'),
42 | ('4', '04-Recibos A'),
43 | ('5', '05-Nota de Venta al Contado A'),
44 | ('6', '06-Factura B'),
45 | ('7', '07-Nota de Debito B'),
46 | ('8', '08-Nota de Credito B'),
47 | ('9', '09-Recibos B'),
48 | ('10', '10-Notas de Venta al Contado B'),
49 | ('11', '11-Factura C'),
50 | ('12', '12-Nota de Debito C'),
51 | ('13', '13-Nota de Credito C'),
52 | ('15', '15-Recibo C'),
53 | ('19', '19-Factura E'),
54 | ('20', '20-Nota de Débito E'),
55 | ('21', '21-Nota de Crédito E'),
56 | ('201', '201-Factura de Crédito Electrónica MiPyMEs (FCE) A'),
57 | ('202', '202-Nota de Débito Electrónica MiPyMEs (FCE) A'),
58 | ('203', '203-Nota de Crédito Electrónica MiPyMEs (FCE) A'),
59 | ('206', '206-Factura de Crédito Electrónica MiPyMEs (FCE) B'),
60 | ('207', '207-Nota de Débito Electrónica MiPyMEs (FCE) B'),
61 | ('208', '208-Nota de Crédito Electrónica MiPyMEs (FCE) B'),
62 | ('211', '211-Factura de Crédito Electrónica MiPyMEs (FCE) C'),
63 | ('212', '212-Nota de Débito Electrónica MiPyMEs (FCE) C'),
64 | ('213', '213-Nota de Crédito Electrónica MiPyMEs (FCE) C')):
65 | sequence = Sequence(
66 | name='%s %s' % (name, type),
67 | sequence_type=sequence_type,
68 | company=company)
69 | sequence.save()
70 | pos.pos_sequences.new(
71 | invoice_type=attr,
72 | invoice_sequence=sequence,
73 | )
74 | pos.save()
75 | return pos
76 |
77 |
78 | def get_pos(company=None, type='manual', number=1, config=None):
79 | "Return the only pos"
80 | Pos = Model.get('account.pos', config=config)
81 |
82 | if not company:
83 | company = get_company()
84 |
85 | pos, = Pos.find([
86 | ('company', '=', company.id),
87 | ('pos_type', '=', type),
88 | ('number', '=', number),
89 | ])
90 | return pos
91 |
92 |
93 | def get_invoice_types(company=None, pos=None, config=None):
94 | "Return invoices types per pos and company"
95 | PosSequence = Model.get('account.pos.sequence', config=config)
96 |
97 | if not company:
98 | company = get_company()
99 |
100 | if not pos:
101 | pos = get_pos(company)
102 |
103 | invoice_types = PosSequence.find([
104 | ('pos', '=', pos.id),
105 | ])
106 | invoice_types = {i.invoice_type: i for i in invoice_types}
107 | return invoice_types
108 |
109 |
110 | def get_tax(name='IVA Ventas 21%', config=None):
111 | "Return tax"
112 | Tax = Model.get('account.tax', config=config)
113 |
114 | tax, = Tax.find([
115 | ('name', '=', name),
116 | ])
117 |
118 | return tax
119 |
120 |
121 | def get_tax_group(code='IVA', kind='sale', afip_kind='gravado', config=None):
122 | "Return tax group"
123 | TaxGroup = Model.get('account.tax.group', config=config)
124 |
125 | group, = TaxGroup.find([
126 | ('code', '=', code),
127 | ('kind', '=', kind),
128 | ('afip_kind', '=', afip_kind),
129 | ])
130 |
131 | return group
132 |
133 |
134 | def get_wsfev1(company=None, config=None):
135 | "return wsfev1 object"
136 | if not company:
137 | company = get_company()
138 | company = set_afip_certs(company, config)
139 |
140 | URL_WSFEv1 = "https://wswhomo.afip.gov.ar/wsfev1/service.asmx?WSDL"
141 | URL_WSAA = "https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl"
142 | cache = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cache')
143 | crt = str(company.pyafipws_certificate)
144 | key = str(company.pyafipws_private_key)
145 |
146 | ta = PyAfipWsWrapper().authenticate('wsfe', crt, key, wsdl=URL_WSAA, cache=cache)
147 | wsfev1 = WSFEv1()
148 | wsfev1.LanzarExcepciones = True
149 | wsfev1.SetTicketAcceso(ta)
150 | wsfev1.Cuit = company.party.vat_number
151 | wsfev1.Conectar(wsdl=URL_WSFEv1, cache=cache, cacert=True)
152 | return wsfev1
153 |
154 |
155 | def get_wsfexv1(company=None, config=None):
156 | "return wsfexv1 object"
157 | if not company:
158 | company = get_company()
159 | company = set_afip_certs(company, config)
160 |
161 | URL_WSAA = "https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl"
162 | URL_WSFEXv1 = "https://wswhomo.afip.gov.ar/wsfexv1/service.asmx?WSDL"
163 |
164 | cache = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cache')
165 | crt = str(company.pyafipws_certificate)
166 | key = str(company.pyafipws_private_key)
167 |
168 | ta = PyAfipWsWrapper().authenticate('wsfex', crt, key, wsdl=URL_WSAA, cache=cache)
169 | wsfexv1 = WSFEXv1()
170 | wsfexv1.LanzarExcepciones = True
171 | wsfexv1.SetTicketAcceso(ta)
172 | wsfexv1.Cuit = company.party.vat_number
173 | wsfexv1.Conectar(wsdl=URL_WSFEXv1, cache=cache, cacert=True)
174 | return wsfexv1
175 |
--------------------------------------------------------------------------------
/pos.py:
--------------------------------------------------------------------------------
1 | # This file is part of the account_invoice_ar module for Tryton.
2 | # The COPYRIGHT file at the top level of this repository contains
3 | # the full copyright notices and license terms.
4 |
5 | from trytond.model import ModelView, ModelSQL, fields, Index
6 | from trytond.pool import Pool
7 | from trytond.pyson import Eval, Id
8 | from trytond.transaction import Transaction
9 |
10 | INVOICE_TYPE_POS = [
11 | ('', ''),
12 | ('1', '01-Factura A'),
13 | ('2', '02-Nota de Débito A'),
14 | ('3', '03-Nota de Crédito A'),
15 | ('4', '04-Recibos A'),
16 | ('5', '05-Nota de Venta al Contado A'),
17 | ('6', '06-Factura B'),
18 | ('7', '07-Nota de Débito B'),
19 | ('8', '08-Nota de Crédito B'),
20 | ('9', '09-Recibos B'),
21 | ('10', '10-Notas de Venta al Contado B'),
22 | ('11', '11-Factura C'),
23 | ('12', '12-Nota de Débito C'),
24 | ('13', '13-Nota de Crédito C'),
25 | ('15', '15-Recibo C'),
26 | ('19', '19-Factura E'),
27 | ('20', '20-Nota de Débito E'),
28 | ('21', '21-Nota de Crédito E'),
29 | ('201', '201-Factura de Crédito Electrónica MiPyMEs A'),
30 | ('202', '202-Nota de Débito Electrónica MiPyMEs A'),
31 | ('203', '203-Nota de Crédito Electrónica MiPyMEs A'),
32 | ('206', '206-Factura de Crédito Electrónica MiPyMEs B'),
33 | ('207', '207-Nota de Débito Electrónica MiPyMEs B'),
34 | ('208', '208-Nota de Crédito Electrónica MiPyMEs B'),
35 | ('211', '211-Factura de Crédito Electrónica MiPyMEs C'),
36 | ('212', '212-Nota de Débito Electrónica MiPyMEs C'),
37 | ('213', '213-Nota de Crédito Electrónica MiPyMEs C'),
38 | ]
39 |
40 |
41 | class Pos(ModelSQL, ModelView):
42 | 'Point of Sale'
43 | __name__ = 'account.pos'
44 |
45 | _states = {'readonly': ~Eval('active', True)}
46 |
47 | company = fields.Many2One('company.company', 'Company', required=True,
48 | states=_states)
49 | number = fields.Integer('Punto de Venta AFIP', required=True,
50 | states=_states, help='Prefijo de emisión habilitado en AFIP')
51 | pos_sequences = fields.One2Many('account.pos.sequence', 'pos',
52 | 'Point of Sale', context={'company': Eval('company', -1)},
53 | states=_states, depends={'company'})
54 | pos_type = fields.Selection([
55 | ('manual', 'Manual'),
56 | ('electronic', 'Electronic'),
57 | ('fiscal_printer', 'Fiscal Printer'),
58 | ], 'Pos Type', required=True, states=_states)
59 | pos_type_string = pos_type.translated('pos_type')
60 | pos_daily_report = fields.Boolean('Cierre diario (ZETA)',
61 | states={'invisible': Eval('pos_type') != 'fiscal_printer'})
62 | pyafipws_electronic_invoice_service = fields.Selection([
63 | ('', ''),
64 | ('wsfe', 'Mercado interno -sin detalle- RG2485 (WSFEv1)'),
65 | #('wsmtxca', 'Mercado interno -con detalle- RG2904 (WSMTXCA)'),
66 | ('wsbfe', 'Bono Fiscal -con detalle- RG2557 (WSMTXCA)'),
67 | ('wsfex', 'Exportación -con detalle- RG2758 (WSFEXv1)'),
68 | ], 'AFIP Web Service',
69 | states={
70 | 'required': Eval('pos_type') == 'electronic',
71 | 'invisible': Eval('pos_type') != 'electronic',
72 | 'readonly': ~Eval('active', True),
73 | },
74 | help='Habilita la facturación electrónica por webservices AFIP')
75 | active = fields.Boolean('Active')
76 |
77 | del _states
78 |
79 | @classmethod
80 | def __setup__(cls):
81 | super().__setup__()
82 | t = cls.__table__()
83 | #cls._sql_indexes.update({
84 | #Index(t, (t.active, Index.Equality())),
85 | #})
86 |
87 | @classmethod
88 | def __register__(cls, module_name):
89 | cursor = Transaction().connection.cursor()
90 | pos_table = cls.__table__()
91 | company_table = Pool().get('company.company').__table__()
92 |
93 | table_h = cls.__table_handler__(module_name)
94 | company_exist = table_h.column_exist('company')
95 |
96 | super().__register__(module_name)
97 |
98 | # Migration from 4.2: company is required
99 | if not company_exist:
100 | cursor.execute(*company_table.select(company_table.id))
101 | company = cursor.fetchone()
102 | if company:
103 | cursor.execute(*pos_table.update([pos_table.company],
104 | [company[0]]))
105 |
106 | @staticmethod
107 | def default_company():
108 | return Transaction().context.get('company')
109 |
110 | @staticmethod
111 | def default_pos_type():
112 | return 'manual'
113 |
114 | @staticmethod
115 | def default_active():
116 | return True
117 |
118 | def get_rec_name(self, name):
119 | if self.pos_type and self.number:
120 | return '[%s] %s' % (str(self.number), self.pos_type_string)
121 |
122 | @classmethod
123 | def search_rec_name(cls, name, clause):
124 | return [('pos_type',) + tuple(clause[1:])]
125 |
126 |
127 | class PosSequence(ModelSQL, ModelView):
128 | 'Point of Sale Sequences'
129 | __name__ = 'account.pos.sequence'
130 |
131 | pos = fields.Many2One('account.pos', 'Point of Sale',
132 | ondelete='CASCADE', required=True)
133 | invoice_type = fields.Selection(INVOICE_TYPE_POS,
134 | 'Tipo Comprobante AFIP', required=True,
135 | help='Tipo de Comprobante AFIP')
136 | invoice_type_string = invoice_type.translated('invoice_type')
137 | invoice_sequence = fields.Many2One('ir.sequence',
138 | 'Sequence',
139 | domain=[
140 | ('sequence_type', '=',
141 | Id('account_invoice', 'sequence_type_account_invoice')),
142 | ('company', '=', Eval('context', {}).get('company', -1)),
143 | ])
144 |
145 | @classmethod
146 | def __setup__(cls):
147 | super().__setup__()
148 | t = cls.__table__()
149 | #cls._sql_indexes.update({
150 | #Index(t, (t.invoice_type, Index.Equality())),
151 | #})
152 |
153 | @classmethod
154 | def __register__(cls, module_name):
155 | cursor = Transaction().connection.cursor()
156 | property_table = 'ir_property'
157 | pos_sequence_table = cls._table
158 |
159 | table_h = cls.__table_handler__(module_name)
160 | invoice_sequence_exist = table_h.column_exist('invoice_sequence')
161 | property_table_exist = table_h.table_exist(property_table)
162 |
163 | super().__register__(module_name)
164 |
165 | # Migration from 4.2: set invoice_sequence
166 | if not invoice_sequence_exist and property_table_exist:
167 | cursor.execute('UPDATE "' + pos_sequence_table + '" '
168 | 'SET invoice_sequence = ('
169 | 'SELECT split_part(value, \',\', 2) '
170 | 'FROM "' + property_table + '" '
171 | 'WHERE split_part(res, \',\', 1) = '
172 | '\'account.pos.sequence\' '
173 | 'AND split_part(res, \',\', 2)::INTEGER = '
174 | '"' + pos_sequence_table + '".id'
175 | ')::INTEGER')
176 |
177 | def get_rec_name(self, name):
178 | if not self.invoice_type_string:
179 | return ''
180 | return self.invoice_type_string.split('-')[1]
181 |
--------------------------------------------------------------------------------
/tests/scenario_recover_invoice_electronic.rst:
--------------------------------------------------------------------------------
1 | ========================
2 | Recover Invoice Scenario
3 | ========================
4 |
5 | Imports::
6 | >>> import datetime as dt
7 | >>> from dateutil.relativedelta import relativedelta
8 | >>> from decimal import Decimal
9 | >>> from operator import attrgetter
10 | >>> from proteus import Model, Wizard
11 | >>> from trytond.tests.tools import activate_modules
12 | >>> from trytond.modules.currency.tests.tools import get_currency
13 | >>> from trytond.modules.company.tests.tools import create_company, \
14 | ... get_company
15 | >>> from trytond.modules.account.tests.tools import create_fiscalyear, \
16 | ... create_chart
17 | >>> from trytond.modules.account_ar.tests.tools import get_accounts
18 | >>> from trytond.modules.account_invoice.tests.tools import \
19 | ... set_fiscalyear_invoice_sequences
20 | >>> from trytond.modules.account_invoice_ar.tests.tools import \
21 | ... create_pos, get_pos, get_invoice_types, get_tax, get_wsfev1
22 | >>> from trytond.modules.party_ar.tests.tools import set_afip_certs
23 | >>> today = dt.date.today()
24 |
25 | Install account_invoice_ar::
26 |
27 | >>> config = activate_modules('account_invoice_ar')
28 |
29 | Create company::
30 |
31 | >>> currency = get_currency('ARS')
32 | >>> currency.afip_code = 'PES'
33 | >>> currency.save()
34 | >>> _ = create_company(currency=currency)
35 | >>> company = get_company()
36 | >>> tax_identifier = company.party.identifiers.new()
37 | >>> tax_identifier.type = 'ar_vat'
38 | >>> tax_identifier.code = '30710158254' # gcoop CUIT
39 | >>> company.party.iva_condition = 'responsable_inscripto'
40 | >>> company.party.save()
41 |
42 | Configure AFIP certificates::
43 |
44 | >>> _ = set_afip_certs(company=company)
45 |
46 | Create fiscal year::
47 |
48 | >>> fiscalyear = set_fiscalyear_invoice_sequences(
49 | ... create_fiscalyear(company))
50 | >>> fiscalyear.click('create_period')
51 | >>> period = fiscalyear.periods[0]
52 | >>> period_ids = [p.id for p in fiscalyear.periods]
53 |
54 | Create chart of accounts::
55 |
56 | >>> _ = create_chart(company, chart='account_ar.root_ar')
57 | >>> accounts = get_accounts(company)
58 | >>> account_receivable = accounts['receivable']
59 | >>> account_revenue = accounts['revenue']
60 | >>> account_expense = accounts['expense']
61 | >>> account_cash = accounts['cash']
62 |
63 | Create point of sale::
64 |
65 | >>> _ = create_pos(company, type='electronic', number=4000, ws='wsfe')
66 | >>> pos = get_pos(type='electronic', number=4000)
67 | >>> invoice_types = get_invoice_types(pos=pos)
68 |
69 | Create taxes::
70 |
71 | >>> sale_tax = get_tax('IVA Ventas 21%')
72 | >>> sale_tax_nogravado = get_tax('IVA Ventas No Gravado')
73 |
74 | Create payment method::
75 |
76 | >>> Journal = Model.get('account.journal')
77 | >>> PaymentMethod = Model.get('account.invoice.payment.method')
78 | >>> Sequence = Model.get('ir.sequence')
79 | >>> journal_cash, = Journal.find([('type', '=', 'cash')])
80 | >>> payment_method = PaymentMethod()
81 | >>> payment_method.name = 'Cash'
82 | >>> payment_method.journal = journal_cash
83 | >>> payment_method.credit_account = account_cash
84 | >>> payment_method.debit_account = account_cash
85 | >>> payment_method.save()
86 |
87 | Create Write Off method::
88 |
89 | >>> WriteOff = Model.get('account.move.reconcile.write_off')
90 | >>> sequence_journal, = Sequence.find(
91 | ... [('sequence_type.name', '=', "Account Journal")], limit=1)
92 | >>> journal_writeoff = Journal(name='Write-Off', type='write-off',
93 | ... sequence=sequence_journal)
94 | >>> journal_writeoff.save()
95 | >>> writeoff_method = WriteOff()
96 | >>> writeoff_method.name = 'Rate loss'
97 | >>> writeoff_method.journal = journal_writeoff
98 | >>> writeoff_method.credit_account = account_expense
99 | >>> writeoff_method.debit_account = account_expense
100 | >>> writeoff_method.save()
101 |
102 | Create party::
103 |
104 | >>> Party = Model.get('party.party')
105 | >>> party = Party(name='Party')
106 | >>> party.iva_condition='responsable_inscripto'
107 | >>> party.vat_number='30688555872'
108 | >>> party.account_receivable = account_receivable
109 | >>> party.save()
110 |
111 | Create party consumidor final::
112 |
113 | >>> Party = Model.get('party.party')
114 | >>> party_cf = Party(name='Party')
115 | >>> party_cf.iva_condition='consumidor_final'
116 | >>> party.account_receivable = account_receivable
117 | >>> party_cf.save()
118 |
119 | Create account category::
120 |
121 | >>> ProductCategory = Model.get('product.category')
122 | >>> account_category = ProductCategory(name="Account Category")
123 | >>> account_category.accounting = True
124 | >>> account_category.account_expense = account_expense
125 | >>> account_category.account_revenue = account_revenue
126 | >>> account_category.customer_taxes.append(sale_tax)
127 | >>> account_category.save()
128 |
129 | Create product::
130 |
131 | >>> ProductUom = Model.get('product.uom')
132 | >>> unit, = ProductUom.find([('name', '=', 'Unit')])
133 | >>> ProductTemplate = Model.get('product.template')
134 | >>> template = ProductTemplate()
135 | >>> template.name = 'product'
136 | >>> template.default_uom = unit
137 | >>> template.type = 'service'
138 | >>> template.list_price = Decimal('40')
139 | >>> template.account_category = account_category
140 | >>> template.save()
141 | >>> product, = template.products
142 |
143 | Create payment term::
144 |
145 | >>> PaymentTerm = Model.get('account.invoice.payment_term')
146 | >>> payment_term = PaymentTerm(name='Term')
147 | >>> line = payment_term.lines.new(type='percent', ratio=Decimal('.5'))
148 | >>> delta, = line.relativedeltas
149 | >>> delta.days = 20
150 | >>> line = payment_term.lines.new(type='remainder')
151 | >>> delta = line.relativedeltas.new(days=40)
152 | >>> payment_term.save()
153 |
154 | SetUp webservice AFIP::
155 |
156 | >>> wsfev1 = get_wsfev1(company, config)
157 |
158 | Get CompUltimoAutorizado and configure sequences::
159 |
160 | >>> cbte_nro = int(wsfev1.CompUltimoAutorizado('1', pos.number))
161 | >>> invoice_types['1'].invoice_sequence.number_next = cbte_nro + 1
162 | >>> invoice_types['1'].invoice_sequence.save()
163 |
164 | >>> cbte_nro = int(wsfev1.CompUltimoAutorizado('3', pos.number))
165 | >>> invoice_types['3'].invoice_sequence.number_next = cbte_nro + 1
166 | >>> invoice_types['3'].invoice_sequence.save()
167 |
168 | >>> cbte_nro = int(wsfev1.CompUltimoAutorizado('6', pos.number))
169 | >>> invoice_types['6'].invoice_sequence.number_next = cbte_nro + 1
170 | >>> invoice_types['6'].invoice_sequence.save()
171 |
172 | >>> cbte_nro = int(wsfev1.CompUltimoAutorizado('11', pos.number))
173 | >>> invoice_types['11'].invoice_sequence.number_next = cbte_nro + 1
174 | >>> invoice_types['11'].invoice_sequence.save()
175 |
176 | Create invoice::
177 |
178 | >>> Invoice = Model.get('account.invoice')
179 | >>> InvoiceLine = Model.get('account.invoice.line')
180 | >>> invoice = Invoice()
181 | >>> invoice.party = party
182 | >>> invoice.pos = pos
183 | >>> invoice.payment_term = payment_term
184 | >>> line = InvoiceLine()
185 | >>> invoice.lines.append(line)
186 | >>> line.product = product
187 | >>> line.quantity = 5
188 | >>> line.unit_price = Decimal('40')
189 | >>> line = InvoiceLine()
190 | >>> invoice.lines.append(line)
191 | >>> line.account = account_revenue
192 | >>> line.taxes.append(sale_tax_nogravado)
193 | >>> line.description = 'Test'
194 | >>> line.quantity = 1
195 | >>> line.unit_price = Decimal(20)
196 | >>> invoice.untaxed_amount
197 | Decimal('220.00')
198 | >>> invoice.tax_amount
199 | Decimal('42.00')
200 | >>> invoice.total_amount
201 | Decimal('262.00')
202 | >>> invoice.invoice_type == invoice_types['1']
203 | True
204 | >>> invoice.save()
205 | >>> invoice.pyafipws_concept = '1'
206 | >>> invoice.click('post')
207 |
208 | Duplicate and test recover last invoice::
209 |
210 | >>> last_cbte_nro = int(wsfev1.CompUltimoAutorizado('1', pos.number))
211 | >>> recover_invoice, = invoice.duplicate()
212 | >>> recover_invoice.pyafipws_concept
213 | '1'
214 | >>> recover_invoice.pyafipws_cae
215 | >>> recover_invoice.pyafipws_cae_due_date
216 | >>> recover_invoice.pos
217 | >>> recover_invoice.invoice_type
218 | >>> recover_invoice.transactions
219 | []
220 | >>> recover = Wizard('account.invoice.recover')
221 | >>> recover.form.pos = invoice.pos
222 | >>> recover.form.invoice_type = invoice.invoice_type
223 | >>> recover.form.cbte_nro = last_cbte_nro
224 | >>> recover.execute('ask_afip')
225 | >>> recover.state
226 | 'ask_afip'
227 | >>> recover.form.invoice = recover_invoice
228 | >>> recover.form.CbteNro == str(last_cbte_nro)
229 | True
230 | >>> recover.form.CAE == invoice.pyafipws_cae
231 | True
232 | >>> recover.execute('save_invoice')
233 | >>> recover_invoice.reload()
234 | >>> recover_invoice.state
235 | 'posted'
236 | >>> bool(recover_invoice.move)
237 | True
238 | >>> recover_invoice.invoice_date == invoice.invoice_date
239 | True
240 | >>> recover_invoice.pos == invoice.pos
241 | True
242 | >>> recover_invoice.invoice_type == invoice.invoice_type
243 | True
244 | >>> recover_invoice.number == invoice.number
245 | True
246 | >>> recover_invoice.pyafipws_cae == invoice.pyafipws_cae
247 | True
248 |
--------------------------------------------------------------------------------
/tests/scenario_invoice_supplier.rst:
--------------------------------------------------------------------------------
1 | =========================
2 | Invoice Supplier Scenario
3 | =========================
4 |
5 | Imports::
6 | >>> import datetime as dt
7 | >>> from dateutil.relativedelta import relativedelta
8 | >>> from decimal import Decimal
9 | >>> from proteus import Model, Wizard
10 | >>> from trytond.tests.tools import activate_modules
11 | >>> from trytond.modules.currency.tests.tools import get_currency
12 | >>> from trytond.modules.company.tests.tools import create_company, \
13 | ... get_company
14 | >>> from trytond.modules.account.tests.tools import create_fiscalyear, \
15 | ... create_chart
16 | >>> from trytond.modules.account_ar.tests.tools import get_accounts
17 | >>> from trytond.modules.account_invoice.tests.tools import \
18 | ... set_fiscalyear_invoice_sequences
19 | >>> from trytond.modules.account_invoice_ar.tests.tools import \
20 | ... get_tax
21 | >>> today = dt.date.today()
22 |
23 | Install account_invoice_ar::
24 |
25 | >>> config = activate_modules('account_invoice_ar')
26 |
27 | Create company::
28 |
29 | >>> currency = get_currency('ARS')
30 | >>> currency.afip_code = 'PES'
31 | >>> currency.save()
32 | >>> _ = create_company(currency=currency)
33 | >>> company = get_company()
34 | >>> tax_identifier = company.party.identifiers.new()
35 | >>> tax_identifier.type = 'ar_vat'
36 | >>> tax_identifier.code = '30710158254' # gcoop CUIT
37 | >>> company.party.iva_condition = 'responsable_inscripto'
38 | >>> company.party.save()
39 |
40 | Create fiscal year::
41 |
42 | >>> fiscalyear = set_fiscalyear_invoice_sequences(
43 | ... create_fiscalyear(company))
44 | >>> fiscalyear.click('create_period')
45 | >>> period_ids = [p.id for p in fiscalyear.periods]
46 |
47 | Create chart of accounts::
48 |
49 | >>> _ = create_chart(company, chart='account_ar.root_ar')
50 | >>> accounts = get_accounts(company)
51 | >>> account_receivable = accounts['receivable']
52 | >>> account_payable = accounts['payable']
53 | >>> account_revenue = accounts['revenue']
54 | >>> account_expense = accounts['expense']
55 | >>> account_tax = accounts['purchase_tax']
56 | >>> account_cash = accounts['cash']
57 |
58 | Create taxes::
59 |
60 | >>> purchase_tax = get_tax('IVA Compras 21%')
61 | >>> purchase_tax_nogravado = get_tax('IVA Compras No Gravado')
62 |
63 | Create party::
64 |
65 | >>> Party = Model.get('party.party')
66 | >>> party = Party(name='Party',
67 | ... iva_condition='responsable_inscripto',
68 | ... vat_number='33333333339')
69 | >>> party.account_payable = account_payable
70 | >>> party.save()
71 |
72 | Create account category::
73 |
74 | >>> ProductCategory = Model.get('product.category')
75 | >>> account_category = ProductCategory(name="Account Category")
76 | >>> account_category.accounting = True
77 | >>> account_category.account_expense = account_expense
78 | >>> account_category.account_revenue = account_revenue
79 | >>> account_category.supplier_taxes.append(purchase_tax)
80 | >>> account_category.save()
81 |
82 | Create product::
83 |
84 | >>> ProductUom = Model.get('product.uom')
85 | >>> unit, = ProductUom.find([('name', '=', 'Unit')])
86 | >>> ProductTemplate = Model.get('product.template')
87 | >>> template = ProductTemplate()
88 | >>> template.name = 'product'
89 | >>> template.default_uom = unit
90 | >>> template.type = 'service'
91 | >>> template.list_price = Decimal('40')
92 | >>> template.account_category = account_category
93 | >>> template.save()
94 | >>> product, = template.products
95 |
96 | Create payment term::
97 |
98 | >>> PaymentTerm = Model.get('account.invoice.payment_term')
99 | >>> payment_term = PaymentTerm(name='Term')
100 | >>> line = payment_term.lines.new(type='remainder')
101 | >>> payment_term.save()
102 |
103 | Create invoice::
104 |
105 | >>> Invoice = Model.get('account.invoice')
106 | >>> InvoiceLine = Model.get('account.invoice.line')
107 | >>> invoice = Invoice()
108 | >>> invoice.type = 'in'
109 | >>> invoice.party = party
110 | >>> invoice.payment_term = payment_term
111 | >>> invoice.invoice_date = today
112 | >>> invoice.tipo_comprobante = '001'
113 | >>> invoice.ref_pos_number = '1'
114 | >>> invoice.ref_voucher_number = '312'
115 | >>> line = InvoiceLine()
116 | >>> invoice.lines.append(line)
117 | >>> line.product = product
118 | >>> line.quantity = 5
119 | >>> line.unit_price = Decimal('20')
120 | >>> line = InvoiceLine()
121 | >>> invoice.lines.append(line)
122 | >>> line.account = account_expense
123 | >>> line.taxes.append(purchase_tax_nogravado)
124 | >>> line.description = 'Test'
125 | >>> line.quantity = 1
126 | >>> line.unit_price = Decimal(10)
127 | >>> invoice.untaxed_amount
128 | Decimal('110.00')
129 | >>> invoice.tax_amount
130 | Decimal('21.00')
131 | >>> invoice.total_amount
132 | Decimal('131.00')
133 | >>> invoice.save()
134 | >>> invoice.reference
135 | '00001-00000312'
136 | >>> invoice.state
137 | 'draft'
138 | >>> bool(invoice.move)
139 | False
140 | >>> invoice.click('validate_invoice')
141 | >>> invoice.state
142 | 'validated'
143 | >>> bool(invoice.move)
144 | True
145 | >>> invoice.move.state
146 | 'draft'
147 | >>> invoice.click('post')
148 | >>> invoice.state
149 | 'posted'
150 | >>> bool(invoice.move)
151 | True
152 | >>> invoice.move.state
153 | 'posted'
154 | >>> invoice.untaxed_amount
155 | Decimal('110.00')
156 | >>> invoice.tax_amount
157 | Decimal('21.00')
158 | >>> invoice.total_amount
159 | Decimal('131.00')
160 | >>> account_payable.reload()
161 | >>> account_payable.debit
162 | Decimal('0.00')
163 | >>> account_payable.credit
164 | Decimal('131.00')
165 | >>> account_expense.reload()
166 | >>> account_expense.debit
167 | Decimal('110.00')
168 | >>> account_expense.credit
169 | Decimal('0.00')
170 | >>> account_tax.reload()
171 | >>> account_tax.debit
172 | Decimal('21.00')
173 | >>> account_tax.credit
174 | Decimal('0.00')
175 |
176 | Credit invoice::
177 |
178 | >>> credit = Wizard('account.invoice.credit', [invoice])
179 | >>> credit.form.with_refund = False
180 | >>> credit.form.invoice_date = invoice.invoice_date
181 | >>> credit.execute('credit')
182 | >>> credit_note, = Invoice.find(
183 | ... [('type', '=', 'in'), ('id', '!=', invoice.id)])
184 | >>> credit_note.state
185 | 'draft'
186 | >>> credit_note.untaxed_amount == -invoice.untaxed_amount
187 | True
188 | >>> credit_note.tax_amount == -invoice.tax_amount
189 | True
190 | >>> credit_note.total_amount == -invoice.total_amount
191 | True
192 | >>> credit_note.tipo_comprobante == '003'
193 | True
194 | >>> credit_note.reference
195 | >>> credit_note.ref_pos_number = '1'
196 | >>> credit_note.ref_voucher_number = '55'
197 | >>> credit_note.invoice_date = today
198 | >>> credit_note.click('validate_invoice')
199 | >>> credit_note.reference
200 | '00001-00000055'
201 |
202 | Create a draft and post invoice::
203 |
204 | >>> invoice = Invoice()
205 | >>> invoice.type = 'in'
206 | >>> invoice.party = party
207 | >>> invoice.payment_term = payment_term
208 | >>> invoice.invoice_date = today
209 | >>> invoice.tipo_comprobante = '081'
210 | >>> invoice.ref_pos_number = '5'
211 | >>> invoice.ref_voucher_number = '333'
212 | >>> line = invoice.lines.new()
213 | >>> line.product = product
214 | >>> line.quantity = 1
215 | >>> line.unit_price = Decimal('20')
216 | >>> invoice.click('post')
217 | >>> invoice.reference
218 | '00005-00000333'
219 |
220 | Credit invoice::
221 |
222 | >>> credit = Wizard('account.invoice.credit', [invoice])
223 | >>> credit.form.with_refund = False
224 | >>> credit.execute('credit')
225 | >>> credit_note, = Invoice.find(
226 | ... [('type', '=', 'in'), ('state', '=', 'draft')])
227 | >>> credit_note.state
228 | 'draft'
229 | >>> credit_note.untaxed_amount == -invoice.untaxed_amount
230 | True
231 | >>> credit_note.tax_amount == -invoice.tax_amount
232 | True
233 | >>> credit_note.total_amount == -invoice.total_amount
234 | True
235 | >>> credit_note.tipo_comprobante == '112'
236 | True
237 | >>> credit_note.reference
238 |
239 | Create a posted and a draft invoice to cancel::
240 |
241 | >>> invoice = Invoice()
242 | >>> invoice.type = 'in'
243 | >>> invoice.party = party
244 | >>> invoice.payment_term = payment_term
245 | >>> invoice.invoice_date = today
246 | >>> invoice.tipo_comprobante = '001'
247 | >>> invoice.ref_pos_number = '1'
248 | >>> invoice.ref_voucher_number = '123'
249 | >>> line = invoice.lines.new()
250 | >>> line.product = product
251 | >>> line.quantity = 1
252 | >>> line.unit_price = Decimal('20')
253 | >>> invoice.click('post')
254 | >>> invoice.reference
255 | '00001-00000123'
256 | >>> invoice_draft, = Invoice.duplicate([invoice])
257 |
258 |
259 | Cancel draft invoice::
260 |
261 | >>> invoice_draft.tipo_comprobante
262 | >>> invoice_draft.reference
263 | >>> invoice_draft.click('cancel')
264 | >>> invoice_draft.state
265 | 'cancelled'
266 | >>> invoice_draft.move
267 | >>> invoice_draft.reconciled
268 |
269 | Cancel posted invoice::
270 |
271 | >>> invoice.click('cancel')
272 | >>> invoice.state
273 | 'cancelled'
274 | >>> invoice.cancel_move is not None
275 | True
276 | >>> invoice.reconciled == today
277 | True
278 |
--------------------------------------------------------------------------------
/tests/scenario_invoice_pos_electronic_fce.rst:
--------------------------------------------------------------------------------
1 | ================
2 | Invoice Scenario
3 | ================
4 |
5 | Imports::
6 | >>> import datetime as dt
7 | >>> from dateutil.relativedelta import relativedelta
8 | >>> from decimal import Decimal
9 | >>> from proteus import Model, Wizard
10 | >>> from trytond.tests.tools import activate_modules
11 | >>> from trytond.modules.currency.tests.tools import get_currency
12 | >>> from trytond.modules.company.tests.tools import create_company, \
13 | ... get_company
14 | >>> from trytond.modules.account.tests.tools import create_fiscalyear, \
15 | ... create_chart
16 | >>> from trytond.modules.account_ar.tests.tools import get_accounts
17 | >>> from trytond.modules.account_invoice.tests.tools import \
18 | ... set_fiscalyear_invoice_sequences
19 | >>> from trytond.modules.account_invoice_ar.tests.tools import \
20 | ... create_pos, get_pos, get_invoice_types, get_tax, get_wsfev1
21 | >>> from trytond.modules.party_ar.tests.tools import set_afip_certs
22 | >>> import pytz
23 | >>> timezone = pytz.timezone('America/Argentina/Buenos_Aires')
24 | >>> today = dt.datetime.now(timezone).date()
25 |
26 | Install account_invoice_ar::
27 |
28 | >>> config = activate_modules('account_invoice_ar')
29 |
30 | Create company::
31 |
32 | >>> currency = get_currency('ARS')
33 | >>> currency.afip_code = 'PES'
34 | >>> currency.save()
35 | >>> _ = create_company(currency=currency)
36 | >>> company = get_company()
37 | >>> tax_identifier = company.party.identifiers.new()
38 | >>> tax_identifier.type = 'ar_vat'
39 | >>> tax_identifier.code = '30710158254' # gcoop CUIT
40 | >>> company.party.iva_condition = 'responsable_inscripto'
41 | >>> company.party.save()
42 |
43 | Configure company timezone::
44 |
45 | >>> company.timezone = 'America/Argentina/Buenos_Aires'
46 | >>> company.save()
47 |
48 | Configure AFIP certificates::
49 |
50 | >>> _ = set_afip_certs(company=company)
51 |
52 | Create fiscal year::
53 |
54 | >>> fiscalyear = set_fiscalyear_invoice_sequences(
55 | ... create_fiscalyear(company))
56 | >>> fiscalyear.click('create_period')
57 | >>> period = fiscalyear.periods[0]
58 | >>> period_ids = [p.id for p in fiscalyear.periods]
59 |
60 | Create chart of accounts::
61 |
62 | >>> _ = create_chart(company, chart='account_ar.root_ar')
63 | >>> accounts = get_accounts(company)
64 | >>> account_receivable = accounts['receivable']
65 | >>> account_payable = accounts['payable']
66 | >>> account_revenue = accounts['revenue']
67 | >>> account_expense = accounts['expense']
68 | >>> account_tax = accounts['sale_tax']
69 | >>> account_cash = accounts['cash']
70 |
71 | Create point of sale::
72 |
73 | >>> _ = create_pos(company, type='electronic', number=4000, ws='wsfe')
74 | >>> pos = get_pos(type='electronic', number=4000)
75 | >>> invoice_types = get_invoice_types(pos=pos)
76 |
77 | Create taxes::
78 |
79 | >>> sale_tax = get_tax('IVA Ventas 21%')
80 | >>> purchase_tax = get_tax('IVA Compras 21%')
81 |
82 | Create payment method::
83 |
84 | >>> Journal = Model.get('account.journal')
85 | >>> PaymentMethod = Model.get('account.invoice.payment.method')
86 | >>> Sequence = Model.get('ir.sequence')
87 | >>> journal_cash, = Journal.find([('type', '=', 'cash')])
88 | >>> payment_method = PaymentMethod()
89 | >>> payment_method.name = 'Cash'
90 | >>> payment_method.journal = journal_cash
91 | >>> payment_method.credit_account = account_cash
92 | >>> payment_method.debit_account = account_cash
93 | >>> payment_method.save()
94 |
95 | Create Write Off method::
96 |
97 | >>> WriteOff = Model.get('account.move.reconcile.write_off')
98 | >>> sequence_journal, = Sequence.find(
99 | ... [('sequence_type.name', '=', "Account Journal")], limit=1)
100 | >>> journal_writeoff = Journal(name='Write-Off', type='write-off',
101 | ... sequence=sequence_journal)
102 | >>> journal_writeoff.save()
103 | >>> writeoff_method = WriteOff()
104 | >>> writeoff_method.name = 'Rate loss'
105 | >>> writeoff_method.journal = journal_writeoff
106 | >>> writeoff_method.credit_account = account_expense
107 | >>> writeoff_method.debit_account = account_expense
108 | >>> writeoff_method.save()
109 |
110 | Create party::
111 |
112 | >>> Party = Model.get('party.party')
113 | >>> party = Party(name='Party')
114 | >>> party.iva_condition='responsable_inscripto'
115 | >>> party.vat_number='30571421352' # CUIT credicoop
116 | >>> party.account_payable = account_payable
117 | >>> party.account_receivable = account_receivable
118 | >>> party.pyafipws_fce = True
119 | >>> party.pyafipws_fce_amount = Decimal('50000')
120 | >>> party.save()
121 |
122 | Create bank party::
123 |
124 | >>> Party = Model.get('party.party')
125 | >>> party_bank = Party(name='Party')
126 | >>> party_bank.iva_condition = 'responsable_inscripto'
127 | >>> party_bank.vat_number='33999242109' # CUIT BAPRO
128 | >>> party_bank.save()
129 |
130 | Create a bank::
131 |
132 | >>> Bank = Model.get('bank')
133 | >>> bank = Bank()
134 | >>> bank.party = party_bank
135 | >>> bank.save()
136 |
137 | Create bank account::
138 |
139 | >>> BankAccount = Model.get('bank.account')
140 | >>> Number = Model.get('bank.account.number')
141 | >>> account_bank = BankAccount()
142 | >>> account_bank.bank = bank
143 | >>> account_bank.journal = journal_cash
144 | >>> account_bank.credit_account = account_cash
145 | >>> account_bank.debit_account = account_cash
146 | >>> account_bank.pyafipws_cbu = True
147 | >>> account_bank.owners.append(company.party)
148 | >>> number = Number()
149 | >>> number.type = 'cbu'
150 | >>> number.number = '2850590940090418135201'
151 | >>> account_bank.numbers.append(number)
152 | >>> account_bank.save()
153 | >>> cbu_number, = account_bank.numbers
154 | >>> cbu_number.number_compact
155 | '2850590940090418135201'
156 |
157 | Create account category::
158 |
159 | >>> ProductCategory = Model.get('product.category')
160 | >>> account_category = ProductCategory(name="Account Category")
161 | >>> account_category.accounting = True
162 | >>> account_category.account_expense = account_expense
163 | >>> account_category.account_revenue = account_revenue
164 | >>> account_category.customer_taxes.append(sale_tax)
165 | >>> account_category.supplier_taxes.append(purchase_tax)
166 | >>> account_category.save()
167 |
168 | Create product::
169 |
170 | >>> ProductUom = Model.get('product.uom')
171 | >>> unit, = ProductUom.find([('name', '=', 'Unit')])
172 | >>> ProductTemplate = Model.get('product.template')
173 | >>> template = ProductTemplate()
174 | >>> template.name = 'product'
175 | >>> template.default_uom = unit
176 | >>> template.type = 'service'
177 | >>> template.list_price = Decimal('40')
178 | >>> template.account_category = account_category
179 | >>> template.save()
180 | >>> product, = template.products
181 |
182 | Create payment term::
183 |
184 | >>> PaymentTerm = Model.get('account.invoice.payment_term')
185 | >>> payment_term = PaymentTerm(name='Term')
186 | >>> line = payment_term.lines.new(type='percent', ratio=Decimal('.5'))
187 | >>> delta, = line.relativedeltas
188 | >>> delta.days = 20
189 | >>> line = payment_term.lines.new(type='remainder')
190 | >>> delta = line.relativedeltas.new(days=40)
191 | >>> payment_term.save()
192 |
193 | SetUp webservice AFIP::
194 |
195 | >>> wsfev1 = get_wsfev1(company, config)
196 |
197 | Get CompUltimoAutorizado and configure sequences::
198 |
199 | >>> cbte_nro = int(wsfev1.CompUltimoAutorizado('1', pos.number))
200 | >>> invoice_types['1'].invoice_sequence.number_next = cbte_nro + 1
201 | >>> invoice_types['1'].invoice_sequence.save()
202 |
203 | >>> cbte_nro = int(wsfev1.CompUltimoAutorizado('201', pos.number))
204 | >>> invoice_types['201'].invoice_sequence.number_next = cbte_nro + 1
205 | >>> invoice_types['201'].invoice_sequence.save()
206 |
207 | >>> cbte_nro = int(wsfev1.CompUltimoAutorizado('203', pos.number))
208 | >>> invoice_types['203'].invoice_sequence.number_next = cbte_nro + 1
209 | >>> invoice_types['203'].invoice_sequence.save()
210 |
211 | >>> cbte_nro = int(wsfev1.CompUltimoAutorizado('206', pos.number))
212 | >>> invoice_types['206'].invoice_sequence.number_next = cbte_nro + 1
213 | >>> invoice_types['206'].invoice_sequence.save()
214 |
215 | >>> cbte_nro = int(wsfev1.CompUltimoAutorizado('211', pos.number))
216 | >>> invoice_types['211'].invoice_sequence.number_next = cbte_nro + 1
217 | >>> invoice_types['211'].invoice_sequence.save()
218 |
219 | Create invoice::
220 |
221 | >>> Invoice = Model.get('account.invoice')
222 | >>> InvoiceLine = Model.get('account.invoice.line')
223 | >>> invoice = Invoice()
224 | >>> invoice.party = party
225 | >>> invoice.pos = pos
226 | >>> invoice.payment_term = payment_term
227 | >>> line = InvoiceLine()
228 | >>> invoice.lines.append(line)
229 | >>> line.product = product
230 | >>> line.quantity = 5
231 | >>> line.unit_price = Decimal('20000')
232 | >>> invoice.untaxed_amount
233 | Decimal('100000.00')
234 | >>> invoice.tax_amount
235 | Decimal('21000.00')
236 | >>> invoice.total_amount
237 | Decimal('121000.00')
238 | >>> invoice.invoice_type == invoice_types['201']
239 | True
240 | >>> invoice.save()
241 |
242 | Test change tax::
243 |
244 | >>> tax_line, = invoice.taxes
245 | >>> tax_line.tax == sale_tax
246 | True
247 | >>> tax_line.tax = None
248 | >>> tax_line.tax = sale_tax
249 |
250 | Test missing pyafipws_concept at invoice::
251 |
252 | >>> invoice.click('post') # doctest: +IGNORE_EXCEPTION_DETAIL
253 | Traceback (most recent call last):
254 | ...
255 | UserError: ...
256 | >>> invoice.state
257 | 'draft'
258 |
259 | Post invoice::
260 |
261 | >>> invoice.pyafipws_concept = '1'
262 | >>> invoice.pyafipws_cbu == account_bank
263 | True
264 | >>> invoice.click('post')
265 | >>> invoice.state
266 | 'posted'
267 | >>> invoice.tax_identifier.code
268 | '30710158254'
269 | >>> invoice.untaxed_amount
270 | Decimal('100000.00')
271 | >>> invoice.tax_amount
272 | Decimal('21000.00')
273 | >>> invoice.total_amount
274 | Decimal('121000.00')
275 | >>> account_receivable.reload()
276 | >>> account_receivable.debit
277 | Decimal('121000.00')
278 | >>> account_receivable.credit
279 | Decimal('0.00')
280 | >>> account_revenue.reload()
281 | >>> account_revenue.debit
282 | Decimal('0.00')
283 | >>> account_revenue.credit
284 | Decimal('100000.00')
285 | >>> account_tax.reload()
286 | >>> account_tax.debit
287 | Decimal('0.00')
288 | >>> account_tax.credit
289 | Decimal('21000.00')
290 |
291 | Credit invoice with refund::
292 |
293 | >>> credit = Wizard('account.invoice.credit', [invoice])
294 | >>> credit.form.with_refund = True
295 | >>> credit.form.invoice_date = invoice.invoice_date
296 | >>> credit.execute('credit')
297 | >>> credit_note, = Invoice.find([
298 | ... ('type', '=', 'out'), ('id', '!=', invoice.id)])
299 | >>> credit_note.state
300 | 'paid'
301 | >>> credit_note.untaxed_amount == -invoice.untaxed_amount
302 | True
303 | >>> credit_note.tax_amount == -invoice.tax_amount
304 | True
305 | >>> credit_note.total_amount == -invoice.total_amount
306 | True
307 | >>> credit_note.origins == invoice.rec_name
308 | True
309 | >>> credit_note.pos == pos
310 | True
311 | >>> credit_note.invoice_type == invoice_types['203']
312 | True
313 | >>> invoice.reload()
314 | >>> invoice.state
315 | 'cancelled'
316 | >>> invoice.reconciled == today
317 | True
318 | >>> account_receivable.reload()
319 | >>> account_receivable.debit
320 | Decimal('121000.00')
321 | >>> account_receivable.credit
322 | Decimal('121000.00')
323 | >>> account_revenue.reload()
324 | >>> account_revenue.debit
325 | Decimal('100000.00')
326 | >>> account_revenue.credit
327 | Decimal('100000.00')
328 | >>> account_tax.reload()
329 | >>> account_tax.debit
330 | Decimal('21000.00')
331 | >>> account_tax.credit
332 | Decimal('21000.00')
333 |
334 | Test post without point of sale::
335 |
336 | >>> invoice, = invoice.duplicate()
337 | >>> invoice.pyafipws_concept
338 | '1'
339 | >>> invoice.pyafipws_cae
340 | >>> invoice.pyafipws_cae_due_date
341 | >>> invoice.pos
342 | >>> invoice.invoice_type
343 | >>> invoice.transactions
344 | []
345 | >>> invoice.click('post') # doctest: +IGNORE_EXCEPTION_DETAIL
346 | Traceback (most recent call last):
347 | ...
348 | UserError: ...
349 | >>> invoice.state
350 | 'draft'
351 |
352 | Create empty invoice::
353 |
354 | >>> invoice = Invoice()
355 | >>> invoice.party = party
356 | >>> invoice.pos = pos
357 | >>> invoice.pyafipws_concept = '1'
358 | >>> invoice.invoice_type == invoice_types['1']
359 | True
360 | >>> invoice.payment_term = payment_term
361 | >>> invoice.click('post')
362 | >>> invoice.state
363 | 'paid'
364 |
365 | Create a paid invoice::
366 |
367 | >>> invoice = Invoice()
368 | >>> invoice.party = party
369 | >>> invoice.pos = pos
370 | >>> invoice.pyafipws_concept = '1'
371 | >>> invoice.payment_term = payment_term
372 | >>> line = invoice.lines.new()
373 | >>> line.product = product
374 | >>> line.quantity = 5
375 | >>> line.unit_price = Decimal('20000')
376 | >>> invoice.invoice_type == invoice_types['201']
377 | True
378 | >>> invoice.click('post')
379 | >>> pay = Wizard('account.invoice.pay', [invoice])
380 | >>> pay.form.payment_method = payment_method
381 | >>> pay.execute('choice')
382 | >>> pay.state
383 | 'end'
384 | >>> invoice.tax_identifier.type
385 | 'ar_vat'
386 | >>> invoice.state
387 | 'paid'
388 |
389 | The invoice is posted when the reconciliation is deleted::
390 |
391 | >>> invoice.payment_lines[0].reconciliation.delete()
392 | >>> invoice.reload()
393 | >>> invoice.state
394 | 'posted'
395 | >>> invoice.tax_identifier.type
396 | 'ar_vat'
397 |
398 | Credit invoice with non line lines::
399 |
400 | >>> invoice = Invoice()
401 | >>> invoice.party = party
402 | >>> invoice.pos = pos
403 | >>> invoice.pyafipws_concept = '1'
404 | >>> invoice.payment_term = payment_term
405 | >>> line = invoice.lines.new()
406 | >>> line.product = product
407 | >>> line.quantity = 5
408 | >>> line.unit_price = Decimal('20000')
409 | >>> line = invoice.lines.new()
410 | >>> line.type = 'comment'
411 | >>> line.description = 'Comment'
412 | >>> invoice.invoice_type == invoice_types['201']
413 | True
414 | >>> invoice.click('post')
415 | >>> credit = Wizard('account.invoice.credit', [invoice])
416 | >>> credit.form.with_refund = True
417 | >>> credit.execute('credit')
418 |
--------------------------------------------------------------------------------
/tests/scenario_invoice.rst:
--------------------------------------------------------------------------------
1 | ================
2 | Invoice Scenario
3 | ================
4 |
5 | Imports::
6 | >>> import datetime as dt
7 | >>> from dateutil.relativedelta import relativedelta
8 | >>> from decimal import Decimal
9 | >>> from proteus import Model, Wizard
10 | >>> from trytond.tests.tools import activate_modules
11 | >>> from trytond.modules.currency.tests.tools import get_currency
12 | >>> from trytond.modules.company.tests.tools import create_company, \
13 | ... get_company
14 | >>> from trytond.modules.account.tests.tools import create_fiscalyear, \
15 | ... create_chart
16 | >>> from trytond.modules.account_ar.tests.tools import get_accounts
17 | >>> from trytond.modules.account_invoice.tests.tools import \
18 | ... set_fiscalyear_invoice_sequences
19 | >>> from trytond.modules.account_invoice_ar.tests.tools import \
20 | ... create_pos, get_pos, get_invoice_types, get_tax
21 | >>> today = dt.date.today()
22 |
23 | Install account_invoice_ar::
24 |
25 | >>> config = activate_modules('account_invoice_ar')
26 |
27 | Create company::
28 |
29 | >>> currency = get_currency('ARS')
30 | >>> currency.afip_code = 'PES'
31 | >>> currency.save()
32 | >>> _ = create_company(currency=currency)
33 | >>> company = get_company()
34 | >>> tax_identifier = company.party.identifiers.new()
35 | >>> tax_identifier.type = 'ar_vat'
36 | >>> tax_identifier.code = '30710158254' # gcoop CUIT
37 | >>> company.party.iva_condition = 'responsable_inscripto'
38 | >>> company.party.save()
39 |
40 | Create fiscal year::
41 |
42 | >>> fiscalyear = set_fiscalyear_invoice_sequences(
43 | ... create_fiscalyear(company))
44 | >>> fiscalyear.click('create_period')
45 | >>> period = fiscalyear.periods[0]
46 | >>> period_ids = [p.id for p in fiscalyear.periods]
47 |
48 | Create chart of accounts::
49 |
50 | >>> _ = create_chart(company, chart='account_ar.root_ar')
51 | >>> accounts = get_accounts(company)
52 | >>> account_receivable = accounts['receivable']
53 | >>> account_payable = accounts['payable']
54 | >>> account_revenue = accounts['revenue']
55 | >>> account_expense = accounts['expense']
56 | >>> account_tax = accounts['sale_tax']
57 | >>> account_cash = accounts['cash']
58 |
59 | Create point of sale::
60 |
61 | >>> _ = create_pos(company)
62 | >>> pos = get_pos()
63 | >>> invoice_types = get_invoice_types()
64 |
65 | Create taxes::
66 |
67 | >>> sale_tax = get_tax('IVA Ventas 21%')
68 | >>> sale_tax_nogravado = get_tax('IVA Ventas No Gravado')
69 |
70 | Create payment method::
71 |
72 | >>> Journal = Model.get('account.journal')
73 | >>> PaymentMethod = Model.get('account.invoice.payment.method')
74 | >>> Sequence = Model.get('ir.sequence')
75 | >>> journal_cash, = Journal.find([('type', '=', 'cash')])
76 | >>> payment_method = PaymentMethod()
77 | >>> payment_method.name = 'Cash'
78 | >>> payment_method.journal = journal_cash
79 | >>> payment_method.credit_account = account_cash
80 | >>> payment_method.debit_account = account_cash
81 | >>> payment_method.save()
82 |
83 | Create Write Off method::
84 |
85 | >>> WriteOff = Model.get('account.move.reconcile.write_off')
86 | >>> sequence_journal, = Sequence.find(
87 | ... [('sequence_type.name', '=', "Account Journal")], limit=1)
88 | >>> journal_writeoff = Journal(name='Write-Off', type='write-off',
89 | ... sequence=sequence_journal)
90 | >>> journal_writeoff.save()
91 | >>> writeoff_method = WriteOff()
92 | >>> writeoff_method.name = 'Rate loss'
93 | >>> writeoff_method.journal = journal_writeoff
94 | >>> writeoff_method.credit_account = account_expense
95 | >>> writeoff_method.debit_account = account_expense
96 | >>> writeoff_method.save()
97 |
98 | Create party::
99 |
100 | >>> Party = Model.get('party.party')
101 | >>> party = Party(name='Party')
102 | >>> party.iva_condition='responsable_inscripto'
103 | >>> party.vat_number='33333333339'
104 | >>> party.account_receivable = account_receivable
105 | >>> party.save()
106 |
107 | Create party consumidor final::
108 |
109 | >>> Party = Model.get('party.party')
110 | >>> party_cf = Party(name='Party')
111 | >>> party_cf.iva_condition='consumidor_final'
112 | >>> party.account_receivable = account_receivable
113 | >>> party_cf.save()
114 |
115 | Create account category::
116 |
117 | >>> ProductCategory = Model.get('product.category')
118 | >>> account_category = ProductCategory(name="Account Category")
119 | >>> account_category.accounting = True
120 | >>> account_category.account_expense = account_expense
121 | >>> account_category.account_revenue = account_revenue
122 | >>> account_category.customer_taxes.append(sale_tax)
123 | >>> account_category.save()
124 |
125 | Create product::
126 |
127 | >>> ProductUom = Model.get('product.uom')
128 | >>> unit, = ProductUom.find([('name', '=', 'Unit')])
129 | >>> ProductTemplate = Model.get('product.template')
130 | >>> template = ProductTemplate()
131 | >>> template.name = 'product'
132 | >>> template.default_uom = unit
133 | >>> template.type = 'service'
134 | >>> template.list_price = Decimal('40')
135 | >>> template.account_category = account_category
136 | >>> template.save()
137 | >>> product, = template.products
138 |
139 | Create payment term::
140 |
141 | >>> PaymentTerm = Model.get('account.invoice.payment_term')
142 | >>> payment_term = PaymentTerm(name='Term')
143 | >>> line = payment_term.lines.new(type='percent', ratio=Decimal('.5'))
144 | >>> delta, = line.relativedeltas
145 | >>> delta.days = 20
146 | >>> line = payment_term.lines.new(type='remainder')
147 | >>> delta = line.relativedeltas.new(days=40)
148 | >>> payment_term.save()
149 |
150 | Create invoice::
151 |
152 | >>> Invoice = Model.get('account.invoice')
153 | >>> InvoiceLine = Model.get('account.invoice.line')
154 | >>> invoice = Invoice()
155 | >>> invoice.party = party
156 | >>> invoice.pos = pos
157 | >>> invoice.payment_term = payment_term
158 | >>> line = InvoiceLine()
159 | >>> invoice.lines.append(line)
160 | >>> line.product = product
161 | >>> line.quantity = 5
162 | >>> line.unit_price = Decimal('40')
163 | >>> line = InvoiceLine()
164 | >>> invoice.lines.append(line)
165 | >>> line.account = account_revenue
166 | >>> line.taxes.append(sale_tax_nogravado)
167 | >>> line.description = 'Test'
168 | >>> line.quantity = 1
169 | >>> line.unit_price = Decimal(20)
170 | >>> invoice.untaxed_amount
171 | Decimal('220.00')
172 | >>> invoice.tax_amount
173 | Decimal('42.00')
174 | >>> invoice.total_amount
175 | Decimal('262.00')
176 | >>> invoice.invoice_type == invoice_types['1']
177 | True
178 | >>> invoice.save()
179 | >>> bool(invoice.has_report_cache)
180 | False
181 |
182 | Test change tax::
183 |
184 | >>> tax_line = invoice.taxes[0]
185 | >>> tax_line.tax == sale_tax
186 | True
187 | >>> tax_line.tax = None
188 | >>> tax_line.tax = sale_tax
189 |
190 | Post invoice::
191 |
192 | >>> invoice.click('post')
193 | >>> invoice.state
194 | 'posted'
195 | >>> invoice.tax_identifier.code
196 | '30710158254'
197 | >>> bool(invoice.has_report_cache)
198 | True
199 | >>> invoice.untaxed_amount
200 | Decimal('220.00')
201 | >>> invoice.tax_amount
202 | Decimal('42.00')
203 | >>> invoice.total_amount
204 | Decimal('262.00')
205 | >>> account_receivable.reload()
206 | >>> account_receivable.debit
207 | Decimal('262.00')
208 | >>> account_receivable.credit
209 | Decimal('0.00')
210 | >>> account_revenue.reload()
211 | >>> account_revenue.debit
212 | Decimal('0.00')
213 | >>> account_revenue.credit
214 | Decimal('220.00')
215 | >>> account_tax.reload()
216 | >>> account_tax.debit
217 | Decimal('0.00')
218 | >>> account_tax.credit
219 | Decimal('42.00')
220 |
221 | Credit invoice with refund::
222 |
223 | >>> credit = Wizard('account.invoice.credit', [invoice])
224 | >>> credit.form.with_refund = True
225 | >>> credit.form.invoice_date = invoice.invoice_date
226 | >>> credit.execute('credit')
227 | >>> invoice.reload()
228 | >>> invoice.state
229 | 'cancelled'
230 | >>> bool(invoice.reconciled)
231 | True
232 | >>> credit_note, = Invoice.find([
233 | ... ('type', '=', 'out'), ('id', '!=', invoice.id)])
234 | >>> credit_note.state
235 | 'paid'
236 | >>> credit_note.untaxed_amount == -invoice.untaxed_amount
237 | True
238 | >>> credit_note.tax_amount == -invoice.tax_amount
239 | True
240 | >>> credit_note.total_amount == -invoice.total_amount
241 | True
242 | >>> credit_note.origins == invoice.rec_name
243 | True
244 | >>> credit_note.pos == pos
245 | True
246 | >>> credit_note.invoice_type == invoice_types['3']
247 | True
248 | >>> invoice.reload()
249 | >>> invoice.state
250 | 'cancelled'
251 | >>> invoice.reconciled == today
252 | True
253 | >>> account_receivable.reload()
254 | >>> account_receivable.debit
255 | Decimal('262.00')
256 | >>> account_receivable.credit
257 | Decimal('262.00')
258 | >>> account_revenue.reload()
259 | >>> account_revenue.debit
260 | Decimal('220.00')
261 | >>> account_revenue.credit
262 | Decimal('220.00')
263 | >>> account_tax.reload()
264 | >>> account_tax.debit
265 | Decimal('42.00')
266 | >>> account_tax.credit
267 | Decimal('42.00')
268 |
269 | Attempt to post invoice without pos::
270 |
271 | >>> invoice, = invoice.duplicate()
272 | >>> invoice.state
273 | 'draft'
274 | >>> invoice.click('post') # doctest: +IGNORE_EXCEPTION_DETAIL
275 | Traceback (most recent call last):
276 | ...
277 | UserError: ...
278 | >>> invoice.state
279 | 'draft'
280 |
281 | Pay invoice::
282 |
283 | >>> invoice.pos = pos
284 | >>> invoice.invoice_type == invoice_types['1']
285 | True
286 | >>> invoice.click('post')
287 |
288 | >>> pay = Wizard('account.invoice.pay', [invoice])
289 | >>> pay.form.amount
290 | Decimal('262.00')
291 | >>> pay.form.amount = Decimal('120.00')
292 | >>> pay.form.payment_method = payment_method
293 | >>> pay.execute('choice')
294 | >>> pay.state
295 | 'end'
296 |
297 | >>> pay = Wizard('account.invoice.pay', [invoice])
298 | >>> pay.form.amount
299 | Decimal('120.00')
300 | >>> pay.form.amount = Decimal('42.00')
301 | >>> pay.form.payment_method = payment_method
302 | >>> pay.execute('choice')
303 | >>> pay.form.type = 'partial'
304 | >>> pay.form.amount
305 | Decimal('42.00')
306 | >>> len(pay.form.lines_to_pay)
307 | 1
308 | >>> len(pay.form.payment_lines)
309 | 0
310 | >>> len(pay.form.lines)
311 | 1
312 | >>> pay.form.amount_writeoff
313 | Decimal('100.00')
314 | >>> pay.execute('pay')
315 |
316 | >>> pay = Wizard('account.invoice.pay', [invoice])
317 | >>> pay.form.amount
318 | Decimal('-20.00')
319 | >>> pay.form.amount = Decimal('99.00')
320 | >>> pay.form.payment_method = payment_method
321 | >>> pay.execute('choice')
322 | >>> pay.form.type = 'writeoff'
323 | >>> pay.form.writeoff = writeoff_method
324 | >>> pay.form.amount
325 | Decimal('99.00')
326 | >>> len(pay.form.lines_to_pay)
327 | 1
328 | >>> len(pay.form.payment_lines)
329 | 1
330 | >>> len(pay.form.lines)
331 | 1
332 | >>> pay.form.amount_writeoff
333 | Decimal('1.00')
334 | >>> pay.execute('pay')
335 |
336 | >>> invoice.state
337 | 'paid'
338 | >>> sorted(l.credit for l in invoice.reconciliation_lines)
339 | [Decimal('1.00'), Decimal('42.00'), Decimal('99.00'), Decimal('120.00')]
340 |
341 | Create empty invoice::
342 |
343 | >>> invoice = Invoice()
344 | >>> invoice.party = party
345 | >>> invoice.pos = pos
346 | >>> invoice.payment_term = payment_term
347 | >>> invoice.click('post') # doctest: +IGNORE_EXCEPTION_DETAIL
348 | Traceback (most recent call last):
349 | ...
350 | UserError: ...
351 | >>> invoice.state
352 | 'draft'
353 |
354 | Create some complex invoice and test its taxes base rounding::
355 |
356 | >>> invoice = Invoice()
357 | >>> invoice.party = party
358 | >>> invoice.pos = pos
359 | >>> invoice.payment_term = payment_term
360 | >>> invoice.invoice_date = today
361 | >>> line = invoice.lines.new()
362 | >>> line.product = product
363 | >>> line.quantity = 1
364 | >>> line.unit_price = Decimal('0.0035')
365 | >>> line = invoice.lines.new()
366 | >>> line.product = product
367 | >>> line.quantity = 1
368 | >>> line.unit_price = Decimal('0.0035')
369 | >>> invoice.save()
370 | >>> invoice.untaxed_amount
371 | Decimal('0.00')
372 | >>> invoice.taxes[0].base == invoice.untaxed_amount
373 | True
374 | >>> found_invoice, = Invoice.find([('untaxed_amount', '=', Decimal(0))])
375 | >>> found_invoice.id == invoice.id
376 | True
377 | >>> found_invoice, = Invoice.find([('total_amount', '=', Decimal(0))])
378 | >>> found_invoice.id == invoice.id
379 | True
380 |
381 | Clear company tax_identifier::
382 |
383 | >>> tax_identifier, = company.party.identifiers
384 | >>> tax_identifier.type = None
385 | >>> tax_identifier.save()
386 |
387 | Create a paid invoice::
388 |
389 | >>> invoice = Invoice()
390 | >>> invoice.party = party
391 | >>> invoice.pos = pos
392 | >>> invoice.payment_term = payment_term
393 | >>> line = invoice.lines.new()
394 | >>> line.product = product
395 | >>> line.quantity = 5
396 | >>> line.unit_price = Decimal('40')
397 |
398 | >>> invoice.click('post') # doctest: +IGNORE_EXCEPTION_DETAIL
399 | Traceback (most recent call last):
400 | ...
401 | UserError: ...
402 | >>> invoice.state
403 | 'draft'
404 |
405 | >>> tax_identifier, = company.party.identifiers
406 | >>> tax_identifier.type = 'ar_vat'
407 | >>> tax_identifier.save()
408 |
409 | >>> invoice.click('post')
410 | >>> pay = Wizard('account.invoice.pay', [invoice])
411 | >>> pay.form.payment_method = payment_method
412 | >>> pay.execute('choice')
413 | >>> pay.state
414 | 'end'
415 | >>> invoice.tax_identifier.type
416 | 'ar_vat'
417 | >>> invoice.state
418 | 'paid'
419 |
420 | The invoice is posted when the reconciliation is deleted::
421 |
422 | >>> invoice.payment_lines[0].reconciliation.delete()
423 | >>> invoice.reload()
424 | >>> invoice.state
425 | 'posted'
426 | >>> invoice.tax_identifier.type
427 | 'ar_vat'
428 |
429 | Credit invoice with non line lines::
430 |
431 | >>> invoice = Invoice()
432 | >>> invoice.party = party
433 | >>> invoice.pos = pos
434 | >>> invoice.payment_term = payment_term
435 | >>> line = invoice.lines.new()
436 | >>> line.product = product
437 | >>> line.quantity = 5
438 | >>> line.unit_price = Decimal('40')
439 | >>> line = invoice.lines.new()
440 | >>> line.type = 'comment'
441 | >>> line.description = 'Comment'
442 | >>> invoice.click('post')
443 | >>> credit = Wizard('account.invoice.credit', [invoice])
444 | >>> credit.form.with_refund = True
445 | >>> credit.execute('credit')
446 |
--------------------------------------------------------------------------------
/tests/scenario_invoice_pos_electronic.rst:
--------------------------------------------------------------------------------
1 | ================
2 | Invoice Scenario
3 | ================
4 |
5 | Imports::
6 | >>> import datetime as dt
7 | >>> import io
8 | >>> from dateutil.relativedelta import relativedelta
9 | >>> from decimal import Decimal
10 | >>> from proteus import Model, Wizard
11 | >>> from trytond.tests.tools import activate_modules
12 | >>> from trytond.modules.currency.tests.tools import get_currency
13 | >>> from trytond.modules.company.tests.tools import create_company, \
14 | ... get_company
15 | >>> from trytond.modules.account.tests.tools import create_fiscalyear, \
16 | ... create_chart
17 | >>> from trytond.modules.account_ar.tests.tools import get_accounts
18 | >>> from trytond.modules.account_invoice.tests.tools import \
19 | ... set_fiscalyear_invoice_sequences
20 | >>> from trytond.modules.account_invoice_ar.tests.tools import \
21 | ... create_pos, get_pos, get_invoice_types, get_tax, get_wsfev1
22 | >>> from trytond.modules.party_ar.tests.tools import set_afip_certs
23 | >>> today = dt.date.today()
24 |
25 | Install account_invoice_ar::
26 |
27 | >>> config = activate_modules('account_invoice_ar')
28 |
29 | Create company::
30 |
31 | >>> currency = get_currency('ARS')
32 | >>> currency.afip_code = 'PES'
33 | >>> currency.save()
34 | >>> _ = create_company(currency=currency)
35 | >>> company = get_company()
36 | >>> tax_identifier = company.party.identifiers.new()
37 | >>> tax_identifier.type = 'ar_vat'
38 | >>> tax_identifier.code = '30710158254' # gcoop CUIT
39 | >>> company.party.iva_condition = 'responsable_inscripto'
40 | >>> company.party.save()
41 |
42 | Configure AFIP certificates::
43 |
44 | >>> _ = set_afip_certs(company=company)
45 |
46 | Create fiscal year::
47 |
48 | >>> fiscalyear = set_fiscalyear_invoice_sequences(
49 | ... create_fiscalyear(company))
50 | >>> fiscalyear.click('create_period')
51 | >>> period = fiscalyear.periods[0]
52 | >>> period_ids = [p.id for p in fiscalyear.periods]
53 |
54 | Create chart of accounts::
55 |
56 | >>> _ = create_chart(company, chart='account_ar.root_ar')
57 | >>> accounts = get_accounts(company)
58 | >>> account_receivable = accounts['receivable']
59 | >>> account_payable = accounts['payable']
60 | >>> account_revenue = accounts['revenue']
61 | >>> account_expense = accounts['expense']
62 | >>> account_tax = accounts['sale_tax']
63 | >>> account_cash = accounts['cash']
64 |
65 | Create point of sale::
66 |
67 | >>> _ = create_pos(company, type='electronic', number=4000, ws='wsfe')
68 | >>> pos = get_pos(type='electronic', number=4000)
69 | >>> invoice_types = get_invoice_types(pos=pos)
70 |
71 | Create taxes::
72 |
73 | >>> sale_tax = get_tax('IVA Ventas 21%')
74 | >>> sale_tax_nogravado = get_tax('IVA Ventas No Gravado')
75 |
76 | Create payment method::
77 |
78 | >>> Journal = Model.get('account.journal')
79 | >>> PaymentMethod = Model.get('account.invoice.payment.method')
80 | >>> Sequence = Model.get('ir.sequence')
81 | >>> journal_cash, = Journal.find([('type', '=', 'cash')])
82 | >>> payment_method = PaymentMethod()
83 | >>> payment_method.name = 'Cash'
84 | >>> payment_method.journal = journal_cash
85 | >>> payment_method.credit_account = account_cash
86 | >>> payment_method.debit_account = account_cash
87 | >>> payment_method.save()
88 |
89 | Create Write Off method::
90 |
91 | >>> WriteOff = Model.get('account.move.reconcile.write_off')
92 | >>> sequence_journal, = Sequence.find(
93 | ... [('sequence_type.name', '=', "Account Journal")], limit=1)
94 | >>> journal_writeoff = Journal(name='Write-Off', type='write-off',
95 | ... sequence=sequence_journal)
96 | >>> journal_writeoff.save()
97 | >>> writeoff_method = WriteOff()
98 | >>> writeoff_method.name = 'Rate loss'
99 | >>> writeoff_method.journal = journal_writeoff
100 | >>> writeoff_method.credit_account = account_expense
101 | >>> writeoff_method.debit_account = account_expense
102 | >>> writeoff_method.save()
103 |
104 | Create party::
105 |
106 | >>> Party = Model.get('party.party')
107 | >>> party = Party(name='Party')
108 | >>> party.iva_condition='responsable_inscripto'
109 | >>> party.vat_number='30688555872'
110 | >>> party.account_receivable = account_receivable
111 | >>> party.save()
112 |
113 | Create party consumidor final::
114 |
115 | >>> Party = Model.get('party.party')
116 | >>> party_cf = Party(name='Party')
117 | >>> party_cf.iva_condition='consumidor_final'
118 | >>> party_cf.account_receivable = account_receivable
119 | >>> party_cf.save()
120 |
121 | Create account category::
122 |
123 | >>> ProductCategory = Model.get('product.category')
124 | >>> account_category = ProductCategory(name="Account Category")
125 | >>> account_category.accounting = True
126 | >>> account_category.account_expense = account_expense
127 | >>> account_category.account_revenue = account_revenue
128 | >>> account_category.customer_taxes.append(sale_tax)
129 | >>> account_category.save()
130 |
131 | Create product::
132 |
133 | >>> ProductUom = Model.get('product.uom')
134 | >>> unit, = ProductUom.find([('name', '=', 'Unit')])
135 | >>> ProductTemplate = Model.get('product.template')
136 | >>> template = ProductTemplate()
137 | >>> template.name = 'product'
138 | >>> template.default_uom = unit
139 | >>> template.type = 'service'
140 | >>> template.list_price = Decimal('40')
141 | >>> template.account_category = account_category
142 | >>> template.save()
143 | >>> product, = template.products
144 |
145 | Create payment term::
146 |
147 | >>> PaymentTerm = Model.get('account.invoice.payment_term')
148 | >>> payment_term = PaymentTerm(name='Term')
149 | >>> line = payment_term.lines.new(type='percent', ratio=Decimal('.5'))
150 | >>> delta, = line.relativedeltas
151 | >>> delta.days = 20
152 | >>> line = payment_term.lines.new(type='remainder')
153 | >>> delta = line.relativedeltas.new(days=40)
154 | >>> payment_term.save()
155 |
156 | SetUp webservice AFIP::
157 |
158 | >>> wsfev1 = get_wsfev1(company, config)
159 |
160 | Get CompUltimoAutorizado and configure sequences::
161 |
162 | >>> cbte_nro = int(wsfev1.CompUltimoAutorizado('1', pos.number))
163 | >>> invoice_types['1'].invoice_sequence.number_next = cbte_nro + 1
164 | >>> invoice_types['1'].invoice_sequence.save()
165 |
166 | >>> cbte_nro = int(wsfev1.CompUltimoAutorizado('3', pos.number))
167 | >>> invoice_types['3'].invoice_sequence.number_next = cbte_nro + 1
168 | >>> invoice_types['3'].invoice_sequence.save()
169 |
170 | >>> cbte_nro = int(wsfev1.CompUltimoAutorizado('6', pos.number))
171 | >>> invoice_types['6'].invoice_sequence.number_next = cbte_nro + 1
172 | >>> invoice_types['6'].invoice_sequence.save()
173 |
174 | >>> cbte_nro = int(wsfev1.CompUltimoAutorizado('11', pos.number))
175 | >>> invoice_types['11'].invoice_sequence.number_next = cbte_nro + 1
176 | >>> invoice_types['11'].invoice_sequence.save()
177 |
178 | Create invoice::
179 |
180 | >>> Invoice = Model.get('account.invoice')
181 | >>> InvoiceLine = Model.get('account.invoice.line')
182 | >>> invoice = Invoice()
183 | >>> invoice.party = party
184 | >>> invoice.pos = pos
185 | >>> invoice.payment_term = payment_term
186 | >>> line = InvoiceLine()
187 | >>> invoice.lines.append(line)
188 | >>> line.product = product
189 | >>> line.quantity = 5
190 | >>> line.unit_price = Decimal('40')
191 | >>> line = InvoiceLine()
192 | >>> invoice.lines.append(line)
193 | >>> line.account = account_revenue
194 | >>> line.taxes.append(sale_tax_nogravado)
195 | >>> line.description = 'Test'
196 | >>> line.quantity = 1
197 | >>> line.unit_price = Decimal(20)
198 | >>> invoice.untaxed_amount
199 | Decimal('220.00')
200 | >>> invoice.tax_amount
201 | Decimal('42.00')
202 | >>> invoice.total_amount
203 | Decimal('262.00')
204 | >>> invoice.invoice_type == invoice_types['1']
205 | True
206 | >>> invoice.save()
207 | >>> bool(invoice.has_report_cache)
208 | False
209 |
210 | Test change tax::
211 |
212 | >>> tax_line = invoice.taxes[0]
213 | >>> tax_line.tax == sale_tax
214 | True
215 | >>> tax_line.tax = None
216 | >>> tax_line.tax = sale_tax
217 |
218 | Test missing pyafipws_concept at invoice::
219 |
220 | >>> invoice.click('post') # doctest: +IGNORE_EXCEPTION_DETAIL
221 | Traceback (most recent call last):
222 | ...
223 | UserError: ...
224 | >>> invoice.state
225 | 'validated'
226 |
227 | Post invoice::
228 |
229 | >>> invoice.pyafipws_concept = '1'
230 | >>> invoice.click('post')
231 | >>> invoice.state
232 | 'posted'
233 | >>> invoice.tax_identifier.code
234 | '30710158254'
235 | >>> bool(invoice.has_report_cache)
236 | True
237 | >>> invoice.untaxed_amount
238 | Decimal('220.00')
239 | >>> invoice.tax_amount
240 | Decimal('42.00')
241 | >>> invoice.total_amount
242 | Decimal('262.00')
243 | >>> account_receivable.reload()
244 | >>> account_receivable.debit
245 | Decimal('262.00')
246 | >>> account_receivable.credit
247 | Decimal('0.00')
248 | >>> account_revenue.reload()
249 | >>> account_revenue.debit
250 | Decimal('0.00')
251 | >>> account_revenue.credit
252 | Decimal('220.00')
253 | >>> account_tax.reload()
254 | >>> account_tax.debit
255 | Decimal('0.00')
256 | >>> account_tax.credit
257 | Decimal('42.00')
258 |
259 | Credit invoice with refund::
260 |
261 | >>> credit = Wizard('account.invoice.credit', [invoice])
262 | >>> credit.form.with_refund = True
263 | >>> credit.form.invoice_date = invoice.invoice_date
264 | >>> credit.execute('credit')
265 | >>> invoice.reload()
266 | >>> invoice.state
267 | 'cancelled'
268 | >>> bool(invoice.reconciled)
269 | True
270 | >>> credit_note, = Invoice.find([
271 | ... ('type', '=', 'out'), ('id', '!=', invoice.id)])
272 | >>> credit_note.state
273 | 'paid'
274 | >>> credit_note.untaxed_amount == -invoice.untaxed_amount
275 | True
276 | >>> credit_note.tax_amount == -invoice.tax_amount
277 | True
278 | >>> credit_note.total_amount == -invoice.total_amount
279 | True
280 | >>> credit_note.origins == invoice.rec_name
281 | True
282 | >>> credit_note.pos == pos
283 | True
284 | >>> credit_note.invoice_type == invoice_types['3']
285 | True
286 | >>> invoice.reload()
287 | >>> invoice.state
288 | 'cancelled'
289 | >>> invoice.reconciled == today
290 | True
291 | >>> account_receivable.reload()
292 | >>> account_receivable.debit
293 | Decimal('262.00')
294 | >>> account_receivable.credit
295 | Decimal('262.00')
296 | >>> account_revenue.reload()
297 | >>> account_revenue.debit
298 | Decimal('220.00')
299 | >>> account_revenue.credit
300 | Decimal('220.00')
301 | >>> account_tax.reload()
302 | >>> account_tax.debit
303 | Decimal('42.00')
304 | >>> account_tax.credit
305 | Decimal('42.00')
306 |
307 | Test post without point of sale::
308 |
309 | >>> invoice, = invoice.duplicate()
310 | >>> invoice.pyafipws_concept
311 | '1'
312 | >>> invoice.pyafipws_cae
313 | >>> invoice.pyafipws_cae_due_date
314 | >>> invoice.pos
315 | >>> invoice.invoice_type
316 | >>> invoice.transactions
317 | []
318 | >>> invoice.click('post') # doctest: +IGNORE_EXCEPTION_DETAIL
319 | Traceback (most recent call last):
320 | ...
321 | UserError: ...
322 | >>> invoice.state
323 | 'draft'
324 |
325 | Test post when clear tax_identifier type::
326 |
327 | >>> tax_identifier, = company.party.identifiers
328 | >>> tax_identifier.type = None
329 | >>> tax_identifier.save()
330 |
331 | >>> invoice.pos = pos
332 | >>> invoice.click('post') # doctest: +IGNORE_EXCEPTION_DETAIL
333 | Traceback (most recent call last):
334 | ...
335 | trytond.model.modelstorage.DomainValidationError: ...
336 | >>> invoice.state
337 | 'draft'
338 |
339 | >>> tax_identifier, = company.party.identifiers
340 | >>> tax_identifier.type = 'ar_vat'
341 | >>> tax_identifier.save()
342 |
343 | Pay invoice::
344 |
345 | >>> invoice.pos = pos
346 | >>> invoice.click('post')
347 | >>> pay = Wizard('account.invoice.pay', [invoice])
348 | >>> pay.form.amount
349 | Decimal('262.00')
350 | >>> pay.form.amount = Decimal('131.00')
351 | >>> pay.form.payment_method = payment_method
352 | >>> pay.execute('choice')
353 | >>> pay.state
354 | 'end'
355 |
356 | >>> pay = Wizard('account.invoice.pay', [invoice])
357 | >>> pay.form.amount
358 | Decimal('131.00')
359 | >>> pay.form.amount = Decimal('31.00')
360 | >>> pay.form.payment_method = payment_method
361 | >>> pay.execute('choice')
362 | >>> pay.form.type = 'partial'
363 | >>> pay.form.amount
364 | Decimal('31.00')
365 | >>> len(pay.form.lines_to_pay)
366 | 1
367 | >>> len(pay.form.payment_lines)
368 | 0
369 | >>> len(pay.form.lines)
370 | 1
371 | >>> pay.form.amount_writeoff
372 | Decimal('100.00')
373 | >>> pay.execute('pay')
374 |
375 | >>> pay = Wizard('account.invoice.pay', [invoice])
376 | >>> pay.form.amount
377 | Decimal('-31.00')
378 | >>> pay.form.amount = Decimal('99.00')
379 | >>> pay.form.payment_method = payment_method
380 | >>> pay.execute('choice')
381 | >>> pay.form.type = 'writeoff'
382 | >>> pay.form.writeoff = writeoff_method
383 | >>> pay.form.amount
384 | Decimal('99.00')
385 | >>> len(pay.form.lines_to_pay)
386 | 1
387 | >>> len(pay.form.payment_lines)
388 | 1
389 | >>> len(pay.form.lines)
390 | 1
391 | >>> pay.form.amount_writeoff
392 | Decimal('1.00')
393 | >>> pay.execute('pay')
394 |
395 | >>> invoice.state
396 | 'paid'
397 | >>> sorted(l.credit for l in invoice.reconciliation_lines)
398 | [Decimal('1.00'), Decimal('31.00'), Decimal('99.00'), Decimal('131.00')]
399 |
400 | Create empty invoice::
401 |
402 | >>> invoice = Invoice()
403 | >>> invoice.party = party
404 | >>> invoice.pos = pos
405 | >>> invoice.pyafipws_concept = '1'
406 | >>> invoice.payment_term = payment_term
407 | >>> invoice.click('post') # doctest: +IGNORE_EXCEPTION_DETAIL
408 | Traceback (most recent call last):
409 | ...
410 | UserError: ...
411 | >>> invoice.state
412 | 'draft'
413 |
414 | Create some complex invoice and test its taxes base rounding::
415 |
416 | >>> invoice = Invoice()
417 | >>> invoice.party = party
418 | >>> invoice.pos = pos
419 | >>> invoice.pyafipws_concept = '1'
420 | >>> invoice.payment_term = payment_term
421 | >>> invoice.invoice_date = today
422 | >>> line = invoice.lines.new()
423 | >>> line.product = product
424 | >>> line.quantity = 1
425 | >>> line.unit_price = Decimal('0.0035')
426 | >>> line = invoice.lines.new()
427 | >>> line.product = product
428 | >>> line.quantity = 1
429 | >>> line.unit_price = Decimal('0.0035')
430 | >>> invoice.save()
431 | >>> invoice.untaxed_amount
432 | Decimal('0.00')
433 | >>> invoice.taxes[0].base == invoice.untaxed_amount
434 | True
435 | >>> found_invoice, = Invoice.find([('untaxed_amount', '=', Decimal(0))])
436 | >>> found_invoice.id == invoice.id
437 | True
438 | >>> found_invoice, = Invoice.find([('total_amount', '=', Decimal(0))])
439 | >>> found_invoice.id == invoice.id
440 | True
441 |
442 | Create a paid invoice::
443 |
444 | >>> invoice = Invoice()
445 | >>> invoice.party = party
446 | >>> invoice.pos = pos
447 | >>> invoice.pyafipws_concept = '1'
448 | >>> invoice.payment_term = payment_term
449 | >>> line = invoice.lines.new()
450 | >>> line.product = product
451 | >>> line.quantity = 5
452 | >>> line.unit_price = Decimal('40')
453 | >>> invoice.click('post')
454 | >>> pay = Wizard('account.invoice.pay', [invoice])
455 | >>> pay.form.payment_method = payment_method
456 | >>> pay.execute('choice')
457 | >>> pay.state
458 | 'end'
459 | >>> invoice.tax_identifier.type
460 | 'ar_vat'
461 | >>> invoice.state
462 | 'paid'
463 |
464 | The invoice is posted when the reconciliation is deleted::
465 |
466 | >>> invoice.payment_lines[0].reconciliation.delete()
467 | >>> invoice.reload()
468 | >>> invoice.state
469 | 'posted'
470 | >>> invoice.tax_identifier.type
471 | 'ar_vat'
472 |
473 | Credit invoice with non line lines::
474 |
475 | >>> invoice = Invoice()
476 | >>> invoice.party = party
477 | >>> invoice.pos = pos
478 | >>> invoice.pyafipws_concept = '1'
479 | >>> invoice.payment_term = payment_term
480 | >>> line = invoice.lines.new()
481 | >>> line.product = product
482 | >>> line.quantity = 5
483 | >>> line.unit_price = Decimal('40')
484 | >>> line = invoice.lines.new()
485 | >>> line.type = 'comment'
486 | >>> line.description = 'Comment'
487 | >>> invoice.click('post')
488 | >>> credit = Wizard('account.invoice.credit', [invoice])
489 | >>> credit.form.with_refund = True
490 | >>> credit.execute('credit')
491 |
492 | Duplicate and test recover last posted invoice::
493 |
494 | >>> posted_invoice = Invoice.find([
495 | ... ('type', '=', 'out'), ('state', '=', 'posted')])[0]
496 | >>> last_cbte_nro = int(wsfev1.CompUltimoAutorizado('1', pos.number))
497 | >>> invoice, = invoice.duplicate()
498 | >>> invoice.pyafipws_concept
499 | '1'
500 | >>> invoice.pyafipws_cae = posted_invoice.pyafipws_cae
501 | >>> invoice.pyafipws_cae_due_date = posted_invoice.pyafipws_cae_due_date
502 | >>> invoice.pos = posted_invoice.pos
503 | >>> invoice.invoice_type = posted_invoice.invoice_type
504 | >>> invoice.number = posted_invoice.number
505 | >>> invoice.transactions
506 | []
507 | >>> invoice.save()
508 | >>> invoice.reload()
509 | >>> invoice.state
510 | 'draft'
511 | >>> invoice.invoice_date = posted_invoice.invoice_date
512 | >>> invoice.click('post')
513 | >>> invoice.state
514 | 'posted'
515 | >>> bool(invoice.move)
516 | True
517 | >>> invoice.pos == posted_invoice.pos
518 | True
519 | >>> invoice.invoice_type == posted_invoice.invoice_type
520 | True
521 | >>> # invoice.number == posted_invoice.number
522 | # True
523 | >>> # invoice.pyafipws_cae == posted_invoice.pyafipws_cae
524 | # True
525 | >>> # invoice.transactions[-1].pyafipws_result == posted_invoice.transactions[-1].pyafipws_result
526 | # True
527 | >>> # posted_invoice.transactions[-1].pyafipws_xml_request
528 | >>> # invoice.transactions[-1].pyafipws_xml_request
529 | >>> # posted_invoice.transactions[-1].pyafipws_xml_response
530 | >>> # invoice.transactions[-1].pyafipws_xml_response
531 |
532 | Post wrong invoice, number and invoice_date should be None::
533 |
534 | >>> company.party.iva_condition = 'monotributo'
535 | >>> company.party.save()
536 |
537 | >>> invoice = Invoice()
538 | >>> invoice.party = party
539 | >>> invoice.pos = pos
540 | >>> invoice.pyafipws_concept = '1'
541 | >>> invoice.payment_term = payment_term
542 | >>> line = invoice.lines.new()
543 | >>> line.product = product
544 | >>> line.quantity = 5
545 | >>> line.unit_price = Decimal('40')
546 | >>> invoice.invoice_type = invoice_types['11'] # Factura C
547 | >>> bool(invoice.move)
548 | False
549 | >>> invoice.state
550 | 'draft'
551 | >>> invoice.number
552 | >>> invoice.invoice_date
553 | >>> invoice.click('post') # doctest: +IGNORE_EXCEPTION_DETAIL
554 | Traceback (most recent call last):
555 | ...
556 | UserError: ...
557 | >>> invoice.state
558 | 'validated'
559 | >>> bool(invoice.move)
560 | False
561 | >>> invoice.number
562 | >>> invoice.invoice_date
563 |
564 | >>> company.party.iva_condition = 'responsable_inscripto'
565 | >>> company.party.save()
566 |
--------------------------------------------------------------------------------
/tests/scenario_invoice_pos_electronic_wsfex.rst:
--------------------------------------------------------------------------------
1 | ================
2 | Invoice Scenario
3 | ================
4 |
5 | Imports::
6 | >>> import datetime as dt
7 | >>> import io
8 | >>> from dateutil.relativedelta import relativedelta
9 | >>> from decimal import Decimal
10 | >>> from proteus import Model, Wizard
11 | >>> from trytond.tests.tools import activate_modules
12 | >>> from trytond.modules.currency.tests.tools import get_currency
13 | >>> from trytond.modules.company.tests.tools import create_company, \
14 | ... get_company
15 | >>> from trytond.modules.account.tests.tools import create_fiscalyear, \
16 | ... create_chart
17 | >>> from trytond.modules.account_ar.tests.tools import get_accounts
18 | >>> from trytond.modules.account_invoice.tests.tools import \
19 | ... set_fiscalyear_invoice_sequences
20 | >>> from trytond.modules.account_invoice_ar.tests.tools import \
21 | ... create_pos, get_pos, get_invoice_types, get_tax, get_wsfexv1
22 | >>> from trytond.modules.party_ar.tests.tools import set_afip_certs
23 | >>> import pytz
24 | >>> timezone = pytz.timezone('America/Argentina/Buenos_Aires')
25 | >>> today = dt.datetime.now(timezone).date()
26 | >>> year = int(today.strftime("%Y"))
27 | >>> month = int(today.strftime("%m"))
28 |
29 | Install account_invoice_ar::
30 |
31 | >>> config = activate_modules('account_invoice_ar')
32 |
33 | Create company::
34 |
35 | >>> currency = get_currency('ARS')
36 | >>> currency.afip_code = 'PES'
37 | >>> currency.save()
38 | >>> _ = create_company(currency=currency)
39 | >>> company = get_company()
40 | >>> tax_identifier = company.party.identifiers.new()
41 | >>> tax_identifier.type = 'ar_vat'
42 | >>> tax_identifier.code = '30710158254' # gcoop CUIT
43 | >>> company.party.iva_condition = 'responsable_inscripto'
44 | >>> company.party.save()
45 |
46 | Configure AFIP certificates::
47 |
48 | >>> _ = set_afip_certs(company=company)
49 |
50 | Create fiscal year::
51 |
52 | >>> fiscalyear = set_fiscalyear_invoice_sequences(
53 | ... create_fiscalyear(company))
54 | >>> fiscalyear.click('create_period')
55 | >>> period = fiscalyear.periods[0]
56 | >>> period_ids = [p.id for p in fiscalyear.periods]
57 |
58 | Create chart of accounts::
59 |
60 | >>> _ = create_chart(company, chart='account_ar.root_ar')
61 | >>> accounts = get_accounts(company)
62 | >>> account_receivable = accounts['receivable']
63 | >>> account_payable = accounts['payable']
64 | >>> account_revenue = accounts['revenue']
65 | >>> account_expense = accounts['expense']
66 | >>> account_tax = accounts['sale_tax']
67 | >>> account_cash = accounts['cash']
68 |
69 | Create point of sale::
70 |
71 | >>> _ = create_pos(company, type='electronic', number=5000, ws='wsfex')
72 | >>> pos = get_pos(type='electronic', number=5000)
73 | >>> pos.number
74 | 5000
75 | >>> invoice_types = get_invoice_types(pos=pos)
76 |
77 | Create taxes::
78 |
79 | >>> sale_tax_exento = get_tax('IVA Ventas Exento')
80 |
81 | Create payment method::
82 |
83 | >>> Journal = Model.get('account.journal')
84 | >>> PaymentMethod = Model.get('account.invoice.payment.method')
85 | >>> Sequence = Model.get('ir.sequence')
86 | >>> journal_cash, = Journal.find([('type', '=', 'cash')])
87 | >>> payment_method = PaymentMethod()
88 | >>> payment_method.name = 'Cash'
89 | >>> payment_method.journal = journal_cash
90 | >>> payment_method.credit_account = account_cash
91 | >>> payment_method.debit_account = account_cash
92 | >>> payment_method.save()
93 |
94 | Create Write Off method::
95 |
96 | >>> WriteOff = Model.get('account.move.reconcile.write_off')
97 | >>> sequence_journal, = Sequence.find(
98 | ... [('sequence_type.name', '=', "Account Journal")], limit=1)
99 | >>> journal_writeoff = Journal(name='Write-Off', type='write-off',
100 | ... sequence=sequence_journal)
101 | >>> journal_writeoff.save()
102 | >>> writeoff_method = WriteOff()
103 | >>> writeoff_method.name = 'Rate loss'
104 | >>> writeoff_method.journal = journal_writeoff
105 | >>> writeoff_method.credit_account = account_expense
106 | >>> writeoff_method.debit_account = account_expense
107 | >>> writeoff_method.save()
108 |
109 | Create AFIP VAT Country::
110 |
111 | >>> AFIPCountry = Model.get('afip.country')
112 | >>> sudafrica = AFIPCountry(name='SUDAFRICA', code='159')
113 | >>> sudafrica.save()
114 |
115 | >>> AFIPVatCountry = Model.get('party.afip.vat.country')
116 | >>> afip_vat_country = AFIPVatCountry()
117 | >>> afip_vat_country.vat_number = '55000001715'
118 | >>> afip_vat_country.afip_country = sudafrica
119 | >>> afip_vat_country.type_code = '0'
120 | >>> afip_vat_country.save()
121 |
122 | Create party::
123 |
124 | >>> Party = Model.get('party.party')
125 | >>> party = Party(name='Party')
126 | >>> tax_identifier = party.identifiers.new()
127 | >>> tax_identifier.type = 'ar_foreign'
128 | >>> tax_identifier.code = '55000001715' # SUDAFRICA, Persona Jurídica
129 | >>> tax_identifier.afip_country = sudafrica
130 | >>> party.iva_condition = 'cliente_exterior'
131 | >>> party.save()
132 |
133 | Create account category::
134 |
135 | >>> ProductCategory = Model.get('product.category')
136 | >>> account_category = ProductCategory(name="Account Category")
137 | >>> account_category.accounting = True
138 | >>> account_category.account_expense = account_expense
139 | >>> account_category.account_revenue = account_revenue
140 | >>> account_category.customer_taxes.append(sale_tax_exento)
141 | >>> account_category.save()
142 |
143 | Create product::
144 |
145 | >>> ProductUom = Model.get('product.uom')
146 | >>> unit, = ProductUom.find([('name', '=', 'Unit')])
147 | >>> ProductTemplate = Model.get('product.template')
148 | >>> template = ProductTemplate()
149 | >>> template.name = 'product'
150 | >>> template.default_uom = unit
151 | >>> template.type = 'service'
152 | >>> template.list_price = Decimal('40')
153 | >>> template.account_category = account_category
154 | >>> template.save()
155 | >>> product, = template.products
156 |
157 | Create payment term::
158 |
159 | >>> PaymentTerm = Model.get('account.invoice.payment_term')
160 | >>> payment_term = PaymentTerm(name='Term')
161 | >>> line = payment_term.lines.new(type='percent', ratio=Decimal('.5'))
162 | >>> delta, = line.relativedeltas
163 | >>> delta.days = 20
164 | >>> line = payment_term.lines.new(type='remainder')
165 | >>> delta = line.relativedeltas.new(days=40)
166 | >>> payment_term.save()
167 |
168 | SetUp webservice AFIP::
169 |
170 | >>> wsfexv1 = get_wsfexv1(company, config)
171 |
172 | GetLastCMP and configure sequences::
173 |
174 | >>> # cbte_nro = int(wsfexv1.GetLastCMP('19', pos.number))
175 | >>> cbte_nro = wsfexv1.GetLastCMP('19', pos.number)
176 |
177 | >>> invoice_types['19'].invoice_sequence.number_next = cbte_nro + 1
178 | >>> invoice_types['19'].invoice_sequence.save()
179 |
180 | >>> cbte_nro = int(wsfexv1.GetLastCMP('20', pos.number))
181 | >>> invoice_types['20'].invoice_sequence.number_next = cbte_nro + 1
182 | >>> invoice_types['20'].invoice_sequence.save()
183 |
184 | >>> cbte_nro = int(wsfexv1.GetLastCMP('21', pos.number))
185 | >>> invoice_types['21'].invoice_sequence.number_next = cbte_nro + 1
186 | >>> invoice_types['21'].invoice_sequence.save()
187 |
188 | Get USD currency and configure rate::
189 |
190 | >>> rate = currency.rates.new()
191 | >>> rate.date = today
192 | >>> rate.rate = Decimal(wsfexv1.GetParamCtz('DOL'))
193 | >>> # rate.get_afip_rate()
194 | >>> currency.save()
195 |
196 | Get USD currency::
197 |
198 | >>> usd = get_currency('USD')
199 | >>> usd.afip_code = 'DOL'
200 | >>> usd.save()
201 |
202 | Create invoice::
203 |
204 | >>> Invoice = Model.get('account.invoice')
205 | >>> InvoiceLine = Model.get('account.invoice.line')
206 | >>> invoice = Invoice()
207 | >>> invoice.party = party
208 | >>> invoice.pos = pos
209 | >>> invoice.payment_term = payment_term
210 | >>> invoice.currency = currency
211 | >>> line = InvoiceLine()
212 | >>> invoice.lines.append(line)
213 | >>> line.product = product
214 | >>> line.quantity = 5
215 | >>> line.unit_price = Decimal('40')
216 | >>> invoice.untaxed_amount
217 | Decimal('200.00')
218 | >>> invoice.tax_amount
219 | Decimal('0.00')
220 | >>> invoice.total_amount
221 | Decimal('200.00')
222 | >>> invoice.invoice_type == invoice_types['19']
223 | True
224 | >>> invoice.save()
225 | >>> bool(invoice.has_report_cache)
226 | False
227 |
228 | Test change tax::
229 |
230 | >>> tax_line = invoice.taxes[0]
231 | >>> tax_line.tax == sale_tax_exento
232 | True
233 | >>> tax_line.tax = None
234 | >>> tax_line.tax = sale_tax_exento
235 |
236 | Test missing pyafipws_concept at invoice::
237 |
238 | >>> invoice.click('post') # doctest: +IGNORE_EXCEPTION_DETAIL
239 | Traceback (most recent call last):
240 | ...
241 | UserError: ...
242 | >>> invoice.state
243 | 'draft'
244 |
245 | Post invoice::
246 |
247 | >>> invoice.pyafipws_concept = '2' # service
248 | >>> invoice.pyafipws_billing_start_date = dt.date(year, month, 1)
249 | >>> invoice.pyafipws_billing_end_date = dt.date(year, month, 10)
250 | >>> invoice.pyafipws_incoterms = 'FOB'
251 | >>> invoice.click('post')
252 | >>> invoice.state
253 | 'posted'
254 | >>> # invoice.pyafipws_cae
255 | >>> # invoice.transactions[0].pyafipws_xml_request
256 | >>> # invoice.transactions[0].pyafipws_xml_response
257 | >>> invoice.tax_identifier.code
258 | '30710158254'
259 | >>> invoice.untaxed_amount
260 | Decimal('200.00')
261 | >>> invoice.tax_amount
262 | Decimal('0.00')
263 | >>> invoice.total_amount
264 | Decimal('200.00')
265 | >>> account_receivable.reload()
266 | >>> account_receivable.debit
267 | Decimal('200.00')
268 | >>> account_receivable.credit
269 | Decimal('0.00')
270 | >>> account_revenue.reload()
271 | >>> account_revenue.debit
272 | Decimal('0.00')
273 | >>> account_revenue.credit
274 | Decimal('200.00')
275 | >>> account_tax.reload()
276 | >>> account_tax.debit
277 | Decimal('0.00')
278 | >>> account_tax.credit
279 | Decimal('0.00')
280 |
281 | Credit invoice with refund::
282 |
283 | >>> credit = Wizard('account.invoice.credit', [invoice])
284 | >>> credit.form.with_refund = True
285 | >>> credit.form.invoice_date = invoice.invoice_date
286 | >>> credit.execute('credit')
287 | >>> credit_note, = Invoice.find([
288 | ... ('type', '=', 'out'), ('id', '!=', invoice.id)])
289 | >>> credit_note.state
290 | 'paid'
291 | >>> # credit_note.pyafipws_cae
292 | >>> # credit_note.transactions[0].pyafipws_xml_request
293 | >>> # credit_note.transactions[0].pyafipws_xml_response
294 | >>> credit_note.untaxed_amount == -invoice.untaxed_amount
295 | True
296 | >>> credit_note.tax_amount == -invoice.tax_amount
297 | True
298 | >>> credit_note.total_amount == -invoice.total_amount
299 | True
300 | >>> credit_note.origins == invoice.rec_name
301 | True
302 | >>> credit_note.pos == pos
303 | True
304 | >>> credit_note.invoice_type == invoice_types['21']
305 | True
306 | >>> credit_note.reference == invoice.number
307 | True
308 | >>> invoice.reload()
309 | >>> invoice.state
310 | 'cancelled'
311 | >>> invoice.reconciled == today
312 | True
313 | >>> account_receivable.reload()
314 | >>> account_receivable.debit
315 | Decimal('200.00')
316 | >>> account_receivable.credit
317 | Decimal('200.00')
318 | >>> account_revenue.reload()
319 | >>> account_revenue.debit
320 | Decimal('200.00')
321 | >>> account_revenue.credit
322 | Decimal('200.00')
323 | >>> account_tax.reload()
324 | >>> account_tax.debit
325 | Decimal('0.00')
326 | >>> account_tax.credit
327 | Decimal('0.00')
328 |
329 | Test post without point of sale::
330 |
331 | >>> invoice, = invoice.duplicate()
332 | >>> invoice.currency = currency
333 | >>> invoice.pyafipws_concept
334 | '2'
335 | >>> invoice.pyafipws_incoterms
336 | 'FOB'
337 | >>> invoice.pyafipws_cae
338 | >>> invoice.pyafipws_cae_due_date
339 | >>> invoice.pos
340 | >>> invoice.invoice_type
341 | >>> invoice.transactions
342 | []
343 | >>> invoice.click('post') # doctest: +IGNORE_EXCEPTION_DETAIL
344 | Traceback (most recent call last):
345 | ...
346 | UserError: ...
347 | >>> invoice.state
348 | 'draft'
349 |
350 | Test post when clear tax_identifier type::
351 |
352 | >>> tax_identifier, = company.party.identifiers
353 | >>> tax_identifier.type = None
354 | >>> tax_identifier.save()
355 |
356 | >>> invoice.click('post') # doctest: +IGNORE_EXCEPTION_DETAIL
357 | Traceback (most recent call last):
358 | ...
359 | UserError: ...
360 | >>> invoice.state
361 | 'draft'
362 |
363 | >>> tax_identifier, = company.party.identifiers
364 | >>> tax_identifier.type = 'ar_vat'
365 | >>> tax_identifier.save()
366 |
367 | Pay invoice::
368 |
369 | >>> invoice.pos = pos
370 | >>> invoice.pyafipws_incoterms = 'FOB'
371 | >>> invoice.click('post')
372 | >>> pay = Wizard('account.invoice.pay', [invoice])
373 | >>> pay.form.amount
374 | Decimal('200.00')
375 | >>> pay.form.amount = Decimal('100.00')
376 | >>> pay.form.payment_method = payment_method
377 | >>> pay.execute('choice')
378 | >>> pay.state
379 | 'end'
380 |
381 | >>> pay = Wizard('account.invoice.pay', [invoice])
382 | >>> pay.form.amount
383 | Decimal('100.00')
384 | >>> pay.form.amount = Decimal('10.00')
385 | >>> pay.form.payment_method = payment_method
386 | >>> pay.execute('choice')
387 | >>> pay.form.type = 'partial'
388 | >>> pay.form.amount
389 | Decimal('10.00')
390 | >>> len(pay.form.lines_to_pay)
391 | 1
392 | >>> len(pay.form.payment_lines)
393 | 0
394 | >>> len(pay.form.lines)
395 | 1
396 | >>> pay.form.amount_writeoff
397 | Decimal('90.00')
398 | >>> pay.execute('pay')
399 |
400 | >>> pay = Wizard('account.invoice.pay', [invoice])
401 | >>> pay.form.amount
402 | Decimal('-10.00')
403 | >>> pay.form.amount = Decimal('89.00')
404 | >>> pay.form.payment_method = payment_method
405 | >>> pay.execute('choice')
406 | >>> pay.form.type = 'writeoff'
407 | >>> pay.form.writeoff = writeoff_method
408 | >>> pay.form.amount
409 | Decimal('89.00')
410 | >>> len(pay.form.lines_to_pay)
411 | 1
412 | >>> len(pay.form.payment_lines)
413 | 1
414 | >>> len(pay.form.lines)
415 | 1
416 | >>> pay.form.amount_writeoff
417 | Decimal('1.00')
418 | >>> pay.execute('pay')
419 |
420 | >>> invoice.state
421 | 'paid'
422 | >>> sorted(l.credit for l in invoice.reconciliation_lines)
423 | [Decimal('1.00'), Decimal('10.00'), Decimal('89.00'), Decimal('100.00')]
424 |
425 | Create empty invoice::
426 |
427 | >>> invoice = Invoice()
428 | >>> invoice.party = party
429 | >>> invoice.pos = pos
430 | >>> invoice.pyafipws_concept = '1'
431 | >>> invoice.pyafipws_incoterms = 'FOB'
432 | >>> invoice.payment_term = payment_term
433 | >>> invoice.click('post') # doctest: +IGNORE_EXCEPTION_DETAIL
434 | Traceback (most recent call last):
435 | ...
436 | UserError: ...
437 | >>> invoice.state
438 | 'draft'
439 |
440 | Create some complex invoice and test its taxes base rounding::
441 |
442 | >>> invoice = Invoice()
443 | >>> invoice.party = party
444 | >>> invoice.pos = pos
445 | >>> invoice.pyafipws_concept = '1'
446 | >>> invoice.pyafipws_incoterms = 'FOB'
447 | >>> invoice.payment_term = payment_term
448 | >>> invoice.invoice_date = today
449 | >>> line = invoice.lines.new()
450 | >>> line.product = product
451 | >>> line.quantity = 1
452 | >>> line.unit_price = Decimal('0.0035')
453 | >>> line = invoice.lines.new()
454 | >>> line.product = product
455 | >>> line.quantity = 1
456 | >>> line.unit_price = Decimal('0.0035')
457 | >>> invoice.save()
458 | >>> invoice.untaxed_amount
459 | Decimal('0.00')
460 | >>> invoice.taxes[0].base == invoice.untaxed_amount
461 | True
462 | >>> found_invoice, = Invoice.find([('untaxed_amount', '=', Decimal(0))])
463 | >>> found_invoice.id == invoice.id
464 | True
465 | >>> found_invoice, = Invoice.find([('total_amount', '=', Decimal(0))])
466 | >>> found_invoice.id == invoice.id
467 | True
468 |
469 | Create a paid invoice::
470 |
471 | >>> invoice = Invoice()
472 | >>> invoice.party = party
473 | >>> invoice.pos = pos
474 | >>> invoice.pyafipws_concept = '1'
475 | >>> invoice.pyafipws_incoterms = 'FOB'
476 | >>> invoice.payment_term = payment_term
477 | >>> line = invoice.lines.new()
478 | >>> line.product = product
479 | >>> line.quantity = 5
480 | >>> line.unit_price = Decimal('40')
481 | >>> invoice.click('post')
482 | >>> pay = Wizard('account.invoice.pay', [invoice])
483 | >>> pay.form.payment_method = payment_method
484 | >>> pay.execute('choice')
485 | >>> pay.state
486 | 'end'
487 | >>> invoice.tax_identifier.type
488 | 'ar_vat'
489 | >>> invoice.state
490 | 'paid'
491 |
492 | The invoice is posted when the reconciliation is deleted::
493 |
494 | >>> invoice.payment_lines[0].reconciliation.delete()
495 | >>> invoice.reload()
496 | >>> invoice.state
497 | 'posted'
498 | >>> invoice.tax_identifier.type
499 | 'ar_vat'
500 |
501 | Credit invoice with non line lines::
502 |
503 | >>> invoice = Invoice()
504 | >>> invoice.party = party
505 | >>> invoice.pos = pos
506 | >>> invoice.pyafipws_concept = '1'
507 | >>> invoice.pyafipws_incoterms = 'FOB'
508 | >>> invoice.payment_term = payment_term
509 | >>> line = invoice.lines.new()
510 | >>> line.product = product
511 | >>> line.quantity = 5
512 | >>> line.unit_price = Decimal('40')
513 | >>> line = invoice.lines.new()
514 | >>> line.type = 'comment'
515 | >>> line.description = 'Comment'
516 | >>> invoice.click('post')
517 | >>> credit = Wizard('account.invoice.credit', [invoice])
518 | >>> credit.form.with_refund = True
519 | >>> credit.execute('credit')
520 |
521 | Duplicate and test recover last posted invoice::
522 |
523 | >>> posted_invoice = Invoice.find([
524 | ... ('type', '=', 'out'), ('state', '=', 'posted')])[0]
525 | >>> last_cbte_nro = int(wsfexv1.GetLastCMP('19', pos.number))
526 | >>> invoice, = invoice.duplicate()
527 | >>> invoice.pyafipws_concept
528 | '1'
529 | >>> invoice.pyafipws_cae = posted_invoice.pyafipws_cae
530 | >>> invoice.pyafipws_cae_due_date = posted_invoice.pyafipws_cae_due_date
531 | >>> invoice.pos = posted_invoice.pos
532 | >>> invoice.invoice_type = posted_invoice.invoice_type
533 | >>> # invoice.number = posted_invoice.number
534 | >>> invoice.pyafipws_incoterms = posted_invoice.pyafipws_incoterms
535 | >>> invoice.transactions
536 | []
537 | >>> invoice.save()
538 | >>> invoice.reload()
539 | >>> invoice.state
540 | 'draft'
541 | >>> invoice.invoice_date = posted_invoice.invoice_date
542 | >>> invoice.click('post')
543 | >>> invoice.state
544 | 'posted'
545 | >>> bool(invoice.move)
546 | True
547 | >>> invoice.pos == posted_invoice.pos
548 | True
549 | >>> invoice.invoice_type == posted_invoice.invoice_type
550 | True
551 | >>> # invoice.number == posted_invoice.number
552 | # True
553 | >>> # invoice.pyafipws_cae == posted_invoice.pyafipws_cae
554 | # True
555 | >>> # invoice.transactions[-1].pyafipws_result == posted_invoice.transactions[-1].pyafipws_result
556 | # True
557 | >>> # posted_invoice.transactions[-1].pyafipws_xml_request
558 | >>> # invoice.transactions[-1].pyafipws_xml_request
559 | >>> # posted_invoice.transactions[-1].pyafipws_xml_response
560 | >>> # invoice.transactions[-1].pyafipws_xml_response
561 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | account_invoice_ar Copyright (C) 2013 gcoop Cooperativa de Software Libre
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------