[IMP] account: onboarding of the invoicing app
-added tour for invoicing -allow to set easily the number of the first invoice/bill/refund of a journal, from the invoice screen -small usability fixes and refactoring
This commit is contained in:
@@ -319,6 +319,8 @@ class AccountJournal(models.Model):
|
||||
if journal.sequence_id:
|
||||
sequence = journal.sequence_id._get_current_sequence()
|
||||
journal.sequence_number_next = sequence.number_next_actual
|
||||
else:
|
||||
journal.sequence_number_next = 1
|
||||
|
||||
@api.multi
|
||||
def _inverse_seq_number_next(self):
|
||||
@@ -341,6 +343,8 @@ class AccountJournal(models.Model):
|
||||
if journal.refund_sequence_id and journal.refund_sequence:
|
||||
sequence = journal.refund_sequence_id._get_current_sequence()
|
||||
journal.refund_sequence_number_next = sequence.number_next_actual
|
||||
else:
|
||||
journal.refund_sequence_number_next = 1
|
||||
|
||||
@api.multi
|
||||
def _inverse_refund_seq_number_next(self):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
import re
|
||||
from lxml import etree
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
@@ -215,6 +216,7 @@ class AccountInvoice(models.Model):
|
||||
|
||||
name = fields.Char(string='Reference/Description', index=True,
|
||||
readonly=True, states={'draft': [('readonly', False)]}, copy=False, help='The name that will be used on account move lines')
|
||||
|
||||
origin = fields.Char(string='Source Document',
|
||||
help="Reference of the document that produced this invoice.",
|
||||
readonly=True, states={'draft': [('readonly', False)]})
|
||||
@@ -340,10 +342,53 @@ class AccountInvoice(models.Model):
|
||||
payments_widget = fields.Text(compute='_get_payment_info_JSON')
|
||||
has_outstanding = fields.Boolean(compute='_get_outstanding_info_JSON')
|
||||
|
||||
#fields use to set the sequence, on the first invoice of the journal
|
||||
sequence_number_next = fields.Char(string='Next Number', compute="_get_sequence_prefix", inverse="_set_sequence_next")
|
||||
sequence_number_next_prefix = fields.Char(string='Next Number', compute="_get_sequence_prefix")
|
||||
|
||||
_sql_constraints = [
|
||||
('number_uniq', 'unique(number, company_id, journal_id, type)', 'Invoice Number must be unique per Company!'),
|
||||
]
|
||||
|
||||
@api.depends('state', 'journal_id', 'date_invoice')
|
||||
def _get_sequence_prefix(self):
|
||||
""" computes the number that will be assigned to the first invoice/bill/refund of a journal, in order to
|
||||
let the user manually change it.
|
||||
"""
|
||||
for invoice in self:
|
||||
journal_sequence = invoice.journal_id.sequence_id
|
||||
if invoice.journal_id.refund_sequence:
|
||||
domain = [('type', '=', invoice.type)]
|
||||
journal_sequence = invoice.type in ['in_refund', 'out_refund'] and invoice.journal_id.refund_sequence_id or invoice.journal_id.sequence_id
|
||||
elif invoice.type in ['in_invoice', 'in_refund']:
|
||||
domain = [('type', 'in', ['in_invoice', 'in_refund'])]
|
||||
else:
|
||||
domain = [('type', 'in', ['out_invoice', 'out_refund'])]
|
||||
if invoice.id:
|
||||
domain += [('id', '<>', invoice.id)]
|
||||
domain += [('journal_id', '=', invoice.journal_id.id), ('state', 'not in', ['draft', 'cancel'])]
|
||||
|
||||
if (invoice.state == 'draft') and not self.search(domain, limit=1):
|
||||
prefix, dummy = journal_sequence.with_context(ir_sequence_date=invoice.date_invoice)._get_prefix_suffix()
|
||||
invoice.sequence_number_next_prefix = prefix
|
||||
number_next = journal_sequence._get_current_sequence().number_next_actual
|
||||
invoice.sequence_number_next = '%%0%sd' % journal_sequence.padding % number_next
|
||||
else:
|
||||
invoice.sequence_number_next_prefix = False
|
||||
invoice.sequence_number_next = 'no'
|
||||
|
||||
@api.multi
|
||||
def _set_sequence_next(self):
|
||||
''' Set the number_next on the sequence related to the invoice/bill/refund'''
|
||||
for invoice in self:
|
||||
nxt = re.sub("[^0-9]", '', invoice.sequence_number_next or '1')
|
||||
result = re.match("(0*)([0-9]+)", nxt)
|
||||
journal_sequence = invoice.journal_id.refund_sequence and invoice.journal_id.refund_sequence_id or invoice.journal_id.sequence_id
|
||||
if result and journal_sequence:
|
||||
#use _get_current_sequence to manage the date range sequences
|
||||
sequence = journal_sequence._get_current_sequence()
|
||||
sequence.number_next = int(result.group(2))
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
onchanges = {
|
||||
@@ -570,13 +615,12 @@ class AccountInvoice(models.Model):
|
||||
date_invoice = self.date_invoice
|
||||
if not date_invoice:
|
||||
date_invoice = fields.Date.context_today(self)
|
||||
if not self.payment_term_id:
|
||||
# When no payment terms defined
|
||||
self.date_due = self.date_due or self.date_invoice
|
||||
else:
|
||||
if self.payment_term_id:
|
||||
pterm = self.payment_term_id
|
||||
pterm_list = pterm.with_context(currency_id=self.company_id.currency_id.id).compute(value=1, date_ref=date_invoice)[0]
|
||||
self.date_due = max(line[0] for line in pterm_list)
|
||||
elif self.date_due and (date_invoice > self.date_due):
|
||||
self.date_due = date_invoice
|
||||
|
||||
@api.multi
|
||||
def action_invoice_draft(self):
|
||||
|
||||
@@ -230,25 +230,30 @@
|
||||
<sheet string="Vendor Bill">
|
||||
<div>
|
||||
<label string="Draft Bill" attrs="{'invisible': ['|',('state','!=','draft'), ('type','!=','in_invoice')]}"/>
|
||||
<label string="- First Number:" attrs="{'invisible': [('sequence_number_next_prefix','=',False)]}"/>
|
||||
<label string="Draft Credit Note" attrs="{'invisible': ['|',('state','!=','draft'), ('type','!=','in_refund')]}"/>
|
||||
<label string="Bill " attrs="{'invisible': ['|',('state', '=', 'draft'), ('type','!=','in_invoice')]}"/>
|
||||
<label string="Credit Note " attrs="{'invisible': ['|',('state', '=', 'draft'), ('type','!=','in_refund')]}"/>
|
||||
<h1 class="mt0">
|
||||
<field name="number" class="oe_inline" attrs="{'invisible': [('state', '=', 'draft')]}"/>
|
||||
<div attrs="{'invisible': [('sequence_number_next_prefix','=',False)]}">
|
||||
<field name="sequence_number_next_prefix" class="oe_inline"/>
|
||||
<field name="sequence_number_next" class="oe_inline" required="1" style="width: 80px !important;"/>
|
||||
</div>
|
||||
</h1>
|
||||
</div>
|
||||
<field name="type" invisible="1"/>
|
||||
<group>
|
||||
<group>
|
||||
<field string="Vendor" name="partner_id"
|
||||
context="{'default_customer': 0, 'search_default_supplier': 1, 'default_supplier': 1}"
|
||||
context="{'default_customer': 0, 'search_default_supplier': 1, 'default_supplier': 1, 'default_company_type': 'company'}"
|
||||
domain="[('supplier', '=', True)]"/>
|
||||
<field name="reference" string="Vendor Reference"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="origin" attrs="{'invisible': [('origin', '=', False)]}"/>
|
||||
<field name="date_invoice" string="Bill Date"/>
|
||||
<field name="date_due"/>
|
||||
<field name="date_due" attrs="{'readonly': [('payment_term_id','!=',False)]}"/>
|
||||
<field name="move_name" invisible="1"/>
|
||||
<field name="currency_id" options="{'no_create': True, 'no_open': True}" groups="base.group_multi_currency"/>
|
||||
<field name="company_currency_id" invisible="1"/>
|
||||
@@ -346,14 +351,14 @@
|
||||
<field name="name">account.invoice.form</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Invoice">
|
||||
<form string="Invoice" class="o_invoice_form">
|
||||
<header>
|
||||
<button name="action_invoice_sent" type="object" string="Send by Email" attrs="{'invisible':['|',('sent','=',True), ('state', 'not in', ('open','paid'))]}" class="oe_highlight" groups="base.group_user"/>
|
||||
<button name="action_invoice_sent" type="object" string="Send by Email" attrs="{'invisible':['|',('sent','=',True), ('state', 'not in', ('open','paid'))]}" class="oe_highlight o_invoice_send" groups="base.group_user"/>
|
||||
<button name="invoice_print" string="Print Invoice" type="object" attrs="{'invisible':['|',('sent','=',True), ('state', 'not in', ('open','paid'))]}" class="oe_highlight" groups="base.group_user"/>
|
||||
<button name="action_invoice_sent" type="object" string="Send by Email" attrs="{'invisible':['|',('sent','=',False), ('state', 'not in', ('open','paid'))]}" groups="base.group_user"/>
|
||||
<button name="invoice_print" string="Print Invoice" type="object" attrs="{'invisible':['|',('sent','=',False), ('state', 'not in', ('open','paid'))]}" groups="base.group_user"/>
|
||||
<button name="%(action_account_invoice_payment)d" id="account_invoice_payment_btn" type="action" states="open" string="Register Payment" groups="account.group_account_invoice" class="oe_highlight"/>
|
||||
<button name="action_invoice_open" type="object" states="draft" string="Validate" class="oe_highlight" groups="account.group_account_invoice"/>
|
||||
<button name="action_invoice_open" type="object" states="draft" string="Validate" class="oe_highlight o_invoice_validate" groups="account.group_account_invoice"/>
|
||||
<button name="%(action_account_invoice_refund)d" type='action' string='Add Credit Note' groups="account.group_account_invoice" attrs="{'invisible': ['|',('type', '=', 'out_refund'), ('state', 'not in', ('open','paid'))]}"/>
|
||||
<button name="action_invoice_draft" states="cancel" string="Reset to Draft" type="object" groups="account.group_account_invoice"/>
|
||||
<field name="state" widget="statusbar" nolabel="1" statusbar_visible="draft,open,paid"/>
|
||||
@@ -365,21 +370,28 @@
|
||||
<sheet string="Invoice">
|
||||
<label string="Credit Note" attrs="{'invisible': ['|',('state','=','draft'), ('type','!=','out_refund')]}"/>
|
||||
<h1 class="mt0">
|
||||
<label string="Draft Invoice" attrs="{'invisible': ['|',('state','not in',('draft',)), ('type','!=','out_invoice')]}"/>
|
||||
<label string="Draft Credit Note" attrs="{'invisible': ['|',('state','not in',('draft',)), ('type','!=','out_refund')]}"/>
|
||||
<field name="number" readonly="1" class="oe_inline" attrs="{'invisible': [('state','in',('draft',))]}"/>
|
||||
<div attrs="{'invisible': [('sequence_number_next_prefix','!=',False)]}">
|
||||
<label string="Draft Invoice" attrs="{'invisible': ['|',('state','not in',('draft',)), ('type','!=','out_invoice')]}"/>
|
||||
<label string="Draft Credit Note" attrs="{'invisible': ['|',('state','not in',('draft',)), ('type','!=','out_refund')]}"/>
|
||||
<field name="number" readonly="1" class="oe_inline" attrs="{'invisible': [('state','in',('draft',))]}"/>
|
||||
</div>
|
||||
<div attrs="{'invisible': [('sequence_number_next_prefix','=',False)]}">
|
||||
<label string="Invoice Number:"/><br/>
|
||||
<field name="sequence_number_next_prefix" class="oe_inline"/>
|
||||
<field name="sequence_number_next" class="oe_inline" required="1" style="width: 80px !important;"/>
|
||||
</div>
|
||||
</h1>
|
||||
<field name="type" invisible="1"/>
|
||||
<group>
|
||||
<group>
|
||||
<field string="Customer" name="partner_id"
|
||||
context="{'search_default_customer':1, 'show_address': 1}"
|
||||
options='{"always_reload": True}'
|
||||
context="{'search_default_customer':1, 'show_address': 1, 'default_company_type': 'company'}"
|
||||
options='{"always_reload": True, "no_quick_create": True}'
|
||||
domain="[('customer', '=', True)]"/>
|
||||
<field name="payment_term_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="date_invoice"/>
|
||||
<field name="date_due" attrs="{'readonly': [('payment_term_id','!=',False)]}"/>
|
||||
<field name="move_name" invisible="1"/>
|
||||
<field name="user_id" groups="base.group_user"/>
|
||||
<label for="currency_id" groups="base.group_multi_currency"/>
|
||||
@@ -454,6 +466,7 @@
|
||||
<page string="Other Info" name="other_info">
|
||||
<group>
|
||||
<group>
|
||||
<field name="payment_term_id"/>
|
||||
<field name="fiscal_position_id" options="{'no_create': True}" />
|
||||
<field name="journal_id" groups="account.group_account_user"
|
||||
options="{'no_create': True}" attrs="{'readonly':[('move_name','!=',False)]}"/>
|
||||
@@ -461,7 +474,6 @@
|
||||
name="account_id" groups="account.group_account_manager"/>
|
||||
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
||||
<field domain="[('partner_id.ref_company_ids', 'in', [company_id])]" name="partner_bank_id" invisible="1"/>
|
||||
<field name="date_due" groups="base.group_no_one"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="move_id" groups="account.group_account_manager" attrs="{'invisible': [('move_id', '=', False)]}"/>
|
||||
|
||||
@@ -2066,7 +2066,7 @@
|
||||
<menuitem id="menu_product_product_action" name="Products" action="product_product_action" parent="product_configuration_menu" sequence="1"/>
|
||||
|
||||
|
||||
<menuitem id="menu_product_template_action" name="Sellable Products"
|
||||
<menuitem id="menu_product_template_action" name="Products"
|
||||
parent="menu_finance_receivables"
|
||||
action="product.product_template_action" sequence="110"/>
|
||||
|
||||
|
||||
@@ -88,6 +88,20 @@
|
||||
<field name="res_model">account.analytic.account</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="partner_view_short_extra">
|
||||
<field name="name">partner.short.view.account</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_short_form" />
|
||||
<field name="priority" eval="20"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="email" position="after">
|
||||
<field name="property_payment_term_id" widget="selection"/>
|
||||
<field name="property_account_position_id" options="{'no_create': True, 'no_open': True}"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record model="ir.ui.view" id="partner_view_buttons">
|
||||
<field name="name">partner.view.buttons</field>
|
||||
<field name="model">res.partner</field>
|
||||
|
||||
@@ -19,6 +19,7 @@ You could use this simplified accounting in case you work with an (external) acc
|
||||
'data': [
|
||||
'views/account_menuitem_views.xml',
|
||||
'views/product_template_views.xml',
|
||||
'views/account_invoicing_templates.xml',
|
||||
],
|
||||
'demo': [
|
||||
],
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
odoo.define('account_invoicing.tour', function(require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var tour = require('web_tour.tour');
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
tour.register('account_invoicing_tour', {
|
||||
'skip_enabled': true,
|
||||
}, [{
|
||||
trigger: ".o_form_readonly .o_invoice_validate",
|
||||
extra_trigger: '.o_invoice_form',
|
||||
content: _t("Click to <b>validate your invoice.</b> A reference will be assigned to this invoice and you will not be able to modify it anymore."),
|
||||
position: "right"
|
||||
}, {
|
||||
trigger: ".o_invoice_send",
|
||||
extra_trigger: '.o_invoice_form',
|
||||
content: _t("Click to <b>send the invoice by email.</b>"),
|
||||
position: "bottom"
|
||||
}, {
|
||||
trigger: ".o_mail_send",
|
||||
content: _t("Click to <b>send the invoice.</b>"),
|
||||
position: "bottom"
|
||||
} ]);
|
||||
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="assets_backend" name="account assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/account_invoicing/static/src/js/tour.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
@@ -74,7 +74,7 @@
|
||||
context="{'default_model': model, 'default_body_html': body, 'default_subject': subject}"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Send" attrs="{'invisible': [('is_log', '=', True)]}" name="send_mail_action" type="object" class="btn-primary"/>
|
||||
<button string="Send" attrs="{'invisible': [('is_log', '=', True)]}" name="send_mail_action" type="object" class="btn-primary o_mail_send"/>
|
||||
<button string="Log" attrs="{'invisible': [('is_log', '=', False)]}" name="send_mail_action" type="object" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-default" special="cancel" />
|
||||
|
||||
|
||||
@@ -300,6 +300,7 @@
|
||||
action='action_payment_transaction'
|
||||
id='payment_transaction_menu'
|
||||
parent='root_payment_menu'
|
||||
groups="base.group_no_one"
|
||||
sequence='20' />
|
||||
|
||||
<!-- Payment Tokens -->
|
||||
|
||||
@@ -179,7 +179,7 @@ class IrSequence(models.Model):
|
||||
number_next = _update_nogap(self, self.number_increment)
|
||||
return self.get_next_char(number_next)
|
||||
|
||||
def get_next_char(self, number_next):
|
||||
def _get_prefix_suffix(self):
|
||||
def _interpolate(s, d):
|
||||
return (s % d) if s else ''
|
||||
|
||||
@@ -208,6 +208,10 @@ class IrSequence(models.Model):
|
||||
interpolated_suffix = _interpolate(self.suffix, d)
|
||||
except ValueError:
|
||||
raise UserError(_('Invalid prefix or suffix for sequence \'%s\'') % (self.get('name')))
|
||||
return interpolated_prefix, interpolated_suffix
|
||||
|
||||
def get_next_char(self, number_next):
|
||||
interpolated_prefix, interpolated_suffix = self._get_prefix_suffix()
|
||||
return interpolated_prefix + '%%0%sd' % self.padding % number_next + interpolated_suffix
|
||||
|
||||
def _create_date_range_seq(self, date):
|
||||
|
||||
Reference in New Issue
Block a user