summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorSimon Lejeune <sle@openerp.com>2014-04-04 13:23:08 +0200
committerSimon Lejeune <sle@openerp.com>2014-04-04 13:23:08 +0200
commitdc8246eb03b37d2be7bff497f656cd183ba8a5c9 (patch)
tree863060310ec765e5d7a1204ab7eea260609eee15 /addons
parent0cbd5fb87b39683689e9694ba369e5a94ef33e1d (diff)
[ADD] sale_layout module allowing to set categories and subtotals on invoice and sale order
bzr revid: sle@openerp.com-20140404112308-aopihnefnf55b4er
Diffstat (limited to 'addons')
-rw-r--r--addons/account/views/report_invoice.xml2
-rw-r--r--addons/sale/views/report_saleorder.xml2
-rw-r--r--addons/sale_layout/__init__.py1
-rw-r--r--addons/sale_layout/__openerp__.py44
-rw-r--r--addons/sale_layout/data/sale_layout_category_data.xml20
-rw-r--r--addons/sale_layout/models/__init__.py1
-rw-r--r--addons/sale_layout/models/sale_layout.py137
-rw-r--r--addons/sale_layout/security/ir.model.access.csv5
-rw-r--r--addons/sale_layout/views/report_invoice_layouted.xml65
-rw-r--r--addons/sale_layout/views/report_quotation_layouted.xml70
-rw-r--r--addons/sale_layout/views/sale_layout_category_view.xml120
-rw-r--r--addons/sale_layout/views/sale_layout_template.xml42
12 files changed, 507 insertions, 2 deletions
diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml
index bc0dc5e92a2..437c9726bf3 100644
--- a/addons/account/views/report_invoice.xml
+++ b/addons/account/views/report_invoice.xml
@@ -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>
diff --git a/addons/sale/views/report_saleorder.xml b/addons/sale/views/report_saleorder.xml
index c6f237e3ac3..40c8afa045d 100644
--- a/addons/sale/views/report_saleorder.xml
+++ b/addons/sale/views/report_saleorder.xml
@@ -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"/>
diff --git a/addons/sale_layout/__init__.py b/addons/sale_layout/__init__.py
new file mode 100644
index 00000000000..bff786c0885
--- /dev/null
+++ b/addons/sale_layout/__init__.py
@@ -0,0 +1 @@
+import models
diff --git a/addons/sale_layout/__openerp__.py b/addons/sale_layout/__openerp__.py
new file mode 100644
index 00000000000..b8fcefc6adc
--- /dev/null
+++ b/addons/sale_layout/__openerp__.py
@@ -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,
+}
diff --git a/addons/sale_layout/data/sale_layout_category_data.xml b/addons/sale_layout/data/sale_layout_category_data.xml
new file mode 100644
index 00000000000..858654e427f
--- /dev/null
+++ b/addons/sale_layout/data/sale_layout_category_data.xml
@@ -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>
diff --git a/addons/sale_layout/models/__init__.py b/addons/sale_layout/models/__init__.py
new file mode 100644
index 00000000000..9e9b002e402
--- /dev/null
+++ b/addons/sale_layout/models/__init__.py
@@ -0,0 +1 @@
+import sale_layout
diff --git a/addons/sale_layout/models/sale_layout.py b/addons/sale_layout/models/sale_layout.py
new file mode 100644
index 00000000000..d6e7e435b99
--- /dev/null
+++ b/addons/sale_layout/models/sale_layout.py
@@ -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'
diff --git a/addons/sale_layout/security/ir.model.access.csv b/addons/sale_layout/security/ir.model.access.csv
new file mode 100644
index 00000000000..119fce977d1
--- /dev/null
+++ b/addons/sale_layout/security/ir.model.access.csv
@@ -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
diff --git a/addons/sale_layout/views/report_invoice_layouted.xml b/addons/sale_layout/views/report_invoice_layouted.xml
new file mode 100644
index 00000000000..4c0b1929484
--- /dev/null
+++ b/addons/sale_layout/views/report_invoice_layouted.xml
@@ -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>
diff --git a/addons/sale_layout/views/report_quotation_layouted.xml b/addons/sale_layout/views/report_quotation_layouted.xml
new file mode 100644
index 00000000000..ad04e271e7d
--- /dev/null
+++ b/addons/sale_layout/views/report_quotation_layouted.xml
@@ -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>
diff --git a/addons/sale_layout/views/sale_layout_category_view.xml b/addons/sale_layout/views/sale_layout_category_view.xml
new file mode 100644
index 00000000000..08f892c127e
--- /dev/null
+++ b/addons/sale_layout/views/sale_layout_category_view.xml
@@ -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>
diff --git a/addons/sale_layout/views/sale_layout_template.xml b/addons/sale_layout/views/sale_layout_template.xml
new file mode 100644
index 00000000000..7b39ea4c632
--- /dev/null
+++ b/addons/sale_layout/views/sale_layout_template.xml
@@ -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>
+