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

194 lines
9.6 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo.fields import Command
from odoo.tools import _
from odoo.tools.date_utils import get_timedelta
class SaleSubscriptionPlan(models.Model):
_name = 'sale.subscription.plan'
_description = 'Subscription Plan'
_order = 'sequence, id'
active = fields.Boolean(default=True)
name = fields.Char(translate=True, required=True, default="Monthly")
sequence = fields.Integer(default=10)
company_id = fields.Many2one('res.company')
# Billing Period, use billing_period property for access to the timedelta
billing_period_value = fields.Integer(string="Billing Period ", required=True, default=1)
billing_period_unit = fields.Selection([("week", "Weeks"), ("month", "Months"), ('year', 'Years')],
string="Unit", required=True, default='month')
billing_period_display = fields.Char(compute='_compute_billing_period_display', string="Billing Period", search='_search_billing_period_display')
billing_period_display_sentence = fields.Char(compute='_compute_billing_period_display_sentence', string="Billing Period Display")
billing_first_day = fields.Boolean(string="Align to Period Start", default=False, help="Align all subscription invoices on the first day of each billing period.")
# Self Service
user_closable = fields.Boolean(string="Closable", default=False,
help="Customer can close their subscriptions.")
user_extend = fields.Boolean("Renew", default=False,
help="Customer can create a renewal quotation for their subscription.")
user_quantity = fields.Boolean("Add Products", default=False,
help="Allow customers to create an Upsell quote to adjust the quantity of products in their subscription."
"Only products that are listed as \"optional products\" can be modified.")
related_plan_id = fields.Many2many("sale.subscription.plan", "sale_subscription_plan_related_plan",
"plan_id", "related_plan_id", string="Optional Plans",
help="Allow your customers to switch from this plan to "
"another on quotation (new subscription or renewal)")
user_closable_options = fields.Selection([
("at_date", "At date"),
("end_of_period", "End of period")
], string="Closeable Plan Options", required=True, default='at_date')
pausable_by_user = fields.Boolean(
string="Pausable",
default=False,
help="Allow customers to temporarily pause their subscription for a specific period. Not available for postpaid subscriptions."
)
# Invoicing
auto_close_limit = fields.Integer(string="Automatic Closing", default=15,
help="Subscriptions late in renewing (e.g., automatic payment is failing continuously,"
"customer didn't pay, etc.) will be automatically closed after this period.")
auto_close_limit_display = fields.Char(string="Automatic Closing After", compute="_compute_auto_close_limit_display")
invoice_mail_template_id = fields.Many2one('mail.template', string='Invoice Email Template',
domain=[('model', '=', 'account.move')],
default=lambda self: self.env.ref('account.email_template_edi_invoice', raise_if_not_found=False),
help="Email template used to send invoicing email automatically.\n"
"Leave it empty if you don't want to send email automatically.")
subscription_rule_ids = fields.One2many(
comodel_name='product.pricelist.item',
inverse_name='plan_id',
string="Recurring Pricing",
domain=[
('plan_id', '!=', None),
'|', ('product_tmpl_id', '=', None), ('product_tmpl_id.active', '=', True)
]
)
# UX
active_subs_count = fields.Integer(compute="_compute_active_subs_count", string="Subscriptions")
subscription_line_count = fields.Integer(compute="_compute_active_subscription_line_count", string="Subscription Count")
_check_for_valid_billing_period_value = models.Constraint(
'CHECK(billing_period_value > 0)',
"Recurring period must be a positive number. Please ensure the input is a valid positive numeric value.",
)
def write(self, vals):
if "related_plan_id" in vals:
old_related = {plan.id: plan.related_plan_id for plan in self}
res = super().write(vals)
if "related_plan_id" in vals:
for plan in self:
if to_remove := old_related[plan.id] - plan.related_plan_id:
to_remove.related_plan_id = [Command.unlink(plan.id)]
if to_add := plan.related_plan_id - old_related[plan.id]:
to_add.related_plan_id = [Command.link(plan.id)]
return res
def _search_billing_period_display(self, operator, value):
if operator != 'in':
return NotImplemented
plan_ids = [
plan.id
for plan in self.env['sale.subscription.plan'].search([])
if any(v in plan.billing_period_display for v in value)
]
return [('id', 'in', plan_ids.ids)]
def _compute_active_subs_count(self):
res = dict(self.env['sale.order']._read_group(
[('plan_id', 'in', self.ids), ('is_subscription', '=', True), ('subscription_state', 'in', ['3_progress', '4_paused'])],
['plan_id'], ['__count'],
))
for plan in self:
plan.active_subs_count = res.get(plan, 0)
def _compute_active_subscription_line_count(self):
domain = [
('order_id.is_subscription', '=', True),
('product_id.recurring_invoice', '=', True),
('subscription_plan_id', 'in', self.ids),
('order_id.subscription_state', 'in', ('3_progress', '4_paused')),
('display_type', '=', False),
]
line_counts = dict(self.env['sale.order.line']._read_group(
domain,
['subscription_plan_id'],
['__count'],
))
for plan in self:
plan.subscription_line_count = line_counts.get(plan, 0)
def action_open_active_sub(self):
return {
'name': _('Subscriptions'),
'view_mode': 'list,form',
'domain': [('plan_id', 'in', self.ids), ('is_subscription', '=', True), ('subscription_state', 'in', ['3_progress', '4_paused'])],
'res_model': 'sale.order',
'type': 'ir.actions.act_window',
}
def action_open_active_subscription_lines(self):
subscription_items_plan = self.env['sale.order'].search([
('plan_id', 'in', self.ids),
('is_subscription', '=', True),
('subscription_state', 'in', ['3_progress', '4_paused'])
]).order_line.filtered(lambda line: not line.display_type and line.product_id.recurring_invoice)
return {
'name': _('Subscription Items'),
'view_mode': 'list',
'views': [(self.env.ref('sale_subscription.sale_subscription_sale_order_line_list').id, 'list')],
'search_view_id': [self.env.ref('sale_subscription.sale_subscription_sales_order_line_filter').id],
'domain': [('id', 'in', subscription_items_plan.ids)],
'context': {'create': False},
'res_model': 'sale.order.line',
'type': 'ir.actions.act_window',
}
@property
def billing_period(self):
if not self.billing_period_unit or not self.billing_period_value:
return False
return get_timedelta(self.billing_period_value, self.billing_period_unit)
@api.depends_context('lang')
@api.depends('billing_period_value', 'billing_period_unit')
def _compute_billing_period_display(self):
for plan in self:
value = plan.billing_period_value
if plan.billing_period_unit == 'week':
plan.billing_period_display = _('%s Weeks', value) if value > 1 else _('1 Week')
elif plan.billing_period_unit == 'month':
plan.billing_period_display = _('%s Months', value) if value > 1 else _('1 Month')
elif plan.billing_period_unit == 'year':
plan.billing_period_display = _('%s Years', value) if value > 1 else _('1 Year')
else:
raise ValueError(f"Invalid Billing Period Unit {plan.billing_period_unit!r}")
@api.depends_context('lang')
@api.depends('billing_period_value', 'billing_period_unit')
def _compute_billing_period_display_sentence(self):
for plan in self:
value = plan.billing_period_value
if plan.billing_period_unit == 'week':
sentence = _('per %d weeks', value) if value > 1 else _('per week')
elif plan.billing_period_unit == 'month':
sentence = _('per %d months', value) if value > 1 else _('per month')
elif plan.billing_period_unit == 'year':
sentence = _('per %d years', value) if value > 1 else _('per year')
else:
raise ValueError(f"Invalid Billing Period Unit {plan.billing_period_unit!r}")
plan.billing_period_display_sentence = sentence
@api.depends('auto_close_limit')
def _compute_auto_close_limit_display(self):
for plan in self:
plan.auto_close_limit_display = self.env._('%s days', plan.auto_close_limit)