Skip to content

[IMP] academic: enrollment development #256

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 43 additions & 43 deletions academic/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,50 +37,50 @@
"contacts",
"report_aeroo",
],
"data": [
"security/academic_security.xml",
"security/ir.model.access.csv",
"data/res_partner_role_data.xml",
"data/res_users_data.xml",
"data/ir_cron.xml",
"security/res_partner_category.xml",
"views/academic_menuitem.xml",
"views/res_partner_views.xml",
"views/academic_group_views.xml",
"views/academic_division_views.xml",
"views/academic_level_views.xml",
"views/academic_study_plan_views.xml",
"views/academic_promotion_views.xml",
"views/academic_section_views.xml",
"views/academic_subject_views.xml",
"views/hr_views.xml",
"views/res_users_views.xml",
"views/res_company_views.xml",
"views/login_page.xml",
"views/sale_order_views.xml",
"views/res_partner_link_views.xml",
"views/res_partner_relationship_views.xml",
"views/account_move_views.xml",
"wizards/portal_wizard_views.xml",
"report/ir_actions_report.xml",
"views/res_partner_category.xml",
"views/account_portal_templates.xml",
"report/report_invoice.xml",
'depends': [
'portal_backend',
'crm',
'board',
'hr',
'website',
'board',
'sale_management',
'account',
'contacts',
'report_aeroo',
],
"demo": [
"demo/res_partner_relationship_demo.xml",
"demo/res_partner_demo.xml",
"demo/academic.subject.csv",
"demo/academic.section.csv",
"demo/academic.level.csv",
"demo/academic.promotion.csv",
"demo/res.partner.csv",
"demo/res.partner.link.csv",
"demo/academic.division.csv",
"demo/academic.study.plan.csv",
"demo/res_company_demo.xml",
"demo/academic_group.xml",
"demo/res_users_demo.xml",
'data': [
'security/academic_security.xml',
'security/ir.model.access.csv',
'data/res_partner_role_data.xml',
'data/res_users_data.xml',
'data/ir_cron.xml',
'security/res_partner_category.xml',
'views/academic_menuitem.xml',
'views/res_partner_views.xml',
'views/academic_group_views.xml',
'views/academic_division_views.xml',
'views/academic_level_views.xml',
'views/academic_study_plan_views.xml',
'views/academic_promotion_views.xml',
'views/academic_section_views.xml',
'views/academic_subject_views.xml',
'views/hr_views.xml',
'views/res_users_views.xml',
'views/res_company_views.xml',
'views/login_page.xml',
'views/sale_order_views.xml',
'views/crm_lead_view.xml',
'views/res_partner_link_views.xml',
'views/res_partner_relationship_views.xml',
'views/account_move_views.xml',
'wizards/portal_wizard_views.xml',
'report/ir_actions_report.xml',
'views/res_partner_category.xml',
'views/account_portal_templates.xml',
'views/academic_group_link.xml',
'views/product_template_views.xml',
'report/report_invoice.xml',
],
"installable": True,
"auto_install": False,
Expand Down
6 changes: 3 additions & 3 deletions academic/demo/academic_group.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<field name="subject_id" ref="academic_subject_1"/>
<field name="teacher_id" ref="res_partner_ignacio_rodriguez"/>
<field name="year" eval="str(datetime.now().year)"/>
<field name="student_ids" eval="[(6, 0, [ref('res_partner_alvaro_diaz'), ref('res_partner_jose_martin_rodriguez'), ref('res_partner_bautista')])]"/>
<!-- <field name="student_ids" eval="[(6, 0, [ref('res_partner_alvaro_diaz'), ref('res_partner_jose_martin_rodriguez'), ref('res_partner_bautista')])]"/> -->
</record>

<record id="academic_group_2" model="academic.group">
Expand All @@ -16,14 +16,14 @@
<field name="subject_id" ref="academic_subject_2"/>
<field name="teacher_id" ref="res_partner_laura_sali"/>
<field name="year" eval="str(datetime.now().year)"/>
<field name="student_ids" eval="[(6, 0, [ref('res_partner_martin_perez'), ref('res_partner_luz'), ref('res_partner_martina')])]"/>
<!-- <field name="student_ids" eval="[(6, 0, [ref('res_partner_martin_perez'), ref('res_partner_luz'), ref('res_partner_martina')])]"/> -->
</record>

<record id="academic_group_3" model="academic.group">
<field name="level_id" ref="academic_course_3"/>
<field name="division_id" ref="academic_division_3"/>
<field name="company_id" ref="base.main_company"/>
<field name="year" eval="str(datetime.now().year)"/>
<field name="student_ids" eval="[(6, 0, [ref('res_partner_mateo'), ref('res_partner_amalia'), ref('res_partner_ignacio')])]"/>
<!-- <field name="student_ids" eval="[(6, 0, [ref('res_partner_mateo'), ref('res_partner_amalia'), ref('res_partner_ignacio')])]"/> -->
</record>
</odoo>
4 changes: 4 additions & 0 deletions academic/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
from . import res_partner_role
from . import res_partner_link
from . import sale_order
from . import crm_lead
from . import sale_order_line
from . import account_move
from . import account_move_line
from . import res_partner_category
from . import academic_group_link
from . import product_template
104 changes: 57 additions & 47 deletions academic/models/academic_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@
# For copyright and license notices, see __manifest__.py file in module root
# directory
##############################################################################
import random
import string
from datetime import date

from odoo import api, fields, models
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError


class AcademicGroup(models.Model):
Expand Down Expand Up @@ -57,21 +56,22 @@ class AcademicGroup(models.Model):
context={"default_partner_type": "teacher"},
domain=[("partner_type", "=", "teacher")],
)
student_ids = fields.Many2many(
"res.partner",
"academic_student_group_ids_student_ids_rel",
"group_id",
"partner_id",
string="Student",
context={"default_partner_type": "student"},
domain=[("partner_type", "=", "student")],
academic_group_link_ids = fields.One2many(
'academic.group.link',
'group_id',
string='Students',
)
name = fields.Char(
compute='_compute_name',
store=True
)
name = fields.Char(compute="_compute_name", store=True)
active = fields.Boolean(default=True)
student_ids_count = fields.Integer(
string="Student Count",
compute="_compute_student_ids_count",
)
active_student_count = fields.Integer(compute='_compute_student_count')
enrolling_student_count = fields.Integer(compute='_compute_student_count')
prospect_student_count = fields.Integer(compute='_compute_student_count')
capacity = fields.Integer()
vacancies = fields.Integer(compute="_compute_vacancies", store=True)

@api.depends("company_id", "level_id", "division_id", "year")
def _compute_name(self):
Expand All @@ -85,34 +85,17 @@ def _compute_name(self):
]
line.name = " - ".join(filter(None, name_parts))

def create_students_users(self):
"""
This function create users if they don't exist for students related
to this group.
"""
self.student_ids.quickly_create_portal_user()
# Creamos contrasenas para todos los students que no tengan una
# explicita (no hashed)
for user in self.student_ids.mapped("user_ids").filtered(lambda x: not x.password):
user.password = "".join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6))

def print_users(self):
"""
This function prints a report with users login and password.
"""
self.ensure_one()
self.create_students_users()
report = (
self.env["ir.actions.report"]
.search([("report_name", "=", "academic.template_report_users")], limit=1)
.report_action(self)
)
return report
@api.depends('academic_group_link_ids')
def _compute_student_count(self):
for group in self:
group.active_student_count = len(group.academic_group_link_ids.filtered(lambda x: x.status in ['active', 'enrolled']))
group.enrolling_student_count = len(group.academic_group_link_ids.filtered(lambda x: x.status in ['enrolling']))
group.prospect_student_count = len(group.academic_group_link_ids.filtered(lambda x: x.status in ['prospect']))

@api.depends("student_ids")
def _compute_student_ids_count(self):
@api.depends('active_student_count', 'capacity')
def _compute_vacancies(self):
for group in self:
group.student_ids_count = len(group.student_ids)
group.vacancies = group.capacity - group.active_student_count

def create_next_year_groups(self):
# estamos pasando de un año a otro sin usar study plan por lo siguiente:
Expand All @@ -131,12 +114,9 @@ def create_next_year_groups(self):
)

if not next_group:
next_group = rec.copy(
default={
"year": rec.year + 1,
"student_ids": False,
}
)
next_group = rec.copy(default={
'year': rec.year + 1,
})

def open_student_view(self):
action = self.env.ref("academic.action_academic_partner_students").read()[0]
Expand All @@ -148,3 +128,33 @@ def open_student_view(self):
}
)
return action

@api.constrains('vacancies')
def _check_vacancies(self):
if self.filtered(lambda x: x.vacancies < 0):
raise ValidationError(_('There can be no negative vacancies. Increase group capacity.'))

def open_specific_student(self):
action = self.env.ref('academic.action_academic_partner_students').read()[0]
domain = [('academic_group_link_ids.group_id', '=', self.id)]
if type := self.env.context.get('type'):
if type == 'active':
domain += [('academic_group_link_ids.status', 'in', ['active', 'enrolled'])]
elif type == 'enrolling':
domain += [('academic_group_link_ids.status', 'in', ['enrolling'])]
else:
domain += [('academic_group_link_ids.status', 'in', ['prospect'])]

action.update({
'domain': domain,
'views': [(False, 'tree'), (False, 'form')],
'context': {'from_open_student_view': True}
})
return action

@api.constrains('academic_group_link_ids')
def _check_unique_student_in_group(self):
for rec in self:
students = rec.academic_group_link_ids.mapped('student_id')
if len(rec.academic_group_link_ids.filtered('student_id')) != len(students):
raise ValidationError(_('There cannot be a repeated student in a group.'))
79 changes: 79 additions & 0 deletions academic/models/academic_group_link.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
##############################################################################
# For copyright and license notices, see __manifest__.py file in module root
# directory
##############################################################################
from odoo import api, fields, models
from odoo.exceptions import UserError


class AcademicGroupLink(models.Model):
_name = 'academic.group.link'
_description = 'Academic Group Link'

group_id = fields.Many2one('academic.group', required=True, ondelete='cascade')
lead_id = fields.Many2one('crm.lead')
student_id = fields.Many2one('res.partner', domain=[('partner_type', '=', 'student')], compute='_compute_student_id', store=True)
registration_so_line_id = fields.Many2one('sale.order.line')
main_so_line_id = fields.Many2one('sale.order.line')
status = fields.Selection(
selection=[
('prospect', 'Prospect'),
('enrolling', 'Enrolling'),
('not_enrolled', 'Not Enrolled'),
('enrolled', 'Enrolled'),
('active', 'Active'),
('inactive', 'Inactive'),
],
compute="_compute_status",
store=True
)
display_name = fields.Char(compute='_compute_display_name')

@api.ondelete(at_uninstall=False)
def _unlink_check_sale_line(self):
for rec in self:
if rec.main_so_line_id:
raise UserError('You cannot delete the link. You must first unlink the main line.')
elif rec.registration_so_line_id:
raise UserError('You cannot delete the link. You must first unlink the registration line.')

@api.depends('registration_so_line_id.state', 'main_so_line_id.state', 'lead_id')
def _compute_status(self):
for rec in self:
reg_line = rec.registration_so_line_id
main_line = rec.main_so_line_id
lead = rec.lead_id

if main_line:
state = main_line.order_id.state
if state == 'sale':
rec.status = 'active'
continue
elif state == 'cancel':
rec.status = 'inactive'
continue

if reg_line:
state = reg_line.order_id.state
if state in ['draft', 'sent']:
rec.status = 'enrolling'
elif state == 'sale':
rec.status = 'enrolled'
elif state == 'cancel':
rec.status = 'not_enrolled'
continue

if lead:
rec.status = 'prospect'
else:
rec.status = False

def _compute_display_name(self):
for rec in self:
rec.display_name = rec.student_id.name or rec.lead_id.partner_id.name or rec.lead_id.contact_name or rec.lead_id.name

@api.depends('lead_id', 'lead_id.partner_id')
def _compute_student_id(self):
for rec in self:
if rec.lead_id and not rec.registration_so_line_id and not rec.main_so_line_id:
rec.student_id = rec.lead_id.partner_id
36 changes: 36 additions & 0 deletions academic/models/crm_lead.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
##############################################################################
# For copyright and license notices, see __manifest__.py file in module root
# directory
##############################################################################
from odoo import api, fields, models


class CrmLead(models.Model):
_inherit = "crm.lead"

group_id = fields.Many2one('academic.group', compute='_compute_group', inverse='_inverse_group')

def _compute_group(self):
for rec in self:
rec.group_id = rec.env['academic.group.link'].search([('lead_id', '=', rec.id)], limit=1).group_id

def _inverse_group(self):
for rec in self:
if not rec.group_id:
rec.env['academic.group.link'].search([('lead_id', '=', rec.id)], limit=1).unlink()

if rec.group_id:
link = rec.env['academic.group.link'].search([('group_id', '=', rec.group_id.id), ('student_id', '=', rec.partner_id.id)], limit=1)
if link:
link.group_id = rec.group_id.id
else:
rec.group_id.academic_group_link_ids = [(0, 0, {'student_id': rec.partner_id.id, 'lead_id': rec.id})]

@api.ondelete(at_uninstall=False)
def _prevent_unlink_if_group_link_protected(self):
""" Al eliminar un lead, se intenta desvincular el group link asociado.
Si el vínculo con el grupo (group link) ya avanzó a un estado protegido
(por ejemplo, con suscripciones activas u otras restricciones), el propio
modelo `academic.group.link` bloqueará el borrado gracias a sus restricciones
"""
self.filtered('group_id').group_id = False
Loading
Loading