181 lines
6.8 KiB
Python
181 lines
6.8 KiB
Python
from odoo import models, fields, api, _
|
|
from odoo.exceptions import UserError
|
|
from datetime import datetime, timedelta
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
|
|
class SubscriptionTemplate(models.Model):
|
|
_name = 'subscription.template'
|
|
_description = 'Subscription Template'
|
|
_order = 'sequence, name'
|
|
|
|
name = fields.Char(string='Template Name', required=True)
|
|
sequence = fields.Integer(string='Sequence', default=10)
|
|
interval_number = fields.Integer(string='Invoice Every', default=1, required=True)
|
|
interval_type = fields.Selection([
|
|
('days', 'Days'),
|
|
('weeks', 'Weeks'),
|
|
('months', 'Months'),
|
|
('years', 'Years'),
|
|
], string='Interval Type', default='months', required=True)
|
|
active = fields.Boolean(string='Active', default=True)
|
|
|
|
product_id = fields.Many2one('product.product', string='Product', required=True)
|
|
quantity = fields.Float(string='Quantity', default=1.0)
|
|
|
|
# FIX: Changed to Float because list_price is Float
|
|
price_unit = fields.Float(string='Price', related='product_id.list_price')
|
|
currency_id = fields.Many2one(related='product_id.currency_id')
|
|
|
|
description = fields.Text(string='Description')
|
|
|
|
def name_get(self):
|
|
result = []
|
|
for record in self:
|
|
name = f"{record.name} ({record.interval_number} {record.interval_type})"
|
|
result.append((record.id, name))
|
|
return result
|
|
|
|
|
|
class Subscription(models.Model):
|
|
_name = 'subscription.subscription'
|
|
_description = 'Subscription'
|
|
_inherit = ['mail.thread', 'mail.activity.mixin']
|
|
_order = 'create_date desc'
|
|
|
|
name = fields.Char(string='Reference', required=True, copy=False, readonly=True, default='New')
|
|
template_id = fields.Many2one('subscription.template', string='Subscription Template', required=True)
|
|
partner_id = fields.Many2one('res.partner', string='Customer', required=True)
|
|
company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.company)
|
|
|
|
# Dates
|
|
start_date = fields.Date(string='Start Date', required=True, default=fields.Date.context_today)
|
|
next_invoice_date = fields.Date(string='Next Invoice Date', required=True, copy=False)
|
|
end_date = fields.Date(string='End Date')
|
|
last_invoice_date = fields.Date(string='Last Invoice Date', copy=False)
|
|
|
|
# Status
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('active', 'Active'),
|
|
('closed', 'Closed'),
|
|
('cancelled', 'Cancelled'),
|
|
], string='Status', default='draft', tracking=True, copy=False)
|
|
|
|
# Pricing
|
|
currency_id = fields.Many2one(related='template_id.currency_id')
|
|
|
|
# FIX: Changed to Float to match template
|
|
price_unit = fields.Float(related='template_id.price_unit')
|
|
|
|
# FIX: Changed to Float
|
|
quantity = fields.Float(related='template_id.quantity')
|
|
recurring_total = fields.Float(string='Recurring Price', compute='_compute_recurring_total', store=True)
|
|
|
|
# Invoicing
|
|
invoice_count = fields.Integer(string='Invoice Count', compute='_compute_invoice_count')
|
|
invoice_ids = fields.One2many('account.move', 'subscription_id', string='Invoices')
|
|
|
|
# Additional info
|
|
note = fields.Text(string='Notes')
|
|
active = fields.Boolean(string='Active', default=True)
|
|
|
|
@api.model
|
|
def create(self, vals):
|
|
if vals.get('name', 'New') == 'New':
|
|
vals['name'] = self.env['ir.sequence'].next_by_code('subscription.subscription') or 'New'
|
|
return super().create(vals)
|
|
|
|
def _compute_recurring_total(self):
|
|
for sub in self:
|
|
sub.recurring_total = sub.price_unit * sub.quantity
|
|
|
|
def _compute_invoice_count(self):
|
|
for sub in self:
|
|
sub.invoice_count = len(sub.invoice_ids)
|
|
|
|
def action_start_subscription(self):
|
|
self.write({
|
|
'state': 'active',
|
|
'next_invoice_date': self.start_date
|
|
})
|
|
return True
|
|
|
|
def action_close_subscription(self):
|
|
self.write({'state': 'closed'})
|
|
return True
|
|
|
|
def action_cancel_subscription(self):
|
|
self.write({'state': 'cancelled'})
|
|
return True
|
|
|
|
def action_draft_subscription(self):
|
|
self.write({'state': 'draft'})
|
|
return True
|
|
|
|
def action_view_invoices(self):
|
|
self.ensure_one()
|
|
return {
|
|
'name': _('Invoices'),
|
|
'type': 'ir.actions.act_window',
|
|
'res_model': 'account.move',
|
|
'view_mode': 'tree,form',
|
|
'domain': [('subscription_id', '=', self.id)],
|
|
'context': {'default_subscription_id': self.id}
|
|
}
|
|
|
|
def generate_invoice(self):
|
|
"""Generate invoice for active subscriptions"""
|
|
for sub in self:
|
|
if sub.state != 'active':
|
|
continue
|
|
|
|
if sub.end_date and sub.next_invoice_date > sub.end_date:
|
|
sub.write({'state': 'closed'})
|
|
continue
|
|
|
|
# Create invoice
|
|
invoice_vals = {
|
|
'move_type': 'out_invoice',
|
|
'partner_id': sub.partner_id.id,
|
|
'company_id': sub.company_id.id,
|
|
'currency_id': sub.currency_id.id,
|
|
'subscription_id': sub.id,
|
|
'invoice_date': sub.next_invoice_date,
|
|
'invoice_origin': f"Subscription: {sub.name}",
|
|
'invoice_line_ids': [(0, 0, {
|
|
'product_id': sub.template_id.product_id.id,
|
|
'name': sub.template_id.description or sub.template_id.product_id.name,
|
|
'quantity': sub.quantity,
|
|
'price_unit': sub.price_unit,
|
|
'account_id': sub.template_id.product_id.property_account_income_id.id or
|
|
sub.template_id.product_id.categ_id.property_account_income_categ_id.id,
|
|
})]
|
|
}
|
|
|
|
invoice = self.env['account.move'].create(invoice_vals)
|
|
|
|
# Update subscription
|
|
next_date = sub.next_invoice_date
|
|
if sub.template_id.interval_type == 'days':
|
|
next_date += timedelta(days=sub.template_id.interval_number)
|
|
elif sub.template_id.interval_type == 'weeks':
|
|
next_date += timedelta(weeks=sub.template_id.interval_number)
|
|
elif sub.template_id.interval_type == 'months':
|
|
next_date += relativedelta(months=sub.template_id.interval_number)
|
|
elif sub.template_id.interval_type == 'years':
|
|
next_date += relativedelta(years=sub.template_id.interval_number)
|
|
|
|
sub.write({
|
|
'last_invoice_date': sub.next_invoice_date,
|
|
'next_invoice_date': next_date,
|
|
})
|
|
|
|
return True
|
|
|
|
|
|
class AccountMove(models.Model):
|
|
_inherit = 'account.move'
|
|
|
|
subscription_id = fields.Many2one('subscription.subscription', string='Subscription', ondelete='set null',
|
|
copy=False) |