diff --git a/addons/sale/__init__.py b/addons/sale/__init__.py
index 2ec8f5b6b2f..09b49c379e8 100644
--- a/addons/sale/__init__.py
+++ b/addons/sale/__init__.py
@@ -3,6 +3,7 @@
import sale
import sale_analytic
+import sale_layout
import sales_team
import res_partner
import wizard
diff --git a/addons/sale/__openerp__.py b/addons/sale/__openerp__.py
index 36ce45996d3..0f06671b920 100644
--- a/addons/sale/__openerp__.py
+++ b/addons/sale/__openerp__.py
@@ -32,6 +32,8 @@ You can choose flexible invoicing methods:
* *On Delivery Order*: Invoices are generated from picking (delivery)
* *Before Delivery*: A Draft invoice is created and must be paid before delivery
+With this module you can personnalize the sale order and invoice report with
+separators, page-breaks or subtotals.
The Dashboard for the Sales Manager will include
------------------------------------------------
@@ -39,7 +41,7 @@ The Dashboard for the Sales Manager will include
* Monthly Turnover (Graph)
""",
'website': 'https://www.odoo.com/page/crm',
- 'depends': ['sales_team','account', 'procurement', 'report'],
+ 'depends': ['sales_team', 'account', 'procurement', 'report'],
'data': [
'wizard/sale_make_invoice_advance.xml',
'security/sale_security.xml',
@@ -47,6 +49,7 @@ The Dashboard for the Sales Manager will include
'sale_sequence.xml',
'sale_report.xml',
'sale_data.xml',
+ 'sale_layout_category_data.xml',
'sale_view.xml',
'sales_team_view.xml',
'res_partner_view.xml',
@@ -55,6 +58,9 @@ The Dashboard for the Sales Manager will include
'res_config_view.xml',
'views/report_saleorder.xml',
'views/sale.xml',
+ 'views/report_invoice_layouted.xml',
+ 'views/sale_layout_category_view.xml',
+ 'views/sale_layout_template.xml',
'sales_team_dashboard.xml',
'sale_tip_data.xml',
],
diff --git a/addons/sale/res_config.py b/addons/sale/res_config.py
index 4212a88b836..706c5a43b6c 100644
--- a/addons/sale/res_config.py
+++ b/addons/sale/res_config.py
@@ -45,10 +45,10 @@ class sale_configuration(osv.TransientModel):
(0, 'Salespeople do not need to view margins when quoting'),
(1, 'Display margins on quotations and sales orders')
], "Margins"),
- 'module_sale_layout': fields.selection([
+ 'group_sale_layout': fields.selection([
(0, 'Do not personalize sale orders and invoice reports'),
- (1, 'Personnalize the sale orders and invoice report with separators, page-breaks or subtotals')
- ], "Sale Reports Layout"),
+ (1, 'Personalize the sale orders and invoice report with separators, page-breaks or subtotals')
+ ], "Sale Reports Layout", implied_group='sale.group_sale_layout'),
'module_website_quote': fields.selection([
(0, 'Print quotes or send by email'),
(1, 'Send online quotations based on templates (advanced)')
diff --git a/addons/sale/res_config_view.xml b/addons/sale/res_config_view.xml
index c00a65f2d41..c19acf8ce67 100644
--- a/addons/sale/res_config_view.xml
+++ b/addons/sale/res_config_view.xml
@@ -18,7 +18,7 @@
-
+
diff --git a/addons/sale/sale.py b/addons/sale/sale.py
index b5eb50994b3..df67907a519 100644
--- a/addons/sale/sale.py
+++ b/addons/sale/sale.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from itertools import groupby
from datetime import datetime, timedelta
from openerp import SUPERUSER_ID
from openerp import api, fields, models, _
@@ -420,11 +421,29 @@ class SaleOrder(models.Model):
})
order.project_id = analytic
+ @api.multi
+ def layout_categories(self):
+ """
+ Returns this order lines ordered by sale_layout_category sequence. Used to render the report.
+ """
+ self.ensure_one()
+ grouped_lines = []
+ for category, lines in groupby(self.order_line, lambda l: l.layout_category_id):
+ grouped_lines.append({
+ 'name': category and category.name or 'Uncategorized',
+ 'subtotal': category and category.subtotal,
+ 'separator': category and category.separator,
+ 'pagebreak': category and category.pagebreak,
+ 'lines': list(lines)
+ })
+
+ return grouped_lines
+
class SaleOrderLine(models.Model):
_name = 'sale.order.line'
_description = 'Sales Order Line'
- _order = 'order_id desc, sequence, id'
+ _order = 'order_id, layout_category_id, sequence, id'
@api.depends('state', 'product_uom_qty', 'qty_delivered', 'qty_to_invoice', 'qty_invoiced')
def _compute_invoice_status(self):
@@ -656,6 +675,10 @@ class SaleOrderLine(models.Model):
help="Number of days between the order confirmation and the shipping of the products to the customer", oldname="delay")
procurement_ids = fields.One2many('procurement.order', 'sale_line_id', string='Procurements')
+ layout_category_id = fields.Many2one('sale.layout_category', string='Section')
+ layout_category_sequence = fields.Integer(related='layout_category_id.sequence', string='Layout Sequence', store=True)
+ # Store is intentionally set in order to keep the "historic" order.
+
@api.multi
def _prepare_invoice_line(self, qty):
"""
@@ -684,6 +707,8 @@ class SaleOrderLine(models.Model):
'discount': self.discount,
'uom_id': self.product_uom.id,
'product_id': self.product_id.id or False,
+ 'layout_category_id': self.layout_category_id and self.layout_category_id.id or False,
+ 'product_id': self.product_id.id or False,
'invoice_line_tax_ids': [(6, 0, self.tax_id.ids)],
'account_analytic_id': self.order_id.project_id.id,
}
@@ -812,10 +837,31 @@ class AccountInvoice(models.Model):
order.message_post(body=_("Invoice %s paid") % (name))
return res
+ @api.multi
+ def layout_categories(self):
+ """
+ Returns this sale order lines ordered by sale_layout_category sequence. Used to render the report.
+ """
+ self.ensure_one()
+ grouped_lines = []
+ for category, lines in groupby(self.invoice_line_ids, lambda l: l.layout_category_id):
+ grouped_lines.append({
+ 'name': category and category.name or 'Uncategorized',
+ 'subtotal': category and category.subtotal,
+ 'separator': category and category.separator,
+ 'pagebreak': category and category.pagebreak,
+ 'lines': list(lines)
+ })
+
+ return grouped_lines
class AccountInvoiceLine(models.Model):
_inherit = 'account.invoice.line'
+ _order = 'invoice_id, layout_category_id, sequence, id'
+
sale_line_ids = fields.Many2many('sale.order.line', 'sale_order_line_invoice_rel', 'invoice_line_id', 'order_line_id', string='Sale Order Lines', readonly=True, copy=False)
+ layout_category_id = fields.Many2one('sale.layout_category', string='Section')
+ layout_category_sequence = fields.Integer(related='layout_category_id.sequence', string='Layout Sequence', store=True, default=0)
class ProcurementOrder(models.Model):
diff --git a/addons/sale/sale_layout.py b/addons/sale/sale_layout.py
new file mode 100644
index 00000000000..914e1fe9e6c
--- /dev/null
+++ b/addons/sale/sale_layout.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from openerp.osv import osv, fields
+
+
+class SaleLayoutCategory(osv.Model):
+ _name = 'sale.layout_category'
+ _order = 'sequence, id'
+ _columns = {
+ 'name': fields.char('Name', required=True, translate=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
+ }
diff --git a/addons/sale/sale_layout_category_data.xml b/addons/sale/sale_layout_category_data.xml
new file mode 100644
index 00000000000..481dde3609e
--- /dev/null
+++ b/addons/sale/sale_layout_category_data.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ Services
+
+
+
+ 1
+
+
+
+ Material
+
+
+
+ 10
+
+
+
diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml
index 50bca75d162..daf0e5c9e57 100644
--- a/addons/sale/sale_view.xml
+++ b/addons/sale/sale_view.xml
@@ -191,6 +191,7 @@
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom, 'company_id': parent.company_id}"
attrs="{'readonly': ['|', ('qty_invoiced', '>', 0), ('procurement_ids', '!=', [])]}"
/>
+
@@ -243,6 +244,7 @@
attrs="{'readonly': ['|', ('qty_invoiced', '>', 0), ('procurement_ids', '!=', [])]}"
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom, 'company_id': parent.company_id}"
/>
+
+
+
+
+
+
+
+
+
+ account.invoice.line.form
+ account.invoice.line
+
+
+
+
+
+
diff --git a/addons/sale/security/ir.model.access.csv b/addons/sale/security/ir.model.access.csv
index 0fb299294d9..6804a97c81e 100644
--- a/addons/sale/security/ir.model.access.csv
+++ b/addons/sale/security/ir.model.access.csv
@@ -35,3 +35,8 @@ access_product_group_res_partner_sale_manager,res_partner group_sale_manager,bas
access_account_move_line_salesman,account.move.line invoice,account.model_account_move_line,base.group_sale_salesman,1,0,0,0
access_sale_order_invoicing_payments,sale.order,model_sale_order,account.group_account_invoice,1,1,0,0
access_sale_order_line_invoicing_payments,sale.order.line,model_sale_order_line,account.group_account_invoice,1,1,0,0
+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,0
+report_layout_category_4,report_layout_category_4,model_sale_layout_category,base.group_sale_salesman_all_leads,1,1,1,0
+report_layout_category_5,report_layout_category_5,model_sale_layout_category,account.group_account_invoice,1,0,0,0
diff --git a/addons/sale/security/sale_security.xml b/addons/sale/security/sale_security.xml
index 165d9fa2486..a98716383f5 100644
--- a/addons/sale/security/sale_security.xml
+++ b/addons/sale/security/sale_security.xml
@@ -24,6 +24,12 @@
+
+ Personalize sale order and invoice report
+
+
+
+
Addresses in Sales Orders
diff --git a/addons/sale/views/report_invoice_layouted.xml b/addons/sale/views/report_invoice_layouted.xml
new file mode 100644
index 00000000000..50c4b944e54
--- /dev/null
+++ b/addons/sale/views/report_invoice_layouted.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+