Skip to content

Commit

Permalink
[ADD] academic_sale_subscription: add new module
Browse files Browse the repository at this point in the history
  • Loading branch information
lef-adhoc committed Aug 27, 2024
1 parent 915b541 commit a3fc487
Show file tree
Hide file tree
Showing 12 changed files with 330 additions and 0 deletions.
70 changes: 70 additions & 0 deletions academic_sale_subscription/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
.. |company| replace:: ADHOC SA

.. |company_logo| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-logo.png
:alt: ADHOC SA
:target: https://www.adhoc.com.ar

.. |icon| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-icon.png

.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: https://www.gnu.org/licenses/agpl
:alt: License: AGPL-3

==========================
Academic Sale Subscription
==========================

This module extends the standard Odoo subscription management capabilities to cater specifically to academic institutions.

Installation
============

To install this module, you need to:

#. Only need to install the module

Configuration
=============

To configure this module, you need to:

#. Nothing to configure

Usage
=====

To use this module, you need to:

#. Just use

.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: http://runbot.adhoc.com.ar/

Bug Tracker
===========

Bugs are tracked on `GitHub Issues
<https://github.com/ingadhoc/{project_repo}/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.

Credits
=======

Images
------

* |company| |icon|

Contributors
------------

Maintainer
----------

|company_logo|

This module is maintained by the |company|.

To contribute to this module, please visit https://www.adhoc.com.ar.
4 changes: 4 additions & 0 deletions academic_sale_subscription/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# © 2016 ADHOC SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models
from . import wizard
24 changes: 24 additions & 0 deletions academic_sale_subscription/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# © 2016 ADHOC SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'Academic Sale Subscription',
'version': "16.0.1.0.0",
'sequence': 14,
'summary': '',
'author': 'ADHOC SA',
'website': 'www.adhoc.com.ar',
'license': 'AGPL-3',
'depends': [
'academic',
'sale_subscription'
],
'data': [
'security/ir.model.access.csv',
'views/sale_order_template_views.xml',
'views/sale_order_views.xml',
'wizard/reenrollment_views.xml'
],
'installable': True,
'auto_install': False,
'application': False,
}
4 changes: 4 additions & 0 deletions academic_sale_subscription/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# © 2016 ADHOC SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import sale_order_template
from . import sale_order
75 changes: 75 additions & 0 deletions academic_sale_subscription/models/sale_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
##############################################################################
# For copyright and license notices, see __manifest__.py file in module root
# directory
##############################################################################
from odoo import models, fields, api
from datetime import datetime


class SaleOrder(models.Model):
_inherit = "sale.order"

renewal_order_id = fields.Many2one('sale.order', on_delete="set null", readonly=True)
renewal_state = fields.Selection(selection=[
('er', 'En proceso de rematriculación'),
('r', 'Rematricula'),
('nr', 'No rematricula'),
('sr', 'Sin proceso de rematriculación')],
compute="_compute_renewal_state",
store=True
)

@api.depends('renewal_order_id', 'renewal_order_id.state')
def _compute_renewal_state(self):
for rec in self:
if not rec.renewal_order_id:
rec.renewal_state = 'sr'
elif rec.renewal_order_id.state in ['sale', 'done']:
rec.renewal_state = 'r'
elif rec.renewal_order_id.state == 'cancel':
rec.renewal_state = 'nr'
else:
rec.renewal_state = 'er'

def action_open_reenrollment_wizard(self):
return {
'type': 'ir.actions.act_window',
'name': 'Re-enrollment Wizard',
'res_model': 'sale.order.reenrollment.wizard',
'view_mode': 'form',
'view_id': self.env.ref('academic_sale_subscription.sale_order_reenrollment_wizard_form').id,
'target': 'new',
'context': {'active_ids': self.env.context.get('active_ids', [])},
}

def generate_reenrollment(self):
sale_order_template_id = self.env.context.get('sale_order_template_id')
subscriptions = self.env['sale.order']
for subscription in self:
new_subscription = self.create({
'partner_id': subscription.partner_id.id,
'sale_order_template_id': sale_order_template_id,
})
new_subscription._onchange_sale_order_template_id()
new_subscription.order_line.filtered(lambda x: x.product_id and x.product_id.recurring_invoice).write({'product_uom_qty': 0})
subscription.renewal_order_id = new_subscription
subscriptions += new_subscription
return subscriptions

def action_confirm(self):
super().action_confirm()
for rec in self.filtered(lambda x: x.is_subscription and x.sale_order_template_id and x.sale_order_template_id.start_day):
rec.order_line.filtered(lambda x: x.product_id and x.product_id.recurring_invoice).write({'product_uom_qty': 1})

start_day = rec.sale_order_template_id.start_day
start_month = int(rec.sale_order_template_id.start_month)

today = fields.Date.today()
current_year = today.year

next_start_date = datetime(current_year, start_month, start_day).date()
if next_start_date < today:
next_start_date = datetime(current_year + 1, start_month, rec.sale_order_template_id.start_day)

rec.next_invoice_date = next_start_date
rec.start_date = next_start_date
45 changes: 45 additions & 0 deletions academic_sale_subscription/models/sale_order_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
##############################################################################
# For copyright and license notices, see __manifest__.py file in module root
# directory
##############################################################################
from odoo import fields, models, api
from odoo.exceptions import ValidationError
from datetime import datetime


class SaleOrderTemplate(models.Model):
_inherit = "sale.order.template"

start_day = fields.Integer()
start_month = fields.Selection(
selection=[
('1', 'January'),
('2', 'February'),
('3', 'March'),
('4', 'April'),
('5', 'May'),
('6', 'June'),
('7', 'July'),
('8', 'August'),
('9', 'September'),
('10', 'October'),
('11', 'November'),
('12', 'December')
]
)

@api.constrains('start_day', 'start_month')
def _check_valid_date(self):
for rec in self:
if not rec.start_month and not rec.start_day:
continue

if bool(rec.start_month) != bool(rec.start_day):
raise ValidationError("If a month is specified, a day must also be specified, and vice versa.")

try:
month = int(rec.start_month)
# Validate that the combination of month and day is correct, considering non-leap years
datetime(year=2023, month=month, day=rec.start_day)
except ValueError:
raise ValidationError("The combination of month and day is not valid. Please enter a correct date.")
2 changes: 2 additions & 0 deletions academic_sale_subscription/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_reenrollment_wizard,academic.group.user,model_sale_order_reenrollment_wizard,academic.group_user,1,1,1,1
13 changes: 13 additions & 0 deletions academic_sale_subscription/views/sale_order_template_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<odoo>
<record id="sale_subscription_template_view_form" model="ir.ui.view">
<field name="name">sale.order.template.form</field>
<field name="model">sale.order.template</field>
<field name="inherit_id" ref="sale_subscription.sale_subscription_template_view_form"/>
<field name="arch" type="xml">
<field name="recurrence_id" position="after">
<field name="start_day" attrs="{'invisible': [('recurrence_id', '=', False)]}"/>
<field name="start_month" attrs="{'invisible': [('recurrence_id', '=', False)]}"/>
</field>
</field>
</record>
</odoo>
43 changes: 43 additions & 0 deletions academic_sale_subscription/views/sale_order_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<odoo>
<record id="model_sale_order_action_reenrollment" model="ir.actions.server">
<field name="name">Re-enrollment</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="binding_model_id" ref="sale.model_sale_order"/>
<field name="binding_view_types">list,form</field>
<field name="state">code</field>
<field name="code">
if records:
action = records.action_open_reenrollment_wizard()
</field>
</record>

<record id="sale_subscription_order_view_form" model="ir.ui.view">
<field name="name">sale.subscription.order.form</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale_subscription.sale_subscription_order_view_form"/>
<field name="arch" type="xml">
<field name="partner_id" position="after">
<field name="renewal_order_id" attrs="{'invisible': ['|', ('is_subscription', '=', False), ('state', 'not in', ['sale', 'done'])]}"/>
<field name="renewal_state" attrs="{'invisible': ['|', ('is_subscription', '=', False), ('state', 'not in', ['sale', 'done'])]}" class="badge rounded-pill bg-primary text-white fs-6 oe_inline"/>
</field>
</field>
</record>

<record id="sale_subscription_view_search" model="ir.ui.view">
<field name="name">sale.order.search</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale_subscription.sale_subscription_view_search"/>
<field name="arch" type="xml">
<filter name="future" position="after">
<separator/>
<filter string="En proceso de rematriculación" name="renewal_state_er" domain="[('renewal_state', '=', 'er')]"/>
<filter string="Rematricula" name="renewal_state_r" domain="[('renewal_state', '=', 'r')]"/>
<filter string="No rematricula" name="renewal_state_nr" domain="[('renewal_state', '=', 'nr')]"/>
<filter string="Sin proceso de rematriculación" name="renewal_state_sr" domain="[('renewal_state', '=', 'sr')]"/>
</filter>
<group position="inside">
<filter string="Renewal State" name="group_by_renewal_state" context="{'group_by': 'renewal_state'}"/>
</group>
</field>
</record>
</odoo>
3 changes: 3 additions & 0 deletions academic_sale_subscription/wizard/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# © 2016 ADHOC SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import reenrollment
30 changes: 30 additions & 0 deletions academic_sale_subscription/wizard/reenrollment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
##############################################################################
# For copyright and license notices, see __manifest__.py file in module root
# directory
##############################################################################
from odoo import fields, models, _
from odoo.exceptions import ValidationError


class SaleOrderReenrollmentWizard(models.TransientModel):
_name = 'sale.order.reenrollment.wizard'

sale_order_template_id = fields.Many2one('sale.order.template', required=True)

def action_generate_reenrollment(self):
active_ids = self.env.context.get('active_ids', [])
sale_order_ids = self.env['sale.order'].browse(active_ids)

sale_orders_with_active_renewal = sale_order_ids.filtered(lambda x: x.renewal_state in ['er', 'r'])
if sale_orders_with_active_renewal:
sale_order_names = "\n- ".join(sale_orders_with_active_renewal.mapped('name'))
raise ValidationError(_("The following subscriptions already meet the conditions of having an active or validated renewal process:\n- %s") % sale_order_names)

subscriptions = sale_order_ids.with_context(sale_order_template_id=self.sale_order_template_id.id).generate_reenrollment()

action = self.env.ref('sale_subscription.sale_subscription_action').read()[0]
action.update({
'domain': [('id', 'in', subscriptions.ids)],
'views': sorted(action['views'], key=lambda v: v[1] != 'tree'), # show tree view first
})
return action
17 changes: 17 additions & 0 deletions academic_sale_subscription/wizard/reenrollment_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<odoo>
<record id="sale_order_reenrollment_wizard_form" model="ir.ui.view">
<field name="name">sale.order.reenrollment.wizard.form</field>
<field name="model">sale.order.reenrollment.wizard</field>
<field name="arch" type="xml">
<form>
<group>
<field name="sale_order_template_id"/>
</group>
<footer>
<button string="Generate" type="object" name="action_generate_reenrollment" class="oe_highlight"/>
<button string="Cancel" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
</record>
</odoo>

0 comments on commit a3fc487

Please sign in to comment.