264 lines
12 KiB
Python
264 lines
12 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from datetime import timedelta
|
|
|
|
from odoo import api, fields, models, _
|
|
from odoo.addons.sale_subscription.models.sale_order import SUBSCRIPTION_STATES, SUBSCRIPTION_PROGRESS_STATE, SUBSCRIPTION_CLOSED_STATE
|
|
|
|
|
|
class SaleOrderLog(models.Model):
|
|
_name = 'sale.order.log'
|
|
_description = 'Sale Order Log'
|
|
_order = 'id desc'
|
|
|
|
# Order related
|
|
order_id = fields.Many2one(
|
|
'sale.order', string='Sale Order',
|
|
required=True, ondelete='cascade', readonly=True,
|
|
bypass_search_access=True, index=True,
|
|
)
|
|
user_id = fields.Many2one('res.users', related='order_id.user_id', string='Salesperson', store=True, precompute=True, depends=[])
|
|
team_id = fields.Many2one('crm.team', related='order_id.team_id', string='Sales Team', store=True, precompute=True, depends=[])
|
|
plan_id = fields.Many2one('sale.subscription.plan', related='order_id.plan_id', string='Recurring Plan', store=True, precompute=True, depends=[])
|
|
company_id = fields.Many2one('res.company', related='order_id.company_id', string='Company', store=True, precompute=True, depends=[])
|
|
currency_id = fields.Many2one('res.currency', related='order_id.currency_id', string='Currency', store=True, precompute=True, depends=[], readonly=False)
|
|
origin_order_id = fields.Many2one('sale.order', string='Origin Contract', store=True, index=True, precompute=True,
|
|
compute='_compute_origin_order_id')
|
|
|
|
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,
|
|
)
|
|
event_date = fields.Date(string='Event Date', required=True, index=True, default=fields.Date.today)
|
|
effective_date = fields.Date(index=True)
|
|
recurring_monthly = fields.Monetary(string='New MRR', required=True,
|
|
help="MRR, after applying the changes of that particular event", readonly=True)
|
|
amount_signed = fields.Monetary(string='MRR change', required=True, readonly=True)
|
|
subscription_state = fields.Selection(selection=SUBSCRIPTION_STATES, help="Subscription stage when the change occurred")
|
|
|
|
@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
|
|
|
|
def _compute_display_name(self):
|
|
for log in self:
|
|
log.display_name = _("Sale order log: %s", log.id)
|
|
|
|
#######################
|
|
# LOG GEN #
|
|
#######################
|
|
|
|
@api.model
|
|
def _create_log(self, order, initial_values):
|
|
old_state = initial_values.get('subscription_state', order.subscription_state) or '1_draft'
|
|
new_state = order.subscription_state
|
|
|
|
# Cancelling a running or churned renewal
|
|
if (order.state == 'cancel' and order.subscription_id
|
|
and old_state in SUBSCRIPTION_PROGRESS_STATE + SUBSCRIPTION_CLOSED_STATE):
|
|
return self._cancel_renewal_logs(order, initial_values)
|
|
|
|
# Cancelling anything else
|
|
if order.state == 'cancel':
|
|
return self._cancel_logs(order, initial_values)
|
|
|
|
# Confirm subscription SO
|
|
if new_state in SUBSCRIPTION_PROGRESS_STATE and old_state == '1_draft':
|
|
return self._create_creation_log(order, initial_values)
|
|
|
|
# Confirm renewal SO
|
|
if new_state in SUBSCRIPTION_PROGRESS_STATE and old_state == '2_renewal':
|
|
return self._create_renewal_transfer_log(order, initial_values)
|
|
|
|
# Reopen churned SO
|
|
if new_state in SUBSCRIPTION_PROGRESS_STATE and old_state == '6_churn':
|
|
return self._unlink_churn_log(order, initial_values)
|
|
|
|
# Churn SO
|
|
if old_state in SUBSCRIPTION_PROGRESS_STATE and new_state == '6_churn':
|
|
return self._create_churn_log(order, initial_values)
|
|
|
|
# SO is not in progress
|
|
if new_state not in SUBSCRIPTION_PROGRESS_STATE and old_state not in SUBSCRIPTION_PROGRESS_STATE:
|
|
return self.env['sale.order.log']
|
|
|
|
# Currency change
|
|
if 'currency_id' in initial_values and initial_values['currency_id'] != order.currency_id:
|
|
return self._create_currency_transfer_log(order, initial_values)
|
|
|
|
# MRR change
|
|
return self._create_mrr_change_log(order, initial_values)
|
|
|
|
@api.model
|
|
def _cancel_renewal_logs(self, order, initial_values):
|
|
order.order_log_ids.unlink()
|
|
# Delete the transfer from the parent to the renewal that is cancelled
|
|
transfer_log = self.search(
|
|
[('order_id', '=', order.subscription_id.id),
|
|
('event_type', '=', '3_transfer'),
|
|
('amount_signed', '<', 0)
|
|
], order='id desc', limit=1)
|
|
order.subscription_id.order_log_ids.filtered(
|
|
lambda log: log.effective_date in transfer_log.mapped('effective_date')
|
|
).effective_date = False
|
|
transfer_log.unlink()
|
|
return self.env['sale.order.log']
|
|
|
|
@api.model
|
|
def _cancel_logs(self, order, initial_values):
|
|
order.order_log_ids.unlink()
|
|
return self.env['sale.order.log']
|
|
|
|
@api.model
|
|
def _create_creation_log(self, order, initial_values):
|
|
return self.create({
|
|
'order_id': order.id,
|
|
'event_type': '0_creation',
|
|
'amount_signed': max(order.recurring_monthly, 0),
|
|
'recurring_monthly': max(order.recurring_monthly, 0),
|
|
'subscription_state': initial_values.get('subscription_state') or '1_draft',
|
|
})
|
|
|
|
@api.model
|
|
def _create_renewal_transfer_log(self, order, initial_values):
|
|
result = self.env['sale.order.log']
|
|
parent_order = order.subscription_id
|
|
parent_churn = parent_order.order_log_ids.filtered(lambda log: log.event_type == '2_churn')
|
|
|
|
# If at least 30 days between next invoice date and start_date, churn and creation instead of transfer
|
|
if parent_order.next_invoice_date + timedelta(days=30) < order.start_date:
|
|
if not parent_churn:
|
|
result += self._create_churn_log(parent_order, {})
|
|
return result + self._create_creation_log(order, initial_values)
|
|
|
|
parent_churn[:1].unlink()
|
|
|
|
parent_transfer_amount = sum(parent_order.order_log_ids.mapped('amount_signed'))
|
|
parent_transfer = self.create({
|
|
'order_id': parent_order.id,
|
|
'event_type': '3_transfer',
|
|
'amount_signed': -parent_transfer_amount,
|
|
'recurring_monthly': 0,
|
|
'subscription_state': parent_order.subscription_state,
|
|
})
|
|
parent_order.order_log_ids.filtered(lambda log: not log.effective_date).effective_date = parent_order.next_invoice_date
|
|
|
|
renewal_transfer_amount = parent_transfer.currency_id._convert(parent_transfer_amount,
|
|
to_currency=order.currency_id,
|
|
company=order.company_id, round=False)
|
|
renewal_transfer = self.create({
|
|
'order_id': order.id,
|
|
'event_type': '3_transfer',
|
|
'amount_signed': renewal_transfer_amount,
|
|
'recurring_monthly': renewal_transfer_amount,
|
|
'subscription_state': initial_values.get('subscription_state', order.subscription_state),
|
|
})
|
|
|
|
initial_values['recurring_monthly'] = renewal_transfer_amount
|
|
return parent_transfer + renewal_transfer + self._create_mrr_change_log(order, initial_values)
|
|
|
|
@api.model
|
|
def _unlink_churn_log(self, order, initial_values):
|
|
churn_logs = order.order_log_ids.filtered(lambda log: log.event_type == '2_churn')
|
|
effective_at_churn = order.order_log_ids.filtered(
|
|
lambda log: log.effective_date in churn_logs.mapped('effective_date')
|
|
)
|
|
effective_at_churn.effective_date = False
|
|
churn_logs.unlink()
|
|
initial_values['recurring_monthly'] = sum(order.order_log_ids.mapped('amount_signed'))
|
|
return self._create_mrr_change_log(order, initial_values)
|
|
|
|
@api.model
|
|
def _create_churn_log(self, order, initial_values):
|
|
# All change are now effective
|
|
order.order_log_ids.filtered(lambda log: not log.effective_date).effective_date = order.next_invoice_date
|
|
return self.create({
|
|
'order_id': order.id,
|
|
'event_type': '2_churn',
|
|
'amount_signed': -sum(order.order_log_ids.mapped('amount_signed')),
|
|
'recurring_monthly': 0,
|
|
'subscription_state': initial_values.get('subscription_state', order.subscription_state),
|
|
'effective_date': order.next_invoice_date,
|
|
})
|
|
|
|
@api.model
|
|
def _create_currency_transfer_log(self, order, initial_values):
|
|
new_mrr = max(order.recurring_monthly, 0)
|
|
old_mrr = max(initial_values.get('recurring_monthly', new_mrr), 0)
|
|
old_mrr_new_currency = initial_values['currency_id']._convert(old_mrr,
|
|
to_currency=order.currency_id,
|
|
company=order.company_id, round=False)
|
|
result = self.create([{
|
|
'order_id': order.id,
|
|
'event_type': '3_transfer',
|
|
'amount_signed': -old_mrr,
|
|
'currency_id': initial_values['currency_id'].id,
|
|
'recurring_monthly': 0,
|
|
'subscription_state': initial_values.get('subscription_state', order.subscription_state),
|
|
}, {
|
|
'order_id': order.id,
|
|
'event_type': '3_transfer',
|
|
'amount_signed': old_mrr_new_currency,
|
|
'recurring_monthly': old_mrr_new_currency,
|
|
'subscription_state': initial_values.get('subscription_state', order.subscription_state),
|
|
}])
|
|
|
|
if old_mrr_new_currency != new_mrr:
|
|
initial_values['recurring_monthly'] = old_mrr_new_currency
|
|
result += self._create_mrr_change_log(order, initial_values)
|
|
|
|
return result
|
|
|
|
@api.model
|
|
def _create_mrr_change_log(self, order, initial_values):
|
|
new_mrr = max(order.recurring_monthly, 0)
|
|
old_mrr = max(initial_values.get('recurring_monthly', new_mrr), 0)
|
|
|
|
if order.currency_id.compare_amounts(old_mrr, new_mrr) == 0:
|
|
return self.env['sale.order.log']
|
|
|
|
return self.create({
|
|
'order_id': order.id,
|
|
'event_type': '1_expansion' if new_mrr > old_mrr else '15_contraction',
|
|
'amount_signed': new_mrr - old_mrr,
|
|
'recurring_monthly': new_mrr,
|
|
'subscription_state': order.subscription_state,
|
|
})
|
|
|
|
@api.model
|
|
def _update_effective_date(self, log_date_values):
|
|
""" Update the effective date of logs based on post date.
|
|
:param: log_order_values: dict containing the list of ids for
|
|
upsells and subscriptions
|
|
"""
|
|
self._write_uninvoiced_logs(log_date_values["subscription"])
|
|
self._write_uninvoiced_logs(log_date_values["upsell"], upsell=True)
|
|
# This method is call as precommit and need to flush manually
|
|
# because precommit.run happened right after the main flush
|
|
self.env.flush_all()
|
|
|
|
def _write_uninvoiced_logs(self, log_date_values, upsell=False):
|
|
if not log_date_values:
|
|
return
|
|
|
|
for order, dates in log_date_values.items():
|
|
if upsell:
|
|
ratio = order._get_ratio_value()
|
|
amount = sum([line.recurring_monthly / ratio for line in order.order_line])
|
|
logs = order.subscription_id.order_log_ids.filtered(lambda log:
|
|
not log.effective_date and
|
|
log.event_date == dates['event_date']
|
|
).sorted(lambda log: abs(log.amount_signed - amount))[:1]
|
|
else:
|
|
logs = order.order_log_ids.filtered(lambda log: not log.effective_date)
|
|
|
|
logs.sudo().effective_date = dates['effective_date']
|