forked from Mapan/odoo17e
176 lines
9.1 KiB
Python
176 lines
9.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import api, fields, models
|
|
from odoo.addons.sale_subscription.models.sale_order import SUBSCRIPTION_STATES, SUBSCRIPTION_PROGRESS_STATE, SUBSCRIPTION_CLOSED_STATE
|
|
from odoo.tools.float_utils import float_is_zero
|
|
|
|
|
|
class SaleOrderLog(models.Model):
|
|
_name = 'sale.order.log'
|
|
_description = 'Sale Order Log'
|
|
_order = 'event_date desc, id desc'
|
|
|
|
order_id = fields.Many2one(
|
|
'sale.order', string='Sale Order',
|
|
required=True, ondelete='cascade', readonly=True,
|
|
auto_join=True
|
|
)
|
|
create_date = fields.Datetime(string='Date', readonly=True)
|
|
event_type = fields.Selection(
|
|
string='Type of event',
|
|
selection=[('0_creation', 'New'),
|
|
('1_expansion', 'Expansion'),
|
|
('15_contraction', 'Contraction'),
|
|
('2_churn', 'Churn'),
|
|
('3_transfer', 'Transfer')],
|
|
required=True,
|
|
readonly=True,
|
|
index=True,
|
|
)
|
|
recurring_monthly = fields.Monetary(string='New MRR', required=True,
|
|
help="MRR, after applying the changes of that particular event", readonly=True)
|
|
subscription_state = fields.Selection(selection=SUBSCRIPTION_STATES, required=True, help="Subscription stage category when the change occurred")
|
|
user_id = fields.Many2one('res.users', string='Salesperson')
|
|
team_id = fields.Many2one('crm.team', string='Sales Team', ondelete="set null")
|
|
amount_signed = fields.Monetary(index=True)
|
|
currency_id = fields.Many2one('res.currency', string='Currency', required=True, readonly=True)
|
|
event_date = fields.Date(string='Event Date', required=True, index=True)
|
|
company_id = fields.Many2one('res.company', string='Company', related='order_id.company_id', store=True, readonly=True)
|
|
origin_order_id = fields.Many2one('sale.order', string='Origin Contract', store=True, index=True,
|
|
compute='_compute_origin_order_id')
|
|
|
|
|
|
@api.depends('order_id')
|
|
def _compute_origin_order_id(self):
|
|
for log in self:
|
|
log.origin_order_id = log.order_id.origin_order_id or log.order_id
|
|
|
|
#######################
|
|
# LOG GEN #
|
|
#######################
|
|
|
|
@api.model
|
|
def _get_change_event_type(self, currency, mrr_difference):
|
|
if currency.compare_amounts(mrr_difference, 0) <= 0:
|
|
return '15_contraction'
|
|
return '1_expansion'
|
|
|
|
@api.model
|
|
def _create_starting_transfer_log(self, values):
|
|
event_date = fields.Date.today()
|
|
sub = self.env['sale.order'].browse(values['order_id'])
|
|
new_currency = sub.currency_id
|
|
parent_currency = sub.subscription_id.currency_id
|
|
parent_recurring_monthly = max(sub.subscription_id.recurring_monthly, 0)
|
|
parent_mrr = parent_currency._convert(parent_recurring_monthly,
|
|
to_currency=new_currency,
|
|
company=sub.env.company,
|
|
date=fields.Date.today(), round=False)
|
|
parent_transfer_log = sub.subscription_id.order_log_ids.filtered(
|
|
lambda l: l.subscription_state == '5_renewed')
|
|
transfer_date = parent_transfer_log and parent_transfer_log.sorted('event_date')[-1].event_date or event_date
|
|
# Creation of renewal: transfer and MRR change
|
|
transfer_values = values.copy()
|
|
transfer_values.update({
|
|
'event_type': '3_transfer',
|
|
'amount_signed': parent_mrr,
|
|
'recurring_monthly': parent_mrr,
|
|
'event_date': transfer_date,
|
|
})
|
|
log = self.sudo().create(transfer_values)
|
|
if not float_is_zero(values['recurring_monthly'] - parent_mrr, precision_rounding=new_currency.rounding):
|
|
values.update({
|
|
'event_type': self._get_change_event_type(new_currency, sub.recurring_monthly - parent_mrr),
|
|
'recurring_monthly': values['recurring_monthly'],
|
|
'amount_signed': values['recurring_monthly'] - parent_mrr,
|
|
'event_date': transfer_date,
|
|
})
|
|
self.sudo().create(values)
|
|
return log
|
|
|
|
@api.model
|
|
def _create_reopen_log(self, values):
|
|
# We reopened a churned contract. We delete the churn log to keep the formal MRR.
|
|
sub = self.env['sale.order'].browse(values['order_id'])
|
|
churn_logs = sub.order_log_ids.filtered(lambda log: log.event_type == '2_churn').sorted('event_date', reverse=True)
|
|
churn_log = churn_logs[:1]
|
|
previous_mrr = 0
|
|
if churn_log:
|
|
previous_mrr = - churn_log.amount_signed
|
|
churn_log.unlink()
|
|
mrr_difference = values['recurring_monthly'] - previous_mrr # arj todo reopen with negative values
|
|
if not float_is_zero(mrr_difference, precision_rounding=sub.currency_id.rounding):
|
|
values.update({
|
|
'event_type': self._get_change_event_type(sub.currency_id, mrr_difference),
|
|
'amount_signed': mrr_difference,
|
|
})
|
|
return self.sudo().create(values)
|
|
|
|
@api.model
|
|
def _create_mrr_log(self, values, initial_values):
|
|
sub = self.env['sale.order'].browse(values['order_id'])
|
|
if sub.subscription_state not in SUBSCRIPTION_PROGRESS_STATE:
|
|
return
|
|
mrr_difference = values.get('amount_signed', 0)
|
|
if sub.origin_order_id: # Is a confirmed renewal ( origin_order_id and category in progress)
|
|
existing_transfer_log = sub.order_log_ids.filtered(lambda ev: ev.event_type == '3_transfer')
|
|
# Warning, sometimes we don't have transfer log but we have similar log:
|
|
# new or expansion going from 0 to RM. We don't want to create transfer in that case.
|
|
similar_log = sub.order_log_ids.filtered(lambda ev: ev.amount_signed == ev.recurring_monthly)
|
|
if not existing_transfer_log and not similar_log and sub.subscription_id.subscription_state == '5_renewed':
|
|
return self._create_starting_transfer_log(values.copy())
|
|
|
|
if not float_is_zero(mrr_difference, precision_rounding=sub.currency_id.rounding):
|
|
values.update({'amount_signed': mrr_difference,
|
|
'recurring_monthly': values['recurring_monthly']})
|
|
if sub.order_log_ids:
|
|
# Simple contraction or extension
|
|
values['event_type'] = self._get_change_event_type(sub.currency_id, mrr_difference)
|
|
else:
|
|
order_start_date = sub.start_date or fields.Date.today()
|
|
values.update({'event_type': '0_creation',
|
|
'event_date': min(fields.Date.today(), order_start_date)})
|
|
return self.sudo().create(values)
|
|
|
|
def _create_stage_log(self, values, initial_values):
|
|
old_state = initial_values['subscription_state']
|
|
new_state = values['subscription_state']
|
|
values['event_date'] = fields.Date.today()
|
|
if not (old_state not in SUBSCRIPTION_PROGRESS_STATE and new_state in SUBSCRIPTION_PROGRESS_STATE) and \
|
|
not (old_state not in SUBSCRIPTION_CLOSED_STATE and new_state in SUBSCRIPTION_CLOSED_STATE):
|
|
return
|
|
# subscription started, churned or transferred to renew
|
|
sub = self.env['sale.order'].browse(values['order_id'])
|
|
if new_state in SUBSCRIPTION_PROGRESS_STATE:
|
|
if old_state == '6_churn':
|
|
# We reopened a churned contract. We delete the churn log to keep the formal MRR.
|
|
return self._create_reopen_log(values)
|
|
if sub.subscription_id and sub.subscription_id.subscription_state != '6_churn':
|
|
# Changed order, old order may create error for reopen renewal
|
|
# Transfer for the renewed value and MRR change for the rest
|
|
return self._create_starting_transfer_log(values)
|
|
else:
|
|
values['event_type'] = '0_creation'
|
|
return self.sudo().create(values)
|
|
else:
|
|
# Closing a subscription: transfer or churn
|
|
if sub.subscription_child_ids.filtered(lambda s: s.subscription_state == '3_progress'):
|
|
values['event_type'] = '3_transfer'
|
|
else:
|
|
values['event_type'] = '2_churn'
|
|
# All logs are stacked today. In the future, there should not be any log in the future
|
|
event_date = fields.Date.today()
|
|
sub.order_log_ids.filtered(lambda l: l.event_date > event_date).event_date = event_date
|
|
return self.sudo().create(values)
|
|
|
|
@api.model
|
|
def _create_log(self, values, initial_values):
|
|
log = None
|
|
initial_state = initial_values.get('subscription_state')
|
|
if initial_state and initial_state != values.get('subscription_state'):
|
|
log = self._create_stage_log(values.copy(), initial_values)
|
|
if not log and values.get('amount_signed') and initial_state != '6_churn': # If we reopen, we don't want addition mrr log
|
|
log = self._create_mrr_log(values.copy(), initial_values)
|
|
return log
|