diff options
| author | Josse Colpaert <jco@odoo.com> | 2019-12-12 14:25:34 +0000 |
|---|---|---|
| committer | Josse Colpaert <jco@odoo.com> | 2020-02-17 16:33:22 +0000 |
| commit | 47eff65afdba3a2a8283342d8b636657f1fac317 (patch) | |
| tree | 3ee74b04a78fa7fd051f9659d7c96008c4fe4e6d /addons/account_debit_note | |
| parent | 8de07a8aa569cf6e21388d67c02344c8fdf0751e (diff) | |
[ADD] account_debit_note: Debit Note functionality
You might think that if the debit note is like the inverse
of a credit note, that it is just a regular invoice.
Well, it is, except that we had several localizations
adding a link with the original invoice for the debit note.
This way, we can share that many2one and add a wizard that is
similar as the one for the credit once and for all.
In order to keep things simple, we always create a
draft debit note. The only option we give, is to copy
the original invoice lines or not.
You can also create a debit note from a credit note,
in which case the type needs to change, but we don't
copy the lines in that case (for now).
opw-2181586
closes odoo/odoo#45542
X-original-commit: f1af011d0966fa6e436168879b7f18fabb955934
Signed-off-by: Josse Colpaert <jco@openerp.com>
Diffstat (limited to 'addons/account_debit_note')
| -rw-r--r-- | addons/account_debit_note/__init__.py | 5 | ||||
| -rw-r--r-- | addons/account_debit_note/__manifest__.py | 23 | ||||
| -rw-r--r-- | addons/account_debit_note/i18n/account_debit_note.pot | 165 | ||||
| -rw-r--r-- | addons/account_debit_note/models/__init__.py | 4 | ||||
| -rw-r--r-- | addons/account_debit_note/models/account_move.py | 30 | ||||
| -rw-r--r-- | addons/account_debit_note/security/ir.model.access.csv | 2 | ||||
| -rw-r--r-- | addons/account_debit_note/tests/__init__.py | 3 | ||||
| -rw-r--r-- | addons/account_debit_note/tests/test_out_debit_note.py | 46 | ||||
| -rw-r--r-- | addons/account_debit_note/views/account_move_view.xml | 28 | ||||
| -rw-r--r-- | addons/account_debit_note/wizard/__init__.py | 4 | ||||
| -rw-r--r-- | addons/account_debit_note/wizard/account_debit_note.py | 90 | ||||
| -rw-r--r-- | addons/account_debit_note/wizard/account_debit_note_view.xml | 40 |
12 files changed, 440 insertions, 0 deletions
diff --git a/addons/account_debit_note/__init__.py b/addons/account_debit_note/__init__.py new file mode 100644 index 00000000000..2ae6446f9dc --- /dev/null +++ b/addons/account_debit_note/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models +from . import wizard diff --git a/addons/account_debit_note/__manifest__.py b/addons/account_debit_note/__manifest__.py new file mode 100644 index 00000000000..07a739af121 --- /dev/null +++ b/addons/account_debit_note/__manifest__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'Debit Notes', + 'version': '1.0', + 'category': 'Accounting/Accounting', + 'summary': 'Debit Notes', + 'description': """ +In a lot of countries, a debit note is used as an increase of the amounts of an existing invoice +or in some specific cases to cancel a credit note. +It is like a regular invoice, but we need to keep track of the link with the original invoice. +The wizard used is similar as the one for the credit note. + """, + 'depends': ['account'], + 'data': [ + 'wizard/account_debit_note_view.xml', + 'views/account_move_view.xml', + 'security/ir.model.access.csv', + ], + 'installable': True, + 'auto_install': False, +} diff --git a/addons/account_debit_note/i18n/account_debit_note.pot b/addons/account_debit_note/i18n/account_debit_note.pot new file mode 100644 index 00000000000..f07668e4af9 --- /dev/null +++ b/addons/account_debit_note/i18n/account_debit_note.pot @@ -0,0 +1,165 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_debit_note +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-02-14 09:23+0000\n" +"PO-Revision-Date: 2020-02-14 09:23+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_debit_note +#: model_terms:ir.ui.view,arch_db:account_debit_note.view_move_form_debit +msgid "<span class=\"o_stat_text\">Debit Notes</span>" +msgstr "" + +#. module: account_debit_note +#: model_terms:ir.ui.view,arch_db:account_debit_note.view_move_form_debit +msgid "Add Debit Note" +msgstr "" + +#. module: account_debit_note +#: model:ir.model,name:account_debit_note.model_account_debit_note +msgid "Add Debit Note wizard" +msgstr "" + +#. module: account_debit_note +#: model_terms:ir.ui.view,arch_db:account_debit_note.view_account_debit_note +msgid "Cancel" +msgstr "" + +#. module: account_debit_note +#: model:ir.model.fields,field_description:account_debit_note.field_account_debit_note__copy_lines +msgid "Copy Lines" +msgstr "" + +#. module: account_debit_note +#: model:ir.actions.act_window,name:account_debit_note.action_view_account_move_debit +#: model_terms:ir.ui.view,arch_db:account_debit_note.view_account_debit_note +msgid "Create Debit Note" +msgstr "" + +#. module: account_debit_note +#: model:ir.model.fields,field_description:account_debit_note.field_account_debit_note__create_uid +msgid "Created by" +msgstr "" + +#. module: account_debit_note +#: model:ir.model.fields,field_description:account_debit_note.field_account_debit_note__create_date +msgid "Created on" +msgstr "" + +#. module: account_debit_note +#: model:ir.model.fields,field_description:account_debit_note.field_account_debit_note__date +#: model_terms:ir.ui.view,arch_db:account_debit_note.view_account_debit_note +msgid "Debit Note Date" +msgstr "" + +#. module: account_debit_note +#: code:addons/account_debit_note/models/account_move.py:0 +#: code:addons/account_debit_note/wizard/account_debit_note.py:0 +#: model:ir.model.fields,field_description:account_debit_note.field_account_move__debit_note_ids +#, python-format +msgid "Debit Notes" +msgstr "" + +#. module: account_debit_note +#: model:ir.model.fields,field_description:account_debit_note.field_account_debit_note__display_name +msgid "Display Name" +msgstr "" + +#. module: account_debit_note +#: model:ir.model.fields,field_description:account_debit_note.field_account_debit_note__id +msgid "ID" +msgstr "" + +#. module: account_debit_note +#: model:ir.model.fields,help:account_debit_note.field_account_debit_note__journal_id +msgid "If empty, uses the journal of the journal entry to be debited." +msgstr "" + +#. module: account_debit_note +#: model:ir.model.fields,help:account_debit_note.field_account_debit_note__copy_lines +msgid "" +"In case you need to do corrections for every line, it can be in handy to " +"copy them. We won't copy them for debit notes from credit notes. " +msgstr "" + +#. module: account_debit_note +#: model:ir.model,name:account_debit_note.model_account_move +msgid "Journal Entries" +msgstr "" + +#. module: account_debit_note +#: model:ir.model.fields,field_description:account_debit_note.field_account_debit_note__journal_type +msgid "Journal Type" +msgstr "" + +#. module: account_debit_note +#: model:ir.model.fields,field_description:account_debit_note.field_account_debit_note____last_update +msgid "Last Modified on" +msgstr "" + +#. module: account_debit_note +#: model:ir.model.fields,field_description:account_debit_note.field_account_debit_note__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: account_debit_note +#: model:ir.model.fields,field_description:account_debit_note.field_account_debit_note__write_date +msgid "Last Updated on" +msgstr "" + +#. module: account_debit_note +#: model:ir.model.fields,field_description:account_debit_note.field_account_debit_note__move_ids +msgid "Move" +msgstr "" + +#. module: account_debit_note +#: model:ir.model.fields,field_description:account_debit_note.field_account_debit_note__move_type +msgid "Move Type" +msgstr "" + +#. module: account_debit_note +#: model:ir.model.fields,field_description:account_debit_note.field_account_move__debit_note_count +msgid "Number of Debit Notes" +msgstr "" + +#. module: account_debit_note +#: model:ir.model.fields,field_description:account_debit_note.field_account_move__debit_origin_id +msgid "Original Invoice Debited" +msgstr "" + +#. module: account_debit_note +#: model:ir.model.fields,field_description:account_debit_note.field_account_debit_note__reason +msgid "Reason" +msgstr "" + +#. module: account_debit_note +#: model:ir.model.fields,help:account_debit_note.field_account_move__debit_note_ids +msgid "The debit notes created for this invoice" +msgstr "" + +#. module: account_debit_note +#: code:addons/account_debit_note/wizard/account_debit_note.py:0 +#, python-format +msgid "This debit note was created from:" +msgstr "" + +#. module: account_debit_note +#: model:ir.model.fields,field_description:account_debit_note.field_account_debit_note__journal_id +msgid "Use Specific Journal" +msgstr "" + +#. module: account_debit_note +#: code:addons/account_debit_note/wizard/account_debit_note.py:0 +#, python-format +msgid "You can only debit posted moves." +msgstr "" diff --git a/addons/account_debit_note/models/__init__.py b/addons/account_debit_note/models/__init__.py new file mode 100644 index 00000000000..2184e77ab4a --- /dev/null +++ b/addons/account_debit_note/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import account_move
\ No newline at end of file diff --git a/addons/account_debit_note/models/account_move.py b/addons/account_debit_note/models/account_move.py new file mode 100644 index 00000000000..75b5768da14 --- /dev/null +++ b/addons/account_debit_note/models/account_move.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, fields, api, _ + +class AccountMove(models.Model): + _inherit = "account.move" + + debit_origin_id = fields.Many2one('account.move', 'Original Invoice Debited', readonly=True, copy=False) + debit_note_ids = fields.One2many('account.move', 'debit_origin_id', 'Debit Notes', + help="The debit notes created for this invoice") + debit_note_count = fields.Integer('Number of Debit Notes', compute='_compute_debit_count') + + @api.depends('debit_note_ids') + def _compute_debit_count(self): + debit_data = self.env['account.move'].read_group([('debit_origin_id', 'in', self.ids)], + ['debit_origin_id'], ['debit_origin_id']) + data_map = {datum['debit_origin_id'][0]: datum['debit_origin_id_count'] for datum in debit_data} + for inv in self: + inv.debit_note_count = data_map.get(inv.id, 0.0) + + def action_view_debit_notes(self): + self.ensure_one() + return { + 'type': 'ir.actions.act_window', + 'name': _('Debit Notes'), + 'res_model': 'account.move', + 'view_mode': 'tree,form', + 'domain': [('debit_origin_id', '=', self.id)], + } diff --git a/addons/account_debit_note/security/ir.model.access.csv b/addons/account_debit_note/security/ir.model.access.csv new file mode 100644 index 00000000000..2e015287121 --- /dev/null +++ b/addons/account_debit_note/security/ir.model.access.csv @@ -0,0 +1,2 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +"access_account_debit_note_user","account_debit_note_group_invoice","model_account_debit_note","account.group_account_invoice",1,1,1,0 diff --git a/addons/account_debit_note/tests/__init__.py b/addons/account_debit_note/tests/__init__.py new file mode 100644 index 00000000000..2e857acab4d --- /dev/null +++ b/addons/account_debit_note/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import test_out_debit_note
\ No newline at end of file diff --git a/addons/account_debit_note/tests/test_out_debit_note.py b/addons/account_debit_note/tests/test_out_debit_note.py new file mode 100644 index 00000000000..a184e3f65a2 --- /dev/null +++ b/addons/account_debit_note/tests/test_out_debit_note.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.tests import tagged +from odoo import fields + + +@tagged('post_install', '-at_install') +class TestAccountDebitNote(AccountTestInvoicingCommon): + + def test_00_debit_note_out_invoice(self): + """ Debit Note of a regular Customer Invoice""" + invoice = self.init_invoice('out_invoice') + invoice.post() + move_debit_note_wiz = self.env['account.debit.note'].with_context(active_model="account.move", + active_ids=invoice.ids).create({ + 'date': fields.Date.from_string('2019-02-01'), + 'reason': 'no reason', + 'copy_lines': True, + }) + move_debit_note_wiz.create_debit() + + # Search for the original invoice + debit_note = self.env['account.move'].search([('debit_origin_id', '=', invoice.id)]) + debit_note.ensure_one() + self.assertEqual(len(debit_note.invoice_line_ids), 2, "Should have copied the invoice lines") + self.assertEqual(debit_note.type, 'out_invoice', 'Type of debit note should be the same as the original invoice') + self.assertEqual(debit_note.state, 'draft', 'We should create debit notes in draft state') + + def test_10_debit_note_in_refund(self): + """ Debit Note of a vendor refund (is a regular vendor bill) """ + invoice = self.init_invoice('in_refund') + invoice.post() + move_debit_note_wiz = self.env['account.debit.note'].with_context(active_model="account.move", + active_ids=invoice.ids).create({ + 'date': fields.Date.from_string('2019-02-01'), + 'reason': 'in order to cancel refund', + }) + move_debit_note_wiz.create_debit() + + # Search for the original invoice + debit_note = self.env['account.move'].search([('debit_origin_id', '=', invoice.id)]) + debit_note.ensure_one() + + self.assertFalse(debit_note.invoice_line_ids, 'We should not copy lines by default on debit notes') + self.assertEqual(debit_note.type, 'in_invoice', 'Type of debit note should not be refund anymore') + self.assertEqual(debit_note.state, 'draft', 'We should create debit notes in draft state')
\ No newline at end of file diff --git a/addons/account_debit_note/views/account_move_view.xml b/addons/account_debit_note/views/account_move_view.xml new file mode 100644 index 00000000000..6deffec1c07 --- /dev/null +++ b/addons/account_debit_note/views/account_move_view.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <record id="view_move_form_debit" model="ir.ui.view"> + <field name="name">account.move.form.debit</field> + <field name="model">account.move</field> + <field name="inherit_id" ref="account.view_move_form"/> + <field name="arch" type="xml"> + <button name="action_reverse" position="after"> + <field name="debit_origin_id" invisible="1"/> + <button name="%(action_view_account_move_debit)d" string='Add Debit Note' + type='action' groups="account.group_account_invoice" + attrs="{'invisible': ['|', '|', ('debit_origin_id', '!=', False), + ('type', 'not in', ('out_invoice', 'in_invoice', 'out_refund', 'in_refund')), ('state', '!=', 'posted')]}"/> + </button> + <div class="oe_button_box" position="inside"> + <button type="object" class="oe_stat_button" name="action_view_debit_notes" icon="fa-plus" attrs="{'invisible': [('debit_note_count', '=', 0)]}"> + <div class="o_field_widget o_stat_info"> + <span class="o_stat_value"><field name="debit_note_count"/></span> + <span class="o_stat_text">Debit Notes</span> + </div> + </button> + </div> + <field name="invoice_origin" position="after"> + <field name="debit_origin_id" attrs="{'invisible': [('debit_origin_id', '=', False)]}"/> + </field> + </field> + </record> +</odoo> diff --git a/addons/account_debit_note/wizard/__init__.py b/addons/account_debit_note/wizard/__init__.py new file mode 100644 index 00000000000..2048c309b0f --- /dev/null +++ b/addons/account_debit_note/wizard/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import account_debit_note
\ No newline at end of file diff --git a/addons/account_debit_note/wizard/account_debit_note.py b/addons/account_debit_note/wizard/account_debit_note.py new file mode 100644 index 00000000000..af553426c12 --- /dev/null +++ b/addons/account_debit_note/wizard/account_debit_note.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +from odoo import models, fields, api +from odoo.tools.translate import _ +from odoo.exceptions import UserError + + +class AccountDebitNote(models.TransientModel): + """ + Add Debit Note wizard: when you want to correct an invoice with a positive amount. + Opposite of a Credit Note, but different from a regular invoice as you need the link to the original invoice. + In some cases, also used to cancel Credit Notes + """ + _name = 'account.debit.note' + _description = 'Add Debit Note wizard' + + move_ids = fields.Many2many('account.move', 'account_move_debit_move', 'debit_id', 'move_id', + domain=[('state', '=', 'posted')]) + date = fields.Date(string='Debit Note Date', default=fields.Date.context_today, required=True) + reason = fields.Char(string='Reason') + journal_id = fields.Many2one('account.journal', string='Use Specific Journal', + help='If empty, uses the journal of the journal entry to be debited.') + copy_lines = fields.Boolean("Copy Lines", + help="In case you need to do corrections for every line, it can be in handy to copy them. " + "We won't copy them for debit notes from credit notes. ") + # computed fields + move_type = fields.Char(compute="_compute_from_moves") + journal_type = fields.Char(compute="_compute_from_moves") + + @api.model + def default_get(self, fields): + res = super(AccountDebitNote, self).default_get(fields) + move_ids = self.env['account.move'].browse(self.env.context['active_ids']) if self.env.context.get('active_model') == 'account.move' else self.env['account.move'] + if any(move.state != "posted" for move in move_ids): + raise UserError(_('You can only debit posted moves.')) + res['move_ids'] = [(6, 0, move_ids.ids)] + return res + + @api.depends('move_ids') + def _compute_from_moves(self): + for record in self: + move_ids = record.move_ids + record.move_type = move_ids[0].type if len(move_ids) == 1 or not any(m.type != move_ids[0].type for m in move_ids) else False + record.journal_type = record.move_type in ['in_refund', 'in_invoice'] and 'purchase' or 'sale' + + def _prepare_default_values(self, move): + if move.type in ('in_refund', 'out_refund'): + type = 'in_invoice' if move.type == 'in_refund' else 'out_invoice' + else: + type = move.type + default_values = { + 'ref': '%s, %s' % (move.name, self.reason) if self.reason else move.name, + 'date': self.date or move.date, + 'invoice_date': move.is_invoice(include_receipts=True) and (self.date or move.date) or False, + 'journal_id': self.journal_id and self.journal_id.id or move.journal_id.id, + 'invoice_payment_term_id': None, + 'debit_origin_id': move.id, + 'type': type, + } + if not self.copy_lines or move.type in [('in_refund', 'out_refund')]: + default_values['line_ids'] = [(5, 0, 0)] + return default_values + + def create_debit(self): + self.ensure_one() + new_moves = self.env['account.move'] + for move in self.move_ids.with_context(include_business_fields=True): #copy sale/purchase links + default_values = self._prepare_default_values(move) + new_move = move.with_context(internal_type='debit_note').copy(default=default_values) # Context key is used for l10n_latam_invoice_document for ar/cl/pe + move_msg = _( + "This debit note was created from:") + " <a href=# data-oe-model=account.move data-oe-id=%d>%s</a>" % ( + move.id, move.name) + new_move.message_post(body=move_msg) + new_moves |= new_move + + action = { + 'name': _('Debit Notes'), + 'type': 'ir.actions.act_window', + 'res_model': 'account.move', + } + if len(new_moves) == 1: + action.update({ + 'view_mode': 'form', + 'res_id': new_moves.id, + }) + else: + action.update({ + 'view_mode': 'tree,form', + 'domain': [('id', 'in', new_moves.ids)], + }) + return action diff --git a/addons/account_debit_note/wizard/account_debit_note_view.xml b/addons/account_debit_note/wizard/account_debit_note_view.xml new file mode 100644 index 00000000000..be21ca6a987 --- /dev/null +++ b/addons/account_debit_note/wizard/account_debit_note_view.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <data> + <record id="view_account_debit_note" model="ir.ui.view"> + <field name="name">account.debit.note.form</field> + <field name="model">account.debit.note</field> + <field name="arch" type="xml"> + <form string="Create Debit Note"> + <field name="move_type" invisible="1"/> + <field name="journal_type" invisible="1"/> + <field name="move_ids" invisible="1"/> + <group> + <group> + <field name="reason"/> + <field name="date" string="Debit Note Date"/> + <field name="copy_lines" attrs="{'invisible': [('move_type', 'in', ['in_refund', 'out_refund'])]}"/> + </group> + <group> + <field name="journal_id" domain="[('type', '=', journal_type)]"/> + </group> + </group> + <footer> + <button string='Create Debit Note' name="create_debit" type="object" class="btn-primary"/> + <button string="Cancel" class="btn-secondary" special="cancel"/> + </footer> + </form> + </field> + </record> + + <record id="action_view_account_move_debit" model="ir.actions.act_window"> + <field name="name">Create Debit Note</field> + <field name="res_model">account.debit.note</field> + <field name="view_mode">tree,form</field> + <field name="view_id" ref="view_account_debit_note"/> + <field name="target">new</field> + <field name="binding_model_id" ref="account.model_account_move" /> + <field name="binding_view_types">list</field> + </record> + </data> +</odoo> |
