mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-27 15:52:01 +02:00
Initial commit: Sale packages
This commit is contained in:
commit
14e3d26998
6469 changed files with 2479670 additions and 0 deletions
5
odoo-bringout-oca-ocb-sale/sale/report/__init__.py
Normal file
5
odoo-bringout-oca-ocb-sale/sale/report/__init__.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import account_invoice_report
|
||||
from . import sale_report
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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(comodel_name='crm.team', string="Sales Team")
|
||||
|
||||
def _select(self):
|
||||
return super()._select() + ", move.team_id as team_id"
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_account_invoice_report_search_inherit" model="ir.ui.view">
|
||||
<field name="name">account.invoice.report.search.inherit</field>
|
||||
<field name="model">account.invoice.report</field>
|
||||
<field name="inherit_id" ref="account.view_account_invoice_report_search"/>
|
||||
<field name="arch" type="xml">
|
||||
<filter name="user" position="after">
|
||||
<filter string="Sales Team" name="sales_channel" context="{'group_by':'team_id'}"/>
|
||||
</filter>
|
||||
<field name="invoice_user_id" position="after">
|
||||
<field name="team_id"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="account_invoice_report_view_tree" model="ir.ui.view">
|
||||
<field name="name">account.invoice.report.view.tree.inherit.sale</field>
|
||||
<field name="model">account.invoice.report</field>
|
||||
<field name="inherit_id" ref="account.account_invoice_report_view_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="invoice_user_id" position="after">
|
||||
<field name="team_id" optional="hide"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_account_invoice_report_salesteam" model="ir.actions.act_window">
|
||||
<field name="name">Invoices Analysis</field>
|
||||
<field name="res_model">account.invoice.report</field>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="domain">[('state', 'not in', ['draft', 'cancel'])]</field>
|
||||
<field name="context">{'search_default_month':1, 'search_default_team_id': [active_id]}</field>
|
||||
<field name="help">From this report, you can have an overview of the amount invoiced to your customer. The search tool can also be used to personalise your Invoices reports and so, match this analysis to your needs.</field>
|
||||
</record>
|
||||
</odoo>
|
||||
27
odoo-bringout-oca-ocb-sale/sale/report/ir_actions_report.xml
Normal file
27
odoo-bringout-oca-ocb-sale/sale/report/ir_actions_report.xml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="action_report_saleorder" model="ir.actions.report">
|
||||
<field name="name">Quotation / Order</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">sale.report_saleorder</field>
|
||||
<field name="report_file">sale.report_saleorder</field>
|
||||
<field name="print_report_name">(object.state in ('draft', 'sent') and 'Quotation - %s' % (object.name)) or 'Order - %s' % (object.name)</field>
|
||||
<field name="binding_model_id" ref="model_sale_order"/>
|
||||
<field name="binding_type">report</field>
|
||||
</record>
|
||||
|
||||
<record id="action_report_pro_forma_invoice" model="ir.actions.report">
|
||||
<field name="name">PRO-FORMA Invoice</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">sale.report_saleorder_pro_forma</field>
|
||||
<field name="report_file">sale.report_saleorder_pro_forma</field>
|
||||
<field name="print_report_name">'PRO-FORMA - %s' % (object.name)</field>
|
||||
<field name="binding_model_id" ref="model_sale_order"/>
|
||||
<field name="binding_type">report</field>
|
||||
<field name="groups_id" eval="[(4, ref('sale.group_proforma_sales'))]"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="report_saleorder_document">
|
||||
<t t-call="web.external_layout">
|
||||
<t t-set="doc" t-value="doc.with_context(lang=doc.partner_id.lang)" />
|
||||
<t t-set="forced_vat" t-value="doc.fiscal_position_id.foreign_vat"/> <!-- So that it appears in the footer of the report instead of the company VAT if it's set -->
|
||||
<t t-set="address">
|
||||
<div t-field="doc.partner_id"
|
||||
t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}' />
|
||||
<p t-if="doc.partner_id.vat"><t t-out="doc.company_id.account_fiscal_country_id.vat_label or 'Tax ID'"/>: <span t-field="doc.partner_id.vat"/></p>
|
||||
</t>
|
||||
<t t-if="doc.partner_shipping_id == doc.partner_invoice_id
|
||||
and doc.partner_invoice_id != doc.partner_id
|
||||
or doc.partner_shipping_id != doc.partner_invoice_id">
|
||||
<t t-set="information_block">
|
||||
<strong>
|
||||
<t t-if="doc.partner_shipping_id == doc.partner_invoice_id">
|
||||
Invoicing and Shipping Address:
|
||||
</t>
|
||||
<t t-else="">
|
||||
Invoicing Address:
|
||||
</t>
|
||||
</strong>
|
||||
<div t-field="doc.partner_invoice_id"
|
||||
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True, "phone_icons": True}'/>
|
||||
<t t-if="doc.partner_shipping_id != doc.partner_invoice_id">
|
||||
<strong>Shipping Address:</strong>
|
||||
<div t-field="doc.partner_shipping_id"
|
||||
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True, "phone_icons": True}'/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
<div class="page">
|
||||
<div class="oe_structure"/>
|
||||
|
||||
<h2 class="mt-4">
|
||||
<span t-if="env.context.get('proforma', False) or is_pro_forma">Pro-Forma Invoice # </span>
|
||||
<span t-elif="doc.state in ['draft','sent']">Quotation # </span>
|
||||
<span t-else="">Order # </span>
|
||||
<span t-field="doc.name"/>
|
||||
</h2>
|
||||
|
||||
<div class="row mt-4 mb-4" id="informations">
|
||||
<div t-if="doc.client_order_ref" class="col-auto col-3 mw-100 mb-2" name="informations_reference">
|
||||
<strong>Your Reference:</strong>
|
||||
<p class="m-0" t-field="doc.client_order_ref"/>
|
||||
</div>
|
||||
<div t-if="doc.date_order" class="col-auto col-3 mw-100 mb-2" name="informations_date">
|
||||
<strong t-if="doc.state in ['draft', 'sent']">Quotation Date:</strong>
|
||||
<strong t-else="">Order Date:</strong>
|
||||
<p class="m-0" t-field="doc.date_order" t-options='{"widget": "date"}'/>
|
||||
</div>
|
||||
<div t-if="doc.validity_date and doc.state in ['draft', 'sent']"
|
||||
class="col-auto col-3 mw-100 mb-2"
|
||||
name="expiration_date">
|
||||
<strong>Expiration:</strong>
|
||||
<p class="m-0" t-field="doc.validity_date"/>
|
||||
</div>
|
||||
<div t-if="doc.user_id.name" class="col-auto col-3 mw-100 mb-2">
|
||||
<strong>Salesperson:</strong>
|
||||
<p class="m-0" t-field="doc.user_id"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Is there a discount on at least one line? -->
|
||||
<t t-set="lines_to_report" t-value="doc._get_order_lines_to_report()"/>
|
||||
<t t-set="display_discount" t-value="any(l.discount for l in lines_to_report)"/>
|
||||
|
||||
<table class="table table-sm o_main_table table-borderless mt-4">
|
||||
<!-- In case we want to repeat the header, remove "display: table-row-group" -->
|
||||
<thead style="display: table-row-group">
|
||||
<tr>
|
||||
<th name="th_description" class="text-start">Description</th>
|
||||
<th name="th_quantity" class="text-end">Quantity</th>
|
||||
<th name="th_priceunit" class="text-end">Unit Price</th>
|
||||
<th name="th_discount" t-if="display_discount" class="text-end">
|
||||
<span>Disc.%</span>
|
||||
</th>
|
||||
<th name="th_taxes" class="text-end">Taxes</th>
|
||||
<th name="th_subtotal" class="text-end">
|
||||
<span groups="account.group_show_line_subtotals_tax_excluded">Amount</span>
|
||||
<span groups="account.group_show_line_subtotals_tax_included">Total Price</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="sale_tbody">
|
||||
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
|
||||
<t t-foreach="lines_to_report" t-as="line">
|
||||
|
||||
<t t-set="current_subtotal" t-value="current_subtotal + line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<t t-set="current_subtotal" t-value="current_subtotal + line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
|
||||
<tr t-att-class="'bg-200 fw-bold o_line_section' if line.display_type == 'line_section' else 'fst-italic o_line_note' if line.display_type == 'line_note' else ''">
|
||||
<t t-if="not line.display_type">
|
||||
<td name="td_name"><span t-field="line.name"/></td>
|
||||
<td name="td_quantity" class="text-end">
|
||||
<span t-field="line.product_uom_qty"/>
|
||||
<span t-field="line.product_uom"/>
|
||||
</td>
|
||||
<td name="td_priceunit" class="text-end">
|
||||
<span t-field="line.price_unit"/>
|
||||
</td>
|
||||
<td t-if="display_discount" class="text-end">
|
||||
<span t-field="line.discount"/>
|
||||
</td>
|
||||
<t t-set="taxes" t-value="', '.join([(tax.description or tax.name) for tax in line.tax_id])"/>
|
||||
<td name="td_taxes" t-attf-class="text-end {{ 'text-nowrap' if len(taxes) < 10 else '' }}">
|
||||
<span t-out="taxes">Tax 15%</span>
|
||||
</td>
|
||||
<td t-if="not line.is_downpayment" name="td_subtotal" class="text-end o_price_total">
|
||||
<span t-field="line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<span t-field="line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
</td>
|
||||
</t>
|
||||
<t t-elif="line.display_type == 'line_section'">
|
||||
<td name="td_section_line" colspan="99">
|
||||
<span t-field="line.name"/>
|
||||
</td>
|
||||
<t t-set="current_section" t-value="line"/>
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
</t>
|
||||
<t t-elif="line.display_type == 'line_note'">
|
||||
<td name="td_note_line" colspan="99">
|
||||
<span t-field="line.name"/>
|
||||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
|
||||
<t t-if="current_section and (line_last or lines_to_report[line_index+1].display_type == 'line_section') and not line.is_downpayment">
|
||||
<tr class="is-subtotal text-end">
|
||||
<td name="td_section_subtotal" colspan="99">
|
||||
<strong class="mr16">Subtotal</strong>
|
||||
<span
|
||||
t-out="current_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="clearfix" name="so_total_summary">
|
||||
<div id="total" class="row" name="total">
|
||||
<div t-attf-class="#{'col-6' if report_type != 'html' else 'col-sm-7 col-md-6'} ms-auto">
|
||||
<table class="table table-sm table-borderless">
|
||||
<!-- Tax totals -->
|
||||
<t t-set="tax_totals" t-value="doc.tax_totals"/>
|
||||
<t t-call="account.document_tax_totals"/>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-if="doc.signature" class="mt-4 ml64 mr4" name="signature">
|
||||
<div class="offset-8">
|
||||
<strong>Signature</strong>
|
||||
</div>
|
||||
<div class="offset-8">
|
||||
<img t-att-src="image_data_uri(doc.signature)" style="max-height: 4cm; max-width: 8cm;"/>
|
||||
</div>
|
||||
<div class="offset-8 text-center">
|
||||
<p t-field="doc.signed_by"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p t-field="doc.note" name="order_note"/>
|
||||
<p t-if="not is_html_empty(doc.payment_term_id.note)">
|
||||
<span t-field="doc.payment_term_id.note"/>
|
||||
</p>
|
||||
<p t-if="doc.fiscal_position_id and not is_html_empty(doc.fiscal_position_id.sudo().note)"
|
||||
id="fiscal_position_remark">
|
||||
<strong>Fiscal Position Remark:</strong>
|
||||
<span t-field="doc.fiscal_position_id.sudo().note"/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
|
||||
<template id="report_saleorder">
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="docs" t-as="doc">
|
||||
<t t-call="sale.report_saleorder_document" t-lang="doc.partner_id.lang"/>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="report_saleorder_pro_forma">
|
||||
<t t-call="web.html_container">
|
||||
<t t-set="is_pro_forma" t-value="True"/>
|
||||
<t t-set="docs" t-value="docs.with_context(proforma=True)"/>
|
||||
<t t-foreach="docs" t-as="doc">
|
||||
<t t-call="sale.report_saleorder_document" t-lang="doc.partner_id.lang"/>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
201
odoo-bringout-oca-ocb-sale/sale/report/sale_report.py
Normal file
201
odoo-bringout-oca-ocb-sale/sale/report/sale_report.py
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
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']
|
||||
|
||||
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_to_deliver = fields.Float('Qty To Deliver', 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, group_operator="avg")
|
||||
discount_amount = fields.Float('Discount Amount', readonly=True)
|
||||
campaign_id = fields.Many2one('utm.campaign', 'Campaign', readonly=True)
|
||||
medium_id = fields.Many2one('utm.medium', 'Medium', readonly=True)
|
||||
source_id = fields.Many2one('utm.source', 'Source', readonly=True)
|
||||
|
||||
order_id = fields.Many2one('sale.order', 'Order #', readonly=True)
|
||||
|
||||
def _with_sale(self):
|
||||
return ""
|
||||
|
||||
def _select_sale(self):
|
||||
select_ = f"""
|
||||
MIN(l.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.product_uom_qty - l.qty_delivered) / u.factor * u2.factor) ELSE 0 END AS qty_to_deliver,
|
||||
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
|
||||
/ {self._case_value_or_one('s.currency_rate')}
|
||||
* {self._case_value_or_one('currency_table.rate')}
|
||||
) ELSE 0
|
||||
END AS price_total,
|
||||
CASE WHEN l.product_id IS NOT NULL THEN SUM(l.price_subtotal
|
||||
/ {self._case_value_or_one('s.currency_rate')}
|
||||
* {self._case_value_or_one('currency_table.rate')}
|
||||
) ELSE 0
|
||||
END AS price_subtotal,
|
||||
CASE WHEN l.product_id IS NOT NULL THEN SUM(l.untaxed_amount_to_invoice
|
||||
/ {self._case_value_or_one('s.currency_rate')}
|
||||
* {self._case_value_or_one('currency_table.rate')}
|
||||
) ELSE 0
|
||||
END AS untaxed_amount_to_invoice,
|
||||
CASE WHEN l.product_id IS NOT NULL THEN SUM(l.untaxed_amount_invoiced
|
||||
/ {self._case_value_or_one('s.currency_rate')}
|
||||
* {self._case_value_or_one('currency_table.rate')}
|
||||
) 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,
|
||||
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
|
||||
/ {self._case_value_or_one('s.currency_rate')}
|
||||
* {self._case_value_or_one('currency_table.rate')}
|
||||
) ELSE 0
|
||||
END AS discount_amount,
|
||||
s.id AS order_id"""
|
||||
|
||||
additional_fields_info = self._select_additional_fields()
|
||||
template = """,
|
||||
%s AS %s"""
|
||||
for fname, query_info in additional_fields_info.items():
|
||||
select_ += template % (query_info, fname)
|
||||
|
||||
return select_
|
||||
|
||||
def _case_value_or_one(self, value):
|
||||
return f"""CASE COALESCE({value}, 0) WHEN 0 THEN 1.0 ELSE {value} END"""
|
||||
|
||||
def _select_additional_fields(self):
|
||||
"""Hook to return additional fields SQL specification for select part of the table query.
|
||||
|
||||
:returns: mapping field -> SQL computation of field, will be converted to '_ AS _field' in the final table definition
|
||||
:rtype: dict
|
||||
"""
|
||||
return {}
|
||||
|
||||
def _from_sale(self):
|
||||
return """
|
||||
sale_order_line l
|
||||
LEFT 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
|
||||
JOIN {currency_table} ON currency_table.company_id = s.company_id
|
||||
""".format(
|
||||
currency_table=self.env['res.currency']._get_query_currency_table(
|
||||
{
|
||||
'multi_company': True,
|
||||
'date': {'date_to': fields.Date.today()}
|
||||
}),
|
||||
)
|
||||
|
||||
def _where_sale(self):
|
||||
return """
|
||||
l.display_type IS NULL"""
|
||||
|
||||
def _group_by_sale(self):
|
||||
return """
|
||||
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,
|
||||
currency_table.rate"""
|
||||
|
||||
def _query(self):
|
||||
with_ = self._with_sale()
|
||||
return f"""
|
||||
{"WITH" + with_ + "(" if with_ else ""}
|
||||
SELECT {self._select_sale()}
|
||||
FROM {self._from_sale()}
|
||||
WHERE {self._where_sale()}
|
||||
GROUP BY {self._group_by_sale()}
|
||||
{")" if with_ else ""}
|
||||
"""
|
||||
|
||||
@property
|
||||
def _table_query(self):
|
||||
return self._query()
|
||||
126
odoo-bringout-oca-ocb-sale/sale/report/sale_report_views.xml
Normal file
126
odoo-bringout-oca-ocb-sale/sale/report/sale_report_views.xml
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_order_product_pivot" model="ir.ui.view">
|
||||
<field name="name">sale.report.pivot</field>
|
||||
<field name="model">sale.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Sales Analysis" sample="1">
|
||||
<field name="team_id" type="col"/>
|
||||
<field name="date" interval="month" type="row"/>
|
||||
<field name="price_subtotal" type="measure"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_order_product_graph" model="ir.ui.view">
|
||||
<field name="name">sale.report.graph</field>
|
||||
<field name="model">sale.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Sales Analysis" type="line" sample="1">
|
||||
<field name="date" interval="day"/>
|
||||
<field name="price_subtotal" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sale_report_view_tree" model="ir.ui.view">
|
||||
<field name="name">sale.report.view.tree</field>
|
||||
<field name="model">sale.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Sales Analysis">
|
||||
<field name="date" widget="date"/>
|
||||
<field name="order_id" optional="show"/>
|
||||
<field name="partner_id" optional="hide"/>
|
||||
<field name="user_id" optional="show" widget="many2one_avatar_user"/>
|
||||
<field name="team_id" optional="show"/>
|
||||
<field name="company_id" optional="show" groups="base.group_multi_company"/>
|
||||
<field name="price_subtotal" optional="hide" sum="Sum of Untaxed Total"/>
|
||||
<field name="price_total" optional="show" sum="Sum of Total"/>
|
||||
<field name="state" optional="hide"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_order_product_search" model="ir.ui.view">
|
||||
<field name="name">sale.report.search</field>
|
||||
<field name="model">sale.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Sales Analysis">
|
||||
<field name="date"/>
|
||||
<filter string="Date" name="year" invisible="1" date="date" default_period="this_year"/>
|
||||
<filter string="Quotations" name="Quotations" domain="[('state','in', ('draft', 'sent'))]"/>
|
||||
<filter string="Sales Orders" name="Sales" domain="[('state','not in',('draft', 'cancel', 'sent'))]"/>
|
||||
<separator/>
|
||||
<filter name="filter_date" date="date" default_period="this_month"/>
|
||||
<filter name="filter_order_date" invisible="1" string="Order Date: Last 365 Days" domain="[('date', '>=', (datetime.datetime.combine(context_today() + relativedelta(days=-365), datetime.time(0,0,0))).strftime('%Y-%m-%d %H:%M:%S'))]"/>
|
||||
<separator/>
|
||||
<field name="user_id"/>
|
||||
<field name="team_id"/>
|
||||
<field name="product_id"/>
|
||||
<field name="categ_id"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="country_id"/>
|
||||
<field name="industry_id"/>
|
||||
<group expand="0" string="Extended Filters">
|
||||
<field name="categ_id" filter_domain="[('categ_id', 'child_of', self)]"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
<group expand="1" string="Group By">
|
||||
<filter string="Salesperson" name="User" context="{'group_by':'user_id'}"/>
|
||||
<filter string="Sales Team" name="sales_channel" context="{'group_by':'team_id'}"/>
|
||||
<filter string="Customer" name="Customer" context="{'group_by':'partner_id'}"/>
|
||||
<filter string="Customer Country" name="country_id" context="{'group_by':'country_id'}"/>
|
||||
<filter string="Customer Industry" name="industry_id" context="{'group_by':'industry_id'}"/>
|
||||
<filter string="Product" name="Product" context="{'group_by':'product_id'}"/>
|
||||
<filter string="Product Category" name="Category" context="{'group_by':'categ_id'}"/>
|
||||
<filter string="Status" name="status" context="{'group_by':'state'}"/>
|
||||
<filter string="Company" name="company" groups="base.group_multi_company" context="{'group_by':'company_id'}"/>
|
||||
<separator/>
|
||||
<filter string="Order Date" name="date" context="{'group_by':'date'}"
|
||||
invisible="context.get('sale_report_view_hide_date')"/>
|
||||
<filter string="Order Date" name="group_by_date_day" context="{'group_by':'date:day'}"
|
||||
invisible="not context.get('sale_report_view_hide_date')"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_order_report_all" model="ir.actions.act_window">
|
||||
<field name="name">Sales Analysis</field>
|
||||
<field name="res_model">sale.report</field>
|
||||
<field name="view_mode">graph,pivot,tree</field>
|
||||
<field name="view_id"></field> <!-- force empty -->
|
||||
<field name="search_view_id" ref="view_order_product_search"/>
|
||||
<field name="domain">[('state', '!=', 'cancel')]</field>
|
||||
<field name="context">{'search_default_Sales':1, 'group_by_no_leaf':1,'group_by':[], 'search_default_filter_order_date': 1}</field>
|
||||
<field name="help">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.</field>
|
||||
</record>
|
||||
|
||||
<record id="report_all_channels_sales_action" model="ir.actions.act_window">
|
||||
<field name="name">Sales Analysis</field>
|
||||
<field name="res_model">sale.report</field>
|
||||
<field name="view_mode">pivot,tree,graph</field>
|
||||
</record>
|
||||
|
||||
<record id="action_order_report_quotation_salesteam" model="ir.actions.act_window">
|
||||
<field name="name">Quotations Analysis</field>
|
||||
<field name="res_model">sale.report</field>
|
||||
<field name="view_mode">graph,tree</field>
|
||||
<field name="domain">[('state','=','draft'),('team_id', '=', active_id)]</field>
|
||||
<field name="context">{'search_default_order_month':1}</field>
|
||||
<field name="help">This report performs analysis on your quotations. 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.</field>
|
||||
</record>
|
||||
|
||||
<record id="action_order_report_so_salesteam" model="ir.actions.act_window">
|
||||
<field name="name">Sales Analysis</field>
|
||||
<field name="res_model">sale.report</field>
|
||||
<field name="view_mode">graph,tree</field>
|
||||
<field name="domain">[('state','not in',('draft','cancel'))]</field>
|
||||
<field name="context">{
|
||||
'search_default_Sales': 1,
|
||||
'search_default_filter_date': 1,
|
||||
'search_default_team_id': [active_id]}</field>
|
||||
<field name="help">This report performs analysis on your 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.</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Loading…
Add table
Add a link
Reference in a new issue