Skip to content

Commit

Permalink
Merge 984cde0 into 0bf3d6b
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexis de Lattre committed Aug 18, 2016
2 parents 0bf3d6b + 984cde0 commit 87c23ec
Show file tree
Hide file tree
Showing 77 changed files with 6,078 additions and 2 deletions.
16 changes: 14 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@ language: python
python:
- "2.7"

sudo: false
sudo: true
cache: pip

addons:
apt:
sources:
- pov-wkhtmltopdf
packages:
- expect-dev # provides unbuffer utility
- python-lxml # because pip installation is slow
- python-simplejson
- python-serial
- wkhtmltopdf # requires the before_install section below

before_install:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"

env:
global:
Expand All @@ -37,7 +44,12 @@ install:
- export PATH=${HOME}/maintainer-quality-tools/travis:${PATH}
- pip install unidecode
- travis_install_nightly
- pip install anybox.testing.openerp # needed for testing account_invoice_line_sort
- pip install anybox.testing.openerp # needed to test account_invoice_line_sort
- pip install invoice2data # needed to test account_invoice_import
- wget -P /tmp http://public.akretion.com/pdftotext-3.04
- sudo mv /tmp/pdftotext-3.04 /usr/local/bin/pdftotext
- sudo chmod 755 /usr/local/bin/pdftotext
- pip install PyPDF2 # needed for account_invoice*zugferd

script:
- travis_run_tests
Expand Down
88 changes: 88 additions & 0 deletions account_invoice_import/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3

======================
Account Invoice Import
======================

This module has been started by lazy accounting users who hate enter they supplier invoices manually in Odoo. Almost all companies have several supplier invoices to enter regularly in the system from the same suppliers: phone bill, electricity bill, Internet access, train tickets, etc. Most of these invoices are available as PDF. We dream that we would be able to automatically extract from the PDF the required information to enter the invoice as supplier invoice in Odoo. To know the full story behind the development of this module, read this `blog post <http://www.akretion.com/blog/akretions-christmas-present-for-the-odoo-community>`_.

In the future, we believe we will have structured information embedded inside the metadata of PDF invoices. There are 2 main standards for electronic invoicing:

* `CII <http://tfig.unece.org/contents/cross-industry-invoice-cii.htm>`_ (Cross-Industry Invoice) developped by `UN/CEFACT <http://www.unece.org/cefact>`_ (United Nations Centre for Trade Facilitation and Electronic Business),
* `UBL <http://ubl.xml.org/>`_ (Universal Business Language) which is an ISO standard (`ISO/IEC 19845 <http://www.iso.org/iso/catalogue_detail.htm?csnumber=66370>`_) developped by `OASIS <https://www.oasis-open.org/>`_ (Organization for the Advancement of Structured Information Standards).

For example, there is already a standard in Germany called `ZUGFeRD <http://www.pdflib.com/knowledge-base/pdfa/zugferd-invoices/>`_ which is based on CII.

This module doesn't do anything useful by itself ; it requires other modules to work: each modules adds a specific invoice format.

Here is how the module works:

* the user starts a wizard and uploads the PDF or XML invoice,
* if it is an XML file, Odoo will parse it to create the invoice (requires additional modules for specific XML formats, such as the module *account_invoice_import_ubl* for the UBL format),
* if it is a PDF file with an embedded XML file in ZUGFeRD/CII format, Odoo will extract the embedded XML file and parse it to create the invoice (requires the module *account_invoice_import_zugferd*),
* otherwise, Odoo will use the *invoice2data* Python library to try to interpret the text of the PDF (requires the module *account_invoice_import_invoice2data*),
* if there is already some draft supplier invoice for this supplier, Odoo will propose to select one to update or create a new draft invoice,
* otherwise, Odoo will directly create a new draft supplier invoice and attach the PDF to it.

This module also works with supplier refunds.

Configuration
=============

Go to the form view of the suppliers and configure it with the following parameters:

* *is a Company ?* is True
* *Supplier* is True
* the *TIN* (i.e. VAT number) is set (the VAT number is used by default when searching the supplier in the Odoo partner database)
* in the *Accounting* tab, create an *Invoice Import Configuration*.

Usage
=====

To use this module, go to the menu *Accounting > Suppliers > Import Invoices* and upload a PDF or XML invoice of your supplier.

.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/95/8.0

Known issues / Roadmap
======================

* Remove dependency on *base_iban* and develop a separate glue module between this module and *base_iban*

* Enhance the update of an existing invoice by analysing the lines (lines are only available when the invoice has an embedded XML file)

* Add a mail gateway to be able to forward the emails that we receive with PDF invoices to a dedicated address ; the gateway would detach the PDF invoice from the email and create the draft supplier invoice in Odoo.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues
<https://github.com/OCA/account-invoicing/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.

Credits
=======

Contributors
------------

* Alexis de Lattre <alexis.delattre@akretion.com>

Maintainer
----------

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

This module is maintained by the OCA.

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

To contribute to this module, please visit https://odoo-community.org.
6 changes: 6 additions & 0 deletions account_invoice_import/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-

from . import partner
from . import account_invoice_import_config
from . import account_invoice
from . import wizard
23 changes: 23 additions & 0 deletions account_invoice_import/__openerp__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# © 2015-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

{
'name': 'Account Invoice Import',
'version': '8.0.1.0.0',
'category': 'Accounting & Finance',
'license': 'AGPL-3',
'summary': 'Import supplier invoices/refunds as PDF or XML files',
'author': 'Akretion,Odoo Community Association (OCA)',
'website': 'http://www.akretion.com',
'depends': ['account', 'base_iban', 'base_business_document_import'],
'external_dependencies': {'python': ['lxml']},
'data': [
'security/ir.model.access.csv',
'account_invoice_import_config_view.xml',
'wizard/account_invoice_import_view.xml',
'partner_view.xml',
],
'images': ['images/sshot-wizard1.png'],
'installable': True,
}
24 changes: 24 additions & 0 deletions account_invoice_import/account_invoice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# © 2015-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from openerp import models, api, _


class AccountInvoice(models.Model):
_inherit = 'account.invoice'

@api.multi
def name_get(self):
"""Add amount_untaxed in name_get of invoices"""
res = super(AccountInvoice, self).name_get()
if self._context.get('invoice_show_amount'):
new_res = []
for (inv_id, name) in res:
inv = self.browse(inv_id)
name += _(' Amount w/o tax: %s %s') % (
inv.amount_untaxed, inv.currency_id.name)
new_res.append((inv_id, name))
return new_res
else:
return res
68 changes: 68 additions & 0 deletions account_invoice_import/account_invoice_import_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
# © 2015-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from openerp import models, fields, api, _
from openerp.exceptions import ValidationError


class AccountInvoiceImportConfig(models.Model):
_name = 'account.invoice.import.config'
_description = 'Configuration for the import of Supplier Invoices'

name = fields.Char(string='Name', required=True)
partner_ids = fields.One2many(
'res.partner', 'invoice_import_id',
string='Partners')
active = fields.Boolean(default=True)
invoice_line_method = fields.Selection([
('1line_no_product', 'Single Line, No Product'),
('1line_static_product', 'Single Line, Static Product'),
('nline_no_product', 'Multi Line, No Product'),
('nline_static_product', 'Multi Line, Static Product'),
('nline_auto_product', 'Multi Line, Auto-selected Product'),
], string='Method for Invoice Line', required=True,
default='1line_no_product',
help="The multi-line methods will not work for PDF invoices "
"that don't have an embedded XML file. "
"The 'Multi Line, Auto-selected Product' method will only work with "
"ZUGFeRD invoices at Comfort or Extended level, not at Basic level.")
company_id = fields.Many2one(
'res.company', string='Company',
ondelete='cascade', required=True,
default=lambda self: self.env['res.company']._company_default_get(
'account.invoice.import.config'))
account_id = fields.Many2one(
'account.account', string='Expense Account',
domain=[('type', 'not in', ('view', 'closed'))])
account_analytic_id = fields.Many2one(
'account.analytic.account', string='Analytic Account',
domain=[('type', '!=', 'view')])
label = fields.Char(
string='Force Description',
help="Force supplier invoice line description")
tax_ids = fields.Many2many(
'account.tax', string='Taxes',
domain=[('type_tax_use', 'in', ('all', 'purchase'))])
static_product_id = fields.Many2one(
'product.product', string='Static Product')

@api.constrains('invoice_line_method', 'account_id', 'static_product_id')
def _check_import_config(self):
for config in self:
if (
config.invoice_line_method == 'static_product' and
not config.static_product_id):
raise ValidationError(_(
"Static Product must be set on the invoice import "
"configuration of supplier '%s' that has a Method "
"for Invoice Line set to 'Static Product'.")
% config.partner_id.name)
if (
config.invoice_line_method == 'no_product' and
not config.account_id):
raise ValidationError(_(
"The Expense Account must be set on the invoice "
"import configuration of supplier '%s' that has a "
"Method for Invoice Line set to 'Without product'.")
% config.partner_id.name)
67 changes: 67 additions & 0 deletions account_invoice_import/account_invoice_import_config_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2015 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
The licence is in the file __openerp__.py
-->

<openerp>
<data>

<record id="account_invoice_import_config_form" model="ir.ui.view">
<field name="name">account.invoice.import.config.form</field>
<field name="model">account.invoice.import.config</field>
<field name="arch" type="xml">
<form string="Account Invoice Import Configuration">
<group name="main">
<field name="name"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="active"/>
</group>
<group string="Accounting Parameters" name="accounting">
<field name="invoice_line_method"/>
<field name="account_id"
attrs="{'invisible': [('invoice_line_method', 'not in', ('1line_no_product', 'nline_no_product'))], 'required': [('invoice_line_method', 'in', ('1line_no_product', 'nline_no_product'))]}"/>
<field name="tax_ids"
widget="many2many_tags"
attrs="{'invisible': [('invoice_line_method', '!=', '1line_no_product')]}"/>
<field name="static_product_id"
attrs="{'invisible': [('invoice_line_method', 'not in', ('1line_static_product', 'nline_static_product'))], 'required': [('invoice_line_method', 'in', ('1line_static_product', 'nline_static_product'))]}"/>
<field name="account_analytic_id"
groups="analytic.group_analytic_accounting"/>
<field name="label"
attrs="{'invisible': [('invoice_line_method', 'not in', ('1line_no_product', '1line_static_product'))]}"/>
</group>
<group name="partner" string="Partners"
invisible="not context.get('invoice_import_config_main_view')">
<field name="partner_ids" nolabel="1" readonly="1"/> <!-- set this field readonly to avoid the frustration of being unable to select an existing partner from this view -->
</group>
</form>
</field>
</record>

<record id="account_invoice_import_config_tree" model="ir.ui.view">
<field name="name">account.invoice.import.config.tree</field>
<field name="model">account.invoice.import.config</field>
<field name="arch" type="xml">
<tree string="Account Invoice Import Configurations">
<field name="name"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="invoice_line_method"/>
</tree>
</field>
</record>

<record id="account_invoice_import_config_action" model="ir.actions.act_window">
<field name="name">Import Supplier Invoices</field>
<field name="res_model">account.invoice.import.config</field>
<field name="view_mode">tree,form</field>
<field name="context">{'invoice_import_config_main_view': True}</field>
</record>

<menuitem id="account_invoice_import_config_menu"
parent="account.menu_configuration_misc"
action="account_invoice_import_config_action" sequence="100"/>

</data>
</openerp>

0 comments on commit 87c23ec

Please sign in to comment.