Files
enterprise-addons/sale_commission/report/achievement_report.py
T

710 lines
30 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import date, datetime
from odoo import models, api, fields
from odoo.fields import Domain
from odoo.addons.resource.models.utils import filter_domain_leaf
class SaleCommissionAchievementReport(models.Model):
_name = 'sale.commission.achievement.report'
_description = "Sales Achievement Report"
_order = 'id'
_auto = False
target_id = fields.Many2one('sale.commission.plan.target', "Period", readonly=True)
plan_id = fields.Many2one('sale.commission.plan', "Commission Plan", readonly=True)
user_id = fields.Many2one('res.users', "Sales Person", readonly=True)
team_id = fields.Many2one('crm.team', "Sales Team", readonly=True)
achieved = fields.Monetary("Achieved", readonly=True, currency_field='currency_id')
target_amount = fields.Monetary(readonly=True, currency_field='currency_id')
commission_target_amount = fields.Monetary(readonly=True, currency_field='currency_id', aggregator='avg',
help="Sum of target amount per plan paid on the same date")
target_rate = fields.Float("Achieved Rate", readonly=True, aggregator='avg',
help="Achieved over the target of that period, meaningless in group by")
commission_rate = fields.Float("Commission Rate", readonly=True, aggregator='sum',
help="Achieved over the commission target amount")
currency_id = fields.Many2one('res.currency', "Currency", readonly=True)
company_id = fields.Many2one('res.company', string='Company', readonly=True)
date = fields.Date(string="Date", readonly=True)
partner_id = fields.Many2one('res.partner', "Customer", readonly=True)
related_res_model = fields.Char(readonly=True)
related_res_id = fields.Many2oneReference("Related", model_field='related_res_model', readonly=True)
################################################################################
# Readonly Cursor hacks
# These methods use a readonly cursor everywhere else in odoo but here we need a RW cursor because
# we are creating a view (necessary even if the view is not materialized).
@api.model
def web_search_read(self, domain, specification, offset=0, limit=None, order=None, count_limit=None):
return super().web_search_read(domain, specification, offset=offset, limit=limit, order=order, count_limit=count_limit)
# Make sure the method is never readonly in this model
web_search_read._readonly = False
@api.model
def formatted_read_group(self, domain, groupby=(), aggregates=(), having=(), offset=0, limit=None, order=None) -> list[dict]:
return super().formatted_read_group(domain, groupby, aggregates, having, offset, limit, order)
formatted_read_group._readonly = False
@api.model
def formatted_read_grouping_sets(self, domain, grouping_sets, aggregates=(), *, order=None):
# In the pivot view, we don't want the hierarchical naming of department_id (hr.department)
return super().formatted_read_grouping_sets(
domain, grouping_sets, aggregates, order=order,
)
formatted_read_grouping_sets._readonly = False
################################################################################
@api.model
def _search(self, domain, *args, **kwargs):
""" Extract the currency conversion date form the date_to field.
It is used to be able to get fixed results not depending on the currency daily rates.
The date is converted to a string to allow updating the date value in view customizations.
"""
# take date_to but not plan_id.date_to
model = self
domain = Domain(domain)
date_to_domain = filter_domain_leaf(domain, lambda field: 'date_to' in field and not 'plan_id' in field)
date_to_domain = date_to_domain.optimize_full(model)
date_to_list = [cond.value for cond in date_to_domain.iter_conditions() if isinstance(cond.value, date)]
if date_to_list and not 'conversion_date' in self.env.context:
conversion_date = max(date_to_list)
model = model.with_context(conversion_date=conversion_date.strftime('%Y-%m-%d'))
return super(SaleCommissionAchievementReport, model)._search(domain, *args, **kwargs)
def open_related(self):
return {
'view_mode': 'form',
'res_model': self.related_res_model,
'res_id': self.related_res_id,
'type': 'ir.actions.act_window',
}
@api.model
def _get_currency_rate(self):
companies = self.env['res.company'].search([], order='id asc')
current_company = self.env.company
conversion_date = fields.Date.today()
if self.env.context.get('conversion_date'):
conversion_date = datetime.strptime(self.env.context['conversion_date'], '%Y-%m-%d')
currency_rate = [(current_company.id, current_company.currency_id.id, conversion_date.strftime('%Y-%m-%d'), 1)]
for comp in companies - current_company:
rate = comp.currency_id._convert(from_amount=1, to_currency=current_company.currency_id, company=current_company, date=conversion_date, round=False)
currency_rate.append((comp.id, comp.currency_id.id, conversion_date.strftime('%Y-%m-%d'), rate))
return f"""currency_rate AS (
SELECT * FROM (VALUES {", ".join(map(str, currency_rate))}) AS currency_values(company_id, currency_id, conversion_date,rate)
)"""
@api.model
def _get_achievement_default_dates(self):
"""Return default date_from, date_to and company sql condition for the achievements filtered results
"""
if self.env.context.get('active_plan_ids'):
plan_ids = self.env['sale.commission.plan'].sudo().browse(self.env.context['active_plan_ids'])
date_from = plan_ids and min(plan_ids.mapped('date_from'))
date_to = plan_ids and max(plan_ids.mapped('date_to'))
else:
all_plan_ids = self.env['sale.commission.plan'].sudo().search([('state', '=', 'approved')])
date_from = all_plan_ids and min(all_plan_ids.mapped('date_from'))
date_to = all_plan_ids and max(all_plan_ids.mapped('date_to'))
return date_from, date_to
@property
def _table_query(self):
users = self.env.context.get('commission_user_ids', [])
if users:
users = self.env['res.users'].browse(users).exists()
teams = self.env.context.get('commission_team_ids', [])
if teams:
teams = self.env['crm.team'].browse(teams).exists()
date_from, date_to = self.env['sale.commission.achievement.report']._get_achievement_default_dates()
today = fields.Date.today().strftime('%Y-%m-%d')
date_from_condition = f"""AND date >= '{datetime.strftime(date_from, "%Y-%m-%d")}'""" if date_from else ""
achievement_view = self._get_report_view(users=users, teams=teams)
if not self._is_materialized_view() and achievement_view:
self.env.cr.execute(achievement_view)
query = f"""
WITH {self._get_currency_rate()}
SELECT cl.id,
cl.target_id,
cl.user_id,
cl.team_id,
cl.achieved * cr.rate AS achieved,
{self.env.company.currency_id.id} AS currency_id,
cl.plan_company_id as company_id,
cl.achievement_company_id as achievement_company_id,
cl.plan_id,
cl.related_res_model,
cl.related_res_id,
cl.date,
cl.partner_id,
era.amount * cr.rate AS target_amount,
era.payment_amount * cr.rate AS commission_target_amount,
CASE
WHEN era.amount IS NULL OR era.amount = 0 THEN 0
ELSE cl.achieved / (era.amount * cr.rate)
END as target_rate,
CASE
WHEN era.payment_amount IS NULL OR era.payment_amount = 0 THEN 0
ELSE cl.achieved / (era.payment_amount * cr.rate)
END as commission_rate
FROM sale_commission_achievement_report_view cl
JOIN sale_commission_plan_target era ON era.id = cl.target_id
JOIN currency_rate cr ON cr.company_id = cl.achievement_company_id
WHERE 1=1
{'AND user_id in (%s)' % ','.join(str(i) for i in users.ids) if users else ''}
{'AND team_id in (%s)' % ','.join(str(i) for i in teams.ids) if teams else ''}
{date_from_condition}
AND date <= '{datetime.strftime(date_to, "%Y-%m-%d") if date_to else today}'
"""
return query
# ==== Materialized Achievement Generation Methods ====
def _get_view_parameters(self):
return "TEMPORARY"
def _view_post_creation(self):
return ""
def _is_materialized_view(self):
return False
def _get_report_view(self, users=None, teams=None):
# test the existance of the view. _get_report_view can be called twice in a single transaction by get_views and web_search_read for example.
# To avoid `psycopg2.errors.DuplicateTable: relation already exists` errors, we only create the view on the first call.
# Unfortunately tools.sql.table_exists can't be used here because the view is created in a temporary schema
self.env.cr.execute("""
SELECT 1 FROM pg_catalog.pg_class AS c
WHERE c.relname = 'sale_commission_achievement_report_view'
AND c.relkind = 'v'::"char"
AND pg_catalog.pg_table_is_visible(c.oid)
""")
res = self.env.cr.fetchone()
if res:
# The view is already defined in this transaction
return
query = f"""
CREATE {self._get_view_parameters()} VIEW sale_commission_achievement_report_view AS
WITH {self._commission_lines_query(users=users, teams=teams)}
SELECT
cl.id,
era.id AS target_id,
cl.user_id AS user_id,
cl.team_id AS team_id,
cl.achieved AS achieved,
cl.currency_id AS currency_id,
-- company_id is the company of the achivement, used for currency conversion
cl.plan_company_id AS plan_company_id,
cl.achievement_company_id as achievement_company_id,
cl.plan_id,
cl.related_res_model,
cl.related_res_id::INTEGER AS related_res_id,
cl.date::date AS date,
cl.partner_id::INTEGER AS partner_id
FROM commission_lines cl
JOIN sale_commission_plan_target era
ON cl.plan_id = era.plan_id
AND cl.date::date >= era.date_from
AND cl.date::date <= era.date_to;
{self._view_post_creation()}
"""
return query
# ==== Query Helpers ====
@api.model
def _rate_to_case(self, rates):
case = "CASE WHEN scpa.type::text = '%s'::text THEN rate ELSE 0::double precision END AS %s"
return ",\n".join(case % (s, s + '_rate') for s in rates)
@api.model
def _get_sale_rates(self):
return ['amount_sold', 'qty_sold']
@api.model
def _get_invoices_rates(self):
return ['amount_invoiced', 'qty_invoiced']
@api.model
def _get_sale_rates_product(self):
return """
rules.amount_sold_rate * sol.price_subtotal::double precision / fo.currency_rate::double precision +
rules.qty_sold_rate * sol.product_uom_qty::double precision
"""
@api.model
def _get_filtered_orders_cte(self, users=None, teams=None):
date_from, date_to = self._get_achievement_default_dates()
today = fields.Date.today().strftime('%Y-%m-%d')
date_from_condition = f"""AND date_order >= '{datetime.strftime(date_from, "%Y-%m-%d")}'::timestamp without time zone""" if date_from else ""
query = f"""
filtered_orders AS (
SELECT
sale_order.id,
sale_order.team_id,
sale_order.state,
sale_order.currency_rate,
sale_order.company_id,
sale_order.currency_id,
sale_order.user_id,
sale_order.date_order,
sale_order.partner_id
FROM sale_order
WHERE sale_order.state::text = 'sale'::text
{'AND user_id in (%s)' % ','.join(str(i) for i in users.ids) if users else ''}
{'AND team_id in (%s)' % ','.join(str(i) for i in teams.ids) if teams else ''}
{date_from_condition}
AND date_order <= '{datetime.strftime(date_to, "%Y-%m-%d") if date_to else today}'
)
"""
return query
@api.model
def _get_filtered_moves_cte(self, users=None, teams=None):
date_from, date_to = self._get_achievement_default_dates()
today = fields.Date.today().strftime('%Y-%m-%d')
date_from_str = date_from and datetime.strftime(date_from, "%Y-%m-%d")
date_from_condition = f"""AND date >= '{date_from_str}'""" if date_from_str else ""
query = f"""
filtered_moves AS (
SELECT
account_move.id::bigint,
account_move.team_id,
account_move.move_type,
account_move.state,
account_move.invoice_currency_rate,
account_move.company_id,
account_move.currency_id,
account_move.invoice_user_id,
account_move.date,
account_move.partner_id
FROM account_move
WHERE account_move.move_type IN ('out_invoice', 'out_refund')
AND state = 'posted'
{'AND invoice_user_id in (%s)' % ','.join(str(i) for i in users.ids) if users else ''}
{'AND team_id in (%s)' % ','.join(str(i) for i in teams.ids) if teams else ''}
{date_from_condition}
AND date <= '{datetime.strftime(date_to, "%Y-%m-%d") if date_to else today}'
)
"""
return query
@api.model
def _get_invoice_rates_product(self):
return """
CASE
WHEN fm.move_type = 'out_invoice' THEN
rules.amount_invoiced_rate * aml.price_subtotal / fm.invoice_currency_rate +
rules.qty_invoiced_rate * aml.quantity
WHEN fm.move_type = 'out_refund' THEN
(rules.amount_invoiced_rate * aml.price_subtotal / fm.invoice_currency_rate +
rules.qty_invoiced_rate * aml.quantity) * -1
END
"""
@api.model
def _select_invoices(self):
return f"""
rules.user_id AS user_id, -- rule user to work with team commission
MAX(fm.team_id) AS team_id,
rules.plan_id,
SUM({self._get_invoice_rates_product()}) AS achieved,
MAX(fm.currency_id) AS currency_id,
MAX(fm.date) AS date,
MAX(rules.company_id) AS plan_company_id,
MAX(fm.company_id) AS achievement_company_id,
fm.id AS related_res_id,
MAX(fm.partner_id) AS partner_id
"""
@api.model
def _join_invoices(self, join_type=None):
if join_type == 'team':
jointure = "fm.team_id = rules.team_id"
else:
# JOIN ON USER
jointure = "fm.invoice_user_id = rules.user_id"
return f"""
JOIN filtered_moves fm ON {jointure}
JOIN account_move_line aml
ON aml.move_id = fm.id
LEFT JOIN product_product pp
ON aml.product_id = pp.id
LEFT JOIN product_template pt
ON pp.product_tmpl_id = pt.id
"""
@api.model
def _where_invoices(self):
where = """
aml.display_type = 'product'
AND fm.move_type in ('out_invoice', 'out_refund')
AND fm.state = 'posted'
"""
return where
@api.model
def _select_rules(self):
return ""
@api.model
def _select_sales(self):
return """
fo.id AS related_res_id,
MAX(fo.partner_id) AS partner_id
"""
@api.model
def _join_sales(self, join_type=None):
if join_type == 'team':
jointure = "fo.team_id = rules.team_id"
else:
# JOIN ON USER
jointure = "fo.user_id = rules.user_id"
return f"""
JOIN filtered_orders fo ON {jointure}
JOIN sale_order_line sol
ON sol.order_id = fo.id
"""
@api.model
def _where_sales(self):
where = """
AND sol.display_type IS NULL
AND (fo.date_order BETWEEN rules.date_from AND rules.date_to)
AND fo.state::TEXT = 'sale'::TEXT
AND (rules.product_id IS NULL OR rules.product_id = sol.product_id)
AND (rules.product_categ_id IS NULL OR rules.product_categ_id = pt.categ_id)
AND COALESCE(sol.is_expense, false) = false
AND COALESCE(sol.is_downpayment, false) = false
"""
return where
@api.model
def _get_filtered_achivement_cte(self, users=None, teams=None):
date_from = None
date_to = None
if self.env.context.get('active_target_ids'):
target_ids = self.env['sale.commission.plan.target'].sudo().browse(self.env.context['active_target_ids'])
date_from = min(target_ids.mapped('date_from'))
date_to = max(target_ids.mapped('date_to'))
elif self.env.context.get('active_plan_ids'):
plan_ids = self.env['sale.commission.plan'].sudo().browse(self.env.context['active_plan_ids'])
date_from = min(plan_ids.mapped('date_from'))
date_to = max(plan_ids.mapped('date_to'))
today = fields.Date.today().strftime('%Y-%m-%d')
date_from_str = date_from and datetime.strftime(date_from, "%Y-%m-%d")
date_from_condition = f"""AND date >= '{date_from_str}'""" if date_from_str else ""
query = f"""
filtered_adjustments AS (
SELECT
a.id,
a.add_user_id,
a.reduce_user_id,
a.company_id,
a.currency_id,
a.currency_rate,
a.achieved,
a.date
FROM sale_commission_achievement a
WHERE 1=1
{date_from_condition}
AND date <= '{datetime.strftime(date_to, "%Y-%m-%d") if date_to else today}'
)
"""
return query
def _achievement_lines_add(self, users=None, teams=None):
# Adjustement added to a salesperson
return f"""
{self._get_filtered_achivement_cte(users=users, teams=teams)},
achievement_commission_lines_add AS (
SELECT
fa.id::bigint *10+8 as id,
scpu.user_id AS user_id,
scp.team_id AS team_id,
scp.id AS plan_id,
fa.achieved::double precision / fa.currency_rate AS achieved,
fa.currency_id AS currency_id,
fa.date AS date,
scp.company_id AS plan_company_id,
scp.company_id AS achievement_company_id,
fa.id AS related_res_id,
-- achievement don't involve a customer; needed to match UNION structure with other sources
NULL::integer AS partner_id,
'sale.commission.achievement'::text AS related_res_model
FROM filtered_adjustments fa
JOIN sale_commission_plan_user scpu ON scpu.id = fa.add_user_id
JOIN sale_commission_plan scp ON scpu.plan_id = scp.id
WHERE scp.active
AND scp.state::text = 'approved'::text
{'AND scpu.user_id in (%s)' % ','.join(str(i) for i in users.ids) if users else ''}
)
""", "achievement_commission_lines_add"
def _achievement_lines_rem(self, users=None, teams=None):
# Adjustement removed to a salesperson
return f"""
achievement_commission_lines_rem AS (
SELECT
fa.id::bigint *10+9 as id,
scpu.user_id AS user_id,
scp.team_id AS team_id,
scp.id AS plan_id,
(- fa.achieved)::double precision / fa.currency_rate AS achieved,
fa.currency_id AS currency_id,
fa.date AS date,
scp.company_id AS plan_company_id,
scp.company_id AS achievement_company_id,
fa.id AS related_res_id,
-- achievement don't involve a customer; needed to match UNION structure with other sources
NULL::integer AS partner_id,
'sale.commission.achievement'::text AS related_res_model
FROM filtered_adjustments fa
JOIN sale_commission_plan_user scpu ON scpu.id = fa.reduce_user_id
JOIN sale_commission_plan scp ON scpu.plan_id = scp.id
WHERE scp.active
AND scp.state::text = 'approved'::text
{'AND scpu.user_id in (%s)' % ','.join(str(i) for i in users.ids) if users else ''}
)
""", "achievement_commission_lines_rem"
def _invoices_lines(self, users=None, teams=None):
return f"""
{self._get_filtered_moves_cte(users=users, teams=teams)},
invoices_rules AS (
SELECT
scpa.id::bigint,
COALESCE(scpu.date_from, scp.date_from) AS date_from,
COALESCE(scpu.date_to, scp.date_to) AS date_to,
scpu.user_id AS user_id,
scp.team_id AS team_id,
scp.id AS plan_id,
scpa.product_id,
scpa.product_categ_id,
scp.company_id,
scp.currency_id AS currency_id,
scp.user_type::text = 'team'::text AS team_rule,
{self._rate_to_case(self._get_invoices_rates())}
{self._select_rules()}
FROM sale_commission_plan_achievement scpa
JOIN sale_commission_plan scp ON scp.id = scpa.plan_id
JOIN sale_commission_plan_user scpu ON scpa.plan_id = scpu.plan_id
WHERE scp.active
AND scp.state::text = 'approved'::text
AND scpa.type::text IN ({','.join("'%s'::character varying" % r for r in self._get_invoices_rates())})
{'AND scpu.user_id in (%s)' % ','.join(str(i) for i in users.ids) if users else ''}
),
invoice_commission_lines_team AS (
SELECT
(MAX(aml.id)::bigint <<20) | max(rules.id)::bigint <<10 | rules.user_id as id,
--max(aml.id)::bigint as id,
{self._select_invoices()}
FROM invoices_rules rules
{self._join_invoices(join_type='team')}
WHERE {self._where_invoices()}
AND rules.team_rule
AND fm.team_id = rules.team_id
{'AND fm.team_id in (%s)' % ','.join(str(i) for i in teams.ids) if teams else ''}
AND fm.date BETWEEN rules.date_from AND rules.date_to
AND (rules.product_id IS NULL OR rules.product_id = aml.product_id)
AND (rules.product_categ_id IS NULL OR rules.product_categ_id = pt.categ_id)
GROUP BY
fm.id,
rules.plan_id,
rules.user_id
), invoice_commission_lines_user AS (
SELECT
-- row_number is needed here because we have 3 integer (32 bits) to use to create a unique 64 bits integer --> not possible
row_number() over(order by fm.id, rules.plan_id, rules.user_id) as id,
{self._select_invoices()}
FROM invoices_rules rules
{self._join_invoices(join_type='user')}
WHERE {self._where_invoices()}
AND NOT rules.team_rule
AND fm.invoice_user_id = rules.user_id
{'AND fm.invoice_user_id in (%s)' % ','.join(str(i) for i in users.ids) if users else ''}
AND fm.date BETWEEN rules.date_from AND rules.date_to
AND (rules.product_id IS NULL OR rules.product_id = aml.product_id)
AND (rules.product_categ_id IS NULL OR rules.product_categ_id = pt.categ_id)
GROUP BY
-- aml.id,
fm.id,
rules.plan_id,
rules.user_id
), invoice_commission_lines AS (
(
SELECT invoice_commission_lines_team.id*10+1 as id,
invoice_commission_lines_team.user_id,
invoice_commission_lines_team.team_id,
invoice_commission_lines_team.plan_id,
invoice_commission_lines_team.achieved,
invoice_commission_lines_team.currency_id,
invoice_commission_lines_team.date,
invoice_commission_lines_team.plan_company_id,
invoice_commission_lines_team.achievement_company_id,
invoice_commission_lines_team.related_res_id,
invoice_commission_lines_team.partner_id,
'account.move' AS related_res_model
FROM invoice_commission_lines_team
)
UNION ALL
(
SELECT invoice_commission_lines_user.id*10+2 as id,
invoice_commission_lines_user.user_id,
invoice_commission_lines_user.team_id,
invoice_commission_lines_user.plan_id,
invoice_commission_lines_user.achieved,
invoice_commission_lines_user.currency_id,
invoice_commission_lines_user.date,
invoice_commission_lines_user.plan_company_id,
invoice_commission_lines_user.achievement_company_id,
invoice_commission_lines_user.related_res_id,
invoice_commission_lines_user.partner_id,
'account.move' AS related_res_model
FROM invoice_commission_lines_user
)
)""", 'invoice_commission_lines'
def _sale_lines(self, users=None, teams=None):
return f"""
{self._get_filtered_orders_cte(users=users, teams=teams)},
sale_rules AS (
SELECT
COALESCE(scpu.date_from, scp.date_from) AS date_from,
COALESCE(scpu.date_to, scp.date_to) AS date_to,
scpu.user_id AS user_id,
scp.team_id AS team_id,
scp.id AS plan_id,
scpa.product_id,
scpa.product_categ_id,
scp.company_id,
scp.currency_id AS currency_id,
scp.user_type::text = 'team'::text AS team_rule,
{self._rate_to_case(self._get_sale_rates())}
{self._select_rules()}
FROM sale_commission_plan_achievement scpa
JOIN sale_commission_plan scp ON scp.id = scpa.plan_id
JOIN sale_commission_plan_user scpu ON scpa.plan_id = scpu.plan_id
WHERE scp.active
AND scp.state::text = 'approved'::text
AND scpa.type IN ({','.join("'%s'::character varying" % r for r in self._get_sale_rates())})
{'AND scpu.user_id in (%s)' % ','.join(str(i) for i in users.ids) if users else ''}
), sale_commission_lines_team AS (
SELECT
MAX(sol.id)::BIGINT *10+3 as id,
rules.user_id,
MAX(rules.team_id) AS team_id,
rules.plan_id,
SUM({self._get_sale_rates_product()}) AS achieved,
MAX(fo.currency_id) AS currency_id,
MAX(fo.date_order) AS date,
MAX(rules.company_id) AS plan_company_id,
MAX(fo.company_id) AS achievement_company_id,
{self._select_sales()}
FROM sale_rules rules
{self._join_sales(join_type='team')}
JOIN product_product pp
ON sol.product_id = pp.id
JOIN product_template pt
ON pp.product_tmpl_id = pt.id
WHERE rules.team_rule
AND fo.team_id = rules.team_id
{'AND fo.team_id in (%s)' % ','.join(str(i) for i in teams.ids) if teams else ''}
{self._where_sales()}
GROUP BY
fo.id,
rules.plan_id,
rules.user_id
), sale_commission_lines_user AS (
SELECT
MAX(sol.id)::BIGINT *10+4 as id,
rules.user_id,
MAX(fo.team_id) AS team_id,
rules.plan_id,
SUM({self._get_sale_rates_product()}) AS achieved,
MAX(fo.currency_id) AS currency_id,
MAX(fo.date_order) AS date,
MAX(rules.company_id) AS plan_company_id,
MAX(fo.company_id) AS achievement_company_id,
{self._select_sales()}
FROM sale_rules rules
{self._join_sales(join_type='user')}
JOIN product_product pp
ON sol.product_id = pp.id
JOIN product_template pt
ON pp.product_tmpl_id = pt.id
WHERE NOT rules.team_rule
AND fo.user_id = rules.user_id
{'AND fo.user_id in (%s)' % ','.join(str(i) for i in users.ids) if users else ''}
{self._where_sales()}
GROUP BY
fo.id,
rules.plan_id,
rules.user_id
), sale_commission_lines AS (
(
SELECT sale_commission_lines_team.id::bigint as id,
sale_commission_lines_team.user_id,
sale_commission_lines_team.team_id,
sale_commission_lines_team.plan_id,
sale_commission_lines_team.achieved,
sale_commission_lines_team.currency_id,
sale_commission_lines_team.date,
sale_commission_lines_team.plan_company_id,
sale_commission_lines_team.achievement_company_id,
sale_commission_lines_team.related_res_id,
sale_commission_lines_team.partner_id,
'sale.order'::text AS related_res_model
FROM sale_commission_lines_team
)
UNION ALL
(
SELECT sale_commission_lines_user.id::bigint as id,
sale_commission_lines_user.user_id,
sale_commission_lines_user.team_id,
sale_commission_lines_user.plan_id,
sale_commission_lines_user.achieved,
sale_commission_lines_user.currency_id,
sale_commission_lines_user.date,
sale_commission_lines_user.plan_company_id,
sale_commission_lines_user.achievement_company_id,
sale_commission_lines_user.related_res_id,
sale_commission_lines_user.partner_id,
'sale.order'::text AS related_res_model
FROM sale_commission_lines_user
)
)""", 'sale_commission_lines'
def _commission_lines_cte(self, users=None, teams=None):
return [self._achievement_lines_add(users, teams),
self._achievement_lines_rem(users, teams),
self._sale_lines(users, teams),
self._invoices_lines(users, teams)]
def _commission_lines_query(self, users=None, teams=None):
ctes = self._commission_lines_cte(users, teams)
queries = [x[0] for x in ctes]
table_names = [x[1] for x in ctes]
# create temporary table to convert currencies
res = f"""
{','.join(queries)},
commission_lines AS (
{' UNION ALL '.join(f'(SELECT * FROM {name})' for name in table_names)}
)
"""
return res
def _pre_achievement_operation(self):
# Override in other modules. Mostly used in tests
self.env.flush_all()
self.env.invalidate_all()
return