first push message

This commit is contained in:
2026-07-01 14:41:49 +07:00
parent 6667dec2bf
commit 58b5f46cc4
2951 changed files with 316619 additions and 0 deletions
+181
View File
@@ -0,0 +1,181 @@
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)