From 3751379f1e9a4c215fb6eb898b4ccc67659b9ace Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Tue, 10 May 2022 21:51:50 +0700 Subject: initial commit 2 --- addons/sale/report/__init__.py | 6 + addons/sale/report/invoice_report.py | 13 ++ addons/sale/report/invoice_report_templates.xml | 21 ++ addons/sale/report/report_all_channels_sales.py | 87 ++++++++ .../report/report_all_channels_sales_views.xml | 8 + addons/sale/report/sale_report.py | 161 +++++++++++++++ addons/sale/report/sale_report.xml | 27 +++ addons/sale/report/sale_report_templates.xml | 230 +++++++++++++++++++++ addons/sale/report/sale_report_views.xml | 76 +++++++ 9 files changed, 629 insertions(+) create mode 100644 addons/sale/report/__init__.py create mode 100644 addons/sale/report/invoice_report.py create mode 100644 addons/sale/report/invoice_report_templates.xml create mode 100644 addons/sale/report/report_all_channels_sales.py create mode 100644 addons/sale/report/report_all_channels_sales_views.xml create mode 100644 addons/sale/report/sale_report.py create mode 100644 addons/sale/report/sale_report.xml create mode 100644 addons/sale/report/sale_report_templates.xml create mode 100644 addons/sale/report/sale_report_views.xml (limited to 'addons/sale/report') diff --git a/addons/sale/report/__init__.py b/addons/sale/report/__init__.py new file mode 100644 index 00000000..90248f47 --- /dev/null +++ b/addons/sale/report/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import sale_report +from . import invoice_report +from . import report_all_channels_sales diff --git a/addons/sale/report/invoice_report.py b/addons/sale/report/invoice_report.py new file mode 100644 index 00000000..f41cc8c3 --- /dev/null +++ b/addons/sale/report/invoice_report.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class AccountInvoiceReport(models.Model): + _inherit = 'account.invoice.report' + + team_id = fields.Many2one('crm.team', string='Sales Team') + + def _select(self): + return super(AccountInvoiceReport, self)._select() + ", move.team_id as team_id" diff --git a/addons/sale/report/invoice_report_templates.xml b/addons/sale/report/invoice_report_templates.xml new file mode 100644 index 00000000..06bcd289 --- /dev/null +++ b/addons/sale/report/invoice_report_templates.xml @@ -0,0 +1,21 @@ + + + + diff --git a/addons/sale/report/report_all_channels_sales.py b/addons/sale/report/report_all_channels_sales.py new file mode 100644 index 00000000..1aa6e960 --- /dev/null +++ b/addons/sale/report/report_all_channels_sales.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models, tools + + +class PosSaleReport(models.Model): + _name = "report.all.channels.sales" + _description = "Sales by Channel (All in One)" + _auto = False + + name = fields.Char('Order Reference', readonly=True) + partner_id = fields.Many2one('res.partner', 'Partner', readonly=True) + product_id = fields.Many2one('product.product', string='Product', readonly=True) + product_tmpl_id = fields.Many2one('product.template', 'Product Template', readonly=True) + date_order = fields.Datetime(string='Date Order', readonly=True) + user_id = fields.Many2one('res.users', 'Salesperson', readonly=True) + categ_id = fields.Many2one('product.category', 'Product Category', readonly=True) + company_id = fields.Many2one('res.company', 'Company', readonly=True) + price_total = fields.Float('Total', readonly=True) + pricelist_id = fields.Many2one('product.pricelist', 'Pricelist', readonly=True) + country_id = fields.Many2one('res.country', 'Partner Country', readonly=True) + price_subtotal = fields.Float(string='Price Subtotal', readonly=True) + product_qty = fields.Float('Product Quantity', readonly=True) + analytic_account_id = fields.Many2one('account.analytic.account', 'Analytic Account', readonly=True) + team_id = fields.Many2one('crm.team', 'Sales Team', readonly=True) + + def _so(self): + so_str = """ + SELECT sol.id AS id, + so.name AS name, + so.partner_id AS partner_id, + sol.product_id AS product_id, + pro.product_tmpl_id AS product_tmpl_id, + so.date_order AS date_order, + so.user_id AS user_id, + pt.categ_id AS categ_id, + so.company_id AS company_id, + sol.price_total / CASE COALESCE(so.currency_rate, 0) WHEN 0 THEN 1.0 ELSE so.currency_rate END AS price_total, + so.pricelist_id AS pricelist_id, + rp.country_id AS country_id, + sol.price_subtotal / CASE COALESCE(so.currency_rate, 0) WHEN 0 THEN 1.0 ELSE so.currency_rate END AS price_subtotal, + (sol.product_uom_qty / u.factor * u2.factor) as product_qty, + so.analytic_account_id AS analytic_account_id, + so.team_id AS team_id + + FROM sale_order_line sol + JOIN sale_order so ON (sol.order_id = so.id) + LEFT JOIN product_product pro ON (sol.product_id = pro.id) + JOIN res_partner rp ON (so.partner_id = rp.id) + LEFT JOIN product_template pt ON (pro.product_tmpl_id = pt.id) + LEFT JOIN product_pricelist pp ON (so.pricelist_id = pp.id) + LEFT JOIN uom_uom u on (u.id=sol.product_uom) + LEFT JOIN uom_uom u2 on (u2.id=pt.uom_id) + WHERE so.state in ('sale','done') + """ + return so_str + + def _from(self): + return """(%s)""" % (self._so()) + + def get_main_request(self): + request = """ + CREATE or REPLACE VIEW %s AS + SELECT id AS id, + name, + partner_id, + product_id, + product_tmpl_id, + date_order, + user_id, + categ_id, + company_id, + price_total, + pricelist_id, + analytic_account_id, + country_id, + team_id, + price_subtotal, + product_qty + FROM %s + AS foo""" % (self._table, self._from()) + return request + + def init(self): + tools.drop_view_if_exists(self.env.cr, self._table) + self.env.cr.execute(self.get_main_request()) diff --git a/addons/sale/report/report_all_channels_sales_views.xml b/addons/sale/report/report_all_channels_sales_views.xml new file mode 100644 index 00000000..e6f3cc06 --- /dev/null +++ b/addons/sale/report/report_all_channels_sales_views.xml @@ -0,0 +1,8 @@ + + + + Sales Analysis + sale.report + pivot + + diff --git a/addons/sale/report/sale_report.py b/addons/sale/report/sale_report.py new file mode 100644 index 00000000..76ec1476 --- /dev/null +++ b/addons/sale/report/sale_report.py @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import tools +from odoo import api, fields, models + + +class SaleReport(models.Model): + _name = "sale.report" + _description = "Sales Analysis Report" + _auto = False + _rec_name = 'date' + _order = 'date desc' + + @api.model + def _get_done_states(self): + return ['sale', 'done', 'paid'] + + name = fields.Char('Order Reference', readonly=True) + date = fields.Datetime('Order Date', readonly=True) + product_id = fields.Many2one('product.product', 'Product Variant', readonly=True) + product_uom = fields.Many2one('uom.uom', 'Unit of Measure', readonly=True) + product_uom_qty = fields.Float('Qty Ordered', readonly=True) + qty_delivered = fields.Float('Qty Delivered', readonly=True) + qty_to_invoice = fields.Float('Qty To Invoice', readonly=True) + qty_invoiced = fields.Float('Qty Invoiced', readonly=True) + partner_id = fields.Many2one('res.partner', 'Customer', readonly=True) + company_id = fields.Many2one('res.company', 'Company', readonly=True) + user_id = fields.Many2one('res.users', 'Salesperson', readonly=True) + price_total = fields.Float('Total', readonly=True) + price_subtotal = fields.Float('Untaxed Total', readonly=True) + untaxed_amount_to_invoice = fields.Float('Untaxed Amount To Invoice', readonly=True) + untaxed_amount_invoiced = fields.Float('Untaxed Amount Invoiced', readonly=True) + product_tmpl_id = fields.Many2one('product.template', 'Product', readonly=True) + categ_id = fields.Many2one('product.category', 'Product Category', readonly=True) + nbr = fields.Integer('# of Lines', readonly=True) + pricelist_id = fields.Many2one('product.pricelist', 'Pricelist', readonly=True) + analytic_account_id = fields.Many2one('account.analytic.account', 'Analytic Account', readonly=True) + team_id = fields.Many2one('crm.team', 'Sales Team', readonly=True) + country_id = fields.Many2one('res.country', 'Customer Country', readonly=True) + industry_id = fields.Many2one('res.partner.industry', 'Customer Industry', readonly=True) + commercial_partner_id = fields.Many2one('res.partner', 'Customer Entity', readonly=True) + state = fields.Selection([ + ('draft', 'Draft Quotation'), + ('sent', 'Quotation Sent'), + ('sale', 'Sales Order'), + ('done', 'Sales Done'), + ('cancel', 'Cancelled'), + ], string='Status', readonly=True) + weight = fields.Float('Gross Weight', readonly=True) + volume = fields.Float('Volume', readonly=True) + + discount = fields.Float('Discount %', readonly=True) + discount_amount = fields.Float('Discount Amount', readonly=True) + campaign_id = fields.Many2one('utm.campaign', 'Campaign') + medium_id = fields.Many2one('utm.medium', 'Medium') + source_id = fields.Many2one('utm.source', 'Source') + + order_id = fields.Many2one('sale.order', 'Order #', readonly=True) + + def _query(self, with_clause='', fields={}, groupby='', from_clause=''): + with_ = ("WITH %s" % with_clause) if with_clause else "" + + select_ = """ + coalesce(min(l.id), -s.id) as id, + l.product_id as product_id, + t.uom_id as product_uom, + CASE WHEN l.product_id IS NOT NULL THEN sum(l.product_uom_qty / u.factor * u2.factor) ELSE 0 END as product_uom_qty, + CASE WHEN l.product_id IS NOT NULL THEN sum(l.qty_delivered / u.factor * u2.factor) ELSE 0 END as qty_delivered, + CASE WHEN l.product_id IS NOT NULL THEN sum(l.qty_invoiced / u.factor * u2.factor) ELSE 0 END as qty_invoiced, + CASE WHEN l.product_id IS NOT NULL THEN sum(l.qty_to_invoice / u.factor * u2.factor) ELSE 0 END as qty_to_invoice, + CASE WHEN l.product_id IS NOT NULL THEN sum(l.price_total / CASE COALESCE(s.currency_rate, 0) WHEN 0 THEN 1.0 ELSE s.currency_rate END) ELSE 0 END as price_total, + CASE WHEN l.product_id IS NOT NULL THEN sum(l.price_subtotal / CASE COALESCE(s.currency_rate, 0) WHEN 0 THEN 1.0 ELSE s.currency_rate END) ELSE 0 END as price_subtotal, + CASE WHEN l.product_id IS NOT NULL THEN sum(l.untaxed_amount_to_invoice / CASE COALESCE(s.currency_rate, 0) WHEN 0 THEN 1.0 ELSE s.currency_rate END) ELSE 0 END as untaxed_amount_to_invoice, + CASE WHEN l.product_id IS NOT NULL THEN sum(l.untaxed_amount_invoiced / CASE COALESCE(s.currency_rate, 0) WHEN 0 THEN 1.0 ELSE s.currency_rate END) ELSE 0 END as untaxed_amount_invoiced, + count(*) as nbr, + s.name as name, + s.date_order as date, + s.state as state, + s.partner_id as partner_id, + s.user_id as user_id, + s.company_id as company_id, + s.campaign_id as campaign_id, + s.medium_id as medium_id, + s.source_id as source_id, + extract(epoch from avg(date_trunc('day',s.date_order)-date_trunc('day',s.create_date)))/(24*60*60)::decimal(16,2) as delay, + t.categ_id as categ_id, + s.pricelist_id as pricelist_id, + s.analytic_account_id as analytic_account_id, + s.team_id as team_id, + p.product_tmpl_id, + partner.country_id as country_id, + partner.industry_id as industry_id, + partner.commercial_partner_id as commercial_partner_id, + CASE WHEN l.product_id IS NOT NULL THEN sum(p.weight * l.product_uom_qty / u.factor * u2.factor) ELSE 0 END as weight, + CASE WHEN l.product_id IS NOT NULL THEN sum(p.volume * l.product_uom_qty / u.factor * u2.factor) ELSE 0 END as volume, + l.discount as discount, + CASE WHEN l.product_id IS NOT NULL THEN sum((l.price_unit * l.product_uom_qty * l.discount / 100.0 / CASE COALESCE(s.currency_rate, 0) WHEN 0 THEN 1.0 ELSE s.currency_rate END))ELSE 0 END as discount_amount, + s.id as order_id + """ + + for field in fields.values(): + select_ += field + + from_ = """ + sale_order_line l + right outer join sale_order s on (s.id=l.order_id) + join res_partner partner on s.partner_id = partner.id + left join product_product p on (l.product_id=p.id) + left join product_template t on (p.product_tmpl_id=t.id) + left join uom_uom u on (u.id=l.product_uom) + left join uom_uom u2 on (u2.id=t.uom_id) + left join product_pricelist pp on (s.pricelist_id = pp.id) + %s + """ % from_clause + + groupby_ = """ + l.product_id, + l.order_id, + t.uom_id, + t.categ_id, + s.name, + s.date_order, + s.partner_id, + s.user_id, + s.state, + s.company_id, + s.campaign_id, + s.medium_id, + s.source_id, + s.pricelist_id, + s.analytic_account_id, + s.team_id, + p.product_tmpl_id, + partner.country_id, + partner.industry_id, + partner.commercial_partner_id, + l.discount, + s.id %s + """ % (groupby) + + return '%s (SELECT %s FROM %s GROUP BY %s)' % (with_, select_, from_, groupby_) + + def init(self): + # self._table = sale_report + tools.drop_view_if_exists(self.env.cr, self._table) + self.env.cr.execute("""CREATE or REPLACE VIEW %s as (%s)""" % (self._table, self._query())) + +class SaleOrderReportProforma(models.AbstractModel): + _name = 'report.sale.report_saleproforma' + _description = 'Proforma Report' + + @api.model + def _get_report_values(self, docids, data=None): + docs = self.env['sale.order'].browse(docids) + return { + 'doc_ids': docs.ids, + 'doc_model': 'sale.order', + 'docs': docs, + 'proforma': True + } diff --git a/addons/sale/report/sale_report.xml b/addons/sale/report/sale_report.xml new file mode 100644 index 00000000..e3160d14 --- /dev/null +++ b/addons/sale/report/sale_report.xml @@ -0,0 +1,27 @@ + + + + + Quotation / Order + sale.order + qweb-pdf + sale.report_saleorder + sale.report_saleorder + (object.state in ('draft', 'sent') and 'Quotation - %s' % (object.name)) or 'Order - %s' % (object.name) + + report + + + + PRO-FORMA Invoice + sale.order + qweb-pdf + sale.report_saleorder_pro_forma + sale.report_saleorder_pro_forma + 'PRO-FORMA - %s' % (object.name) + + report + + + + diff --git a/addons/sale/report/sale_report_templates.xml b/addons/sale/report/sale_report_templates.xml new file mode 100644 index 00000000..861c257b --- /dev/null +++ b/addons/sale/report/sale_report_templates.xml @@ -0,0 +1,230 @@ + + + + + + + + + + diff --git a/addons/sale/report/sale_report_views.xml b/addons/sale/report/sale_report_views.xml new file mode 100644 index 00000000..32fa2d9e --- /dev/null +++ b/addons/sale/report/sale_report_views.xml @@ -0,0 +1,76 @@ + + + + + sale.report.pivot + sale.report + + + + + + + + + + + sale.report.graph + sale.report + + + + + + + + + + sale.report.search + sale.report + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sales Analysis + sale.report + graph,pivot + + + {'search_default_Sales':1, 'group_by_no_leaf':1,'group_by':[]} + This report performs analysis on your quotations and sales orders. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application. + + -- cgit v1.2.3