# -*- coding: utf-8 -*- ############################################################################# # # Cybrosys Technologies Pvt. Ltd. # # Copyright (C) 2022-TODAY Cybrosys Technologies() # Author: Cybrosys Techno Solutions() # # 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 . # ############################################################################# from odoo import _, api, models, fields, SUPERUSER_ID import datetime from odoo.exceptions import UserError from dateutil.relativedelta import relativedelta class SubscriptionPackageProductLine(models.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', 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 (%)") tax_id = fields.Many2many('account.tax', string="Taxes", related='product_id.taxes_id', ondelete='restrict', readonly=False) price_total = fields.Monetary(store=True, readonly=True) price_tax = fields.Monetary(store=True, readonly=True) 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='Currency', store=True, related='subscription_id.partner_id') @api.depends('product_qty', 'unit_price', 'discount', 'tax_id', 'currency_id') def _compute_total_amount(self): """ Calculate subtotal amount of product line """ for line in self: price = line.unit_price * (1 - (line.discount or 0.0) / 100.0) taxes = line.tax_id._origin.compute_all( price, line.subscription_id._origin.currency_id, line.product_qty, product=line.product_id, partner=line.subscription_id._origin.partner_id) line.write({ 'price_tax': sum( t.get('amount', 0.0) for t in taxes.get('taxes', [])), 'price_total': taxes['total_included'], 'total_amount': taxes['total_excluded'], }) class SubscriptionPackage(models.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='Period Start Date', store=True, ondelete='restrict') date_started = fields.Date(string='Subsciption Start date', store=True, ondelete='restrict', readonly=True) next_invoice_date = fields.Date(string='Next Invoice Date', store=False, readonly=False, 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.analytic.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', store=True) 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='Untaxed Amount', compute='_compute_total_recurring_price', store=True) tax_total = fields.Float("Taxes", readonly=True) total_with_tax = fields.Monetary("Total Recurring Price", readonly=True, store=True) @api.depends('invoice_count') def _compute_invoice_count(self): """ Calculate Invoice count based on subscription package """ 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( [('subscription_id', '=', self.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.depends('start_date') def _compute_next_invoice_date(self): pending_subscriptions = self.env['subscription.package'].search( [('stage_category', '=', 'progress')]) for sub in pending_subscriptions: if sub.start_date: sub.next_invoice_date = sub.start_date + relativedelta( days=sub.plan_id.renewal_time) else: sub.next_invoice_date = None 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': [('subscription_id', '=', self.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_payment(self): """ Button to create invoice for subscription package""" this_products_line = [] for rec in self.product_line_ids: rec_list = [0, 0, {'product_id': rec.product_id.id, 'quantity': rec.product_qty}] this_products_line.append(rec_list) invoices = self.env['account.move'].search( [('subscription_id', '=', self.id), ('state', '=', 'draft')]) orders = self.env['sale.order'].search( [('subscription_id', '=', self.id), ('invoice_status', '=', 'no')]) if invoices: for invoice in invoices: invoice.action_post() if orders and invoices: for order in orders: order.action_confirm() for invoice in invoices: invoice.action_post() out_invoice = self.env['account.move'].create( { 'move_type': 'out_invoice', 'date': fields.Date.today(), 'invoice_date': fields.Date.today(), 'partner_id': self.partner_invoice_id.id, 'currency_id': self.partner_invoice_id.currency_id.id, 'invoice_line_ids': this_products_line }) self.env['account.move'].payment_id = out_invoice.id if self.stage_category == 'progress': values = {'start_date': datetime.datetime.today()} self.write(values) return { 'name': 'Subscription Payment', 'type': 'ir.actions.act_window', 'res_model': 'account.move', 'view_mode': 'form', 'res_id': out_invoice.id } def button_start_date(self): """Button to start subscription package""" stage_id = (self.env['subscription.package.stage'].search([ ('category', '=', 'progress')], limit=1).id) 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: if not rec.product_line_ids: raise UserError("Empty order lines !! Please add the " "subscription product.") else: rec.write( {'stage_id': stage_id, 'date_started': fields.Date.today(), 'start_date': fields.Date.today()}) 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( [('subscription_id', '=', self.id), ('invoice_status', '=', 'no')]) if orders: for order in orders: order.action_confirm() so_id = self.env['sale.order'].create({ '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 def create(self, vals): """It displays subscription product in partner and generate sequence""" 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(SubscriptionPackage, self).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 """ plan_id = self.env['subscription.package.plan'].search( [('id', '=', self.plan_id.id)]) if plan_id.short_code and self.reference_code: self.name = plan_id.short_code + '/' + self.reference_code + '-' + self.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 find_renew_date(self, next_invoice, date_started, end): if end == 0: end_date = next_invoice difference = (next_invoice - date_started).days / 10 renew_date = next_invoice - relativedelta( days=difference) close_date = next_invoice else: end_date = fields.Date.add(date_started, days=end) close = date_started + relativedelta(days=end) difference = (close - date_started).days / 10 renew_date = close - relativedelta( days=difference) close_date = close data = {'renew_date': renew_date, 'end_date': end_date, 'close_date': close_date} return data 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() # today_date = datetime.datetime.strptime('05102023', '%d%m%Y').date() pending_subscription = False for pending_subscription in pending_subscriptions: get_dates = self.find_renew_date( pending_subscription.next_invoice_date, pending_subscription.date_started, pending_subscription.plan_id.days_to_end) renew_date = get_dates['renew_date'] end_date = get_dates['end_date'] print(renew_date) pending_subscription.close_date = get_dates['close_date'] if (today_date == end_date) and ( pending_subscription.plan_id.limit_choice != 'manual'): display_msg = ("
The renewal limit has been exceeded " "today for this subscription based on the " "current subscription plan.
") pending_subscription.message_post(body=display_msg) pending_subscription.is_closed = True reason = (self.env['subscription.package.stop'].search([ ('name', '=', 'Renewal Limit Exceeded')]).id) pending_subscription.close_reason = reason pending_subscription.closed_by = self.user_id pending_subscription.close_date = fields.Date.today() stage = (self.env['subscription.package.stage'].search([ ('category', '=', 'closed')]).id) values = {'stage_id': stage, 'to_renew': False} pending_subscription.write(values) if today_date == pending_subscription.next_invoice_date: 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, 'price_unit': rec.unit_price, 'discount': rec.discount, 'tax_ids': rec.tax_id}] this_products_line.append(rec_list) self.env['account.move'].create( { 'move_type': 'out_invoice', 'date': today_date, 'invoice_date': today_date, 'state': 'draft', 'subscription_id': pending_subscription.id, '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._compute_invoice_count() pending_subscription.write({'to_renew': False, 'start_date': pending_subscription.next_invoice_date}) new_date = self.find_renew_date( pending_subscription.next_invoice_date, pending_subscription.date_started, pending_subscription.plan_id.days_to_end) pending_subscription.write( {'close_date': new_date['close_date']}) if today_date == new_date['renew_date']: self.env.ref( 'subscription_package' '.mail_template_subscription_renew').send_mail( pending_subscription.id, force_send=True) pending_subscription.write({'to_renew': True}) if today_date == renew_date: self.env.ref( 'subscription_package.mail_template_subscription_renew').send_mail( pending_subscription.id, force_send=True) pending_subscription.write({'to_renew': True}) return dict(pending=pending_subscription) @api.depends('product_line_ids.total_amount', 'product_line_ids.price_total', 'product_line_ids.tax_id') def _compute_total_recurring_price(self): """ Calculate recurring price """ for record in self: total_recurring = 0 total_tax = 0.0 for line in record.product_line_ids: if line.total_amount != line.price_total: line_tax = line.price_total - line.total_amount total_tax += line_tax total_recurring += line.total_amount record['total_recurring_price'] = total_recurring record['tax_total'] = total_tax total_with_tax = total_recurring + total_tax record['total_with_tax'] = total_with_tax def action_renew(self): return self.button_sale_order()