diff --git a/account_invoice_reference/__init__.py b/account_invoice_reference/__init__.py new file mode 100644 index 00000000..ce74e081 --- /dev/null +++ b/account_invoice_reference/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Guewen Baconnier +# Copyright 2014 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from . import account_move diff --git a/account_invoice_reference/__openerp__.py b/account_invoice_reference/__openerp__.py new file mode 100644 index 00000000..ab0f0499 --- /dev/null +++ b/account_invoice_reference/__openerp__.py @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Guewen Baconnier +# Copyright 2014 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +{'name' : 'Invoices Reference', + 'version' : '1.0', + 'author' : 'Camptocamp', + 'maintainer': 'Camptocamp', + 'license': 'AGPL-3', + 'category': 'category', + 'complexity': "easy", + 'depends' : ['account', + ], + 'description': """ +Invoices Reference +================== + +Aims to simplify the "references" fields on the invoices. + +We observed difficulties for the users to file the references (name, +origin, free reference) and above all, to understand which field will be +copied in the reference field of the move and move lines. + +The approach here is to state simple rules with one concern: consistency. +The reference of the move lines must be the number of the document at their very +origin (number of a sales order, of an external document like a supplier +invoice, ...). The goal is for the accountant to be able to trace to the +source document from a ledger). +The description of a line should always be... well, a description. Not a number +or a cryptic reference. + +It particularly fits with other modules of the bank-statement-reconcile series +as account_advanced_reconcile_transaction_ref. + +Fields +------ + +Enumerating the information we need in an invoice, we find that the +mandatory fields are: + +* Invoice Number +* Description +* Internal Reference ("our reference") +* External Reference ("customer or supplier reference") +* Optionally, a technical transaction reference (credit card payment gateways, SEPA, ...) + +Now, on the move lines: + +* Name +* Reference +* Optionally, a technical transaction reference (added by the module `base_transaction_id`) + +Let's see how the information will be organized with this module. + +Customers Invoices / Refunds +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +-----------------+-----------------+------------------------------+ + | Information | Invoice field | Instead of (in base modules) | + +=================+=================+==============================+ + | Invoice number | Invoice number | Invoice number | + +-----------------+-----------------+------------------------------+ + | Description | Name | -- | + +-----------------+-----------------+------------------------------+ + | Internal Ref | Origin | Origin | + +-----------------+-----------------+------------------------------+ + | External Ref | Reference | Name | + +-----------------+-----------------+------------------------------+ + +Information propagated to the move lines: + + +-----------------+------------------------------------+ + | Move line field | Invoice field | + +=================+====================================+ + | Description | Name | + +-----------------+------------------------------------+ + | Reference | Origin, or Invoice number if empty | + +-----------------+------------------------------------+ + + +Supplier Invoices / Refunds +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Supplier invoices have an additional field `supplier_invoice_number` +that we consider as redundant with the reference field. This field is kept +and even set as mandatory, while the reference field is hidden. + + +-----------------+-----------------+------------------------------+ + | Information | Invoice field | Instead of (in base modules) | + +=================+=================+==============================+ + | Invoice number | Invoice number | Invoice number | + +-----------------+-----------------+------------------------------+ + | Description | Name | -- | + +-----------------+-----------------+------------------------------+ + | Internal Ref | Origin | Origin | + +-----------------+-----------------+------------------------------+ + | External Ref | Supplier number | Supplier number | + +-----------------+-----------------+------------------------------+ + +The reference field is hidden when the reference type is "free reference", +because it is already filed in the Supplier invoice number. + +Information propagated to the move lines: + + +-----------------+---------------------------------------------+ + | Move line field | Invoice field | + +=================+=============================================+ + | Description | Name | + +-----------------+---------------------------------------------+ + | Reference | Supplier number, or Invoice number if empty | + +-----------------+---------------------------------------------+ + + """, + 'website': 'http://www.camptocamp.com', + 'data': ['account_invoice_view.xml', + ], + 'test': ['test/out_invoice_with_origin.yml', + 'test/out_invoice_without_origin.yml', + 'test/in_invoice_with_supplier_number.yml', + 'test/in_invoice_without_supplier_number.yml', + 'test/out_refund_with_origin.yml', + 'test/out_refund_without_origin.yml', + 'test/in_refund_with_supplier_number.yml', + 'test/in_refund_without_supplier_number.yml', + + ], + 'installable': True, + 'auto_install': False, +} diff --git a/account_invoice_reference/account_invoice_view.xml b/account_invoice_reference/account_invoice_view.xml new file mode 100644 index 00000000..62bff28a --- /dev/null +++ b/account_invoice_reference/account_invoice_view.xml @@ -0,0 +1,49 @@ + + + + + account.invoice.supplier.form + account.invoice + + + + 1 + + + + + + + + {'invisible': [('reference_type', '=', 'none')]} + + + + + + + + + + + account.invoice.form + account.invoice + + + + + + + + + + + + + + + diff --git a/account_invoice_reference/account_move.py b/account_invoice_reference/account_move.py new file mode 100644 index 00000000..b33a30e8 --- /dev/null +++ b/account_invoice_reference/account_move.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Guewen Baconnier +# Copyright 2014 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp.osv import orm, fields + + +class account_move(orm.Model): + _inherit = 'account.move' + + def create(self, cr, uid, vals, context=None): + if context is None: + context = {} + # invoice from which the move is generated + invoice = context.get('invoice') + if invoice: + assert isinstance(invoice, orm.browse_record) + invoice_obj = self.pool['account.invoice'] + ref = invoice_obj._ref_from_invoice(cr, uid, invoice, context=context) + vals = vals.copy() + vals['ref'] = ref + move_id = super(account_move, self).\ + create(cr, uid, vals, context=context) + return move_id + + +class account_invoice(orm.Model): + _inherit = 'account.invoice' + + def _ref_from_invoice(self, cr, uid, invoice, context=None): + if invoice.type in ('out_invoice', 'out_refund'): + return invoice.origin + elif invoice.type in ('in_invoice', 'in_refund'): + return invoice.supplier_invoice_number + + def action_number(self, cr, uid, ids, context=None): + # force the number of the invoice to be updated for the + # subsequent browse + self.write(cr, uid, ids, {}) + + for invoice in self.browse(cr, uid, ids, context=context): + ref = self._ref_from_invoice(cr, uid, invoice, context=context) + if not ref: + ref = invoice.number + move_id = invoice.move_id.id if invoice.move_id else False + + self.write(cr, uid, ids, {'internal_number': invoice.number}, + context=context) + cr.execute('UPDATE account_move SET ref=%s ' + 'WHERE id=%s AND (ref is null OR ref = \'\')', + (ref, move_id)) + cr.execute('UPDATE account_move_line SET ref=%s ' + 'WHERE move_id=%s AND (ref is null OR ref = \'\')', + (ref, move_id)) + cr.execute('UPDATE account_analytic_line SET ref=%s ' + 'FROM account_move_line ' + 'WHERE account_move_line.move_id = %s ' + 'AND account_analytic_line.move_id = account_move_line.id', + (ref, move_id)) + return True + + def create(self, cr, uid, vals, context=None): + if (vals.get('supplier_invoice_reference') and not + vals.get('reference')): + vals['reference'] = vals['supplier_invoice_reference'] + return super(account_invoice, self).create(cr, uid, vals, + context=context) + + def write(self, cr, uid, ids, vals, context=None): + if vals.get('supplier_invoice_reference'): + if isinstance(ids, (int, long)): + ids = [ids] + for invoice in self.browse(cr, uid, ids, context=context): + local_vals = vals + if not invoice.reference: + locvals = vals.copy() + locvals['reference'] = vals['supplier_invoice_reference'] + super(account_invoice, self).write(cr, uid, [invoice.id], + locvals, context=context) + return True + else: + return super(account_invoice, self).write(cr, uid, ids, vals, + context=context) diff --git a/account_invoice_reference/i18n/account_invoice_reference.pot b/account_invoice_reference/i18n/account_invoice_reference.pot new file mode 100644 index 00000000..45f9cd06 --- /dev/null +++ b/account_invoice_reference/i18n/account_invoice_reference.pot @@ -0,0 +1,42 @@ +# Translation of OpenERP Server. +# This file contains the translation of the following modules: +# * account_invoice_reference +# +msgid "" +msgstr "" +"Project-Id-Version: OpenERP Server 7.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-01-24 14:43+0000\n" +"PO-Revision-Date: 2014-01-24 14:43+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: account_invoice_reference +#: view:account.invoice:0 +msgid "Enter the reference in the Supplier Invoice Number field" +msgstr "" + +#. module: account_invoice_reference +#: view:account.invoice:0 +msgid "{'invisible': [('reference_type', '=', 'none')]}" +msgstr "" + +#. module: account_invoice_reference +#: model:ir.model,name:account_invoice_reference.model_account_move +msgid "Account Entry" +msgstr "" + +#. module: account_invoice_reference +#: view:account.invoice:0 +msgid "Customer Reference" +msgstr "" + +#. module: account_invoice_reference +#: model:ir.model,name:account_invoice_reference.model_account_invoice +msgid "Invoice" +msgstr "" + diff --git a/account_invoice_reference/i18n/fr.po b/account_invoice_reference/i18n/fr.po new file mode 100644 index 00000000..6b7771c0 --- /dev/null +++ b/account_invoice_reference/i18n/fr.po @@ -0,0 +1,42 @@ +# Translation of OpenERP Server. +# This file contains the translation of the following modules: +# * account_invoice_reference +# +msgid "" +msgstr "" +"Project-Id-Version: OpenERP Server 7.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-01-24 14:43+0000\n" +"PO-Revision-Date: 2014-01-24 14:43+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: account_invoice_reference +#: view:account.invoice:0 +msgid "Enter the reference in the Supplier Invoice Number field" +msgstr "Veuillez saisir la référence dans le champ Numéro de facture fournisseur" + +#. module: account_invoice_reference +#: view:account.invoice:0 +msgid "{'invisible': [('reference_type', '=', 'none')]}" +msgstr "" + +#. module: account_invoice_reference +#: model:ir.model,name:account_invoice_reference.model_account_move +msgid "Account Entry" +msgstr "Pièce comptable" + +#. module: account_invoice_reference +#: view:account.invoice:0 +msgid "Customer Reference" +msgstr "Référence client" + +#. module: account_invoice_reference +#: model:ir.model,name:account_invoice_reference.model_account_invoice +msgid "Invoice" +msgstr "Facture" + diff --git a/account_invoice_reference/test/in_invoice_with_supplier_number.yml b/account_invoice_reference/test/in_invoice_with_supplier_number.yml new file mode 100644 index 00000000..aaba9e44 --- /dev/null +++ b/account_invoice_reference/test/in_invoice_with_supplier_number.yml @@ -0,0 +1,40 @@ +- + In order to check if the reference of the move is the supplier invoice number of + the invoice (supplier invoices), I create an invoice +- + !record {model: account.invoice, id: invoice_reference_supplier_number, view: account.invoice_supplier_form}: + account_id: account.a_pay + check_total: 3000.0 + company_id: base.main_company + currency_id: base.EUR + invoice_line: + - account_id: account.a_expense + name: '[PCSC234] PC Assemble SC234' + price_unit: 300.0 + product_id: product.product_product_3 + quantity: 10.0 + uos_id: product.product_uom_unit + journal_id: account.expenses_journal + partner_id: base.res_partner_12 + reference_type: none + supplier_invoice_number: ZZZ246 + type: in_invoice +- + Set again the type of the invoice (not set on the first one...) +- + !record {model: account.invoice, id: invoice_reference_supplier_number, view: account.invoice_supplier_form}: + type: in_invoice +- + Ensure that the invoice is a supplier invoice +- + !assert {model: account.invoice, id: invoice_reference_supplier_number}: + - type == 'in_invoice' +- + I create invoice by clicking on Create button +- + !workflow {model: account.invoice, action: invoice_open, ref: invoice_reference_supplier_number} +- + I check that the reference of the move is the supplier invoice number of the invoice +- + !assert {model: account.invoice, id: invoice_reference_supplier_number}: + - move_id.ref == 'ZZZ246' diff --git a/account_invoice_reference/test/in_invoice_without_supplier_number.yml b/account_invoice_reference/test/in_invoice_without_supplier_number.yml new file mode 100644 index 00000000..d2833484 --- /dev/null +++ b/account_invoice_reference/test/in_invoice_without_supplier_number.yml @@ -0,0 +1,39 @@ +- + In order to check if the reference of the move is the supplier invoice number of + the invoice (supplier invoices), I create an invoice +- + !record {model: account.invoice, id: invoice_reference_no_supplier_number, view: account.invoice_supplier_form}: + account_id: account.a_pay + check_total: 3000.0 + company_id: base.main_company + currency_id: base.EUR + invoice_line: + - account_id: account.a_expense + name: '[PCSC234] PC Assemble SC234' + price_unit: 300.0 + product_id: product.product_product_3 + quantity: 10.0 + uos_id: product.product_uom_unit + journal_id: account.expenses_journal + partner_id: base.res_partner_12 + reference_type: none + type: in_invoice +- + Set again the type of the invoice (not set on the first one...) +- + !record {model: account.invoice, id: invoice_reference_supplier_number, view: account.invoice_supplier_form}: + type: in_invoice +- + Ensure that the invoice is a supplier invoice +- + !assert {model: account.invoice, id: invoice_reference_supplier_number}: + - type == 'in_invoice' +- + I create invoice by clicking on Create button +- + !workflow {model: account.invoice, action: invoice_open, ref: invoice_reference_no_supplier_number} +- + I check that the reference of the move is the number of the invoice +- + !assert {model: account.invoice, id: invoice_reference_no_supplier_number}: + - move_id.ref == number diff --git a/account_invoice_reference/test/in_refund_with_supplier_number.yml b/account_invoice_reference/test/in_refund_with_supplier_number.yml new file mode 100644 index 00000000..b67e71bf --- /dev/null +++ b/account_invoice_reference/test/in_refund_with_supplier_number.yml @@ -0,0 +1,40 @@ +- + In order to check if the reference of the move is the supplier invoice number of + the refund (supplier refunds), I create a refund +- + !record {model: account.invoice, id: refund_reference_supplier_number, view: account.invoice_supplier_form}: + account_id: account.a_pay + check_total: 3000.0 + company_id: base.main_company + currency_id: base.EUR + invoice_line: + - account_id: account.a_expense + name: '[PCSC234] PC Assemble SC234' + price_unit: 300.0 + product_id: product.product_product_3 + quantity: 10.0 + uos_id: product.product_uom_unit + journal_id: account.expenses_journal + partner_id: base.res_partner_12 + reference_type: none + supplier_invoice_number: RZZZ246 + type: in_refund +- + Set again the type of the refund (not set on the first one...) +- + !record {model: account.invoice, id: refund_reference_supplier_number, view: account.invoice_supplier_form}: + type: in_refund +- + Ensure that the refund is a supplier refund +- + !assert {model: account.invoice, id: refund_reference_supplier_number}: + - type == 'in_refund' +- + I create refund by clicking on Create button +- + !workflow {model: account.invoice, action: invoice_open, ref: refund_reference_supplier_number} +- + I check that the reference of the move is the supplier refund number of the refund +- + !assert {model: account.invoice, id: refund_reference_supplier_number}: + - move_id.ref == 'RZZZ246' diff --git a/account_invoice_reference/test/in_refund_without_supplier_number.yml b/account_invoice_reference/test/in_refund_without_supplier_number.yml new file mode 100644 index 00000000..c82f10a3 --- /dev/null +++ b/account_invoice_reference/test/in_refund_without_supplier_number.yml @@ -0,0 +1,39 @@ +- + In order to check if the reference of the move is the supplier refund number of + the refund (supplier refunds), I create an refund +- + !record {model: account.invoice, id: refund_reference_no_supplier_number, view: account.invoice_supplier_form}: + account_id: account.a_pay + check_total: 3000.0 + company_id: base.main_company + currency_id: base.EUR + invoice_line: + - account_id: account.a_expense + name: '[PCSC234] PC Assemble SC234' + price_unit: 300.0 + product_id: product.product_product_3 + quantity: 10.0 + uos_id: product.product_uom_unit + journal_id: account.expenses_journal + partner_id: base.res_partner_12 + reference_type: none + type: in_refund +- + Set again the type of the refund (not set on the first one...) +- + !record {model: account.invoice, id: refund_reference_supplier_number, view: account.invoice_supplier_form}: + type: in_refund +- + Ensure that the refund is a supplier refund +- + !assert {model: account.invoice, id: refund_reference_supplier_number}: + - type == 'in_refund' +- + I create refund by clicking on Create button +- + !workflow {model: account.invoice, action: invoice_open, ref: refund_reference_no_supplier_number} +- + I check that the reference of the move is the number of the refund +- + !assert {model: account.invoice, id: refund_reference_no_supplier_number}: + - move_id.ref == number diff --git a/account_invoice_reference/test/out_invoice_with_origin.yml b/account_invoice_reference/test/out_invoice_with_origin.yml new file mode 100644 index 00000000..02bae0bc --- /dev/null +++ b/account_invoice_reference/test/out_invoice_with_origin.yml @@ -0,0 +1,23 @@ +- + In order to check if the reference of the move is the origin of + the invoice (customer invoices), I create an invoice +- + !record {model: account.invoice, id: invoice_reference_origin}: + payment_term: account.account_payment_term_advance + journal_id: account.sales_journal + partner_id: base.res_partner_3 + reference_type: none + name: 'Test Customer Invoice' + origin: ABC123 + invoice_line: + - product_id: product.product_product_5 + quantity: 10.0 +- + I create invoice by clicking on Create button +- + !workflow {model: account.invoice, action: invoice_open, ref: invoice_reference_origin} +- + I check that the reference of the move is the origin of the invoice +- + !assert {model: account.invoice, id: invoice_reference_origin}: + - move_id.ref == 'ABC123' diff --git a/account_invoice_reference/test/out_invoice_without_origin.yml b/account_invoice_reference/test/out_invoice_without_origin.yml new file mode 100644 index 00000000..836d758c --- /dev/null +++ b/account_invoice_reference/test/out_invoice_without_origin.yml @@ -0,0 +1,22 @@ +- + In order to check if the reference of the move is the number of + the invoice (customer invoices) when it has no origin, I create an invoice +- + !record {model: account.invoice, id: invoice_reference_no_origin}: + payment_term: account.account_payment_term_advance + journal_id: account.sales_journal + partner_id: base.res_partner_3 + reference_type: none + name: 'Test Customer Invoice no origin' + invoice_line: + - product_id: product.product_product_5 + quantity: 10.0 +- + I create invoice by clicking on Create button +- + !workflow {model: account.invoice, action: invoice_open, ref: invoice_reference_no_origin} +- + I check that the reference of the move is the number of the invoice +- + !assert {model: account.invoice, id: invoice_reference_no_origin}: + - move_id.ref == number diff --git a/account_invoice_reference/test/out_refund_with_origin.yml b/account_invoice_reference/test/out_refund_with_origin.yml new file mode 100644 index 00000000..d50e9aeb --- /dev/null +++ b/account_invoice_reference/test/out_refund_with_origin.yml @@ -0,0 +1,34 @@ +- + In order to check if the reference of the move is the origin of + the refund (customer refunds), I create a refund +- + !record {model: account.invoice, id: refund_reference_origin, view: account.invoice_form}: + payment_term: account.account_payment_term_advance + journal_id: account.sales_journal + partner_id: base.res_partner_3 + reference_type: none + name: 'Test Customer refund' + origin: RABC123 + invoice_line: + - product_id: product.product_product_5 + quantity: 10.0 + type: out_refund +- + Set again the type of the invoice (not set on the first one...) +- + !record {model: account.invoice, id: refund_reference_origin, view: account.invoice_form}: + type: out_refund +- + Ensure that the invoice is a customer refund +- + !assert {model: account.invoice, id: refund_reference_origin}: + - type == 'out_refund' +- + I create a refund by clicking on Create button +- + !workflow {model: account.invoice, action: invoice_open, ref: refund_reference_origin} +- + I check that the reference of the move is the origin of the refund +- + !assert {model: account.invoice, id: refund_reference_origin}: + - move_id.ref == 'RABC123' diff --git a/account_invoice_reference/test/out_refund_without_origin.yml b/account_invoice_reference/test/out_refund_without_origin.yml new file mode 100644 index 00000000..1ce3fb46 --- /dev/null +++ b/account_invoice_reference/test/out_refund_without_origin.yml @@ -0,0 +1,33 @@ +- + In order to check if the reference of the move is the number of + the refund (customer refunds) when it has no origin, I create a refund +- + !record {model: account.invoice, id: refund_reference_no_origin}: + payment_term: account.account_payment_term_advance + journal_id: account.sales_journal + partner_id: base.res_partner_3 + reference_type: none + name: 'Test Customer refund no origin' + invoice_line: + - product_id: product.product_product_5 + quantity: 10.0 + type: out_refund +- + Set again the type of the invoice (not set on the first one...) +- + !record {model: account.invoice, id: refund_reference_no_origin, view: account.invoice_form}: + type: out_refund +- + Ensure that the invoice is a customer refund +- + !assert {model: account.invoice, id: refund_reference_no_origin}: + - type == 'out_refund' +- + I create refund by clicking on Create button +- + !workflow {model: account.invoice, action: invoice_open, ref: refund_reference_no_origin} +- + I check that the reference of the move is the number of the refund +- + !assert {model: account.invoice, id: refund_reference_no_origin}: + - move_id.ref == number