diff --git a/academic/__manifest__.py b/academic/__manifest__.py index 352a2153..f9d37f2b 100644 --- a/academic/__manifest__.py +++ b/academic/__manifest__.py @@ -42,8 +42,10 @@ 'security/ir.model.access.csv', 'views/academic_menuitem.xml', 'views/res_partner_views.xml', + 'wizards/group_move_student_wizard_views.xml', 'views/academic_group_views.xml', 'views/academic_division_views.xml', + 'views/academic_shift_views.xml', 'views/academic_level_views.xml', 'views/academic_study_plan_views.xml', 'views/academic_promotion_views.xml', @@ -71,6 +73,7 @@ 'demo/res.partner.csv', 'demo/res.partner.link.csv', 'demo/academic.division.csv', + 'demo/academic.shift.csv', 'demo/academic.study.plan.csv', 'demo/res_company_demo.xml', 'demo/academic_group.xml', diff --git a/academic/demo/academic.shift.csv b/academic/demo/academic.shift.csv new file mode 100644 index 00000000..91616776 --- /dev/null +++ b/academic/demo/academic.shift.csv @@ -0,0 +1,3 @@ +id,name +academic_shift_manana,Mañana +academic_shift_tarde,Tarde diff --git a/academic/demo/academic_group.xml b/academic/demo/academic_group.xml index f3c4eca3..7b080282 100644 --- a/academic/demo/academic_group.xml +++ b/academic/demo/academic_group.xml @@ -1,29 +1,55 @@ - + - - - - - + - - - - - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/academic/models/__init__.py b/academic/models/__init__.py index 239784aa..450d5048 100644 --- a/academic/models/__init__.py +++ b/academic/models/__init__.py @@ -2,6 +2,7 @@ # For copyright and license notices, see __manifest__.py file in module root # directory ############################################################################## +from . import academic_shift from . import academic_division from . import academic_group from . import academic_level diff --git a/academic/models/academic_group.py b/academic/models/academic_group.py index b41b8a96..b816e853 100644 --- a/academic/models/academic_group.py +++ b/academic/models/academic_group.py @@ -4,6 +4,7 @@ ############################################################################## from odoo import api, models, fields, _ from datetime import date +from odoo.exceptions import UserError import random import string @@ -11,47 +12,63 @@ class AcademicGroup(models.Model): _name = 'academic.group' _description = 'group' - _order = 'sequence' + _order = "company_id, year desc, level_id asc, division_id asc" _rec_names_search = ['level_id.name', 'level_id.section_id.name', 'division_id.name', 'year', 'subject_id.name'] _sql_constraints = [ ('group_unique', - 'unique(subject_id, company_id, level_id, year, division_id)', - 'Group should be unique per Institution, Subject,' - ' Course-Division and Year')] - - type = fields.Selection([ - ('student', 'Student'), - ('teacher', 'Teacher'), - ('administrator', 'Administrator'), - ('gral_administrator', 'gral_administrator'), - ('parent', 'Relative')] - ) + 'unique(subject_id, company_id, parent_id, shift_id, level_id, year, division_id)', + 'Group should be unique per Institution, Subject, Course-Division and Year')] + + # virtual_group = fields.Boolean() year = fields.Integer( required=True, default=date.today().year, + compute='_compute_year', + store=True, + readonly=False, + recursive=True, + precompute=True, ) division_id = fields.Many2one( 'academic.division', string='Division', ) + shift_id = fields.Many2one( + 'academic.shift', + string='Shift', + ) company_id = fields.Many2one( 'res.company', string='Company', required=True, context={'default_is_company': True}, - default=lambda self: self.env.company + default=lambda self: self.env.company, + compute='_compute_company', + store=True, + readonly=False, + recursive=True, + precompute=True, ) study_plan_level_ids = fields.Many2many(related='company_id.study_plan_id.level_ids') level_id = fields.Many2one( 'academic.level', string='Level', required=True, + compute='_compute_level', + store=True, + readonly=False, + recursive=True, + precompute=True, ) subject_id = fields.Many2one( 'academic.subject', string='Subject', required=False, + compute='_compute_subject', + store=True, + readonly=False, + recursive=True, ) teacher_id = fields.Many2one( 'res.partner', @@ -72,12 +89,49 @@ class AcademicGroup(models.Model): complete_name = fields.Char( compute='_compute_complete_name', ) + # TODO borrar en 18 si nadie se quejo de que lo sacamos de UI sequence = fields.Integer(help='Used to order Groups', default=10) active = fields.Boolean(default=True) student_ids_count = fields.Integer( string='Student Count', compute='_compute_student_ids_count', + store=True, + readonly=False, ) + capacity = fields.Integer(compute='_compute_capacity', store=True, readonly=False, recursive=True,) + vacancy = fields.Integer(compute='_compute_vacancy', store=True, readonly=False) + parent_id = fields.Many2one('academic.group',) + child_ids = fields.One2many('academic.group', 'parent_id') + + @api.depends('child_ids.capacity') + def _compute_capacity(self): + for rec in self.filtered('child_ids'): + rec.capacity = sum(rec.child_ids.mapped('capacity')) + + @api.depends('capacity', 'student_ids_count') + def _compute_vacancy(self): + for rec in self: + rec.vacancy = rec.capacity - rec.student_ids_count + + @api.depends('parent_id.level_id') + def _compute_level(self): + for rec in self.filtered('parent_id'): + rec.level_id = rec.parent_id.level_id + + @api.depends('parent_id.company_id') + def _compute_company(self): + for rec in self.filtered('parent_id'): + rec.company_id = rec.parent_id.company_id + + @api.depends('parent_id.subject_id') + def _compute_subject(self): + for rec in self.filtered('parent_id'): + rec.subject_id = rec.parent_id.subject_id + + @api.depends('parent_id.year') + def _compute_year(self): + for rec in self.filtered('parent_id'): + rec.year = rec.parent_id.year def _compute_display_name(self): for rec in self.filtered('complete_name'): @@ -90,25 +144,20 @@ def _compute_display_name(self): 'division_id', 'year') def _compute_complete_name(self): - """ Forms complete name of location from parent location to - child location. - @return: Dictionary of values - """ for line in self: - name = line.company_id.name - name += ', ' + line.level_id.name + name = line.company_id.name or '' + name += ', {}'.format(line.level_id.name or '') + if line.division_id: - name += ' ' + line.division_id.name - name += ' - ' + line.level_id.section_id.name - name += ' - ' + _('Year: ') + str(line.year) - line.complete_name = name - - def copy(self, default=None): - if default is None: - default = {} - default = default.copy() - default['division_id'] = False - return super(AcademicGroup, self).copy(default) + name += ' {}'.format(line.division_id.name or '') + + section_name = line.level_id.section_id.name if line.level_id and line.level_id.section_id else '' + if section_name: + name += ' - {}'.format(section_name) + + name += ' - {}{}'.format(_('Year: '), line.year or '') + + line.complete_name = name.strip(', - ') def create_students_users(self): ''' @@ -135,7 +184,28 @@ def print_users(self): limit=1).report_action(self) return report - @api.depends('student_ids') + @api.depends('child_ids.student_ids', 'student_ids') def _compute_student_ids_count(self): for group in self: - group.student_ids_count = len(group.student_ids) + group.student_ids_count = len(group.student_ids) + sum(group.child_ids.mapped('student_ids_count')) + + def _get_all_student(self): + all_student_ids = self.student_ids.ids + for child in self.child_ids: + all_student_ids.extend(child._get_all_student()) + return list(set(all_student_ids)) + + def open_student_list(self): + self.ensure_one() + action = self.env["ir.actions.actions"]._for_xml_id('academic.action_academic_partner_students') + + all_student = self._get_all_student() + if all_student: + action['domain'] = [('partner_type', '=', 'student'), ('id', 'in', all_student)] + + return action + + @api.ondelete(at_uninstall=False) + def _protect_unlink(self): + if self.filtered(lambda x: x.student_ids or x.child_ids): + raise UserError('No se pueden borrar groups con estudiantes o que tengan grupos hijos') diff --git a/academic/models/academic_level.py b/academic/models/academic_level.py index d4fc72a7..6d7bff3a 100644 --- a/academic/models/academic_level.py +++ b/academic/models/academic_level.py @@ -33,4 +33,7 @@ class AcademicLevel(models.Model): @api.depends('name', 'section_id.name') def _compute_display_name(self): for rec in self: - rec.display_name = rec.name + ' - ' + rec.section_id.name if rec.name else '' + rec.display_name = "{} - {}".format( + rec.name or '', + rec.section_id.name or '' + ).strip(' - ') diff --git a/academic/models/academic_shift.py b/academic/models/academic_shift.py new file mode 100644 index 00000000..3a1d1800 --- /dev/null +++ b/academic/models/academic_shift.py @@ -0,0 +1,14 @@ +############################################################################## +# For copyright and license notices, see __manifest__.py file in module root +# directory +############################################################################## +from odoo import models, fields + + +class AcademicShift(models.Model): + _name = 'academic.shift' + _description = 'shift' + + name = fields.Char( + required=True, + ) diff --git a/academic/models/res_company.py b/academic/models/res_company.py index 1f7d2428..d453ee9a 100644 --- a/academic/models/res_company.py +++ b/academic/models/res_company.py @@ -3,6 +3,7 @@ # directory ############################################################################## from odoo import models, fields +import datetime class ResCompany(models.Model): @@ -19,3 +20,44 @@ class ResCompany(models.Model): string='Plan de Estudio' ) family_required = fields.Boolean() + + def create_study_plan_groups(self): + for rec in self: + for level in rec.study_plan_id.level_ids: + year = fields.Date.add(fields.Date.today(), years=1).year + existing_groups_levels = self.env['academic.group'].search( + [('year', '=', year), ('level_id', '=', level.id), ('company_id', '=', rec.id), ('parent_id', '=', False)]).mapped('level_id') + if level in existing_groups_levels: + continue + self.env['academic.group'].create({ + 'year': year, + 'level_id': level.id, + 'company_id': rec.id, + }) + + def create_groups_next_year(self): + self.create_study_plan_groups() + for rec in self: + year = fields.Date.today().year + for group in self.env['academic.group'].search([('year', '=', year), ('company_id', '=', rec.id), ('parent_id', '!=', False)]): + if group.level_id not in group.study_plan_level_ids: + # TODO ver que hacemos con groups que no estan en plan de estudio + continue + current_index = group.study_plan_level_ids.ids.index(group.level_id.id) + # este seria el ultimo año del plan de estudios + if current_index + 1 < len(group.study_plan_level_ids): + next_level = group.study_plan_level_ids[current_index + 1] + parent_group = self.env['academic.group'].search( + [('year', '=', year + 1), ('level_id', '=', next_level.id), ('company_id', '=', rec.id), ('parent_id', '=', False)]) + group.copy({ + 'year': year + 1, + 'level_id': next_level.id, + 'parent_id': parent_group.id, + # por ahora nos pidieron que no copiemos estudiantes + 'student_ids': [(5, 0, 0)], + }) + # existing_groups_levels = self.env['academic.group'].search( + # [('year', '=', year), ('level_id', '=', level.id), ('company_id', '=', rec.id), ('parent_id', '=', False)]).mapped('level_id') + # for group in rec.company_group_ids: + # if current_index + 1 < len(group.study_plan_level_ids): + # next_level = group.study_plan_level_ids[current_index + 1] diff --git a/academic/models/res_partner.py b/academic/models/res_partner.py index 473e9b13..03285ef3 100644 --- a/academic/models/res_partner.py +++ b/academic/models/res_partner.py @@ -183,7 +183,12 @@ def _compute_same_dni_partner_id(self): @api.depends('student_group_ids') def _compute_current_main_group(self): for rec in self: + # por ahora este campo no permite el caso de un estudiante en mas de un colegio + # eventualmente, si es necesario, tal vez haciedno el campo company depant con la implentación json de 18+ + # ya tengamos lo que necesitamos student_group = rec.student_group_ids.filtered(lambda g: g.year == date.today().year and not g.subject_id) - if len(student_group) > 1: - raise ValidationError("There shouldn't be two groups in the same year without a subject for partner %s." % rec.name) - rec.current_main_group_id = student_group + # if len(student_group) > 1: + # raise ValidationError( + # "There shouldn't be two groups in the same year without a subject for partner %s. (Existing groups: %s)" % ( + # rec.name, student_group.mapped('display_name'))) + rec.current_main_group_id = student_group[:1] diff --git a/academic/models/res_partner_link.py b/academic/models/res_partner_link.py index c63833f5..f35b1341 100644 --- a/academic/models/res_partner_link.py +++ b/academic/models/res_partner_link.py @@ -21,6 +21,7 @@ class ResPartner(models.Model): role_ids = fields.Many2many('res.partner.role', string='Roles') partner_id = fields.Many2one('res.partner', required=True, ondelete='restrict', check_company=True) note = fields.Text(string="Notas") + sequence = fields.Integer(default=10) # @api.constrains('student_id', 'family_id') # def _check_student_or_family(self): diff --git a/academic/security/ir.model.access.csv b/academic/security/ir.model.access.csv index 84156ffe..9fc84970 100644 --- a/academic/security/ir.model.access.csv +++ b/academic/security/ir.model.access.csv @@ -13,6 +13,8 @@ access_academic_section_manager,academic.section.manager,model_academic_section, access_academic_section_user,academic.section.user,model_academic_section,group_user,1,1,1,1 access_academic_promotion_manager,academic.promotion.manager,model_academic_promotion,group_manager,1,1,1,1 access_academic_promotion_user,academic.promotion.user,model_academic_promotion,group_user,1,1,1,1 +access_academic_shift_manager,academic.shift.manager,model_academic_shift,group_manager,1,1,1,1 +access_academic_shift_user,academic.shift.user,model_academic_shift,group_user,1,1,1,1 access_academic_division_manager,academic.division.manager,model_academic_division,group_manager,1,1,1,1 access_academic_division_user,academic.division.user,model_academic_division,group_user,1,1,1,1 access_group_global,academic.group.global,model_academic_group,base.group_user,1,0,0,0 @@ -23,6 +25,7 @@ access_res_partner_category_global,academic.res_partner_category.global,base.mod access_academic_section_global,academic.res_partner_category.global,model_academic_section,base.group_user,1,0,0,0 access_academic_promotion_global,academic.academic_promotion_global,model_academic_promotion,base.group_user,1,0,0,0 access_academic_division_global,academic.academic_division_global,model_academic_division,base.group_user,1,0,0,0 +access_academic_shift_global,academic.academic_shift_global,model_academic_shift,base.group_user,1,0,0,0 access_group_portal_administrator,academic.group.portal_administrator,academic.model_academic_group,group_portal_administrator,1,1,1,1 access_res_partner_portal_administrator,academic.res_partner.portal_administrator,base.model_res_partner,group_portal_administrator,1,1,1,1 access_res_partner_relationship_user,access_res_partner_relationship_user,model_res_partner_relationship,base.group_user,1,0,0,0 @@ -32,3 +35,4 @@ access_hr_employee_asignatures_system_user,hr.employee.asignatures system user,m academic.access_res_partner_role,access_res_partner_role,academic.model_res_partner_role,base.group_user,1,0,0,0 academic.access_res_partner_link_all,access_res_partner_link_all,academic.model_res_partner_link,base.group_user,1,0,0,0 academic.access_res_partner_link_user,access_res_partner_link_user,academic.model_res_partner_link,group_user,1,1,1,1 +access_academic_group_move_student_wizard,academic_group_move_student_wizard,academic.model_academic_group_move_student_wizard,academic.group_manager,1,1,1,1 \ No newline at end of file diff --git a/academic/views/academic_group_views.xml b/academic/views/academic_group_views.xml index 7f39eaab..5d37bb8d 100644 --- a/academic/views/academic_group_views.xml +++ b/academic/views/academic_group_views.xml @@ -6,17 +6,21 @@ academic.group - - - - - - - - - - - + + + + + + + + + + + + + + @@ -27,27 +31,50 @@ academic.group
+ +
-
- - - - - - - - - - - + + + + + + + + + + + + - + + + + + + + + + + + + + + + +