[ADD] sale_layout module allowing to set categories and subtotals on invoice and sale order

bzr revid: sle@openerp.com-20140404112308-aopihnefnf55b4er
This commit is contained in:
Simon Lejeune 2014-04-04 13:23:08 +02:00
parent 0cbd5fb87b
commit dc8246eb03
12 changed files with 507 additions and 2 deletions

View File

@ -54,7 +54,7 @@
<th class="text-right">Amount</th>
</tr>
</thead>
<tbody>
<tbody class="invoice_tbody">
<tr t-foreach="o.invoice_line" t-as="l">
<td><span t-field="l.name"/></td>
<td><span t-field="l.quantity"/></td>

View File

@ -64,7 +64,7 @@
<th class="text-right">Price</th>
</tr>
</thead>
<tbody>
<tbody class="sale_tbody">
<tr t-foreach="o.order_line" t-as="l">
<td>
<span t-field="l.name"/>

View File

@ -0,0 +1 @@
import models

View File

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2014-Today OpenERP SA (<http://www.openerp.com>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'Sale Layout',
'version': '1.0',
'sequence': 14,
'summary': 'Sale Layout, page-break, subtotals, separators, report',
'description': """
Manage your sales reports
=========================
With this module you can personnalize the sale order and invoice report with
separators, page-breaks or subtotals.
""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'depends': ['sale', 'report'],
'category': 'Sale',
'data': ['views/sale_layout_category_view.xml',
'views/report_invoice_layouted.xml',
'views/report_quotation_layouted.xml',
'views/sale_layout_template.xml',
'security/ir.model.access.csv'],
'demo': ['data/sale_layout_category_data.xml'],
'installable': True,
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<record id="sale_layout_cat_1" model="sale_layout.category">
<field name="name">Services</field>
<field name="subtotal" eval="True"></field>
<field name="separator" eval="True"></field>
<field name="pagebreak" eval="True"></field>
<field name="sequence">1</field>
</record>
<record id="sale_layout_cat_2" model="sale_layout.category">
<field name="name">Material</field>
<field name="subtotal" eval="True"></field>
<field name="separator" eval="True"></field>
<field name="pagebreak" eval="False"></field>
<field name="sequence">10</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1 @@
import sale_layout

View File

@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2014-Today OpenERP SA (<http://www.openerp.com>).
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import osv, fields
from itertools import groupby
def grouplines(self, ordered_lines, sortkey):
"""Return lines from a specified invoice or sale order grouped by category"""
grouped_lines = []
for key, valuesiter in groupby(ordered_lines, sortkey):
group = {}
group['category'] = key
group['lines'] = list(v for v in valuesiter)
if 'subtotal' in key and key.subtotal is True:
group['subtotal'] = sum(line.price_subtotal for line in group['lines'])
grouped_lines.append(group)
return grouped_lines
class SaleLayoutCategory(osv.Model):
_name = 'sale_layout.category'
_order = 'sequence'
_columns = {
'name': fields.char('Name', required=True),
'sequence': fields.integer('Sequence', required=True),
'subtotal': fields.boolean('Add subtotal'),
'separator': fields.boolean('Add separator'),
'pagebreak': fields.boolean('Add pagebreak')
}
_defaults = {
'subtotal': True,
'separator': True,
'pagebreak': False,
'sequence': 10
}
# We want to forbid edit of a category if it is already linked to a report.
def _check(self, cr, uid, ids):
for cat in self.browse(cr, uid, ids):
invoice_obj = self.pool.get('account.invoice.line')
sale_obj = self.pool.get('sale.order.line')
ids = invoice_obj.search(cr, uid, [('sale_layout_cat_id', '=', cat.id)])
ids += sale_obj.search(cr, uid, [('sale_layout_cat_id', '=', cat.id)])
if len(ids) > 0:
return False
return True
_constraints = [(
_check,
'This category could not be modified nor deleted because it is still used in an invoice or'
' a sale report.', ['name']
)]
class AccountInvoice(osv.Model):
_inherit = 'account.invoice'
def sale_layout_lines(self, cr, uid, ids, context, invoice_id, *args, **kwargs):
"""
Returns invoice lines from a specified invoice ordered by
sale_layout_category sequence. Used in sale_layout module.
:Parameters:
-'invoice_id' (int): specify the concerned invoice.
"""
ordered_lines = self.browse(cr, uid, invoice_id, context=context).invoice_line
# We chose to group first by category model and, if not present, by invoice name
sortkey = lambda x: x.sale_layout_cat_id if x.sale_layout_cat_id else ''
return grouplines(self, ordered_lines, sortkey)
class AccountInvoiceLine(osv.Model):
_inherit = 'account.invoice.line'
_columns = {
'sale_layout_cat_id': fields.many2one('sale_layout.category',
'Layout Category'),
'categ_sequence': fields.related('sale_layout_cat_id',
'sequence', type='integer',
string='Layout Sequence', store=True)
# Store is intentionally set in order to keep the "historic" order.
}
_order = 'invoice_id, categ_sequence, sequence, id'
class SaleOrder(osv.Model):
_inherit = 'sale.order'
def sale_layout_lines(self, cr, uid, ids, context, order_id, *args, **kwargs):
"""
Returns order lines from a specified sale ordered by
sale_layout_category sequence. Used in sale_layout module.
:Parameters:
-'order_id' (int): specify the concerned sale order.
"""
ordered_lines = self.browse(cr, uid, order_id, context=context).order_line
sortkey = lambda x: x.sale_layout_cat_id if x.sale_layout_cat_id else ''
return grouplines(self, ordered_lines, sortkey)
class SaleOrderLine(osv.Model):
_inherit = 'sale.order.line'
_columns = {
'sale_layout_cat_id': fields.many2one('sale_layout.category',
'Layout Category'),
'categ_sequence': fields.related('sale_layout_cat_id',
'sequence', type='integer',
string='Layout Sequence', store=True)
# Store is intentionally set in order to keep the "historic" order.
}
_order = 'order_id, categ_sequence, sequence, id'

View File

@ -0,0 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
report_layout_category_1,report_layout_category_1,model_sale_layout_category,base.group_sale_manager,1,1,1,1
report_layout_category_2,report_layout_category_2,model_sale_layout_category,account.group_account_manager,1,1,1,1
report_layout_category_3,report_layout_category_3,model_sale_layout_category,base.group_sale_salesman,1,1,1,O
report_layout_category_4,report_layout_category_4,model_sale_layout_category,base.group_sale_salesman_all_leads,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 report_layout_category_1 report_layout_category_1 model_sale_layout_category base.group_sale_manager 1 1 1 1
3 report_layout_category_2 report_layout_category_2 model_sale_layout_category account.group_account_manager 1 1 1 1
4 report_layout_category_3 report_layout_category_3 model_sale_layout_category base.group_sale_salesman 1 1 1 O
5 report_layout_category_4 report_layout_category_4 model_sale_layout_category base.group_sale_salesman_all_leads 1 1 1 0

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="report_invoice_layouted" inherit_id="account.report_invoice_document">
<xpath expr="//table/tbody[@class='invoice_tbody']" position="replace">
<t t-foreach="o.sale_layout_lines(invoice_id=o.id)" t-as="p">
<!-- Name of the category -->
<t t-call="sale_layout.category_template"/>
<!-- Lines associated -->
<t t-foreach="p['lines']" t-as="l">
<tr>
<td><span t-field="l.name"/></td>
<td><span t-field="l.quantity"/></td>
<td groups="product.group_uom"><span t-field="l.uos_id"/></td>
<td class="text-right">
<span t-field="l.price_unit"/>
</td>
<td groups="sale.group_discount_per_so_line"><span t-field="l.discount"/></td>
<td class="text-right">
<span t-esc="', '.join(map(lambda x: x.name, l.invoice_line_tax_id))"/>
</td>
<td class="text-right">
<span t-field="l.price_subtotal"
t-field-options='{"widget": "monetary", "display_currency": "o.currency_id"}'/>
</td>
</tr>
</t>
<!-- Subtotal -->
<t t-call="sale_layout.subtotal_template"/>
<!-- Separator -->
<t t-call="sale_layout.separator_template"/>
<!-- Pagebreak -->
<t t-if="'pagebreak' in p['category'] and p['category'].pagebreak is True">
<t t-if="p_index &lt; p_size - 1">
<![CDATA[
</tbody>
</table>
<p style="page-break-before:always;"> </p>
<table class="table table-condensed">
]]>
<thead>
<tr>
<th>Description</th>
<th>Quantity</th>
<th groups="product.group_uom">Unit of measure</th>
<th class="text-right">Unit Price</th>
<th class="text-right" groups="sale.group_discount_per_so_line">Discount (%)</th>
<th class="text-right">Taxes</th>
<th class="text-right">Amount</th>
</tr>
</thead>
<![CDATA[
<tbody>
]]>
</t>
</t>
</t>
</xpath>
</template>
</data>
</openerp>

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="report_sale_layouted" inherit_id="sale.report_saleorder">
<xpath expr="//table/tbody[@class='sale_tbody']" position="replace">
<t t-foreach="o.sale_layout_lines(order_id = o.id)" t-as="p">
<!-- Name of the category -->
<t t-call="sale_layout.category_template" />
<!-- Lines associated -->
<t t-foreach="p['lines']" t-as="l">
<tr>
<td>
<span t-field="l.name"/>
</td>
<td>
<span t-esc="', '.join(map(lambda x: x.name, l.tax_id))"/>
</td>
<td class="text-right">
<span t-field="l.product_uom_qty"/>
<span groups="product.group_uom" t-field="l.product_uom"/>
</td>
<td class="text-right">
<span t-field="l.price_unit"/>
</td>
<td groups="sale.group_discount_per_so_line">
<span t-field="l.discount"/>
</td>
<td class="text-right">
<span t-field="l.price_subtotal"
t-field-options='{"widget": "monetary", "display_currency": "o.pricelist_id.currency_id"}'/>
</td>
</tr>
</t>
<!-- Subtotal -->
<t t-call="sale_layout.subtotal_template" />
<!-- Separator -->
<t t-call="sale_layout.separator_template" />
<!-- Pagebreak -->
<t t-if="'pagebreak' in p['category'] and p['category'].pagebreak is True">
<t t-if="p_index &lt; p_size - 1">
<![CDATA[
</tbody>
</table>
<p style="page-break-before:always;"> </p>
<table class="table table-condensed">
]]>
<thead>
<tr>
<th>Description</th>
<th>Taxes</th>
<th class="text-right">Quantity</th>
<th class="text-right">Unit Price</th>
<th groups="sale.group_discount_per_so_line">Disc.(%)</th>
<th class="text-right">Price</th>
</tr>
</thead>
<![CDATA[
<tbody>
]]>
</t>
</t>
</t>
</xpath>
</template>
</data>
</openerp>

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Sale order form -->
<record model="ir.ui.view" id="view_order_form_inherit_1">
<field name="name">sale.order.form.inherit_1</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//notebook/page/field[@name='order_line']/tree" position="attributes">
<attribute name="editable"></attribute>
</xpath>
</data>
</field>
</record>
<record model="ir.ui.view" id="view_order_form_inherit_2">
<field name="name">sale.order.line.form.inherit_2</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='order_line']/form/group/group/field[@name='product_id']" position="before">
<field name="sale_layout_cat_id"/>
</xpath>
</data>
</field>
</record>
<!-- Invoice form -->
<record model="ir.ui.view" id="view_invoice_form_inherit_1">
<field name="name">account.invoice.form.inherit_1</field>
<field name="model">account.invoice</field>
<field name="inherit_id" ref="account.invoice_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//notebook/page/field[@name='invoice_line']/tree" position="attributes">
<attribute name="editable"></attribute>
</xpath>
</data>
</field>
</record>
<record model="ir.ui.view" id="view_invoice_line_form_inherit_2">
<field name="name">account.invoice.line.form.inherit_2</field>
<field name="model">account.invoice.line</field>
<field name="inherit_id" ref="account.view_invoice_line_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//group/group/field[@name='product_id']" position="before">
<field name="sale_layout_cat_id"/>
</xpath>
</data>
</field>
</record>
<!-- Report condiguration -->
<record id="report_configuration_form_view" model="ir.ui.view">
<field name="name">report.configuration.form.view</field>
<field name="model">sale_layout.category</field>
<field name="arch" type="xml">
<form string="Report Configuration" version="7.0">
<group>
<field name="name"/>
<field name="subtotal" widget="checkbox"/>
<field name="separator" widget="checkbox"/>
<field name="pagebreak" widget="checkbox"/>
<field name="sequence"/>
</group>
</form>
</field>
</record>
<record id="report_configuration_tree_view" model="ir.ui.view">
<field name="name">report.configuration.form.view</field>
<field name="model">sale_layout.category</field>
<field name="arch" type="xml">
<tree string="Report Configuration">
<field name="name"/>
<field name="subtotal" widget="checkbox"/>
<field name="separator" widget="checkbox"/>
<field name="pagebreak" widget="checkbox"/>
<field name="sequence"/>
</tree>
</field>
</record>
<record id="report_configuration_search_view" model="ir.ui.view" >
<field name="name">report.configuration.search.view</field>
<field name="model">sale_layout.category</field>
<field name="arch" type="xml">
<search string="Search Name">
<filter string="Total" domain="[('subtotal','=','True')]"/>
<filter string="Separator" domain="[('separator','=','True')]"/>
<filter string="Break" domain="[('pagebreak','=','True')]"/>
<group string="Group By Name">
<filter string="Name" context="{'group_by' : 'name'}"/>
</group>
</search>
</field>
</record>
<record id='report_configuration_action' model='ir.actions.act_window'>
<field name="name">Report Configuration</field>
<field name="res_model">sale_layout.category</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem
action="report_configuration_action"
id="Report_configuration"
parent="base.menu_base_config"
sequence="25"
name="Report Layout Categories"
groups="base.group_sale_manager,base.group_sale_salesman,account.group_account_manager,account.group_account_user"
/>
</data>
</openerp>

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="category_template">
<!-- Category name -->
<t t-if="p['category']">
<tr>
<td colspan="100" class="active" style="font-weight: bold; padding-left: 10px; border-bottom: 1px solid black;">
<t t-if="p['category'].name">
<t t-esc="p['category'].name"></t>
</t>
<t t-if="not p['category'].name">
Uncategorized
</t>
</td>
</tr>
</t>
</template>
<template id="subtotal_template">
<!-- Subtotal -->
<t t-if="'subtotal' in p['category'] and p['category'].subtotal is True">
<tr class="text-right">
<td colspan="100">
<strong>Subtotal: </strong>
<t t-esc="p['subtotal']"></t>
</td>
</tr>
</t>
</template>
<template id="separator_template">
<!-- Separator -->
<t t-if="'separator' in p['category'] and p['category'].separator is True">
<tr class="text-center">
<td colspan="100" class="active">
***
</td>
</tr>
</t>
</template>
</data>
</openerp>