From 7786203e33d30181ed8c2f8fb95ec7db4fad889b Mon Sep 17 00:00:00 2001 From: Franco Leyes Date: Tue, 25 Mar 2025 14:43:22 +0000 Subject: [PATCH 1/4] [IMP] academic: enrollment development --- academic/__manifest__.py | 7 +- academic/demo/academic.level.csv | 22 +- academic/demo/academic.section.csv | 14 +- academic/demo/academic.study.plan.csv | 3 - academic/demo/academic_group.xml | 12 +- academic/demo/res.partner.csv | 64 +++--- academic/demo/res_company_demo.xml | 6 +- academic/models/__init__.py | 4 +- academic/models/academic_group.py | 208 +++++++++++++++--- academic/models/academic_level.py | 32 +-- academic/models/academic_section.py | 18 ++ academic/models/academic_study_plan.py | 16 -- academic/models/account_move.py | 5 +- academic/models/crm_lead.py | 16 ++ academic/models/hr.py | 2 - academic/models/product_template.py | 12 + academic/models/res_company.py | 2 +- academic/models/res_partner.py | 34 +-- academic/models/sale_order_line.py | 22 ++ academic/security/academic_security.xml | 7 - academic/security/ir.model.access.csv | 3 - academic/views/academic_group_views.xml | 57 +++-- academic/views/academic_level_views.xml | 65 ------ academic/views/academic_section_views.xml | 24 +- academic/views/academic_study_plan_views.xml | 42 ---- academic/views/crm_lead_view.xml | 17 +- academic/views/hr_views.xml | 4 +- academic/views/product_template_views.xml | 12 + academic/views/res_company_views.xml | 2 +- academic/views/res_partner_views.xml | 62 +----- academic/views/sale_order_views.xml | 4 + academic_sale_subscription/__manifest__.py | 1 + .../models/academic_group.py | 30 +++ .../models/res_partner.py | 12 + .../views/academic_group_views.xml | 12 + .../views/res_partner_views.xml | 4 +- .../wizard/academic_order_wizard.py | 22 +- .../wizard/academic_order_wizard_views.xml | 3 +- demo_academic/__manifest__.py | 22 +- 39 files changed, 496 insertions(+), 408 deletions(-) delete mode 100644 academic/demo/academic.study.plan.csv delete mode 100644 academic/models/academic_study_plan.py create mode 100644 academic/models/crm_lead.py create mode 100644 academic/models/product_template.py create mode 100644 academic/models/sale_order_line.py delete mode 100644 academic/views/academic_level_views.xml delete mode 100644 academic/views/academic_study_plan_views.xml create mode 100644 academic/views/product_template_views.xml create mode 100644 academic_sale_subscription/views/academic_group_views.xml diff --git a/academic/__manifest__.py b/academic/__manifest__.py index 306ed564..2846c11c 100644 --- a/academic/__manifest__.py +++ b/academic/__manifest__.py @@ -28,6 +28,7 @@ "images": [], "depends": [ "portal_backend", + "crm", "board", "hr", "website", @@ -49,8 +50,6 @@ "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", @@ -67,19 +66,19 @@ "report/ir_actions_report.xml", "views/res_partner_category.xml", "views/account_portal_templates.xml", + "views/product_template_views.xml", "report/report_invoice.xml", ], "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.section.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", diff --git a/academic/demo/academic.level.csv b/academic/demo/academic.level.csv index dde51656..7c0e1d9f 100644 --- a/academic/demo/academic.level.csv +++ b/academic/demo/academic.level.csv @@ -1,14 +1,8 @@ -id,name,section_id/id -academic_course_1,Primer Grado,academic_section_1 -academic_course_2,Segundo Grado,academic_section_1 -academic_course_3,Tercer Grado,academic_section_1 -academic_course_4,Cuarto Grado,academic_section_1 -academic_course_5,Quinto Grado,academic_section_1 -academic_course_6,Sexto Grado,academic_section_1 -academic_course_7,Séptimo Grado,academic_section_1 -academic_course_8,Primer Grado,academic_section_2 -academic_course_9,Segundo Grado,academic_section_2 -academic_course_10,Tercero Grado,academic_section_2 -academic_course_11,Cuarto Grado,academic_section_2 -academic_course_12,Quinto Grado,academic_section_2 -academic_course_13,Sexto Grado,academic_section_2 +id,name +academic_level_1,Primer año +academic_level_2,Segundo año +academic_level_3,Tercer año +academic_level_4,Cuarto año +academic_level_5,Quinto año +academic_level_6,Sexto año +academic_level_7,Séptimo año diff --git a/academic/demo/academic.section.csv b/academic/demo/academic.section.csv index e1d1a78e..dc98412c 100644 --- a/academic/demo/academic.section.csv +++ b/academic/demo/academic.section.csv @@ -1,3 +1,11 @@ -id,name -academic_section_1,Primaria -academic_section_2,Secundaria +id,name,correlative_ids/id,level_ids/id +academic_section_1,Nivel Inicial,,"academic_level_1" +academic_section_2,Primario 1 a 6,academic_section_1,"academic_level_1,academic_level_2,academic_level_3,academic_level_4,academic_level_5,academic_level_6" +academic_section_3,Primario 1 a 7,academic_section_1,"academic_level_1,academic_level_2,academic_level_3,academic_level_4,academic_level_5,academic_level_6,academic_level_7" +academic_section_4,Secundario 1 a 5,academic_section_3,"academic_level_1,academic_level_2,academic_level_3,academic_level_4,academic_level_5" +academic_section_5,Secundario 1 a 6,academic_section_2,"academic_level_1,academic_level_2,academic_level_3,academic_level_4,academic_level_5,academic_level_6" +academic_section_6,Inglés Básico,,"academic_level_1,academic_level_2,academic_level_3" +academic_section_7,Inglés Avanzado,academic_section_6,"academic_level_1,academic_level_2,academic_level_3" +academic_section_8,Inglés Técnico,academic_section_7,"academic_level_1,academic_level_2,academic_level_3" +academic_section_9,Ingeniería Industrial,,"academic_level_1,academic_level_2,academic_level_3,academic_level_4,academic_level_5" +academic_section_10,Maestría en Ciencias de Datos,academic_section_9,"academic_level_1,academic_level_2" diff --git a/academic/demo/academic.study.plan.csv b/academic/demo/academic.study.plan.csv deleted file mode 100644 index c8706d65..00000000 --- a/academic/demo/academic.study.plan.csv +++ /dev/null @@ -1,3 +0,0 @@ -id,name,level_ids/id -academic_study_plan_1,Primario hasta 6,"academic_course_1,academic_course_2,academic_course_3,academic_course_4,academic_course_5,academic_course_6,academic_course_8,academic_course_9,academic_course_10,academic_course_11,academic_course_12,academic_course_13" -academic_study_plan_2,Primario hasta 7,"academic_course_1,academic_course_2,academic_course_3,academic_course_4,academic_course_5,academic_course_6,academic_course_7,academic_course_8,academic_course_9,academic_course_10,academic_course_11,academic_course_12,academic_course_13" diff --git a/academic/demo/academic_group.xml b/academic/demo/academic_group.xml index f3c4eca3..20da73a7 100644 --- a/academic/demo/academic_group.xml +++ b/academic/demo/academic_group.xml @@ -1,29 +1,29 @@ - + + - - + + - - + + - diff --git a/academic/demo/res.partner.csv b/academic/demo/res.partner.csv index 1b1cb17c..96fc48b3 100644 --- a/academic/demo/res.partner.csv +++ b/academic/demo/res.partner.csv @@ -1,32 +1,32 @@ -id,name,partner_type,is_company,section_id/id,promotion_id/id,sex,parent_id/id,links_by_student,vat -res_partner_flia_mufaza,Flia. Mufaza,family,False,,,,,,10000000 -res_partner_flia_frias,Flia. Frias,family,False,,,,,True,10000001 -res_partner_flia_pampa,Flia. Pampa,family,False,,,,,,10000002 -res_partner_ignacio_rodriguez,Ignacio Rodriguez,teacher,False,,,M,,,10000003 -res_partner_laura_sali,Laura Sali,teacher,False,,,F,,,10000004 -res_partner_alvaro_diaz,Alvaro Diaz,student,False,,academic_promotion_1,M,,,10000005 -res_partner_jose_martin_rodriguez,José Martín Rodriguez,student,False,,academic_promotion_1,M,,,10000006 -res_partner_martin_perez,Martin Perez,student,False,,academic_promotion_2,M,,,10000007 -res_partner_juan_gomez,Juan Gomez,administrator,False,academic_section_1,,M,,,10000008 -res_partner_roberto_martin,Roberto Martin,administrator,False,academic_section_1,,M,,,10000009 -res_partner_malena_apdes,Malena Apdes,,False,,,F,,,10000010 -res_partner_luz,Luz Mufaza,student,False,,academic_promotion_2,F,res_partner_flia_mufaza,,10000011 -res_partner_bautista,Bautista Mufaza,student,False,,academic_promotion_2,M,res_partner_flia_mufaza,,10000012 -res_partner_mateo,Mateo Frias,student,False,,academic_promotion_2,M,res_partner_flia_frias,,10000013 -res_partner_amalia,Amalia Frias,student,False,,academic_promotion_2,F,res_partner_flia_frias,,10000014 -res_partner_ignacio,Ignacio Frias,student,False,,academic_promotion_2,M,res_partner_flia_frias,,10000015 -res_partner_martina,Martina Pampa,student,False,,academic_promotion_2,M,res_partner_flia_pampa,,10000016 -res_partner_gonzalo,Gonzalo Mufaza,relative,False,,,M,,,10000017 -res_partner_teresa,Teresa Ciudadela,relative,False,,,F,,,10000018 -res_partner_marina,Marina Priti,relative,False,,,F,,,10000019 -res_partner_juan,Juan Frias,relative,False,,,M,,,10000020 -res_partner_manuel,Manuel Pampa,relative,False,,,M,,,10000021 -res_partner_cecilia,Cecilia Todi,relative,False,,,F,,,10000022 -res_partner_marcelo,Marcelo Pampa,relative,False,,,M,,,10000023 -res_partner_laura,Laura Perez,relative,False,,,F,,,10000024 -res_partner_cristina,Cristina Garcia,relative,False,,,F,,,10000025 -res_partner_rosa,Rosa Roma,relative,False,,,F,,,10000026 -res_partner_daniel,Daniel Priti,relative,False,,,M,,,10000027 -res_partner_jose_luis,Jose Luis Mufaza,relative,False,,,M,,,10000028 -res_partner_hospital_espanol,Hospital Español,,True,,,,,,10000029 -res_partner_sanatorio_amercano,Sanatorio Americano,,True,,,,,,10000030 +id,name,partner_type,is_company,promotion_id/id,sex,parent_id/id,links_by_student,vat +res_partner_flia_mufaza,Flia. Mufaza,family,False,,,,,10000000 +res_partner_flia_frias,Flia. Frias,family,False,,,,True,10000001 +res_partner_flia_pampa,Flia. Pampa,family,False,,,,,10000002 +res_partner_ignacio_rodriguez,Ignacio Rodriguez,teacher,False,,M,,,10000003 +res_partner_laura_sali,Laura Sali,teacher,False,,F,,,10000004 +res_partner_alvaro_diaz,Alvaro Diaz,student,False,academic_promotion_1,M,,,10000005 +res_partner_jose_martin_rodriguez,José Martín Rodriguez,student,False,academic_promotion_1,M,,,10000006 +res_partner_martin_perez,Martin Perez,student,False,academic_promotion_2,M,,,10000007 +res_partner_juan_gomez,Juan Gomez,administrator,False,,M,,,10000008 +res_partner_roberto_martin,Roberto Martin,administrator,False,,M,,,10000009 +res_partner_malena_apdes,Malena Apdes,,False,,F,,,10000010 +res_partner_luz,Luz Mufaza,student,False,academic_promotion_2,F,res_partner_flia_mufaza,,10000011 +res_partner_bautista,Bautista Mufaza,student,False,academic_promotion_2,M,res_partner_flia_mufaza,,10000012 +res_partner_mateo,Mateo Frias,student,False,academic_promotion_2,M,res_partner_flia_frias,,10000013 +res_partner_amalia,Amalia Frias,student,False,academic_promotion_2,F,res_partner_flia_frias,,10000014 +res_partner_ignacio,Ignacio Frias,student,False,academic_promotion_2,M,res_partner_flia_frias,,10000015 +res_partner_martina,Martina Pampa,student,False,academic_promotion_2,M,res_partner_flia_pampa,,10000016 +res_partner_gonzalo,Gonzalo Mufaza,relative,False,,M,,,10000017 +res_partner_teresa,Teresa Ciudadela,relative,False,,F,,,10000018 +res_partner_marina,Marina Priti,relative,False,,F,,,10000019 +res_partner_juan,Juan Frias,relative,False,,M,,,10000020 +res_partner_manuel,Manuel Pampa,relative,False,,M,,,10000021 +res_partner_cecilia,Cecilia Todi,relative,False,,F,,,10000022 +res_partner_marcelo,Marcelo Pampa,relative,False,,M,,,10000023 +res_partner_laura,Laura Perez,relative,False,,F,,,10000024 +res_partner_cristina,Cristina Garcia,relative,False,,F,,,10000025 +res_partner_rosa,Rosa Roma,relative,False,,F,,,10000026 +res_partner_daniel,Daniel Priti,relative,False,,M,,,10000027 +res_partner_jose_luis,Jose Luis Mufaza,relative,False,,M,,,10000028 +res_partner_hospital_espanol,Hospital Español,,True,,,,,10000029 +res_partner_sanatorio_amercano,Sanatorio Americano,,True,,,,,10000030 diff --git a/academic/demo/res_company_demo.xml b/academic/demo/res_company_demo.xml index 44c4d169..c7ad85c4 100644 --- a/academic/demo/res_company_demo.xml +++ b/academic/demo/res_company_demo.xml @@ -15,7 +15,7 @@ APDES ASOCIACIÓN PARA LA PROMOCIÓN DEPORTIVA, EDUCATIVA Y SOCIAL - + @@ -27,8 +27,8 @@ 31 Colegio Los Arroyos - + @@ -39,8 +39,8 @@ Colegio Los Molinos - + diff --git a/academic/models/__init__.py b/academic/models/__init__.py index 141fabce..dea923aa 100644 --- a/academic/models/__init__.py +++ b/academic/models/__init__.py @@ -5,7 +5,6 @@ from . import academic_division from . import academic_group from . import academic_level -from . import academic_study_plan from . import academic_promotion from . import academic_section from . import academic_subject @@ -17,6 +16,9 @@ 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 product_template diff --git a/academic/models/academic_group.py b/academic/models/academic_group.py index 85a5c932..17560725 100644 --- a/academic/models/academic_group.py +++ b/academic/models/academic_group.py @@ -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): @@ -43,11 +42,19 @@ class AcademicGroup(models.Model): context={"default_is_company": True}, default=lambda self: self.env.company, ) - study_plan_level_ids = fields.Many2many(related="company_id.study_plan_id.level_ids") + section_ids = fields.Many2many("academic.section", related="company_id.section_ids") + section_id = fields.Many2one( + "academic.section", + string="Study Plan", + required=True, + domain="[('id', 'in', section_ids)]", + ) + level_ids = fields.Many2many(related="section_id.level_ids") level_id = fields.Many2one( "academic.level", string="Level", required=True, + domain="[('id', 'in', level_ids)]", ) subject_id = fields.Many2one("academic.subject", string="Subject", required=False, index=True) teacher_id = fields.Many2one( @@ -57,6 +64,23 @@ class AcademicGroup(models.Model): context={"default_partner_type": "teacher"}, domain=[("partner_type", "=", "teacher")], ) + fee_so_line_ids = fields.One2many( + "sale.order.line", + "group_id", + string="Main SO Line", + domain=[("product_id.academic_product_type", "=", "fee")], + ) + registration_so_line_ids = fields.One2many( + "sale.order.line", + "group_id", + string="Registration SO Line", + domain=[("product_id.academic_product_type", "=", "registration")], + ) + opportunities_ids = fields.One2many( + "crm.lead", + "group_id", + string="Opportunities", + ) student_ids = fields.Many2many( "res.partner", "academic_student_group_ids_student_ids_rel", @@ -65,54 +89,75 @@ class AcademicGroup(models.Model): string="Student", context={"default_partner_type": "student"}, domain=[("partner_type", "=", "student")], + compute="_compute_student_ids", + store=True, + readonly=False, ) 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", - ) + fee_student_count = fields.Integer(compute="_compute_fee_student_count") + no_fee_student_count = fields.Integer(compute="_compute_no_fee_student_count") + registration_student_count = fields.Integer(compute="_compute_registration_student_count") + opportunities_student_count = fields.Integer(compute="_compute_opportunities_student_count") + capacity = fields.Integer() + vacancies = fields.Integer(compute="_compute_vacancies", store=True) + manage_sale_workflow = fields.Boolean(compute="_compute_manage_sale_workflow", store=True, readonly=False) @api.depends("company_id", "level_id", "division_id", "year") def _compute_name(self): for line in self: name_parts = [ line.company_id.name, - line.level_id.name if line.level_id else None, + line.section_id.name, + line.level_id.name, line.division_id.name if line.division_id else None, - line.level_id.section_id.name if line.level_id and line.level_id.section_id else None, self.env._("Year: %s", line.year), ] 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 + def _compute_fee_student_count(self): + for group in self: + group.fee_student_count = len( + group.fee_so_line_ids.filtered(lambda x: x.state == "sale").mapped("order_id.partner_id") + ) - @api.depends("student_ids") - def _compute_student_ids_count(self): + def _compute_no_fee_student_count(self): for group in self: - group.student_ids_count = len(group.student_ids) + group.no_fee_student_count = len( + group.registration_so_line_ids.filtered(lambda x: x.state in ["sale"]).mapped("order_id.partner_id") + - group.fee_so_line_ids.mapped("order_id.partner_id") + ) + + def _compute_registration_student_count(self): + for group in self: + group.registration_student_count = len( + group.registration_so_line_ids.filtered(lambda x: x.state in ["draft", "sent"]).mapped( + "order_id.partner_id" + ) + ) + + def _compute_opportunities_student_count(self): + for group in self: + group.opportunities_student_count = len( + group.opportunities_ids.filtered( + lambda x: x.active + and x.partner_id + not in group.registration_so_line_ids.mapped("order_id.partner_id") + | group.fee_so_line_ids.mapped("order_id.partner_id") + ) + ) + + @api.depends("fee_so_line_ids.order_id.state", "capacity", "manage_sale_workflow") + def _compute_vacancies(self): + for group in self: + if group.manage_sale_workflow: + group.vacancies = group.capacity - len( + group.registration_so_line_ids.filtered(lambda x: x.order_id.state == "sale").mapped( + "order_id.partner_id" + ) + ) + else: + group.vacancies = group.capacity - len(group.student_ids) def create_next_year_groups(self): # estamos pasando de un año a otro sin usar study plan por lo siguiente: @@ -148,3 +193,94 @@ 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_opportunities(self): + action = self.env["ir.actions.actions"]._for_xml_id("crm.crm_lead_action_pipeline") + action.update( + { + "context": {"search_default_group_id": self.id}, + } + ) + return action + + def open_registration_sales(self): + action = self.env["ir.actions.actions"]._for_xml_id("sale.action_quotations_with_onboarding") + action.update({"domain": [("id", "in", self.registration_so_line_ids.mapped("order_id").ids)], "context": {}}) + return action + + def open_no_fee_students(self): + action = self.env.ref("academic.action_academic_partner_students").read()[0] + action.update( + { + "domain": [ + ( + "id", + "in", + ( + self.registration_so_line_ids.filtered(lambda x: x.state in ["sale"]).mapped( + "order_id.partner_id" + ) + - self.fee_so_line_ids.mapped("order_id.partner_id") + ).ids, + ) + ], + "views": [(False, "list"), (False, "form")], + "context": {"from_open_student_view": True}, + } + ) + return action + + def open_fee_sales(self): + action = self.env["ir.actions.actions"]._for_xml_id("sale.action_quotations_with_onboarding") + action.update( + { + "domain": [ + ("id", "in", self.fee_so_line_ids.filtered(lambda x: x.state == "sale").mapped("order_id").ids) + ], + "context": {}, + } + ) + return action + + def open_students(self): + action = self.env.ref("academic.action_academic_partner_students").read()[0] + action.update( + { + "domain": [("id", "in", self.student_ids.ids)], + "views": [(False, "list"), (False, "form")], + "context": {"from_open_student_view": True}, + } + ) + return action + + @api.depends("manage_sale_workflow", "registration_so_line_ids.state", "fee_so_line_ids.state") + def _compute_student_ids(self): + for group in self.filtered("manage_sale_workflow"): + group.student_ids = group.fee_so_line_ids.filtered(lambda x: x.order_id.state == "sale").mapped( + "order_id.partner_id" + ) | group.registration_so_line_ids.filtered(lambda x: x.order_id.state == "sale").mapped( + "order_id.partner_id" + ) + + @api.constrains("capacity") + def _check_capacity(self): + if self.filtered(lambda x: x.capacity <= 0): + raise ValidationError(self.env._("The capacity must be greater than 0.")) + + @api.depends("subject_id") + def _compute_manage_sale_workflow(self): + groups_with_subject = self.filtered("subject_id") + groups_with_subject.manage_sale_workflow = False + (self - groups_with_subject).manage_sale_workflow = True + + def write(self, vals): + if "manage_sale_workflow" in vals and not ( + self.env.user.has_group("academic.group_manager") or self.env.user._is_admin() + ): + raise ValidationError(_("You are not allowed to modify the sale workflow setting.")) + return super().write(vals) diff --git a/academic/models/academic_level.py b/academic/models/academic_level.py index 1279ecb1..d5c58876 100644 --- a/academic/models/academic_level.py +++ b/academic/models/academic_level.py @@ -2,32 +2,18 @@ # For copyright and license notices, see __manifest__.py file in module root # directory ############################################################################## -from odoo import api, fields, models +from odoo import fields, models class AcademicLevel(models.Model): _name = "academic.level" _description = "level" - _order = "sequence" - _rec_names_search = ["name", "section_id.name"] + _sql_constraints = [ + ( + "unique_level_name", + "UNIQUE(name)", + "The level name must be unique.", + ) + ] - sequence = fields.Integer() - name = fields.Char( - required=True, - translate=True, - ) - section_id = fields.Many2one( - "academic.section", - string="Section", - required=True, - ) - group_ids = fields.One2many( - "academic.group", - "level_id", - string="Groups", - ) - - @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 "" + name = fields.Char(required=True) diff --git a/academic/models/academic_section.py b/academic/models/academic_section.py index fd71fa8d..cb8fc6c5 100644 --- a/academic/models/academic_section.py +++ b/academic/models/academic_section.py @@ -8,7 +8,25 @@ class AcademicSection(models.Model): _name = "academic.section" _description = "section" + _order = "sequence" name = fields.Char( required=True, ) + correlative_ids = fields.Many2many( + "academic.section", + "academic_section_correlative_ids_rel", + "section_id", + "correlative_id", + string="Correlative Study Plans", + ) + level_ids = fields.Many2many( + "academic.level", + "academic_section_level_ids_rel", + "section_id", + "level_id", + string="Levels", + ) + sequence = fields.Integer( + default=10, + ) diff --git a/academic/models/academic_study_plan.py b/academic/models/academic_study_plan.py deleted file mode 100644 index f6fd3463..00000000 --- a/academic/models/academic_study_plan.py +++ /dev/null @@ -1,16 +0,0 @@ -from odoo import fields, models - - -class StudyPlan(models.Model): - _name = "academic.study.plan" - _description = "Study Plan" - - name = fields.Char(required=True) - - level_ids = fields.Many2many( - "academic.level", - "academic_study_plan_ids_level_ids_rel", - "academic_study_plan_id", - "level_id", - string="Levels", - ) diff --git a/academic/models/account_move.py b/academic/models/account_move.py index 677d468d..af9394d1 100644 --- a/academic/models/account_move.py +++ b/academic/models/account_move.py @@ -8,7 +8,10 @@ class AccountMove(models.Model): # Este campo solo lo uso para calcular el dominio del student_id ya que implica una búsqueda por el rol de pago. student_ids = fields.Many2many("res.partner", string="Students List", compute="_compute_student_ids") student_id = fields.Many2one( - "res.partner", domain="[('id', 'in', student_ids), ('partner_type', '=', 'student')]", index=True + "res.partner", + domain="[('id', 'in', student_ids), ('partner_type', '=', 'student')]", + index=True, + context={"default_partner_type": "student"}, ) require_student_on_invoices = fields.Boolean(related="company_id.require_student_on_invoices") is_academic_sale = fields.Boolean(compute="_compute_is_academic_sale") diff --git a/academic/models/crm_lead.py b/academic/models/crm_lead.py new file mode 100644 index 00000000..c7b77bfc --- /dev/null +++ b/academic/models/crm_lead.py @@ -0,0 +1,16 @@ +############################################################################## +# For copyright and license notices, see __manifest__.py file in module root +# directory +############################################################################## +from odoo import fields, models + + +class CrmLead(models.Model): + _inherit = "crm.lead" + + group_id = fields.Many2one("academic.group") + + def _create_customer(self): + if self.group_id: + return super(CrmLead, self.with_context(default_partner_type="student"))._create_customer() + return super()._create_customer() diff --git a/academic/models/hr.py b/academic/models/hr.py index a69d2a4a..c1999682 100644 --- a/academic/models/hr.py +++ b/academic/models/hr.py @@ -51,5 +51,3 @@ class Employee(models.Model): comodel_name="hr.employee.asignatures", inverse_name="teacher_id", ) - - study_plan_level_ids = fields.Many2many(related="company_id.study_plan_id.level_ids") diff --git a/academic/models/product_template.py b/academic/models/product_template.py new file mode 100644 index 00000000..7398229b --- /dev/null +++ b/academic/models/product_template.py @@ -0,0 +1,12 @@ +from odoo import fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + academic_product_type = fields.Selection( + selection=[ + ("fee", "Fee"), + ("registration", "Registration"), + ], + ) diff --git a/academic/models/res_company.py b/academic/models/res_company.py index 20370beb..d96bff7f 100644 --- a/academic/models/res_company.py +++ b/academic/models/res_company.py @@ -14,7 +14,7 @@ class ResCompany(models.Model): "company_id", string="Groups", ) - study_plan_id = fields.Many2one(comodel_name="academic.study.plan", string="Plan de Estudio") + section_ids = fields.Many2many("academic.section", string="Study Plans") family_required = fields.Boolean(default=True) # Falta revisar la implementación con ese booleano en False require_student_on_invoices = fields.Boolean(default=True) diff --git a/academic/models/res_partner.py b/academic/models/res_partner.py index bb6acab8..fd56afba 100644 --- a/academic/models/res_partner.py +++ b/academic/models/res_partner.py @@ -30,10 +30,6 @@ class ResPartner(models.Model): readonly=False, store=True, ) - section_id = fields.Many2one( - "academic.section", - string="Section", - ) promotion_id = fields.Many2one( "academic.promotion", string="Promotion", @@ -91,6 +87,7 @@ class ResPartner(models.Model): compute="_compute_payment_responsible", store=True, ) + parent_id = fields.Many2one(context={"default_partner_type": "family"}) @api.depends("parent_links_by_student", "parent_id.student_link_ids") def _compute_student_links(self): @@ -141,8 +138,6 @@ def _compute_student_links(self): # create tantas cosas student_ids = fields.One2many("res.partner", "parent_id") company_id = fields.Many2one(compute="_compute_company_id", store=True, readonly=False) - # company_type = fields.Selection(selection_add=[('family', 'Family')]) - # is_family = fields.Boolean() same_dni_partner_id = fields.Many2one( "res.partner", string="Partner with same DNI", @@ -157,18 +152,6 @@ def _compute_student_links(self): category_id = fields.Many2many(check_company=True) student_count = fields.Integer(compute="_compute_student_count", store=True) - # @api.depends('is_family') - # def _compute_company_type(self): - # families = self.filtered(lambda x: x.is_company and x.is_family) - # families.company_type = 'family' - # return super(ResPartner, self - families)._compute_company_type() - - # def _write_company_type(self): - # families = self.filtered(lambda x: x.company_type == 'family') - # families.is_company = True - # families.is_family = True - # return super(ResPartner, self - families)._write_company_type() - @api.constrains("company_id", "partner_type", "parent_id") def _check_family_configured(self): if self.filtered( @@ -191,14 +174,6 @@ def _compute_related_user_id(self): def _compute_partner_type(self): self.filtered(lambda x: x.is_company and x.partner_type).partner_type = False - def quickly_create_portal_user(self): - """Metodo que crea o activa usuario inactivo en el grupo portal que - se defina - """ - # TODO: el metodo onchange_portal_id no existe. - # Esto dejo de usarse pero queda el codigo por posible implementacion a futuro - raise UserError(_("Esta función se encuentra en desarrollo!")) - @api.depends("parent_id") def _compute_company_id(self): """ @@ -296,10 +271,3 @@ def web_search_read(self, domain, specification, offset=0, limit=None, order=Non def _compute_student_count(self): for rec in self.filtered(lambda x: x.partner_type == "family"): rec.student_count = len(rec.student_ids) - - @api.constrains("partner_type") - def _check_groups_student(self): - if self.env.context.get("install_mode"): - return True - if self.filtered(lambda x: x.partner_type == "student" and not x.student_group_ids): - raise UserError(_("The student must belong to at least one academic group.")) diff --git a/academic/models/sale_order_line.py b/academic/models/sale_order_line.py new file mode 100644 index 00000000..ff5f83d7 --- /dev/null +++ b/academic/models/sale_order_line.py @@ -0,0 +1,22 @@ +############################################################################## +# For copyright and license notices, see __manifest__.py file in module root +# directory +############################################################################## +from odoo import api, fields, models + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + group_id = fields.Many2one("academic.group", compute="_compute_group_id", store=True, readonly=False) + academic_product_type = fields.Selection( + related="product_id.academic_product_type", + ) + + @api.depends("academic_product_type") + def _compute_group_id(self): + for rec in self: + if rec.academic_product_type and rec.order_id.opportunity_id.group_id: + rec.group_id = rec.order_id.opportunity_id.group_id + else: + rec.group_id = False diff --git a/academic/security/academic_security.xml b/academic/security/academic_security.xml index 87e2bce7..39bb9d67 100644 --- a/academic/security/academic_security.xml +++ b/academic/security/academic_security.xml @@ -94,13 +94,6 @@ - - portal administrator: only section groups - - [('level_id.section_id','=',user.partner_id.section_id.id)] - - - portal gral administrator: all groups diff --git a/academic/security/ir.model.access.csv b/academic/security/ir.model.access.csv index fe635e06..1b44b507 100644 --- a/academic/security/ir.model.access.csv +++ b/academic/security/ir.model.access.csv @@ -7,8 +7,6 @@ access_academic_level_manager,academic.level.manager,model_academic_level,group_ access_academic_level_user,academic.level.user,model_academic_level,group_user,1,0,0,0 access_academic_subject_manager,academic.subject.manager,model_academic_subject,group_manager,1,1,1,1 access_academic_subject_user,academic.subject.user,model_academic_subject,group_user,1,1,1,1 -access_academic_study_plan_manager,academic.study_plan.manager,model_academic_study_plan,group_manager,1,1,1,1 -access_academic_study_plan_user,academic.study_plan.user,model_academic_study_plan,group_user,1,1,1,1 access_academic_section_manager,academic.section.manager,model_academic_section,group_manager,1,1,1,1 access_academic_section_user,academic.section.user,model_academic_section,group_user,1,0,0,0 access_academic_promotion_manager,academic.promotion.manager,model_academic_promotion,group_manager,1,1,1,1 @@ -18,7 +16,6 @@ access_academic_division_user,academic.division.user,model_academic_division,gro access_group_global,academic.group.global,model_academic_group,base.group_user,1,0,0,0 access_subject_global,academic.subject.global,model_academic_subject,base.group_user,1,0,0,0 access_level_global,academic.level.global,model_academic_level,base.group_user,1,0,0,0 -access_study_plan_global,academic.study_plan.global,model_academic_study_plan,base.group_user,1,0,0,0 access_res_partner_category_global,academic.res_partner_category.global,base.model_res_partner_category,base.group_user,1,0,0,0 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 diff --git a/academic/views/academic_group_views.xml b/academic/views/academic_group_views.xml index 55bba81a..74b26e2b 100644 --- a/academic/views/academic_group_views.xml +++ b/academic/views/academic_group_views.xml @@ -13,9 +13,6 @@ - - - @@ -28,27 +25,49 @@ academic.group
+ +
+ +
- + + +
- - - - - - - - - - + + + + + + + + + + + + + + + - +
@@ -66,10 +85,13 @@ + - + + +
@@ -118,7 +140,6 @@ - diff --git a/academic/views/academic_level_views.xml b/academic/views/academic_level_views.xml deleted file mode 100644 index b2d33cde..00000000 --- a/academic/views/academic_level_views.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - academic.level.select - academic.level - - - - - - - - - - - - - - - academic.level.form - academic.level - - -
-
- - - - - - - - - -
-
- - - - - academic.level.list - academic.level - - - - - - - - - - - Levels - academic.level - list,form - - - [] - - - - -
diff --git a/academic/views/academic_section_views.xml b/academic/views/academic_section_views.xml index 094e5f8c..b218e66a 100644 --- a/academic/views/academic_section_views.xml +++ b/academic/views/academic_section_views.xml @@ -1,48 +1,48 @@ - - academic.section.select academic.section - - + + - academic.section.form academic.section -
- + + + +
- - academic.section.list academic.section - + + + + - Sections + Study Plans academic.section list,form @@ -50,6 +50,6 @@ [] - +
diff --git a/academic/views/academic_study_plan_views.xml b/academic/views/academic_study_plan_views.xml deleted file mode 100644 index 0a9d7f80..00000000 --- a/academic/views/academic_study_plan_views.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - Study Plan - ir.actions.act_window - academic.study.plan - list,form - - - - academic.study.plan.view.form - academic.study.plan - -
- - - - - - - - - - - -
-
-
- - - academic.study.plan.view.list - academic.study.plan - - - - - - - - - -
diff --git a/academic/views/crm_lead_view.xml b/academic/views/crm_lead_view.xml index 78583eb1..0d43928b 100644 --- a/academic/views/crm_lead_view.xml +++ b/academic/views/crm_lead_view.xml @@ -1,9 +1,24 @@ + + + crm.lead.form.inherit.group_id + crm.lead + + + + + + + + - crm.lead.form.inherit + crm.lead.form.inherit.group_id crm.lead + 1 diff --git a/academic/views/hr_views.xml b/academic/views/hr_views.xml index fa746d82..6eb55d6d 100644 --- a/academic/views/hr_views.xml +++ b/academic/views/hr_views.xml @@ -6,13 +6,11 @@ - - diff --git a/academic/views/sale_order_views.xml b/academic/views/sale_order_views.xml index d2d4691b..ce11df6b 100644 --- a/academic/views/sale_order_views.xml +++ b/academic/views/sale_order_views.xml @@ -26,6 +26,10 @@ 1 + + + +
diff --git a/academic_sale_subscription/__manifest__.py b/academic_sale_subscription/__manifest__.py index 4a6600fb..fa6f666f 100644 --- a/academic_sale_subscription/__manifest__.py +++ b/academic_sale_subscription/__manifest__.py @@ -18,6 +18,7 @@ "views/res_partner_views.xml", "views/sale_subscription_views.xml", "views/res_config_settings_views.xml", + "views/academic_group_views.xml", ], "installable": True, "auto_install": False, diff --git a/academic_sale_subscription/models/academic_group.py b/academic_sale_subscription/models/academic_group.py index e935acce..19873698 100644 --- a/academic_sale_subscription/models/academic_group.py +++ b/academic_sale_subscription/models/academic_group.py @@ -12,3 +12,33 @@ def open_order_wizard(self): action = self.env.ref("academic_sale_subscription.action_view_academic_order_wizard").read()[0] action.update({"context": {"default_student_ids": self.student_ids.ids}}) return action + + def _compute_fee_student_count(self): + super()._compute_fee_student_count() + for group in self: + group.fee_student_count = len( + group.fee_so_line_ids.filtered( + lambda x: x.order_id.subscription_state in ["3_progress", "4_paused"] + ).mapped("order_id.partner_id") + ) + + def open_fee_sales(self): + super().open_fee_sales() + action = self.env["ir.actions.actions"]._for_xml_id("sale.action_quotations_with_onboarding") + action.update( + { + "domain": [ + ( + "id", + "in", + self.fee_so_line_ids.filtered( + lambda x: x.order_id.subscription_state in ["3_progress", "4_paused"] + ) + .mapped("order_id") + .ids, + ) + ], + "context": {}, + } + ) + return action diff --git a/academic_sale_subscription/models/res_partner.py b/academic_sale_subscription/models/res_partner.py index 3d21d087..c2a659ae 100644 --- a/academic_sale_subscription/models/res_partner.py +++ b/academic_sale_subscription/models/res_partner.py @@ -22,3 +22,15 @@ def _compute_current_subscription(self): lambda line: not line.order_id.end_date or line.order_id.next_invoice_date < line.order_id.end_date ) ) + + def open_academic_order_wizard(self): + action = self.env["ir.actions.actions"]._for_xml_id( + "academic_sale_subscription.action_view_academic_order_wizard" + ) + if academic_group := self.env.context.get("academic_group_id"): + action.update( + { + "context": {"academic_group_id": academic_group}, + } + ) + return action diff --git a/academic_sale_subscription/views/academic_group_views.xml b/academic_sale_subscription/views/academic_group_views.xml new file mode 100644 index 00000000..f772a5e5 --- /dev/null +++ b/academic_sale_subscription/views/academic_group_views.xml @@ -0,0 +1,12 @@ + + + academic.group.form + academic.group + + + + + + diff --git a/academic_sale_subscription/views/res_partner_views.xml b/academic_sale_subscription/views/res_partner_views.xml index bf668d30..4a6c6913 100644 --- a/academic_sale_subscription/views/res_partner_views.xml +++ b/academic_sale_subscription/views/res_partner_views.xml @@ -6,8 +6,8 @@
-
- - - -
- + @@ -57,17 +45,15 @@ - - + - - +
@@ -91,7 +77,6 @@ -
diff --git a/academic/views/res_company_views.xml b/academic/views/res_company_views.xml index 765f5f97..990892ad 100644 --- a/academic/views/res_company_views.xml +++ b/academic/views/res_company_views.xml @@ -34,7 +34,6 @@
-
diff --git a/academic/views/res_partner_views.xml b/academic/views/res_partner_views.xml index 882d311e..2531ee17 100644 --- a/academic/views/res_partner_views.xml +++ b/academic/views/res_partner_views.xml @@ -82,17 +82,6 @@ {'no_create': True}
- - is_company or not parent_id or partner_type in ['student', 'family'] - - - - - partner_type in ['student', 'family'] or not is_company and parent_id - - @@ -196,7 +185,7 @@ +
+ + + + +
+ + + + + + + + manage_sale_workflow + + + + academic.group.list + academic.group + + + + + + + + diff --git a/academic/views/account_move_views.xml b/academic_sale_subscription/views/account_move_views.xml similarity index 100% rename from academic/views/account_move_views.xml rename to academic_sale_subscription/views/account_move_views.xml diff --git a/academic/views/account_portal_templates.xml b/academic_sale_subscription/views/account_portal_templates.xml similarity index 100% rename from academic/views/account_portal_templates.xml rename to academic_sale_subscription/views/account_portal_templates.xml diff --git a/academic/views/crm_lead_view.xml b/academic_sale_subscription/views/crm_lead_views.xml similarity index 100% rename from academic/views/crm_lead_view.xml rename to academic_sale_subscription/views/crm_lead_views.xml diff --git a/academic/views/product_template_views.xml b/academic_sale_subscription/views/product_template_views.xml similarity index 100% rename from academic/views/product_template_views.xml rename to academic_sale_subscription/views/product_template_views.xml diff --git a/academic_sale_subscription/views/res_company_views.xml b/academic_sale_subscription/views/res_company_views.xml new file mode 100644 index 00000000..1ec9e1b4 --- /dev/null +++ b/academic_sale_subscription/views/res_company_views.xml @@ -0,0 +1,15 @@ + + + + + res.company.form + res.company + + + + + + + + + diff --git a/academic_sale_subscription/views/res_partner_views.xml b/academic_sale_subscription/views/res_partner_views.xml index 4a6c6913..62f5f910 100644 --- a/academic_sale_subscription/views/res_partner_views.xml +++ b/academic_sale_subscription/views/res_partner_views.xml @@ -51,6 +51,18 @@
+ + + is_company or not parent_id or partner_type in ['student', 'family'] + + + + + partner_type in ['student', 'family'] or not is_company and parent_id + +
diff --git a/academic/views/sale_order_views.xml b/academic_sale_subscription/views/sale_order_views.xml similarity index 100% rename from academic/views/sale_order_views.xml rename to academic_sale_subscription/views/sale_order_views.xml diff --git a/academic_sale_subscription/wizard/__init__.py b/academic_sale_subscription/wizard/__init__.py index a0f6f3f5..a3ec7c95 100644 --- a/academic_sale_subscription/wizard/__init__.py +++ b/academic_sale_subscription/wizard/__init__.py @@ -1,3 +1,4 @@ # © 2016 ADHOC SA # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from . import academic_order_wizard +from . import account_move_send diff --git a/academic/wizards/account_move_send.py b/academic_sale_subscription/wizard/account_move_send.py similarity index 100% rename from academic/wizards/account_move_send.py rename to academic_sale_subscription/wizard/account_move_send.py From 0950a367aa78b7f7f4e169deee9cf3d63e4fde71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Scarafia?= Date: Wed, 28 May 2025 15:05:03 -0300 Subject: [PATCH 4/4] [IMP] academic: better demo --- README.md | 1 - academic/demo/res_company_demo.xml | 29 +++++++++++++++++++++++++++-- academic/demo/res_partner_demo.xml | 10 ++++++++++ academic/demo/res_users_demo.xml | 6 +++--- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f2dad9db..9cf8001a 100644 --- a/README.md +++ b/README.md @@ -12,4 +12,3 @@ Academic Evaluation Modules ADHOC **Adhoc SA** - www.adhoc.com.ar -. diff --git a/academic/demo/res_company_demo.xml b/academic/demo/res_company_demo.xml index c63a9a63..d0051352 100644 --- a/academic/demo/res_company_demo.xml +++ b/academic/demo/res_company_demo.xml @@ -18,6 +18,18 @@ + + + + + + + + Colegio Los Senderos + + + + @@ -27,7 +39,7 @@ Colegio Los Arroyos - + @@ -38,7 +50,20 @@ Colegio Los Molinos - + + + + + + + + + + + + Facultad de Ingeniería + + diff --git a/academic/demo/res_partner_demo.xml b/academic/demo/res_partner_demo.xml index 80523f79..385b5b5e 100644 --- a/academic/demo/res_partner_demo.xml +++ b/academic/demo/res_partner_demo.xml @@ -11,6 +11,16 @@ + + Facultad de Ingeniería + + + + + Jardín Los Senderos + + + Ignacio Rodriguez diff --git a/academic/demo/res_users_demo.xml b/academic/demo/res_users_demo.xml index bca85fc3..3ff46474 100644 --- a/academic/demo/res_users_demo.xml +++ b/academic/demo/res_users_demo.xml @@ -3,15 +3,15 @@ - + - + - +