Files
enterprise-addons/sale_subscription/models/account_move.py
T

120 lines
6.7 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from functools import partial
from odoo import fields, models, _
from odoo.addons.sale_subscription.models.sale_order import SUBSCRIPTION_PROGRESS_STATE
from dateutil.relativedelta import relativedelta
class AccountMove(models.Model):
_inherit = 'account.move'
def write(self, vals):
res = super().write(vals)
if 'matched_payment_ids' in vals:
for move in self.filtered_domain([('payment_state', 'in', ['paid', 'in_payment']), ('move_type', '=', 'out_invoice')]):
move._reopen_paid_churned_subscription()
return res
def _post(self, soft=True):
posted_moves = super()._post(soft=soft)
automatic_invoice = self.env.context.get('recurring_automatic')
all_subscription_ids = set()
log_date_values = {"upsell": {}, "subscription": {}}
for move in posted_moves:
if not move.invoice_line_ids.subscription_id:
continue
if move.move_type != 'out_invoice':
if move.move_type == 'out_refund':
body = _("The following refund %s has been made on this contract. Please check the next invoice date if necessary.", move._get_html_link())
for so in move.invoice_line_ids.subscription_id:
# Normally, only one subscription_id per move, but we handle multiple contracts as a precaution
so.message_post(body=body)
continue
log_order_ids = []
for aml in move.invoice_line_ids:
if not aml.subscription_id or aml.is_downpayment:
continue
# check if the sale order linked to the invoice is an upsell
# Upsells don't update the next_invoice_date. Historical data could be unaligned and they could mess up periods.
sale_order = aml.sale_line_ids.order_id
upsell_so = sale_order.filtered(lambda so: so.subscription_state == '7_upsell')
subscription = aml.subscription_id - upsell_so.subscription_id
all_subscription_ids.add(subscription.id)
log_order_ids.append(aml.subscription_id.id)
if upsell_so:
log_date_values["upsell"][upsell_so] = {
'effective_date': aml.deferred_start_date or move.date,
'event_date': upsell_so.date_order.date(),
}
if subscription:
log_date_values["subscription"][subscription] = {
'effective_date': aml.deferred_start_date or move.date,
'event_date': subscription.date_order.date(),
}
all_subscriptions = self.env['sale.order'].browse(all_subscription_ids)
for subscription in all_subscriptions:
# Invoice validation will increment the next invoice date
if subscription.subscription_state in SUBSCRIPTION_PROGRESS_STATE + ['6_churn']:
# We increment the next invoice date for progress sub and churn one.
# Churn sub are reopened in the _post_process of payment transaction.
# Renewed sub should not be incremented as the renewal is the running contract.
# Invoices for renewed contract can be posted when the delivered products arrived after the renewal date.
last_invoice_end_date = subscription.invoice_ids.invoice_line_ids.filtered(lambda aml: aml.subscription_id == subscription)._get_max_invoiced_date()
subscription.next_invoice_date = last_invoice_end_date + relativedelta(days=1) if last_invoice_end_date else subscription.start_date
if subscription.order_line._is_all_postpaid():
# If all recurring lines are postpaid, we don't rely on deferred_end_date
subscription.next_invoice_date += subscription.plan_id.billing_period
subscription.last_reminder_date = False
subscription.pending_transaction = False
if subscription.user_pause_start:
subscription.user_pause_start = False
if all_subscriptions:
# update the renewal quotes to start at the next invoice date values
renewal_quotes = self.env['sale.order'].search([
('subscription_id', 'in', all_subscriptions.ids),
('subscription_state', '=', '2_renewal'),
('state', 'in', ['draft', 'sent'])])
for quote in renewal_quotes:
next_invoice_date = quote.subscription_id.next_invoice_date
if not quote.start_date or quote.start_date < next_invoice_date:
quote.update({
'next_invoice_date': next_invoice_date,
'start_date': next_invoice_date,
})
if not automatic_invoice:
all_subscriptions._post_invoice_hook()
self.env.cr.precommit.add(partial(self.env['sale.order.log']._update_effective_date, log_date_values))
return posted_moves
def _message_auto_subscribe_followers(self, updated_values, subtype_ids):
""" Assignment emails are sent when an account.move is created with a
different user_id than the current one. For subscriptions it can send
thousands of email depending on the database size.
This override prevent the assignment emails to be sent to the
salesperson.
"""
res = super()._message_auto_subscribe_followers(updated_values, subtype_ids)
user_id = updated_values.get('user_id')
if user_id and self.invoice_line_ids.subscription_id:
for move in self:
salesperson = move.invoice_line_ids.subscription_id.user_id
if salesperson and user_id in salesperson.ids and user_id != self.env.user.id:
partner_ids = salesperson.partner_id.ids
res = [(v[0], v[1], False) for v in res if v[0] in partner_ids and v[2] == 'mail.message_user_assigned']
return res
def _reopen_paid_churned_subscription(self):
# Re-open churned subscriptions after payment.
sub_order = self.line_ids.subscription_id
close_reasons_ids = self.env['sale.order.close.reason']._get_reason_to_reopen()
for order in sub_order:
cutoff_date = fields.Date.today() - relativedelta(days=order.plan_id.auto_close_limit)
# Prevent reopening churned subscriptions if cutoff_date is after next_invoice_date
if order.subscription_state == '6_churn' and order.next_invoice_date >= cutoff_date and order.close_reason_id.id in close_reasons_ids:
order.set_open()