You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

383 lines
18 KiB

# -*- coding: utf-8 -*-
#############################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2022-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
#
# You can modify it under the terms of the GNU LESSER
# GENERAL PUBLIC LICENSE (LGPL v3), Version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# (LGPL v3) along with this program.
# If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
import datetime
from dateutil.relativedelta import relativedelta
from odoo import _, api, models, fields, SUPERUSER_ID
from odoo.exceptions import UserError
class SubscriptionPackageProductLine(models.Model):
"""Subscription Package Product Line Model"""
_name = 'subscription.package.product.line'
_description = 'Subscription Product Lines'
subscription_id = fields.Many2one('subscription.package', store=True,
string='Subscription')
company_id = fields.Many2one('res.company', string='Company', store=True,
related='subscription_id.company_id')
create_date = fields.Datetime(string='Create date', store=True,
default=fields.Datetime.now)
user_id = fields.Many2one('res.users', string='Salesperson', store=True,
related='subscription_id.user_id')
product_id = fields.Many2one('product.product', string='Product',
store=True, ondelete='restrict',
domain=[('is_subscription', '=', True)])
product_qty = fields.Float(string='Quantity', store=True, default=1.0)
product_uom_id = fields.Many2one('uom.uom', string='UoM', store=True,
related='product_id.uom_id',
ondelete='restrict')
uom_catg_id = fields.Many2one('uom.category', string='UoM Category',
store=True,
related='product_id.uom_id.category_id')
unit_price = fields.Float(string='Unit Price', store=True, readonly=False,
related='product_id.list_price')
discount = fields.Float(string="Discount (%)")
currency_id = fields.Many2one('res.currency', string='Currency',
store=True,
related='subscription_id.currency_id')
total_amount = fields.Monetary(string='Subtotal', store=True,
compute='_compute_total_amount')
sequence = fields.Integer('Sequence', help="Determine the display order",
index=True)
res_partner_id = fields.Many2one('res.partner', string='Partner',
store=True,
related='subscription_id.partner_id')
@api.depends('product_qty', 'unit_price', 'discount')
def _compute_total_amount(self):
""" Calculate subtotal amount of product line """
for rec in self:
if rec.product_id:
rec.total_amount = rec.unit_price * rec.product_qty
if rec.discount != 0:
rec.total_amount -= rec.total_amount * (rec.discount / 100)
def _valid_field_parameter(self, field, name):
if name == 'ondelete':
return True
return super(SubscriptionPackageProductLine,
self)._valid_field_parameter(field, name)
class SubscriptionPackage(models.Model):
"""Subscription Package Model"""
_name = 'subscription.package'
_description = 'Subscription Package'
_rec_name = 'name'
_inherit = ['mail.thread', 'mail.activity.mixin']
@api.model
def _read_group_stage_ids(self, categories, domain, order):
""" Read all the stages and display it in the kanban view,
even if it is empty."""
category_ids = categories._search([], order=order,
access_rights_uid=SUPERUSER_ID)
return categories.browse(category_ids)
def _default_stage_id(self):
"""Setting default stage"""
rec = self.env['subscription.package.stage'].search([], limit=1,
order='sequence ASC')
return rec.id if rec else None
name = fields.Char(string='Name', default="New", compute='_compute_name',
store=True, required=True)
partner_id = fields.Many2one('res.partner', string='Customer', store=True)
partner_invoice_id = fields.Many2one('res.partner',
string='Invoice Address',
related='partner_id', readonly=False)
partner_shipping_id = fields.Many2one('res.partner',
string='Shipping/Service Address',
related='partner_id',
readonly=False)
plan_id = fields.Many2one('subscription.package.plan',
string='Subscription Plan')
start_date = fields.Date(string='Start Date', store=True,
ondelete='restrict')
next_invoice_date = fields.Date(string='Next Invoice Date', readonly=False,
store=True,
compute="_compute_next_invoice_date")
company_id = fields.Many2one('res.company', string='Company',
default=lambda self: self.env.company,
required=True)
user_id = fields.Many2one('res.users', string='Sales Person',
default=lambda self: self.env.user)
sale_order = fields.Many2one('sale.order', string="Sale Order")
to_renew = fields.Boolean(string='To Renew', default=False)
tag_ids = fields.Many2many('account.account.tag', string='Tags')
stage_id = fields.Many2one('subscription.package.stage', string='Stage',
default=lambda self: self._default_stage_id(),
index=True,
group_expand='_read_group_stage_ids')
invoice_count = fields.Integer(string='Invoices',
compute='_compute_invoice_count')
so_count = fields.Integer(string='Sales', compute='_compute_sale_count')
description = fields.Text(string='Description')
analytic_account_id = fields.Many2one('account.analytic.account',
string='Analytic Account')
product_line_ids = fields.One2many('subscription.package.product.line',
'subscription_id', ondelete='restrict',
string='Products Line')
currency_id = fields.Many2one('res.currency', string='Currency',
readonly=True, default=lambda
self: self.env.company.currency_id)
current_stage = fields.Char(string='Current Stage', default='Draft',
store=True, compute='_compute_current_stage')
reference_code = fields.Char(string='Reference', store=True)
is_closed = fields.Boolean(string="Closed", default=False)
close_reason = fields.Many2one('subscription.package.stop',
string='Close Reason')
closed_by = fields.Many2one('res.users', string='Closed By')
close_date = fields.Date(string='Closed on')
stage_category = fields.Selection(related='stage_id.category', store=True)
invoice_mode = fields.Selection(related="plan_id.invoice_mode")
total_recurring_price = fields.Float(string='Recurring Price',
compute='_compute_total_recurring_price',
store=True)
def _valid_field_parameter(self, field, name):
if name == 'ondelete':
return True
return super(SubscriptionPackage,
self)._valid_field_parameter(field, name)
""" Calculate Invoice count based on subscription package """
@api.depends('invoice_count')
def _compute_invoice_count(self):
sale_id = self.env['sale.order'].search(
[('id', '=', self.sale_order.id)])
invoices = sale_id.order_line.invoice_lines.move_id.filtered(
lambda r: r.move_type in ('out_invoice', 'out_refund'))
invoices.write({'subscription_id': self.id})
invoice_count = self.env['account.move'].search_count(
[('subscription_id', '=', self.id)])
if invoice_count > 0:
self.invoice_count = invoice_count
else:
self.invoice_count = 0
@api.depends('so_count')
def _compute_sale_count(self):
""" Calculate sale order count based on subscription package """
self.so_count = self.env['sale.order'].search_count(
[('id', '=', self.sale_order.id)])
@api.depends('stage_id')
def _compute_current_stage(self):
""" It displays current stage for subscription package """
for rec in self:
rec.current_stage = rec.env['subscription.package.stage'].search(
[('id', '=', rec.stage_id.id)]).category
@api.constrains('start_date')
def _compute_next_invoice_date(self):
for sub in self.env['subscription.package'].search([]):
if sub.start_date:
sub.next_invoice_date = sub.start_date + relativedelta(
days=sub.plan_id.renewal_time)
def button_invoice_count(self):
""" It displays invoice based on subscription package """
return {
'name': 'Invoices',
'domain': [('subscription_id', '=', self.id)],
'view_type': 'form',
'res_model': 'account.move',
'view_mode': 'tree,form',
'type': 'ir.actions.act_window',
'context': {
"create": False
}
}
def button_sale_count(self):
""" It displays sale order based on subscription package """
return {
'name': 'Products',
'domain': [('id', '=', self.sale_order.id)],
'view_type': 'form',
'res_model': 'sale.order',
'view_mode': 'tree,form',
'type': 'ir.actions.act_window',
'context': {
"create": False
}
}
def button_close(self):
""" Button for subscription close wizard """
return {
'name': "Subscription Close Reason",
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'subscription.close.wizard',
'target': 'new'
}
def button_start_date(self):
"""Button to start subscription package"""
if not self.start_date:
self.start_date = datetime.date.today()
for rec in self:
if len(rec.env['subscription.package.stage'].search(
[('category', '=', 'draft')])) > 1:
raise UserError(
_('More than one stage is having category "Draft". '
'Please change category of stage to "In Progress", '
'only one stage is allowed to have category "Draft"'))
else:
rec.write(
{'stage_id': (rec.env[
'subscription.package.stage'].search([
('category', '=', 'draft')]).id) + 1})
def button_sale_order(self):
"""Button to create sale order"""
this_products_line = []
for rec in self.product_line_ids:
rec_list = [0, 0, {'product_id': rec.product_id.id,
'product_uom_qty': rec.product_qty,
'discount': rec.discount}]
this_products_line.append(rec_list)
orders = self.env['sale.order'].search(
[('id', '=', self.sale_order_count),
('invoice_status', '=', 'no')])
if orders:
for order in orders:
order.action_confirm()
so_id = self.env['sale.order'].create({
'id': self.sale_order_count,
'partner_id': self.partner_id.id,
'partner_invoice_id': self.partner_id.id,
'partner_shipping_id': self.partner_id.id,
'is_subscription': True,
'subscription_id': self.id,
'order_line': this_products_line
})
self.sale_order = so_id
return {
'name': _('Sales Orders'),
'type': 'ir.actions.act_window',
'res_model': 'sale.order',
'domain': [('id', '=', so_id.id)],
'view_mode': 'tree,form',
'context': {
"create": False
}
}
@api.model_create_multi
def create(self, vals_list):
"""It displays subscription product in partner and generate sequence"""
for vals in vals_list:
partner = self.env['res.partner'].search(
[('id', '=', vals.get('partner_id'))])
partner.active_subscription = True
if vals.get('reference_code', 'New') is False:
vals['reference_code'] = self.env['ir.sequence'].next_by_code(
'sequence.reference.code') or 'New'
create_id = super().create(vals)
return create_id
@api.depends('reference_code')
def _compute_name(self):
"""It displays record name as combination of short code, reference
code and partner name """
for rec in self:
plan_id = self.env['subscription.package.plan'].search(
[('id', '=', rec.plan_id.id)])
if plan_id.short_code and rec.reference_code:
rec.name = plan_id.short_code + '/' + rec.reference_code + '-' + rec.partner_id.name
def set_close(self):
""" Button to close subscription package """
stage = self.env['subscription.package.stage'].search(
[('category', '=', 'closed')], limit=1).id
for sub in self:
values = {'stage_id': stage, 'to_renew': False}
sub.write(values)
return True
def close_limit_cron(self):
""" It Checks renew date, close date. It will send mail when renew date """
pending_subscriptions = self.env['subscription.package'].search(
[('stage_category', '=', 'progress')])
today_date = fields.Date.today()
pending_subscription = False
close_subscription = False
for pending_subscription in pending_subscriptions:
if pending_subscription.start_date:
pending_subscription.close_date = pending_subscription.start_date + relativedelta(
days=pending_subscription.plan_id.days_to_end)
difference = (pending_subscription.close_date -
pending_subscription.start_date).days / 10
renew_date = pending_subscription.close_date - relativedelta(
days=difference)
if today_date == renew_date:
pending_subscription.to_renew = True
self.env.ref(
'subscription_package.mail_template_subscription_renew').send_mail(
pending_subscription.id, force_send=True)
if pending_subscription.plan_id.invoice_mode == 'draft_invoice':
this_products_line = []
for rec in pending_subscription.product_line_ids:
rec_list = [0, 0, {'product_id': rec.product_id.id,
'quantity': rec.product_qty}]
this_products_line.append(rec_list)
b = self.env['account.move'].create(
{
'move_type': 'out_invoice',
'subscription_id': pending_subscription.id,
'date': fields.Date.today(),
'invoice_date': fields.Date.today(),
'state': 'draft',
'partner_id': pending_subscription.partner_invoice_id.id,
'currency_id': pending_subscription.partner_invoice_id.currency_id.id,
'invoice_line_ids': this_products_line
})
pending_subscription.write({'to_renew': False,
'start_date': datetime.datetime.today()})
close_subscriptions = self.env['subscription.package'].search(
[('stage_category', '=', 'progress'), ('to_renew', '=', True)])
for close_subscription in close_subscriptions:
close_subscription.close_date = close_subscription.start_date + relativedelta(
days=close_subscription.plan_id.days_to_end)
if today_date == close_subscription.close_date:
close_subscription.set_close()
return dict(pending=pending_subscription, closed=close_subscription)
@api.depends('product_line_ids.total_amount')
def _compute_total_recurring_price(self):
""" Calculate recurring price """
for record in self:
total_recurring = 0
for line in record.product_line_ids:
total_recurring += line.total_amount
record['total_recurring_price'] = total_recurring
def action_renew(self):
return self.button_sale_order()