Add module account_invoice_update_wizard
This commit is contained in:
committed by
Yannick Vaucher
parent
cc3b9d73ae
commit
96680c893f
3
account_invoice_update_wizard/__init__.py
Normal file
3
account_invoice_update_wizard/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import wizard
|
||||||
34
account_invoice_update_wizard/__openerp__.py
Normal file
34
account_invoice_update_wizard/__openerp__.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# © 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'Account Invoice Update Wizard',
|
||||||
|
'version': '8.0.1.0.0',
|
||||||
|
'category': 'Accounting & Finance',
|
||||||
|
'license': 'AGPL-3',
|
||||||
|
'summary': 'Wizard to update non-legal fields of an open/paid invoice',
|
||||||
|
'description': """
|
||||||
|
Account Invoice Update Wizard
|
||||||
|
=============================
|
||||||
|
|
||||||
|
This module adds a button *Update Invoice* on Customer and Supplier invoices in Open or Paid state. This button starts a wizard which allows the user to update non-legal fields of the invoice:
|
||||||
|
|
||||||
|
* Source Document
|
||||||
|
* Reference/Description
|
||||||
|
* Payment terms (update allowed only to a payment term with same number of terms of the same amount and on invoices without any payment)
|
||||||
|
* Bank Account
|
||||||
|
* Salesman
|
||||||
|
* Notes
|
||||||
|
* Description of invoice lines
|
||||||
|
|
||||||
|
""",
|
||||||
|
'author': 'Akretion',
|
||||||
|
'website': 'http://www.akretion.com',
|
||||||
|
'depends': ['account'],
|
||||||
|
'data': [
|
||||||
|
'wizard/account_invoice_update_view.xml',
|
||||||
|
'views/account_invoice.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
}
|
||||||
31
account_invoice_update_wizard/views/account_invoice.xml
Normal file
31
account_invoice_update_wizard/views/account_invoice.xml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
© 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
||||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
-->
|
||||||
|
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<record id="invoice_supplier_form" model="ir.ui.view">
|
||||||
|
<field name="model">account.invoice</field>
|
||||||
|
<field name="inherit_id" ref="account.invoice_supplier_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<button name="invoice_cancel" position="before">
|
||||||
|
<button name="%(account_invoice_update_action)d" type="action" string="Update Invoice" states="open,paid" groups="account.group_account_invoice"/>
|
||||||
|
</button>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="invoice_form" model="ir.ui.view">
|
||||||
|
<field name="model">account.invoice</field>
|
||||||
|
<field name="inherit_id" ref="account.invoice_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<button name="invoice_cancel" position="before">
|
||||||
|
<button name="%(account_invoice_update_action)d" type="action" string="Update Invoice" states="open,paid" groups="account.group_account_invoice"/>
|
||||||
|
</button>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
||||||
3
account_invoice_update_wizard/wizard/__init__.py
Normal file
3
account_invoice_update_wizard/wizard/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import account_invoice_update
|
||||||
206
account_invoice_update_wizard/wizard/account_invoice_update.py
Normal file
206
account_invoice_update_wizard/wizard/account_invoice_update.py
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# © 2017 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 Warning as UserError
|
||||||
|
import openerp.addons.decimal_precision as dp
|
||||||
|
|
||||||
|
|
||||||
|
class AccountInvoiceUpdate(models.TransientModel):
|
||||||
|
_name = 'account.invoice.update'
|
||||||
|
_description = 'Wizard to update non-legal fields of invoice'
|
||||||
|
|
||||||
|
invoice_id = fields.Many2one(
|
||||||
|
'account.invoice', string='Invoice', required=True,
|
||||||
|
readonly=True)
|
||||||
|
type = fields.Selection(related='invoice_id.type', readonly=True)
|
||||||
|
company_id = fields.Many2one(
|
||||||
|
related='invoice_id.company_id', readonly=True)
|
||||||
|
commercial_partner_id = fields.Many2one(
|
||||||
|
related='invoice_id.commercial_partner_id', readonly=True)
|
||||||
|
user_id = fields.Many2one('res.users', string='Salesperson')
|
||||||
|
# I use the same field name as the original invoice field name
|
||||||
|
# even if it the name is "bad"
|
||||||
|
# Updating payment_term will not work if you use
|
||||||
|
# the OCA module account_constraints (you will just get an error)
|
||||||
|
payment_term = fields.Many2one(
|
||||||
|
'account.payment.term', string='Payment Terms')
|
||||||
|
reference = fields.Char(string='Invoice Reference')
|
||||||
|
name = fields.Char(string='Reference/Description')
|
||||||
|
origin = fields.Char(string='Source Document')
|
||||||
|
comment = fields.Text('Additional Information')
|
||||||
|
partner_bank_id = fields.Many2one(
|
||||||
|
'res.partner.bank', string='Bank Account')
|
||||||
|
line_ids = fields.One2many(
|
||||||
|
'account.invoice.line.update', 'parent_id', string='Invoice Lines')
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _simple_fields2update(self):
|
||||||
|
'''List boolean, date, datetime, char, text fields'''
|
||||||
|
return ['reference', 'name', 'origin', 'comment']
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _m2o_fields2update(self):
|
||||||
|
return ['payment_term', 'user_id', 'partner_bank_id']
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _prepare_default_get(self, invoice):
|
||||||
|
res = {'invoice_id': invoice.id, 'line_ids': []}
|
||||||
|
for sfield in self._simple_fields2update():
|
||||||
|
res[sfield] = invoice[sfield]
|
||||||
|
for m2ofield in self._m2o_fields2update():
|
||||||
|
res[m2ofield] = invoice[m2ofield].id or False
|
||||||
|
for line in invoice.invoice_line:
|
||||||
|
res['line_ids'].append({
|
||||||
|
'invoice_line_id': line.id,
|
||||||
|
'name': line.name,
|
||||||
|
'quantity': line.quantity,
|
||||||
|
'price_subtotal': line.price_subtotal,
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def default_get(self, fields_list):
|
||||||
|
res = super(AccountInvoiceUpdate, self).default_get(fields_list)
|
||||||
|
assert self._context.get('active_model') == 'account.invoice',\
|
||||||
|
'active_model should be account.invoice'
|
||||||
|
inv = self.env['account.invoice'].browse(self._context['active_id'])
|
||||||
|
res = self._prepare_default_get(inv)
|
||||||
|
return res
|
||||||
|
|
||||||
|
@api.onchange('type')
|
||||||
|
def type_on_change(self):
|
||||||
|
res = {'domain': {}}
|
||||||
|
if self.type in ('out_invoice', 'out_refund'):
|
||||||
|
res['domain']['partner_bank_id'] =\
|
||||||
|
"[('partner_id.ref_companies', 'in', [company_id])]"
|
||||||
|
else:
|
||||||
|
res['domain']['partner_bank_id'] =\
|
||||||
|
"[('partner_id', '=', commercial_partner_id)]"
|
||||||
|
return res
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _prepare_invoice(self):
|
||||||
|
vals = {}
|
||||||
|
inv = self.invoice_id
|
||||||
|
for sfield in self._simple_fields2update():
|
||||||
|
if self[sfield] != inv[sfield]:
|
||||||
|
vals[sfield] = self[sfield]
|
||||||
|
for m2ofield in self._m2o_fields2update():
|
||||||
|
if self[m2ofield] != inv[m2ofield]:
|
||||||
|
vals[m2ofield] = self[m2ofield].id or False
|
||||||
|
if 'payment_term' in vals:
|
||||||
|
pterm_list = self.payment_term.compute(
|
||||||
|
value=1, date_ref=inv.date_invoice)[0]
|
||||||
|
if pterm_list:
|
||||||
|
vals['date_due'] = max(line[0] for line in pterm_list)
|
||||||
|
return vals
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _prepare_invoice_line(self, line):
|
||||||
|
vals = {}
|
||||||
|
if line.name != line.invoice_line_id.name:
|
||||||
|
vals['name'] = line.name
|
||||||
|
return vals
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _prepare_move(self):
|
||||||
|
mvals = {}
|
||||||
|
inv = self.invoice_id
|
||||||
|
ini_ref = inv.move_id.ref
|
||||||
|
ref = inv.reference or inv.name
|
||||||
|
if ini_ref != ref:
|
||||||
|
mvals['ref'] = ref
|
||||||
|
return mvals
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def _update_payment_term_move(self):
|
||||||
|
self.ensure_one()
|
||||||
|
inv = self.invoice_id
|
||||||
|
if (
|
||||||
|
self.payment_term and
|
||||||
|
self.payment_term != inv.payment_term and
|
||||||
|
inv.move_id and
|
||||||
|
inv.move_id.period_id.state == 'draft'):
|
||||||
|
# I don't update pay term when the invoice is partially (or fully)
|
||||||
|
# paid because if you have a payment term with several lines
|
||||||
|
# of the same amount, you would also have to take into account
|
||||||
|
# the reconcile marks to put the new maturity date on the right
|
||||||
|
# lines
|
||||||
|
if inv.payment_ids:
|
||||||
|
raise UserError(_(
|
||||||
|
"This wizard doesn't support the update of payment "
|
||||||
|
"terms on an invoice which is partially or fully "
|
||||||
|
"paid."))
|
||||||
|
prec = self.env['decimal.precision'].precision_get('Account')
|
||||||
|
term_res = self.payment_term.compute(
|
||||||
|
inv.amount_total, inv.date_invoice)[0]
|
||||||
|
new_pterm = {} # key = int(amount * 100), value = [date1, date2]
|
||||||
|
for entry in term_res:
|
||||||
|
amount = int(entry[1] * 10 * prec)
|
||||||
|
if amount in new_pterm:
|
||||||
|
new_pterm[amount].append(entry[0])
|
||||||
|
else:
|
||||||
|
new_pterm[amount] = [entry[0]]
|
||||||
|
mlines = {} # key = int(amount * 100), value : [line1, line2]
|
||||||
|
for line in inv.move_id.line_id:
|
||||||
|
if line.account_id == inv.account_id:
|
||||||
|
amount = int(abs(line.credit - line.debit) * 10 * prec)
|
||||||
|
if amount in mlines:
|
||||||
|
mlines[amount].append(line)
|
||||||
|
else:
|
||||||
|
mlines[amount] = [line]
|
||||||
|
for iamount, lines in mlines.iteritems():
|
||||||
|
if len(lines) != len(new_pterm.get(iamount, [])):
|
||||||
|
raise UserError(_(
|
||||||
|
"The original payment term '%s' doesn't have the "
|
||||||
|
"same terms (number of terms and/or amount) as the "
|
||||||
|
"new payment term '%s'. You can only switch to a "
|
||||||
|
"payment term that has the same number of terms "
|
||||||
|
"with the same amount.") % (
|
||||||
|
inv.payment_term.name, self.payment_term.name))
|
||||||
|
for line in lines:
|
||||||
|
line.date_maturity = new_pterm[iamount].pop()
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def run(self):
|
||||||
|
self.ensure_one()
|
||||||
|
inv = self.invoice_id
|
||||||
|
updated = False
|
||||||
|
# re-write date_maturity on move line
|
||||||
|
self._update_payment_term_move()
|
||||||
|
ivals = self._prepare_invoice()
|
||||||
|
if ivals:
|
||||||
|
updated = True
|
||||||
|
inv.write(ivals)
|
||||||
|
for line in self.line_ids:
|
||||||
|
ilvals = self._prepare_invoice_line(line)
|
||||||
|
if ilvals:
|
||||||
|
updated = True
|
||||||
|
line.invoice_line_id.write(ilvals)
|
||||||
|
if inv.move_id and inv.move_id.period_id.state == 'draft':
|
||||||
|
mvals = self._prepare_move()
|
||||||
|
if mvals:
|
||||||
|
inv.move_id.write(mvals)
|
||||||
|
if updated:
|
||||||
|
inv.message_post(_(
|
||||||
|
'Non-legal fields of invoice updated via the Invoice Update '
|
||||||
|
'wizard.'))
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class AccountInvoiceLineUpdate(models.TransientModel):
|
||||||
|
_name = 'account.invoice.line.update'
|
||||||
|
_description = 'Update non-legal fields of invoice lines'
|
||||||
|
|
||||||
|
parent_id = fields.Many2one(
|
||||||
|
'account.invoice.update', string='Wizard', ondelete='cascade')
|
||||||
|
invoice_line_id = fields.Many2one(
|
||||||
|
'account.invoice.line', string='Invoice Line', readonly=True)
|
||||||
|
name = fields.Text(string='Description', required=True)
|
||||||
|
quantity = fields.Float(
|
||||||
|
string='Quantity', digits=dp.get_precision('Product Unit of Measure'),
|
||||||
|
readonly=True)
|
||||||
|
price_subtotal = fields.Float(
|
||||||
|
string='Amount', readonly=True, digits=dp.get_precision('Account'))
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
© 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
||||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
-->
|
||||||
|
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<record id="account_invoice_update_form" model="ir.ui.view">
|
||||||
|
<field name="model">account.invoice.update</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Update Invoice Wizard">
|
||||||
|
<group name="main">
|
||||||
|
<field name="invoice_id" invisible="1"/>
|
||||||
|
<field name="type" invisible="1"/>
|
||||||
|
<field name="company_id" invisible="1"/>
|
||||||
|
<field name="commercial_partner_id" invisible="1"/>
|
||||||
|
<field name="reference" attrs="{'invisible': [('type', 'not in', ('in_invoice', 'in_refund'))]}"/>
|
||||||
|
<field name="origin"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="payment_term" widget="selection"/>
|
||||||
|
<field name="partner_bank_id"/>
|
||||||
|
<field name="user_id"/>
|
||||||
|
<field name="comment"/>
|
||||||
|
</group>
|
||||||
|
<group name="lines">
|
||||||
|
<field name="line_ids" nolabel="1">
|
||||||
|
<tree editable="bottom">
|
||||||
|
<field name="invoice_line_id" invisible="1"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="quantity"/>
|
||||||
|
<field name="price_subtotal"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</group>
|
||||||
|
<footer>
|
||||||
|
<button name="run" type="object" class="oe_highlight" string="Update"/>
|
||||||
|
or
|
||||||
|
<button special="cancel" string="Cancel" class="oe_link"/>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="account_invoice_update_action" model="ir.actions.act_window">
|
||||||
|
<field name="name">Invoice Update Wizard</field>
|
||||||
|
<field name="res_model">account.invoice.update</field>
|
||||||
|
<field name="view_mode">form</field>
|
||||||
|
<field name="target">new</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</openerp>
|
||||||
Reference in New Issue
Block a user