[IMP] sale,account: Move pro-forma to sales and make it a quote report

Purpose
=======

we use to make proforma invoice to get payment before the creation of the real invoice. In the current odoo, the proforma state is too late. Because you have to confirm the SO to generate the draft invoice and so the proforma. But, if you do that, you have generated a subscription or a delivery order in the mean time. A proforma is actually a quote. In old ERP they used to make proforma to send a quote  and don't generate invoice number.

Specification
=============

- Remove everything related to Proforma from accounting
    - Remove the configuration of pro-forma from account setting
    - Invoice form, remove "Pro-froma" button, stage, report title
- Add proforma configuration option in sales setting under "Quotation and Sales" section
- In quote/sale, add Pro-forma under Print
- A pro-forma is a quote, but the title of the report is Pro-Forma and not Quotation
- button (secondary) on quotation form "SEND PRO-FORMA" to send proforma by mail attached with related report.
   - button
   - mail template
   - when preparing a quotation this button is visible but if even one invoice this should be invisible (as confirming SO doesn't generate invoice currently)
This commit is contained in:
Mansi Gandhi
2017-02-08 17:11:07 +01:00
committed by Yannick Tivisse
parent ff2f188d20
commit c3cf05af3e
27 changed files with 89 additions and 76 deletions
@@ -29,7 +29,7 @@
% if object.number:
invoice <strong>${object.number}</strong>
% else:
pro-forma invoice
invoice
% endif
% if object.origin:
(with reference: ${object.origin})
+4 -12
View File
@@ -221,8 +221,6 @@ class AccountInvoice(models.Model):
state = fields.Selection([
('draft','Draft'),
('proforma', 'Pro-forma'),
('proforma2', 'Pro-forma'),
('open', 'Open'),
('paid', 'Paid'),
('cancel', 'Cancelled'),
@@ -549,18 +547,12 @@ class AccountInvoice(models.Model):
attachment.unlink()
return True
@api.multi
def action_invoice_proforma2(self):
if self.filtered(lambda inv: inv.state != 'draft'):
raise UserError(_("Invoice must be a draft in order to set it to Pro-forma."))
return self.write({'state': 'proforma2'})
@api.multi
def action_invoice_open(self):
# lots of duplicate calls to action_invoice_open, so we remove those already open
to_open_invoices = self.filtered(lambda inv: inv.state != 'open')
if to_open_invoices.filtered(lambda inv: inv.state not in ['proforma2', 'draft']):
raise UserError(_("Invoice must be in draft or Pro-forma state in order to validate it."))
if to_open_invoices.filtered(lambda inv: inv.state != 'draft'):
raise UserError(_("Invoice must be in draft state in order to validate it."))
to_open_invoices.action_date_assign()
to_open_invoices.action_move_create()
return to_open_invoices.invoice_validate()
@@ -583,8 +575,8 @@ class AccountInvoice(models.Model):
@api.multi
def action_invoice_cancel(self):
if self.filtered(lambda inv: inv.state not in ['proforma2', 'draft', 'open']):
raise UserError(_("Invoice must be in draft, Pro-forma or open state in order to be cancelled."))
if self.filtered(lambda inv: inv.state not in ['draft', 'open']):
raise UserError(_("Invoice must be in draft or open state in order to be cancelled."))
return self.action_cancel()
@api.multi
@@ -175,7 +175,7 @@ class account_journal(models.Model):
number_waiting = 0
for result in query_results:
cur = self.env['res.currency'].browse(result.get('currency'))
if result.get('state') in ['draft', 'proforma', 'proforma2']:
if result.get('state') == 'draft':
number_draft += 1
sum_draft += cur.compute(result.get('amount_total'), currency)
elif result.get('state') == 'open':
-3
View File
@@ -32,9 +32,6 @@ class AccountConfigSettings(models.TransientModel):
module_account_deferred_revenue = fields.Boolean(string="Revenue Recognition")
module_account_budget = fields.Boolean(string='Budget Management')
module_account_reports = fields.Boolean("Dynamic Reports")
group_proforma_invoices = fields.Boolean(string='Allow pro-forma invoices',
implied_group='account.group_proforma_invoices',
help="Allows you to put invoices in pro-forma state.")
module_account_reports_followup = fields.Boolean("Enable payment followup management")
default_sale_tax_id = fields.Many2one('account.tax', string="Default Sale Tax",
default_model="account.config.settings", company_dependent=True, oldname="default_sale_tax")
@@ -55,8 +55,6 @@ class AccountInvoiceReport(models.Model):
], readonly=True)
state = fields.Selection([
('draft', 'Draft'),
('proforma', 'Pro-forma'),
('proforma2', 'Pro-forma'),
('open', 'Open'),
('paid', 'Paid'),
('cancel', 'Cancelled')
@@ -72,8 +72,7 @@
<filter string="This Year" name="thisyear" domain="['|', ('date', '=', False), '&amp;',('date','&lt;=', time.strftime('%%Y-12-31')),('date','&gt;=',time.strftime('%%Y-01-01'))]" help="Journal invoices with period in current year"/>
<separator/>
<filter string="To Invoice" domain="[('state','=','draft')]" help = "Draft Invoices"/>
<filter string="Pro-forma" domain="['|', ('state','=','proforma'),('state','=','proforma2')]"/>
<filter string="Invoiced" name="current" domain="[('state','not in', ('draft','cancel','proforma','proforma2'))]"/>
<filter string="Invoiced" name="current" domain="[('state','not in', ('draft','cancel'))]"/>
<separator/>
<filter string="This Month" name="this_month" domain="[('date', '&gt;=', context_today().strftime('%%Y-%%m-01'))]"/>
<filter string="Last Month" name="last_month" domain="['&amp;', ('date', '&gt;=', (context_today() - relativedelta(months=1)).strftime('%%Y-%%m-01')), ('date', '&lt;', context_today().strftime('%%Y-%%m-01'))]"/>
@@ -25,12 +25,6 @@
<field name="implied_ids" eval="[(4, ref('group_account_user'))]"/>
</record>
<record id="group_proforma_invoices" model="res.groups">
<field name="name">Pro-forma Invoices</field>
<field name="category_id" ref="base.module_category_hidden"/>
</record>
<record id="group_warning_account" model="res.groups">
<field name="name">A warning can be set on a partner (Account)</field>
<field name="category_id" ref="base.module_category_hidden"/>
@@ -70,12 +70,6 @@ class TestAccountCustomerInvoice(AccountTestUsers):
# I check that Initially customer invoice is in the "Draft" state
self.assertEquals(self.account_invoice_customer0.state, 'draft')
# I change the state of invoice to "Proforma2" by clicking PRO-FORMA button
self.account_invoice_customer0.action_invoice_proforma2()
# I check that the invoice state is now "Proforma2"
self.assertEquals(self.account_invoice_customer0.state, 'proforma2')
# I check that there is no move attached to the invoice
self.assertEquals(len(self.account_invoice_customer0.move_id), 0)
+9 -13
View File
@@ -173,7 +173,7 @@
</div>
<div class="col-xs-6">
<span class="pull-right text-right">
<field name="state" widget="kanban_label_selection" options="{'classes': {'draft': 'default', 'cancel': 'default', 'none': 'danger', 'proforma': 'warning', 'proforma2': 'warning', 'open': 'warning',
<field name="state" widget="kanban_label_selection" options="{'classes': {'draft': 'default', 'cancel': 'default', 'none': 'danger', 'open': 'warning',
'paid': 'success'}}"/>
</span>
</div>
@@ -217,10 +217,10 @@
<field name="arch" type="xml">
<form string="Vendor Bill">
<header>
<button name="action_invoice_open" type="object" states="draft,proforma2" string="Validate" class="oe_highlight" groups="account.group_account_invoice"/>
<button name="action_invoice_open" type="object" states="draft" string="Validate" class="oe_highlight" groups="account.group_account_invoice"/>
<button name="%(action_account_invoice_payment)d" type="action" states="open" string="Register Payment" groups="account.group_account_invoice" class="oe_highlight"/>
<button name="%(action_account_invoice_refund)d" type='action' string='Ask Refund' groups="account.group_account_invoice" attrs="{'invisible': ['|',('type', 'in', ['in_refund','out_refund']),('state','not in',('open','paid'))]}"/>
<button name="action_invoice_cancel" type="object" states="draft,proforma2" string="Cancel Bill" groups="account.group_account_invoice"/>
<button name="action_invoice_cancel" type="object" states="draft" string="Cancel Bill" groups="account.group_account_invoice"/>
<button name="action_invoice_draft" states="cancel" string="Set to Draft" type="object" groups="account.group_account_invoice"/>
<field name="state" widget="statusbar" statusbar_visible="draft,open,paid" />
</header>
@@ -348,16 +348,14 @@
<field name="arch" type="xml">
<form string="Invoice">
<header>
<button name="action_invoice_sent" type="object" string="Send by Email" attrs="{'invisible':['|',('sent','=',True), ('state', 'not in', ('open', 'proforma', 'proforma2'))]}" class="oe_highlight" groups="base.group_user"/>
<button name="action_invoice_sent" type="object" string="Send by Email" attrs="{'invisible':['|',('sent','=',True), ('state', '!=', 'open')]}" class="oe_highlight" groups="base.group_user"/>
<button name="invoice_print" string="Print Invoice" type="object" attrs="{'invisible':['|',('sent','=',True), ('state', '!=', 'open')]}" 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', 'proforma', 'proforma2'))]}" groups="base.group_user"/>
<button name="action_invoice_sent" type="object" string="Send by Email" attrs="{'invisible':['|',('sent','=',False), ('state', '!=', 'open')]}" groups="base.group_user"/>
<button name="invoice_print" string="Print Invoice" type="object" attrs="{'invisible':['|',('sent','=',False), ('state', '!=', 'open')]}" groups="base.group_user"/>
<button name="%(action_account_invoice_payment)d" 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="proforma2" string="Validate" groups="account.group_account_invoice"/>
<button name="action_invoice_proforma2" type="object" states="draft" string="PRO-FORMA" groups="account.group_proforma_invoices"/>
<button name="%(action_account_invoice_refund)d" type='action' string='Refund Invoice' groups="account.group_account_invoice" attrs="{'invisible': ['|',('type', '=', 'out_refund'), ('state', 'not in', ('open','proforma2','paid'))]}"/>
<button name="action_invoice_cancel" type="object" states="draft,proforma2,open" string="Cancel Invoice" groups="base.group_no_one"/>
<button name="%(action_account_invoice_refund)d" type='action' string='Refund Invoice' groups="account.group_account_invoice" attrs="{'invisible': ['|',('type', '=', 'out_refund'), ('state', 'not in', ('open','paid'))]}"/>
<button name="action_invoice_cancel" type="object" states="draft,open" string="Cancel Invoice" groups="base.group_no_one"/>
<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"/>
</header>
@@ -366,8 +364,7 @@
</div>
<field name="has_outstanding" invisible="1"/>
<sheet string="Invoice">
<label string="Pro Forma Invoice" attrs="{'invisible': [('state','not in',('proforma','proforma2'))]}"/>
<label string="Refund" attrs="{'invisible': ['|',('state','in',('draft','proforma','proforma2')), ('type','&lt;&gt;','out_refund')]}"/>
<label string="Refund" attrs="{'invisible': ['|',('state','=','draft'), ('type','&lt;&gt;','out_refund')]}"/>
<h1 class="mt0">
<label string="Draft Invoice" attrs="{'invisible': ['|',('state','not in',('draft',)), ('type','&lt;&gt;','out_invoice')]}"/>
<label string="Draft Refund" attrs="{'invisible': ['|',('state','not in',('draft',)), ('type','&lt;&gt;','out_refund')]}"/>
@@ -511,8 +508,7 @@
<search string="Search Invoice">
<field name="number" string="Invoice" filter_domain="['|','|','|', ('number','ilike',self), ('origin','ilike',self), ('reference', 'ilike', self), ('partner_id', 'child_of', self)]"/>
<field name="journal_id" />
<filter name="draft" string="Draft" domain="[('state','in',('draft', 'proforma'))]"/>
<filter name="proforma" string="Proforma" domain="[('state','=','proforma2')]" groups="account.group_proforma_invoices"/>
<filter name="draft" string="Draft" domain="[('state','=','draft')]"/>
<filter name="invoices" string="Invoices" domain="['&amp;', ('state','in',['open','paid']),('type','in',('out_invoice','in_invoice'))]"/>
<filter name="refunds" string="Refunds" domain="['&amp;', ('state','in',['open','paid']),('type','in',('out_refund','in_refund'))]"/>
<separator/>
@@ -266,7 +266,7 @@
<div class="col-xs-6 o_kanban_primary_right">
<div class="row">
<div class="col-xs-6">
<a type="object" name="open_action" context="{'search_default_draft': '1', 'search_default_proforma': '1'}">
<a type="object" name="open_action" context="{'search_default_draft': '1'}">
<span t-if="journal_type == 'sale'" title="Invoices to validate"><t t-esc="dashboard.number_draft"/> Invoices to validate</span>
<span t-if="journal_type == 'purchase'" title="Draft bills"><t t-esc="dashboard.number_draft"/> Draft bills</span>
</a>
-1
View File
@@ -15,7 +15,6 @@
<h2>
<span t-if="o.type == 'out_invoice' and (o.state == 'open' or o.state == 'paid')">Invoice</span>
<span t-if="o.type == 'out_invoice' and o.state == 'proforma2'">PRO-FORMA</span>
<span t-if="o.type == 'out_invoice' and o.state == 'draft'">Draft Invoice</span>
<span t-if="o.type == 'out_invoice' and o.state == 'cancel'">Cancelled Invoice</span>
<span t-if="o.type == 'out_refund'">Refund</span>
-11
View File
@@ -145,17 +145,6 @@
</div>
<h2>Invoicing</h2>
<div class="row mt16 o_settings_container" id="invoicing_settings">
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="group_proforma_invoices"/>
</div>
<div class="o_setting_right_pane">
<label string="Pro Forma"/>
<div class="text-muted">
Get a pro forma status for invoices
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="group_warning_account"/>
@@ -50,8 +50,8 @@ class AccountInvoiceRefund(models.TransientModel):
date = False
description = False
for inv in inv_obj.browse(context.get('active_ids')):
if inv.state in ['draft', 'proforma2', 'cancel']:
raise UserError(_('Cannot refund draft/proforma/cancelled invoice.'))
if inv.state in ['draft', 'cancel']:
raise UserError(_('Cannot refund draft/cancelled invoice.'))
if inv.reconciled and mode in ('cancel', 'modify'):
raise UserError(_('Cannot refund invoice which is already reconciled, invoice should be unreconciled first. You can only refund this invoice.'))
@@ -17,8 +17,8 @@ class AccountInvoiceConfirm(models.TransientModel):
active_ids = context.get('active_ids', []) or []
for record in self.env['account.invoice'].browse(active_ids):
if record.state not in ('draft', 'proforma', 'proforma2'):
raise UserError(_("Selected invoice(s) cannot be confirmed as they are not in 'Draft' or 'Pro-Forma' state."))
if record.state != 'draft':
raise UserError(_("Selected invoice(s) cannot be confirmed as they are not in 'Draft' state."))
record.action_invoice_open()
return {'type': 'ir.actions.act_window_close'}
@@ -17,7 +17,7 @@
<field name="inherit_id" ref="account.invoice_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='action_invoice_cancel']" position="replace">
<button name="action_invoice_cancel" type="object" states="draft,proforma2,open" string="Cancel Invoice" groups="account.group_account_invoice"/>
<button name="action_invoice_cancel" type="object" states="draft,open" string="Cancel Invoice" groups="account.group_account_invoice"/>
</xpath>
</field>
</record>
@@ -28,7 +28,7 @@
<field name="inherit_id" ref="account.invoice_supplier_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='action_invoice_cancel']" position="replace">
<button name="action_invoice_cancel" type="object" states="draft,proforma2,open" string="Cancel Invoice" groups="account.group_account_invoice"/>
<button name="action_invoice_cancel" type="object" states="draft,open" string="Cancel Invoice" groups="account.group_account_invoice"/>
</xpath>
</field>
</record>
+1 -1
View File
@@ -68,7 +68,7 @@ class MembershipLine(models.Model):
line.state = 'canceled'
continue
istate = fetched[0]
if istate in ('draft', 'proforma'):
if istate == 'draft':
line.state = 'waiting'
elif istate == 'open':
line.state = 'invoiced'
+1 -1
View File
@@ -122,7 +122,7 @@ class Partner(models.Model):
s = 1
elif mstate == 'cancel' and s != 0 and s != 1:
s = 2
elif (mstate == 'draft' or mstate == 'proforma') and s != 0 and s != 1:
elif mstate == 'draft' and s != 0 and s != 1:
s = 3
if s == 4:
for mline in partner.member_lines:
@@ -14,7 +14,6 @@
<h2>
<span t-if="o.type == 'out_invoice' and (o.state == 'open' or o.state == 'paid')">Invoice</span>
<span t-if="o.type == 'out_invoice' and o.state == 'proforma2'">PRO-FORMA</span>
<span t-if="o.type == 'out_invoice' and o.state == 'draft'">Draft Invoice</span>
<span t-if="o.type == 'out_invoice' and o.state == 'cancel'">Cancelled Invoice</span>
<span t-if="o.type == 'out_refund'">Refund</span>
+7 -2
View File
@@ -26,7 +26,12 @@
% endif
,</p>
<p>
Here is your ${doc_name} <strong>${object.name}</strong>
Here is
% if ctx.get('proforma')
in attachment, your pro-forma
% else
the ${doc_name} <strong>${object.name}</strong>
% endif
% if object.origin:
(with reference: ${object.origin} )
% endif
@@ -34,8 +39,8 @@ amounting in <strong>${object.amount_total} ${object.pricelist_id.currency_id.na
from ${object.company_id.name}.
</p>
% if is_online and not ctx.get('proforma'):
<br/><br/>
% if is_online:
<center>
<a href="${access_url}" style="background-color: #1abc9c; padding: 20px; text-decoration: none; color: #fff; border-radius: 5px; font-size: 16px;" class="o_default_snippet_text">${access_name}</a>
<br/><br/><br/>
+4 -4
View File
@@ -407,16 +407,16 @@ class SaleOrder(models.Model):
compose_form_id = ir_model_data.get_object_reference('mail', 'email_compose_message_wizard_form')[1]
except ValueError:
compose_form_id = False
ctx = dict()
ctx.update({
ctx = {
'default_model': 'sale.order',
'default_res_id': self.ids[0],
'default_use_template': bool(template_id),
'default_template_id': template_id,
'default_composition_mode': 'comment',
'mark_so_as_sent': True,
'custom_layout': "sale.mail_template_data_notification_email_sale_order"
})
'custom_layout': "sale.mail_template_data_notification_email_sale_order",
'proforma': self.env.context.get('proforma', False)
}
return {
'type': 'ir.actions.act_window',
'view_type': 'form',
@@ -48,6 +48,8 @@ class SaleConfiguration(models.TransientModel):
"Show total",
implied_group='sale.group_show_price_total',
group='base.group_portal,base.group_user,base.group_public')
group_proforma_sales = fields.Boolean(string="Pro-Forma", implied_group='sale.group_proforma_sales',
help="Allows you to send pro-forma.")
sale_show_tax = fields.Selection([
('subtotal', 'Tax-Included Prices'),
('total', 'Tax-Excluded Prices')], "Tax Display",
+14
View File
@@ -122,3 +122,17 @@ class SaleReport(models.Model):
FROM ( %s )
%s
)""" % (self._table, self._select(), self._from(), self._group_by()))
class SaleOrderReportProforma(models.AbstractModel):
_name = 'report.sale.report_saleproforma'
@api.multi
def render_html(self, docids, data=None):
docs = self.env['sale.order'].browse(docids)
docargs = {
'doc_ids': docs.ids,
'doc_model': 'sale.order',
'docs': docs,
'proforma': True
}
return self.env['report'].render('sale.report_saleorder', docargs)
+12
View File
@@ -12,5 +12,17 @@
<record id="report_sale_order" model="ir.actions.report.xml">
<field name="print_report_name">(object.state in ('draft', 'sent') and 'Quotation'+'-'+(object.name) or 'Order'+'-'+(object.name))</field>
</record>
<report
string="Pro-Forma"
id="report_proforma_quotation"
model="sale.order"
report_type="qweb-pdf"
name="sale.report_saleproforma"
file="sale.report_saleorder"
/>
<record id="report_proforma_quotation" model="ir.actions.report.xml">
<field name="print_report_name">'Pro-Forma'+'-'+(object.name)</field>
</record>
</data>
</odoo>
+7 -2
View File
@@ -30,8 +30,13 @@
</div>
<h2>
<span t-if="doc.state not in ['draft','sent']">Order # </span>
<span t-if="doc.state in ['draft','sent']">Quotation # </span>
<t t-if="not proforma">
<span t-if="doc.state not in ['draft','sent']">Order # </span>
<span t-if="doc.state in ['draft','sent']">Quotation # </span>
</t>
<t t-if="proforma">
<span>Pro-Forma # </span>
</t>
<span t-field="doc.name"/>
</h2>
+5
View File
@@ -46,6 +46,11 @@
<field name="category_id" ref="base.module_category_hidden"/>
</record>
<record id="group_proforma_sales" model="res.groups">
<field name="name">Pro-forma Invoices</field>
<field name="category_id" ref="base.module_category_hidden"/>
</record>
<record id="group_analytic_accounting" model="res.groups">
<field name="name">Analytic Accounting for Sales</field>
<field name="category_id" ref="base.module_category_hidden"/>
@@ -246,6 +246,17 @@
</div>
</div>
</div>
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="group_proforma_sales"/>
</div>
<div class="o_setting_right_pane">
<label for="group_proforma_sales"/>
<div class="text-muted">
Allows you to send Pro-Forma to your customers
</div>
</div>
</div>
</div>
<h2>Invoicing</h2>
<div class="row mt16 o_settings_container">
+2
View File
@@ -154,9 +154,11 @@
type="action" context="{'default_advance_payment_method': 'percentage'}"
attrs="{'invisible': ['|',('invoice_status', '!=', 'no'), ('state', '!=', 'sale')]}"/>
<button name="action_quotation_send" string="Send by Email" type="object" states="draft" class="btn-primary"/>
<button name="action_quotation_send" type="object" string="Send PRO-FORMA" groups="sale.group_proforma_sales" class="btn-primary" attrs="{'invisible': ['|', ('state', 'in', ['sent', 'sale']), ('invoice_count','&gt;=',1)]}" context="{'proforma': True}"/>
<button name="print_quotation" string="Print" type="object" states="draft" class="btn-primary"/>
<button name="action_confirm" states="sent" string="Confirm Sale" class="btn-primary o_sale_confirm" type="object" />
<button name="action_confirm" states="draft" string="Confirm Sale" class="o_sale_confirm" type="object" />
<button name="action_quotation_send" type="object" string="Send PRO-FORMA" groups="sale.group_proforma_sales" attrs="{'invisible': ['|', ('state', '=', 'draft'), ('invoice_count','&gt;=',1)]}" context="{'proforma': True}"/>
<button name="print_quotation" string="Print" type="object" states="sent,sale"/>
<button name="action_quotation_send" string="Send by Email" type="object" states="sent,sale"/>
<button name="action_cancel" states="draft,sent,sale" type="object" string="Cancel"/>