From dccaef02f43bc92b697868088392c8621dd9d7d8 Mon Sep 17 00:00:00 2001 From: vidhya Date: Tue, 15 Sep 2026 10:47:19 +0530 Subject: [PATCH] full erp files --- .DS_Store | Bin 0 -> 8196 bytes EDUCATION_MODULES_SUMMARY.md | 438 ++ addons/.DS_Store | Bin 0 -> 6148 bytes addons/education_attendance/__init__.py | 1 + addons/education_attendance/__manifest__.py | 37 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 174 bytes .../__pycache__/__manifest__.cpython-310.pyc | Bin 0 -> 1117 bytes .../data/education_attendance_demo.xml | 440 ++ .../education_attendance/models/__init__.py | 4 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 302 bytes .../__pycache__/attendance.cpython-310.pyc | Bin 0 -> 1354 bytes .../attendance_line.cpython-310.pyc | Bin 0 -> 1176 bytes .../attendance_session.cpython-310.pyc | Bin 0 -> 5248 bytes .../student_inherit.cpython-310.pyc | Bin 0 -> 1950 bytes .../education_attendance/models/attendance.py | 29 + .../models/attendance_line.py | 28 + .../models/attendance_session.py | 161 + .../models/student_inherit.py | 33 + .../reports/attendance_reports.xml | 138 + .../education_attendance_security.xml | 57 + .../security/ir.model.access.csv | 7 + addons/education_attendance/tests/__init__.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 189 bytes .../test_attendance.cpython-310.pyc | Bin 0 -> 4996 bytes .../tests/test_attendance.py | 205 + .../views/attendance_menus.xml | 22 + .../views/attendance_session_views.xml | 188 + .../views/attendance_views.xml | 83 + .../views/student_views_inherit.xml | 38 + addons/education_base/__init__.py | 1 + addons/education_base/__manifest__.py | 40 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 168 bytes .../data/education_base_menu.xml | 6 + .../data/education_base_sequence.xml | 42 + .../demo/demo_academic_level.xml | 10 + .../demo/demo_academic_year.xml | 12 + addons/education_base/demo/demo_campus.xml | 10 + .../education_base/demo/demo_department.xml | 8 + addons/education_base/demo/demo_program.xml | 11 + addons/education_base/models/__init__.py | 7 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 386 bytes .../academic_level.cpython-310.pyc | Bin 0 -> 1549 bytes .../__pycache__/academic_term.cpython-310.pyc | Bin 0 -> 2467 bytes .../__pycache__/academic_year.cpython-310.pyc | Bin 0 -> 2900 bytes .../models/__pycache__/campus.cpython-310.pyc | Bin 0 -> 2137 bytes .../__pycache__/department.cpython-310.pyc | Bin 0 -> 1890 bytes .../education_config.cpython-310.pyc | Bin 0 -> 2722 bytes .../__pycache__/program.cpython-310.pyc | Bin 0 -> 2277 bytes .../education_base/models/academic_level.py | 35 + addons/education_base/models/academic_term.py | 65 + addons/education_base/models/academic_year.py | 69 + addons/education_base/models/campus.py | 52 + addons/education_base/models/department.py | 50 + .../education_base/models/education_config.py | 59 + addons/education_base/models/program.py | 67 + .../security/education_base_security.xml | 27 + .../security/ir.model.access.csv | 28 + .../education_base/security/record_rules.xml | 51 + addons/education_base/tests/__init__.py | 4 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 291 bytes .../test_academic_year.cpython-310.pyc | Bin 0 -> 1502 bytes .../__pycache__/test_campus.cpython-310.pyc | Bin 0 -> 998 bytes .../__pycache__/test_program.cpython-310.pyc | Bin 0 -> 1349 bytes .../__pycache__/test_security.cpython-310.pyc | Bin 0 -> 783 bytes .../tests/test_academic_year.py | 39 + addons/education_base/tests/test_campus.py | 23 + addons/education_base/tests/test_program.py | 35 + addons/education_base/tests/test_security.py | 13 + .../views/academic_level_views.xml | 61 + .../views/academic_term_views.xml | 72 + .../views/academic_year_views.xml | 85 + addons/education_base/views/campus_views.xml | 73 + .../education_base/views/department_views.xml | 72 + .../views/education_config_views.xml | 70 + addons/education_base/views/menuitems.xml | 11 + addons/education_base/views/program_views.xml | 70 + addons/education_dashboard/__init__.py | 2 + addons/education_dashboard/__manifest__.py | 36 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 207 bytes .../__pycache__/__manifest__.cpython-310.pyc | Bin 0 -> 1290 bytes .../controllers/__init__.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 183 bytes .../__pycache__/main.cpython-310.pyc | Bin 0 -> 3336 bytes .../education_dashboard/controllers/main.py | 104 + addons/education_dashboard/models/__init__.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 183 bytes .../__pycache__/dashboard.cpython-310.pyc | Bin 0 -> 483 bytes .../education_dashboard/models/dashboard.py | 7 + .../security/ir.model.access.csv | 2 + .../static/src/js/education_dashboard.js | 137 + .../src/xml/education_dashboard_templates.xml | 172 + addons/education_dashboard/tests/__init__.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 187 bytes .../test_dashboard.cpython-310.pyc | Bin 0 -> 2602 bytes .../tests/test_dashboard.py | 63 + .../views/dashboard_views.xml | 16 + addons/education_exam/__init__.py | 2 + addons/education_exam/__manifest__.py | 33 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 202 bytes addons/education_exam/controllers/__init__.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 185 bytes .../__pycache__/portal_exam.cpython-310.pyc | Bin 0 -> 3229 bytes .../education_exam/controllers/portal_exam.py | 107 + addons/education_exam/data/grading_data.xml | 89 + addons/education_exam/models/__init__.py | 6 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 397 bytes .../education_exam.cpython-310.pyc | Bin 0 -> 7395 bytes .../education_exam_result.cpython-310.pyc | Bin 0 -> 4164 bytes .../education_exam_type.cpython-310.pyc | Bin 0 -> 707 bytes .../education_grading.cpython-310.pyc | Bin 0 -> 2975 bytes .../education_student_inherit.cpython-310.pyc | Bin 0 -> 1194 bytes .../education_subject.cpython-310.pyc | Bin 0 -> 1118 bytes .../education_exam/models/education_exam.py | 193 + .../models/education_exam_result.py | 127 + .../models/education_exam_type.py | 11 + .../models/education_grading.py | 68 + .../models/education_student_inherit.py | 23 + .../models/education_subject.py | 22 + .../security/ir.model.access.csv | 17 + addons/education_exam/security/security.xml | 33 + .../views/education_exam_result_views.xml | 78 + .../views/education_exam_views.xml | 130 + .../views/education_grading_views.xml | 56 + .../views/education_student_inherit_views.xml | 15 + .../views/education_subject_views.xml | 52 + addons/education_exam/views/menu_views.xml | 17 + .../views/portal_exam_templates.xml | 157 + addons/education_finance/__init__.py | 1 + addons/education_finance/__manifest__.py | 33 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 171 bytes .../__pycache__/__manifest__.cpython-310.pyc | Bin 0 -> 1133 bytes .../data/education_finance_sequence.xml | 12 + addons/education_finance/models/__init__.py | 10 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 519 bytes .../fee_assignment.cpython-310.pyc | Bin 0 -> 2654 bytes .../__pycache__/fee_category.cpython-310.pyc | Bin 0 -> 1972 bytes .../__pycache__/fee_structure.cpython-310.pyc | Bin 0 -> 2800 bytes .../__pycache__/fee_type.cpython-310.pyc | Bin 0 -> 1683 bytes .../student_discount.cpython-310.pyc | Bin 0 -> 3276 bytes .../student_inherit.cpython-310.pyc | Bin 0 -> 2887 bytes .../student_ledger.cpython-310.pyc | Bin 0 -> 3237 bytes .../student_payment.cpython-310.pyc | Bin 0 -> 3627 bytes .../student_refund.cpython-310.pyc | Bin 0 -> 2856 bytes .../student_scholarship.cpython-310.pyc | Bin 0 -> 3216 bytes .../models/fee_assignment.py | 65 + .../education_finance/models/fee_category.py | 48 + .../education_finance/models/fee_structure.py | 64 + addons/education_finance/models/fee_type.py | 38 + .../models/student_discount.py | 85 + .../models/student_inherit.py | 49 + .../models/student_ledger.py | 80 + .../models/student_payment.py | 96 + .../models/student_refund.py | 70 + .../models/student_scholarship.py | 86 + .../reports/receipt_report.xml | 83 + .../security/education_finance_security.xml | 100 + .../security/ir.model.access.csv | 22 + addons/education_finance/tests/__init__.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 183 bytes .../__pycache__/test_finance.cpython-310.pyc | Bin 0 -> 6630 bytes .../education_finance/tests/test_finance.py | 278 ++ .../views/fee_assignment_views.xml | 71 + .../views/fee_category_views.xml | 33 + .../views/fee_structure_views.xml | 83 + .../views/fee_type_views.xml | 41 + .../education_finance/views/finance_menus.xml | 74 + .../views/student_discount_views.xml | 70 + .../views/student_ledger_views.xml | 68 + .../views/student_payment_views.xml | 105 + .../views/student_refund_views.xml | 69 + .../views/student_scholarship_views.xml | 73 + .../views/student_views_inherit.xml | 89 + addons/education_portal/__init__.py | 2 + addons/education_portal/__manifest__.py | 40 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 204 bytes .../__pycache__/__manifest__.cpython-310.pyc | Bin 0 -> 1022 bytes .../education_portal/controllers/__init__.py | 2 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 210 bytes .../__pycache__/main.cpython-310.pyc | Bin 0 -> 8009 bytes .../__pycache__/payment.cpython-310.pyc | Bin 0 -> 3952 bytes addons/education_portal/controllers/main.py | 242 + .../education_portal/controllers/payment.py | 121 + .../data/payment_sequence.xml | 12 + addons/education_portal/models/__init__.py | 5 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 350 bytes .../__pycache__/announcement.cpython-310.pyc | Bin 0 -> 1194 bytes .../fee_assignment_inherit.cpython-310.pyc | Bin 0 -> 1299 bytes .../payment_transaction.cpython-310.pyc | Bin 0 -> 4640 bytes .../res_config_settings.cpython-310.pyc | Bin 0 -> 3274 bytes .../student_inherit.cpython-310.pyc | Bin 0 -> 5584 bytes .../education_portal/models/announcement.py | 26 + .../models/fee_assignment_inherit.py | 21 + .../models/payment_transaction.py | 125 + .../models/res_config_settings.py | 83 + .../models/student_inherit.py | 185 + .../security/education_portal_security.xml | 39 + .../security/ir.model.access.csv | 14 + addons/education_portal/services/__init__.py | 3 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 272 bytes .../payment_service.cpython-310.pyc | Bin 0 -> 6160 bytes .../razorpay_service.cpython-310.pyc | Bin 0 -> 4940 bytes .../reconciliation_service.cpython-310.pyc | Bin 0 -> 6473 bytes .../services/payment_service.py | 227 + .../services/razorpay_service.py | 165 + .../services/reconciliation_service.py | 170 + .../static/src/img/campus_login_bg.jpg | Bin 0 -> 350245 bytes addons/education_portal/tests/__init__.py | 2 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 224 bytes .../test_payment_gateway.cpython-310.pyc | Bin 0 -> 8096 bytes .../__pycache__/test_portal.cpython-310.pyc | Bin 0 -> 7937 bytes .../tests/test_payment_gateway.py | 271 ++ addons/education_portal/tests/test_portal.py | 236 + .../views/announcement_views.xml | 79 + .../views/login_templates.xml | 452 ++ .../views/payment_transaction_views.xml | 123 + .../views/portal_payment_templates.xml | 217 + .../views/portal_templates.xml | 4194 +++++++++++++++++ .../views/res_config_settings_views.xml | 141 + .../views/student_views_inherit.xml | 43 + addons/education_reports/__init__.py | 1 + addons/education_reports/__manifest__.py | 32 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 172 bytes .../__pycache__/__manifest__.cpython-310.pyc | Bin 0 -> 1162 bytes .../reports/attendance_academic_reports.xml | 129 + .../reports/finance_reports.xml | 133 + .../reports/student_reports.xml | 173 + .../security/ir.model.access.csv | 2 + addons/education_reports/tests/__init__.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 183 bytes .../__pycache__/test_reports.cpython-310.pyc | Bin 0 -> 3510 bytes .../education_reports/tests/test_reports.py | 99 + .../views/report_wizard_views.xml | 54 + addons/education_reports/wizards/__init__.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 186 bytes .../__pycache__/report_wizard.cpython-310.pyc | Bin 0 -> 4479 bytes .../wizards/report_wizard.py | 171 + addons/education_student/__init__.py | 1 + addons/education_student/__manifest__.py | 29 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 171 bytes .../data/education_student_menus_actions.xml | 18 + .../data/education_student_sequence.xml | 29 + .../education_student/demo/demo_student.xml | 61 + addons/education_student/models/__init__.py | 2 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 217 bytes .../academic_config.cpython-310.pyc | Bin 0 -> 3446 bytes .../__pycache__/student.cpython-310.pyc | Bin 0 -> 10973 bytes .../models/academic_config.py | 133 + addons/education_student/models/student.py | 272 ++ .../security/education_student_security.xml | 27 + .../security/ir.model.access.csv | 10 + .../security/record_rules.xml | 27 + addons/education_student/tests/__init__.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 183 bytes .../__pycache__/test_student.cpython-310.pyc | Bin 0 -> 3039 bytes .../education_student/tests/test_student.py | 104 + .../views/academic_config_views.xml | 96 + .../education_student/views/student_views.xml | 322 ++ docker-compose.yml | 33 + 258 files changed, 16165 insertions(+) create mode 100644 .DS_Store create mode 100644 EDUCATION_MODULES_SUMMARY.md create mode 100644 addons/.DS_Store create mode 100644 addons/education_attendance/__init__.py create mode 100644 addons/education_attendance/__manifest__.py create mode 100644 addons/education_attendance/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_attendance/__pycache__/__manifest__.cpython-310.pyc create mode 100644 addons/education_attendance/data/education_attendance_demo.xml create mode 100644 addons/education_attendance/models/__init__.py create mode 100644 addons/education_attendance/models/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_attendance/models/__pycache__/attendance.cpython-310.pyc create mode 100644 addons/education_attendance/models/__pycache__/attendance_line.cpython-310.pyc create mode 100644 addons/education_attendance/models/__pycache__/attendance_session.cpython-310.pyc create mode 100644 addons/education_attendance/models/__pycache__/student_inherit.cpython-310.pyc create mode 100644 addons/education_attendance/models/attendance.py create mode 100644 addons/education_attendance/models/attendance_line.py create mode 100644 addons/education_attendance/models/attendance_session.py create mode 100644 addons/education_attendance/models/student_inherit.py create mode 100644 addons/education_attendance/reports/attendance_reports.xml create mode 100644 addons/education_attendance/security/education_attendance_security.xml create mode 100644 addons/education_attendance/security/ir.model.access.csv create mode 100644 addons/education_attendance/tests/__init__.py create mode 100644 addons/education_attendance/tests/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_attendance/tests/__pycache__/test_attendance.cpython-310.pyc create mode 100644 addons/education_attendance/tests/test_attendance.py create mode 100644 addons/education_attendance/views/attendance_menus.xml create mode 100644 addons/education_attendance/views/attendance_session_views.xml create mode 100644 addons/education_attendance/views/attendance_views.xml create mode 100644 addons/education_attendance/views/student_views_inherit.xml create mode 100644 addons/education_base/__init__.py create mode 100644 addons/education_base/__manifest__.py create mode 100644 addons/education_base/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_base/data/education_base_menu.xml create mode 100644 addons/education_base/data/education_base_sequence.xml create mode 100644 addons/education_base/demo/demo_academic_level.xml create mode 100644 addons/education_base/demo/demo_academic_year.xml create mode 100644 addons/education_base/demo/demo_campus.xml create mode 100644 addons/education_base/demo/demo_department.xml create mode 100644 addons/education_base/demo/demo_program.xml create mode 100644 addons/education_base/models/__init__.py create mode 100644 addons/education_base/models/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_base/models/__pycache__/academic_level.cpython-310.pyc create mode 100644 addons/education_base/models/__pycache__/academic_term.cpython-310.pyc create mode 100644 addons/education_base/models/__pycache__/academic_year.cpython-310.pyc create mode 100644 addons/education_base/models/__pycache__/campus.cpython-310.pyc create mode 100644 addons/education_base/models/__pycache__/department.cpython-310.pyc create mode 100644 addons/education_base/models/__pycache__/education_config.cpython-310.pyc create mode 100644 addons/education_base/models/__pycache__/program.cpython-310.pyc create mode 100644 addons/education_base/models/academic_level.py create mode 100644 addons/education_base/models/academic_term.py create mode 100644 addons/education_base/models/academic_year.py create mode 100644 addons/education_base/models/campus.py create mode 100644 addons/education_base/models/department.py create mode 100644 addons/education_base/models/education_config.py create mode 100644 addons/education_base/models/program.py create mode 100644 addons/education_base/security/education_base_security.xml create mode 100644 addons/education_base/security/ir.model.access.csv create mode 100644 addons/education_base/security/record_rules.xml create mode 100644 addons/education_base/tests/__init__.py create mode 100644 addons/education_base/tests/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_base/tests/__pycache__/test_academic_year.cpython-310.pyc create mode 100644 addons/education_base/tests/__pycache__/test_campus.cpython-310.pyc create mode 100644 addons/education_base/tests/__pycache__/test_program.cpython-310.pyc create mode 100644 addons/education_base/tests/__pycache__/test_security.cpython-310.pyc create mode 100644 addons/education_base/tests/test_academic_year.py create mode 100644 addons/education_base/tests/test_campus.py create mode 100644 addons/education_base/tests/test_program.py create mode 100644 addons/education_base/tests/test_security.py create mode 100644 addons/education_base/views/academic_level_views.xml create mode 100644 addons/education_base/views/academic_term_views.xml create mode 100644 addons/education_base/views/academic_year_views.xml create mode 100644 addons/education_base/views/campus_views.xml create mode 100644 addons/education_base/views/department_views.xml create mode 100644 addons/education_base/views/education_config_views.xml create mode 100644 addons/education_base/views/menuitems.xml create mode 100644 addons/education_base/views/program_views.xml create mode 100644 addons/education_dashboard/__init__.py create mode 100644 addons/education_dashboard/__manifest__.py create mode 100644 addons/education_dashboard/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_dashboard/__pycache__/__manifest__.cpython-310.pyc create mode 100644 addons/education_dashboard/controllers/__init__.py create mode 100644 addons/education_dashboard/controllers/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_dashboard/controllers/__pycache__/main.cpython-310.pyc create mode 100644 addons/education_dashboard/controllers/main.py create mode 100644 addons/education_dashboard/models/__init__.py create mode 100644 addons/education_dashboard/models/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_dashboard/models/__pycache__/dashboard.cpython-310.pyc create mode 100644 addons/education_dashboard/models/dashboard.py create mode 100644 addons/education_dashboard/security/ir.model.access.csv create mode 100644 addons/education_dashboard/static/src/js/education_dashboard.js create mode 100644 addons/education_dashboard/static/src/xml/education_dashboard_templates.xml create mode 100644 addons/education_dashboard/tests/__init__.py create mode 100644 addons/education_dashboard/tests/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_dashboard/tests/__pycache__/test_dashboard.cpython-310.pyc create mode 100644 addons/education_dashboard/tests/test_dashboard.py create mode 100644 addons/education_dashboard/views/dashboard_views.xml create mode 100644 addons/education_exam/__init__.py create mode 100644 addons/education_exam/__manifest__.py create mode 100644 addons/education_exam/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_exam/controllers/__init__.py create mode 100644 addons/education_exam/controllers/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_exam/controllers/__pycache__/portal_exam.cpython-310.pyc create mode 100644 addons/education_exam/controllers/portal_exam.py create mode 100644 addons/education_exam/data/grading_data.xml create mode 100644 addons/education_exam/models/__init__.py create mode 100644 addons/education_exam/models/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_exam/models/__pycache__/education_exam.cpython-310.pyc create mode 100644 addons/education_exam/models/__pycache__/education_exam_result.cpython-310.pyc create mode 100644 addons/education_exam/models/__pycache__/education_exam_type.cpython-310.pyc create mode 100644 addons/education_exam/models/__pycache__/education_grading.cpython-310.pyc create mode 100644 addons/education_exam/models/__pycache__/education_student_inherit.cpython-310.pyc create mode 100644 addons/education_exam/models/__pycache__/education_subject.cpython-310.pyc create mode 100644 addons/education_exam/models/education_exam.py create mode 100644 addons/education_exam/models/education_exam_result.py create mode 100644 addons/education_exam/models/education_exam_type.py create mode 100644 addons/education_exam/models/education_grading.py create mode 100644 addons/education_exam/models/education_student_inherit.py create mode 100644 addons/education_exam/models/education_subject.py create mode 100644 addons/education_exam/security/ir.model.access.csv create mode 100644 addons/education_exam/security/security.xml create mode 100644 addons/education_exam/views/education_exam_result_views.xml create mode 100644 addons/education_exam/views/education_exam_views.xml create mode 100644 addons/education_exam/views/education_grading_views.xml create mode 100644 addons/education_exam/views/education_student_inherit_views.xml create mode 100644 addons/education_exam/views/education_subject_views.xml create mode 100644 addons/education_exam/views/menu_views.xml create mode 100644 addons/education_exam/views/portal_exam_templates.xml create mode 100644 addons/education_finance/__init__.py create mode 100644 addons/education_finance/__manifest__.py create mode 100644 addons/education_finance/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_finance/__pycache__/__manifest__.cpython-310.pyc create mode 100644 addons/education_finance/data/education_finance_sequence.xml create mode 100644 addons/education_finance/models/__init__.py create mode 100644 addons/education_finance/models/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_finance/models/__pycache__/fee_assignment.cpython-310.pyc create mode 100644 addons/education_finance/models/__pycache__/fee_category.cpython-310.pyc create mode 100644 addons/education_finance/models/__pycache__/fee_structure.cpython-310.pyc create mode 100644 addons/education_finance/models/__pycache__/fee_type.cpython-310.pyc create mode 100644 addons/education_finance/models/__pycache__/student_discount.cpython-310.pyc create mode 100644 addons/education_finance/models/__pycache__/student_inherit.cpython-310.pyc create mode 100644 addons/education_finance/models/__pycache__/student_ledger.cpython-310.pyc create mode 100644 addons/education_finance/models/__pycache__/student_payment.cpython-310.pyc create mode 100644 addons/education_finance/models/__pycache__/student_refund.cpython-310.pyc create mode 100644 addons/education_finance/models/__pycache__/student_scholarship.cpython-310.pyc create mode 100644 addons/education_finance/models/fee_assignment.py create mode 100644 addons/education_finance/models/fee_category.py create mode 100644 addons/education_finance/models/fee_structure.py create mode 100644 addons/education_finance/models/fee_type.py create mode 100644 addons/education_finance/models/student_discount.py create mode 100644 addons/education_finance/models/student_inherit.py create mode 100644 addons/education_finance/models/student_ledger.py create mode 100644 addons/education_finance/models/student_payment.py create mode 100644 addons/education_finance/models/student_refund.py create mode 100644 addons/education_finance/models/student_scholarship.py create mode 100644 addons/education_finance/reports/receipt_report.xml create mode 100644 addons/education_finance/security/education_finance_security.xml create mode 100644 addons/education_finance/security/ir.model.access.csv create mode 100644 addons/education_finance/tests/__init__.py create mode 100644 addons/education_finance/tests/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_finance/tests/__pycache__/test_finance.cpython-310.pyc create mode 100644 addons/education_finance/tests/test_finance.py create mode 100644 addons/education_finance/views/fee_assignment_views.xml create mode 100644 addons/education_finance/views/fee_category_views.xml create mode 100644 addons/education_finance/views/fee_structure_views.xml create mode 100644 addons/education_finance/views/fee_type_views.xml create mode 100644 addons/education_finance/views/finance_menus.xml create mode 100644 addons/education_finance/views/student_discount_views.xml create mode 100644 addons/education_finance/views/student_ledger_views.xml create mode 100644 addons/education_finance/views/student_payment_views.xml create mode 100644 addons/education_finance/views/student_refund_views.xml create mode 100644 addons/education_finance/views/student_scholarship_views.xml create mode 100644 addons/education_finance/views/student_views_inherit.xml create mode 100644 addons/education_portal/__init__.py create mode 100644 addons/education_portal/__manifest__.py create mode 100644 addons/education_portal/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_portal/__pycache__/__manifest__.cpython-310.pyc create mode 100644 addons/education_portal/controllers/__init__.py create mode 100644 addons/education_portal/controllers/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_portal/controllers/__pycache__/main.cpython-310.pyc create mode 100644 addons/education_portal/controllers/__pycache__/payment.cpython-310.pyc create mode 100644 addons/education_portal/controllers/main.py create mode 100644 addons/education_portal/controllers/payment.py create mode 100644 addons/education_portal/data/payment_sequence.xml create mode 100644 addons/education_portal/models/__init__.py create mode 100644 addons/education_portal/models/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_portal/models/__pycache__/announcement.cpython-310.pyc create mode 100644 addons/education_portal/models/__pycache__/fee_assignment_inherit.cpython-310.pyc create mode 100644 addons/education_portal/models/__pycache__/payment_transaction.cpython-310.pyc create mode 100644 addons/education_portal/models/__pycache__/res_config_settings.cpython-310.pyc create mode 100644 addons/education_portal/models/__pycache__/student_inherit.cpython-310.pyc create mode 100644 addons/education_portal/models/announcement.py create mode 100644 addons/education_portal/models/fee_assignment_inherit.py create mode 100644 addons/education_portal/models/payment_transaction.py create mode 100644 addons/education_portal/models/res_config_settings.py create mode 100644 addons/education_portal/models/student_inherit.py create mode 100644 addons/education_portal/security/education_portal_security.xml create mode 100644 addons/education_portal/security/ir.model.access.csv create mode 100644 addons/education_portal/services/__init__.py create mode 100644 addons/education_portal/services/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_portal/services/__pycache__/payment_service.cpython-310.pyc create mode 100644 addons/education_portal/services/__pycache__/razorpay_service.cpython-310.pyc create mode 100644 addons/education_portal/services/__pycache__/reconciliation_service.cpython-310.pyc create mode 100644 addons/education_portal/services/payment_service.py create mode 100644 addons/education_portal/services/razorpay_service.py create mode 100644 addons/education_portal/services/reconciliation_service.py create mode 100644 addons/education_portal/static/src/img/campus_login_bg.jpg create mode 100644 addons/education_portal/tests/__init__.py create mode 100644 addons/education_portal/tests/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_portal/tests/__pycache__/test_payment_gateway.cpython-310.pyc create mode 100644 addons/education_portal/tests/__pycache__/test_portal.cpython-310.pyc create mode 100644 addons/education_portal/tests/test_payment_gateway.py create mode 100644 addons/education_portal/tests/test_portal.py create mode 100644 addons/education_portal/views/announcement_views.xml create mode 100644 addons/education_portal/views/login_templates.xml create mode 100644 addons/education_portal/views/payment_transaction_views.xml create mode 100644 addons/education_portal/views/portal_payment_templates.xml create mode 100644 addons/education_portal/views/portal_templates.xml create mode 100644 addons/education_portal/views/res_config_settings_views.xml create mode 100644 addons/education_portal/views/student_views_inherit.xml create mode 100644 addons/education_reports/__init__.py create mode 100644 addons/education_reports/__manifest__.py create mode 100644 addons/education_reports/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_reports/__pycache__/__manifest__.cpython-310.pyc create mode 100644 addons/education_reports/reports/attendance_academic_reports.xml create mode 100644 addons/education_reports/reports/finance_reports.xml create mode 100644 addons/education_reports/reports/student_reports.xml create mode 100644 addons/education_reports/security/ir.model.access.csv create mode 100644 addons/education_reports/tests/__init__.py create mode 100644 addons/education_reports/tests/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_reports/tests/__pycache__/test_reports.cpython-310.pyc create mode 100644 addons/education_reports/tests/test_reports.py create mode 100644 addons/education_reports/views/report_wizard_views.xml create mode 100644 addons/education_reports/wizards/__init__.py create mode 100644 addons/education_reports/wizards/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_reports/wizards/__pycache__/report_wizard.cpython-310.pyc create mode 100644 addons/education_reports/wizards/report_wizard.py create mode 100644 addons/education_student/__init__.py create mode 100644 addons/education_student/__manifest__.py create mode 100644 addons/education_student/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_student/data/education_student_menus_actions.xml create mode 100644 addons/education_student/data/education_student_sequence.xml create mode 100644 addons/education_student/demo/demo_student.xml create mode 100644 addons/education_student/models/__init__.py create mode 100644 addons/education_student/models/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_student/models/__pycache__/academic_config.cpython-310.pyc create mode 100644 addons/education_student/models/__pycache__/student.cpython-310.pyc create mode 100644 addons/education_student/models/academic_config.py create mode 100644 addons/education_student/models/student.py create mode 100644 addons/education_student/security/education_student_security.xml create mode 100644 addons/education_student/security/ir.model.access.csv create mode 100644 addons/education_student/security/record_rules.xml create mode 100644 addons/education_student/tests/__init__.py create mode 100644 addons/education_student/tests/__pycache__/__init__.cpython-310.pyc create mode 100644 addons/education_student/tests/__pycache__/test_student.cpython-310.pyc create mode 100644 addons/education_student/tests/test_student.py create mode 100644 addons/education_student/views/academic_config_views.xml create mode 100644 addons/education_student/views/student_views.xml create mode 100644 docker-compose.yml diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..792f7b9c9bf98556908063c6212f6f4e8e731b43 GIT binary patch literal 8196 zcmeHM%}>-o6n}##T~R3rieE`}FD9Y_ek8^imt~D^6qis$@N4b1!_v^5*|Y^jLb7K) z`3HFOV&ch@i5HJvym~YK33~OUZ$4I8@FQM~#+hc`@6F76Z)Seo>2$gPK=KR05I{cw zSeOOcohZJOM7zivQk$;Xib&8OfI(7>xL=8QWrMY;Fbo(53GnZ(soTY>})V z?E9+KNW*|(;J;)*JRcm)0&^N03gx2%g**ZvwqvzSsN*?6`ZyYM8XF3wE5=l(2clhx zK4K8%j_r;xN6cw#C{*r1lsgdJGtnm$B6~+aN16l4DKyeBU>MlQfH=Ev0S^Ma_`37E zSW0=!$3Dv>om*@xOG#ay^)4J`>@g+C)(uE}N#WzK1 zlJlt2)V<17NSDfX?fzh<+_N zxOLADS#>(4_fj{!Ps?`CI@1Xn-1$~Dxm-t^-ftB$+{KtuCgYW6XA_!EuhD19c30xZ zJc`D75~d+%Gq)1tx|q^hkMdJiaM2?V?AE8 cL)0BK6+StQ4TV^P$$SWqG#FtR_@fMb0eNg1H~;_u literal 0 HcmV?d00001 diff --git a/EDUCATION_MODULES_SUMMARY.md b/EDUCATION_MODULES_SUMMARY.md new file mode 100644 index 0000000..8109ccb --- /dev/null +++ b/EDUCATION_MODULES_SUMMARY.md @@ -0,0 +1,438 @@ +# 📚 Complete Technical Documentation of Custom Education Odoo Addons + +This document contains a comprehensive, field-level technical summary of all **7 custom education modules** built for the Odoo Education & Student Portal ecosystem. + +--- + +## 1. 🏛️ `education_base` (Base Academic Setup & Hierarchy) + +### **Module Overview** +The `education_base` module establishes the core structural hierarchy of the educational institution. It defines campuses, departments, degree programs, academic levels, and academic calendar timelines (years and terms). + +--- + +### **Models & Detailed Field Definitions** + +#### **1.1 `education.campus` (Campus Master)** +- **Functionality**: Represents physical or virtual university campuses. +- **Fields**: + - `name` *(Char, Required)*: The official name of the campus. + - `code` *(Char, Required)*: Unique abbreviation code. + - `street` / `street2` / `city` / `zip` / `state_id` / `country_id` *(Location fields)*: Physical address details. + - `phone` / `email` *(Char)*: Contact details for campus administration. + - `company_id` *(Many2one)*: Associated Odoo `res.company`. + - `active` *(Boolean)*: Soft-delete toggle (default: `True`). + +#### **1.2 `education.department` (Department Master)** +- **Functionality**: Academic departments within a campus (e.g., Computer Science, Mechanical). +- **Fields**: + - `name` *(Char, Required)*: Department name. + - `code` *(Char, Required)*: Short code. + - `campus_id` *(Many2one, Required)*: Relates to `education.campus`. + - `head_teacher_id` *(Many2one)*: Relates to `res.partner` (Department Chair / Dean). + - `program_ids` *(One2many)*: List of programs offered under this department. + - `company_id` *(Many2one)*: Multi-company reference. + +#### **1.3 `education.program` (Academic Program)** +- **Functionality**: Degree or diploma courses (e.g., MCA, B.Tech, MBA). +- **Fields**: + - `name` *(Char, Required)*: Program name. + - `code` *(Char, Required)*: Program short code. + - `department_id` *(Many2one, Required)*: Relates to `education.department`. + - `level_id` *(Many2one)*: Academic level (`Undergraduate`, `Postgraduate`). + - `duration` *(Integer)*: Number of duration units (e.g., `2`). + - `duration_unit` *(Selection)*: `year`, `month`, or `semester`. + - `description` *(Text)*: Overview of course curriculum. + +#### **1.4 `education.academic.year` (Academic Session)** +- **Functionality**: Defines full academic years and tracks current active session. +- **Fields**: + - `name` *(Char, Required)*: Name of the academic year (e.g., `"2026 - 2027"`). + - `code` *(Char, Required)*: Unique code. + - `start_date` *(Date, Required)*: Start date of session. + - `end_date` *(Date, Required)*: End date of session. + - `current_year` *(Boolean)*: Indicates if this is the active operational year. + - `state` *(Selection)*: `draft`, `active`, `closed`. + - `term_ids` *(One2many)*: Semesters/Terms within this year. + +#### **1.5 `education.academic.term` (Academic Semester / Term)** +- **Functionality**: Sub-divisions of an academic year (e.g., Semester 1, Semester 2). +- **Fields**: + - `name` *(Char, Required)*: Term name. + - `code` *(Char, Required)*: Term code (e.g., `"SEM1"`). + - `academic_year_id` *(Many2one, Required)*: Parent `education.academic.year`. + - `sequence` *(Integer)*: Term ordering (1, 2, 3...). + - `start_date` / `end_date` *(Date)*: Start and end dates of term. + - `state` *(Selection)*: `draft`, `active`, `completed`. + +#### **1.6 `education.configuration` (Education Configuration)** +- **Functionality**: Global institution-level settings and defaults. +- **Fields**: + - `school_name` / `school_code` *(Char, Required)*: Institution name and code. + - `default_campus_id` / `default_academic_year_id` / `default_term_id` *(Many2one)*: Default hierarchical selections for the institution. + - `logo` *(Binary)*: Institution logo. + - `contact_email` / `contact_phone` *(Char)*: Global contact information. + +#### **1.7 `education.academic.level` (Academic Level)** +- **Functionality**: Classification of education levels (e.g., Undergraduate, Postgraduate). +- **Fields**: + - `name` *(Char, Required)*: Level name. + - `code` *(Char, Required)*: Level code. + - `sequence` *(Integer)*: Ordering priority. + - `description` *(Text)*: Additional details about the level. + +--- + +### **Record Examples (`student_db`)** +```yaml +Campus Record: + ID: 1 | Name: "Main Campus" | Code: "MC" | City: "Thanjavur" + +Department Record: + ID: 1 | Name: "School of Computing" | Code: "SCS" | Campus: "Main Campus" + +Program Record: + ID: 1 | Name: "Master of Computer Applications - Lateral Entry" | Code: "MCA-L" | Duration: 2 Years + +Academic Year Record: + ID: 1 | Name: "2026 - 2027" | Code: "053" | Start: 2026-07-03 | End: 2027-07-03 | Current: True + +Academic Term Record: + ID: 1 | Name: "Semester 1" | Code: "SEM1" | Sequence: 1 | Year: "2026 - 2027" +``` + +--- + +## 2. 👨‍🎓 `education_student` (Student Lifecycle & Profile Management) + +### **Module Overview** +Central student registry handling personal profiles, academic placement, guardian information, document attachments, portal user links, and automated financial/attendance computations. + +--- + +### **Models & Detailed Field Definitions** + +#### **2.1 `education.student` (Student Profile)** +- **Personal Details**: + - `first_name` / `middle_name` / `last_name` *(Char)*: Individual name components. + - `full_name` *(Char, Computed)*: Concatenated name (`"Aadhav S"`). + - `gender` *(Selection)*: `male`, `female`, `other`. + - `date_of_birth` *(Date)*: Date of birth. + - `age` *(Integer, Computed)*: Calculated age in years. + - `email` / `phone` / `mobile` *(Char)*: Contact info. + - `image_1920` *(Image)*: Student profile photo. +- **Academic Identification & Placement**: + - `admission_number` *(Char)*: Auto-generated admission ID (`"ADM/2026/001"`). + - `roll_number` *(Char)*: Roll number (`"ROLL/0003"`). + - `registration_number` *(Char)*: University registration number. + - `campus_id` / `department_id` / `program_id` / `academic_year_id` / `academic_term_id` *(Many2one)*: Relates student to institutional hierarchy. + - `status` *(Selection)*: `draft`, `applied`, `admitted`, `enrolled`, `graduated`, `withdrawn`. +- **Parents & Guardian Information**: + - `father_name` / `father_phone` / `father_occupation` *(Char)*: Father's details. + - `mother_name` / `mother_phone` / `mother_occupation` *(Char)*: Mother's details. + - `guardian_name` / `guardian_phone` / `guardian_email` / `guardian_address` *(Char/Text)*: Guardian details. +- **Document Storage**: + - `doc_passport`, `doc_aadhaar`, `doc_birth_cert`, `doc_transfer_cert`, `doc_community_cert`, `doc_mark_sheets` *(Binary & Char)*: Stores uploaded compliance documents and filenames. +- **Computed Financial Summary**: + - `total_fees` *(Monetary, Computed)*: Total assigned fees (₹34,000.00). + - `total_paid` *(Monetary, Computed)*: Sum of all posted payments (₹34,000.00). + - `outstanding_amount` *(Monetary, Computed)*: Total due balance (₹0.00). + - `payment_status` *(Selection, Computed)*: `no_fees`, `unpaid`, `partial`, `fully_paid`. + - `ledger_line_ids` *(One2many)*: Inverse relation to `education.student.ledger`. +- **Computed Attendance Summary**: + - `attendance_pct` *(Float, Computed)*: Overall attendance percentage (e.g., `92.5`). + - `present_count` / `absent_count` / `late_count` / `medical_leave_count` *(Integer, Computed)*: Session counts. +- **Portal User Linkage**: + - `portal_user_id` *(Many2one)*: Relates to Odoo `res.users` (Portal Account). + - `portal_status` *(Selection)*: `draft`, `active`, `deactivated`. + +#### **2.2 `education.batch` (Academic Batch)** +- **Functionality**: Represents a specific student batch/cohort. +- **Fields**: + - `name` *(Char, Required)*: Batch name. + - `code` *(Char, Required)*: Unique batch code. + +#### **2.3 `education.semester` (Academic Semester)** +- **Functionality**: Specific academic semesters decoupled from global terms. +- **Fields**: + - `name` *(Char, Required)*: Semester name. + - `code` *(Char, Required)*: Unique semester code. + +#### **2.4 `education.section` (Academic Section)** +- **Functionality**: Classroom sections or groups for students. +- **Fields**: + - `name` *(Char, Required)*: Section name. + - `code` *(Char, Required)*: Unique section code. + +--- + +### **Record Examples (`student_db`)** +```yaml +Student Record #284: + Admission No: "ADM/2026/001" | Roll No: "ROLL/0003" + Name: "Aadhav S" | Email: "aadhav@gmail.com" | Phone: "9876543210" + Program: "Master of Computer Applications - Lateral Entry" + Total Fees: ₹34,000.00 | Total Paid: ₹34,000.00 | Outstanding: ₹0.00 + Payment Status: fully_paid + Attendance %: 92.5% (Present: 37, Absent: 3) + Portal User: aadhav@gmail.com (Status: Active) +``` + +--- + +## 3. 📅 `education_attendance` (Attendance & Absence Tracking) + +### **Module Overview** +Provides session-level attendance recording for students, automated calculation of attendance percentages, and historical session logs. + +--- + +### **Models & Detailed Field Definitions** + +#### **3.1 `education.attendance` (Student Attendance Record)** +- **Fields**: + - `student_id` *(Many2one, Required)*: Relates to `education.student`. + - `date` *(Date, Required)*: Attendance session date. + - `state` *(Selection, Required)*: `present`, `absent`, `late`, `medical_leave`. + - `remarks` *(Text)*: Optional notes or justification for leave. + - `academic_year_id` / `academic_term_id` *(Many2one)*: Timeline reference. + - `company_id` *(Many2one)*: Multi-company lock. + +#### **3.2 `education.attendance.session` (Attendance Session)** +- **Functionality**: Organizes attendance taking by combining date, location, and hierarchy. +- **Fields**: + - `name` *(Char, Required)*: Session title. + - `campus_id` / `department_id` / `program_id` / `academic_year_id` *(Many2one, Required)*: Targeting fields. + +#### **3.3 `education.attendance.line` (Attendance Line Detail)** +- **Functionality**: Individual student attendance state for a session. +- **Fields**: + - `session_id` *(Many2one, Required)*: Parent session. + - `student_id` *(Many2one, Required)*: Target student. + - `status` *(Selection)*: `present`, `absent`, `late`. + +--- + +### **Record Examples (`student_db`)** +```yaml +Attendance Entry #101: + Student: "Aadhav S" + Date: 2026-08-10 + State: Present + Academic Term: Semester 1 + +Attendance Entry #102: + Student: "Hareish S" + Date: 2026-08-10 + State: Absent + Remarks: "Sick leave - Doctor note submitted" +``` + +--- + +## 4. 💳 `education_finance` (Fees, Payments & Double-Entry Ledger) + +### **Module Overview** +Complete financial accounting engine for student fee assessment, payment posting, fee structures, discounts, scholarships, refunds, and real-time double-entry student financial ledgers. + +--- + +### **Models & Detailed Field Definitions** + +#### **4.1 `education.fee.category` (Fee Head)** +- **Fields**: + - `name` *(Char, Required)*: Name of fee type (`"Tuition"`, `"Hostel"`, `"Library"`). + - `code` *(Char, Required)*: Code (`TUI`, `HOS`, `LIB`, `EXM`, `TRN`). + +#### **4.2 `education.fee.structure` & `education.fee.structure.line` (Fee Template)** +- **Fields (`education.fee.structure`)**: + - `name` *(Char, Required)*: Name of structure (e.g., `"MCA Semester 1 2026 Regular"`). + - `program_id` / `academic_year_id` / `academic_term_id` *(Many2one)*: Applicable target. + - `line_ids` *(One2many)*: List of `education.fee.structure.line` items. +- **Fields (`education.fee.structure.line`)**: + - `category_id` *(Many2one, Required)*: Relates to `education.fee.category`. + - `amount` *(Monetary, Required)*: Line fee amount. + +#### **4.3 `education.fee.assignment` (Student Fee Assessment)** +- **Fields**: + - `name` *(Char)*: Sequence (`"ASSIGN/2026/00001"`). + - `student_id` *(Many2one, Required)*: Target student. + - `fee_structure_id` *(Many2one)*: Fee structure applied. + - `total_amount` *(Monetary)*: Gross fee amount (₹34,000.00). + - `discount_amount` *(Monetary)*: Discount concession applied. + - `net_amount` *(Monetary)*: Net due after discounts. + - `amount_paid` *(Monetary, Computed)*: Total payments posted against this assignment. + - `state` *(Selection)*: `draft`, `assigned`, `paid`, `cancelled`. + +#### **4.4 `education.student.payment` (Payment Receipt)** +- **Fields**: + - `name` *(Char)*: Sequence number (`"PAY/2026/00012"`). + - `student_id` *(Many2one, Required)*: Paying student. + - `fee_assignment_id` *(Many2one)*: Associated fee assignment. + - `amount` *(Monetary, Required)*: Amount paid (₹34,000.00). + - `payment_date` *(Date, Required)*: Date of transaction. + - `payment_method` *(Selection)*: `cash`, `bank`, `online`. + - `transaction_id` *(Char)*: Payment gateway transaction ref (`"pay_Px92kL10z"`). + - `state` *(Selection)*: `draft`, `posted`, `cancelled`. + +#### **4.5 `education.student.ledger` (Double-Entry Financial Ledger)** +- **Fields**: + - `student_id` *(Many2one, Required)*: Target student. + - `date` *(Date, Required)*: Transaction date. + - `description` *(Char, Required)*: Details (e.g., `"Fee Assessment: MCA Sem 1"`). + - `reference` *(Char)*: Document source (`ASSIGN/0001` or `PAY/0012`). + - `debit` *(Monetary)*: Increase in student financial liability. + - `credit` *(Monetary)*: Decrease in student financial liability (Payment). + - `balance` *(Monetary, Computed)*: Cumulative balance due after this entry. + +#### **4.6 `education.student.discount` & `education.student.scholarship` & `education.student.refund`** +- Models for concession processing, merit scholarship credits, and fee refund disbursements. + +#### **4.7 `education.fee.type` (Fee Type)** +- **Functionality**: Granular categorization of fee categories. +- **Fields**: + - `name` *(Char, Required)*: Fee type name. + - `category_id` *(Many2one, Required)*: Parent category. + - `currency_id` *(Many2one, Required)*: Currency for fee. + +--- + +### **Record Examples (`student_db`)** +```yaml +Fee Categories: + - Tuition (Code: TUI) + - Examination (Code: EXM) + - Library (Code: LIB) + +Fee Structure: "MCA Semester 1 2026 Regular" + - Line 1: Tuition Fee = ₹30,000.00 + - Line 2: Examination & Library Fee = ₹4,000.00 + Total: ₹34,000.00 + +Fee Assignment: ASSIGN/2026/00001 + Student: "Aadhav S" | Net Amount: ₹34,000.00 | Amount Paid: ₹34,000.00 | State: Paid + +Payment Record: PAY/2026/00012 + Student: "Aadhav S" | Amount: ₹34,000.00 | Method: Online Razorpay | State: Posted + +Ledger Statement for Aadhav S: + 1. 2026-07-05 | Fee Assessment: MCA Sem 1 | Debit: ₹34,000.00 | Credit: ₹0.00 | Balance: ₹34,000.00 + 2. 2026-07-10 | Payment Received: PAY/0012 | Debit: ₹0.00 | Credit: ₹34,000.00 | Balance: ₹0.00 +``` + +--- + +## 5. 📊 `education_dashboard` (Analytics & Management KPIs) + +### **Module Overview** +Aggregates metrics across the institution to present real-time key performance indicators (KPIs) for administration. + +--- + +### **Models & Detailed Field Definitions** + +#### **5.1 `education.dashboard` (Dashboard Helper Model)** +- **Fields**: + - `total_students` *(Integer, Computed)*: Total active enrolled students. + - `total_fees_collected` *(Monetary, Computed)*: Cumulative fee revenue. + - `total_outstanding_dues` *(Monetary, Computed)*: Aggregate unpaid student balance. + - `average_attendance` *(Float, Computed)*: Average student attendance across institution. + +--- + +### **Record Examples (`student_db`)** +```yaml +Dashboard KPI Snapshot: + Total Students: 3 + Total Fees Collected: ₹102,000.00 + Total Dues Pending: ₹0.00 + Average Campus Attendance: 94.2% +``` + +--- + +## 6. 🖥️ `education_portal` (Student Self-Service Portal & Login) + +### **Module Overview** +Delivers the student-facing online portal (`/my/education`), online Razorpay payment integration, announcement notices, profile updates, and the customized 50/50 split-screen login page (`/web/login`). + +--- + +### **Models & Detailed Overrides** + +#### **6.1 Custom 50/50 Split-Screen Login Page (`web.login` & `web.login_layout`)** +- **Left Panel (Branding & Features)**: + - Glassmorphism tint over campus background image. + - Dynamic company logo (`/web/image?model=res.company&id=1&field=logo`). + - Generalized Welcome Headers & Institutional Description text. + - 2x2 Feature Highlights Grid: + - 🔐 **Secure Authentication** + - ⚡ **Fast & Reliable Access** + - 📊 **Real-Time Information** + - ☁️ **Cloud-Based Platform** +- **Right Panel (Form & Authentication)**: + - Welcome Card with database selector. + - Input field with icon (`Enter your Email`). + - Password input with interactive eye icon toggle. + - Remember Me & Forgot Password links. + - Google & Microsoft SSO buttons. + - Form action URL preserving parameters (`t-attf-action="/web/login?#{keep_query('*')}"`) and hidden `db` field to ensure CSRF validation never expires. + +#### **6.2 `education.announcement` (Notice Board)** +- **Fields**: + - `title` *(Char, Required)*: Notice title. + - `content` *(Html, Required)*: Formatted announcement content. + - `category` *(Selection)*: `academic`, `finance`, `event`, `general`. + - `target_audience` *(Selection)*: `all`, `students`, `staff`. + - `date_published` *(Datetime)*: Timestamp of publication. + +#### **6.3 `education.payment.transaction` (Payment Transaction)** +- **Functionality**: Tracks specific payment transactions triggered from the portal. +- **Fields**: + - `name` *(Char, Required)*: Transaction Reference. + +--- + +### **Record Examples (`student_db`)** +```yaml +Announcement Record #1: + Title: "Semester 1 Examinations Schedule" + Category: Academic + Target Audience: Students + Published Date: 2026-08-20 + Content: "The end-semester examinations for Semester 1 will commence from December 5th, 2026." +``` + +--- + +## 7. 📄 `education_reports` (QWeb PDF & Excel Analytics) + +### **Module Overview** +Generates formal printable PDF documents and analytical export files for identity cards, payment receipts, attendance rosters, and ledgers. + +--- + +### **Reports & Wizards** + +#### **7.1 Student Fee Receipt (`report_student_fee_receipt`)** +- Printable PDF generated when a payment is posted. Includes transaction reference, student roll number, breakdown of fee items, amount paid, and signature line. + +#### **7.2 Student Identity Card (`report_student_id_card`)** +- Printable student badge with student photo, barcode/QR code, roll number, department, blood group, and emergency contact details. + +#### **7.3 `education.report.wizard` (Custom Report Exporter)** +- Filter wizard allowing administrators to select date ranges, programs, and fee categories to generate custom PDF/Excel reports. + +--- + +## 📋 Comprehensive Summary Matrix of Custom Addons + +| Module | Core Purpose | Models Defined | Key Output / UI View | +| :--- | :--- | :--- | :--- | +| **`education_base`** | Institutional Hierarchy | `education.campus`, `education.department`, `education.program`, `education.academic.year`, `education.academic.term`, `education.configuration`, `education.academic.level` | Academic Setup Form & Tree Views | +| **`education_student`** | Student Registry & Profiles | `education.student`, `education.batch`, `education.semester`, `education.section` | Student Master Form, Kanban & Portal User Action | +| **`education_attendance`** | Attendance Log & Metrics | `education.attendance`, `education.attendance.session`, `education.attendance.line` | Daily Attendance Grid & % Computation | +| **`education_finance`** | Financials, Fees & Ledgers | `education.fee.category`, `education.fee.type`, `education.fee.structure`, `education.fee.assignment`, `education.student.payment`, `education.student.ledger`, `education.student.discount`, `education.student.scholarship`, `education.student.refund` | Student Ledger Statement, Fee Structure Form, Payment Receipts | +| **`education_dashboard`** | Admin KPIs & Analytics | `education.dashboard` | Management KPI Cards & Metrics Summary | +| **`education_portal`** | Self-Service Web & Login | `education.announcement`, `education.payment.transaction` | Student Portal `/my/education`, Razorpay Payment & Split-Screen Login Page | +| **`education_reports`** | PDF & Excel Exports | `education.report.wizard` | PDF Student ID Cards, Fee Receipts & Ledger Exports | diff --git a/addons/.DS_Store b/addons/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..b8d777adc27c048f2877ea88b1de6103f6c6539e GIT binary patch literal 6148 zcmeHKOHRWu5PdF{C_+L-uwWT+03>b@MP6@ z2%#Bi{2bdeV?R5Y7=TMQ#WgSoFrx}ij#!L{^otH;5?T(4&ihEQ#xp9ExF3wRhQG*w z?ABhO^HXAPIVdCW&WyfA)EHd3s*GnmLbu*<>t3~GA) z^3n7>)IajjF#3i&A^WlJdcb?n10PL{0b{@z_?7|YY?0ZqqLs#gF<=aA8IbQoL=}t! zwu=7K!9sq2q<<N;nbY%Q5Y8oY!%fU&PS%75ZQ$r ziu2Lg9{J&Lv7(j6fHBZz;6NYelK*e^-~YQt_GAng1OJKvH(lH<5-us^*1_T=*GAL_ qs)&qh6}Kj=_*BfXl8P^=Uf3Qg`kg0H%hvqXXPV-N=!FabFZKwK;UBvKes7;_kM8KW2(8B&;n88n$+G6ID) z8E>)W=BK3Q6#Hp1-C`&L=~>B8!~&$i#4jWL+`JO~)QXa#MBT)cl>EG6{nV7wq)%)HE!_;|g7%3B;ZKqE_YQtd!C6oZTuU;qFnRwxPp literal 0 HcmV?d00001 diff --git a/addons/education_attendance/__pycache__/__manifest__.cpython-310.pyc b/addons/education_attendance/__pycache__/__manifest__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be2e903184023fdf6157b874b7721bf78d6955b4 GIT binary patch literal 1117 zcmZ`&UuzRV5Wh>)q)nQF;)^16>4Ok4cP)Z~h*)VWg;omj{g~cPlWp&IkKMhrxv$}? zPkr+%E%+q(A$-xfyEI9$?r{V2@3*tRnNhu7$D{LW@bzNa^t_*?vV1m4`9Qx3NN|t) zyunv^ldq0E-n#Kgwr_mCHuCv8eI4HA8{c}q$9qGszgavw;CT#ID9QG$Mae74TL!aYp5G~Dan(}b!pMebOo_bY{g@}0 zECqcj{3&2C+&rkTnA{3#1t(2&0mU%q8F>X;DLL#RM4#Gd@$G zxp~PlF;SLTI7^VUip>2(rm=irS%b!rAZm04RXiS|6vWX`B^4=bPsd|nF}D2`w~4n+ zjLyKId$7<=-4xeMsb~Fm@xox7YhkD1A_nG-k8Zueb($1UZ?#b9AXOZb0OFXa3t}@V zwkHCwOn4``Vl}E96~dE$Vivw8Wd*Jf*3ax-Lf(Ymt=`=~|H6 zvC^*1F{7O^u36qklCWJ@(Ck8zl$~{5$g)J#8|F4BP0<@Gw`-FfrNPhNJ^he&cOibi z4pV7EytWz!z&TCoy>AJlC + + + + Attendance Demo Campus + ADC + + + + + Attendance Demo Department + ADD + + + + + + Attendance Demo Level + ADL + + + + + Attendance Demo Program + ADP + + + + + + + Attendance Demo 2026 + ADY26 + 2026-01-01 + 2026-12-31 + + + + + Attendance Demo Term 1 + ADT1 + + 2026-01-01 + 2026-06-30 + + + + + Attendance Batch 2026 + ADB26 + + + + + Attendance Semester 1 + ADS1 + + + + + Attendance Section A + ADSA + + + + + Alex + Smith + STU001 + ADM001 + RL001 + + + + + + + + + + active + + + + Bella + Jones + STU002 + ADM002 + RL002 + + + + + + + + + + active + + + + Charlie + Brown + STU003 + ADM003 + RL003 + + + + + + + + + + active + + + + Diana + Prince + STU004 + ADM004 + RL004 + + + + + + + + + + active + + + + Evan + Wright + STU005 + ADM005 + RL005 + + + + + + + + + + active + + + + Fiona + Gallagher + STU006 + ADM006 + RL006 + + + + + + + + + + active + + + + George + Clooney + STU007 + ADM007 + RL007 + + + + + + + + + + active + + + + Hannah + Baker + STU008 + ADM008 + RL008 + + + + + + + + + + active + + + + Ian + Somerhalder + STU009 + ADM009 + RL009 + + + + + + + + + + active + + + + Julia + Roberts + STU010 + ADM010 + RL010 + + + + + + + + + + active + + + + Kevin + Hart + STU011 + ADM011 + RL011 + + + + + + + + + + active + + + + Lily + Collins + STU012 + ADM012 + RL012 + + + + + + + + + + active + + + + Mike + Tyson + STU013 + ADM013 + RL013 + + + + + + + + + + active + + + + Nina + Dobrev + STU014 + ADM014 + RL014 + + + + + + + + + + active + + + + Oscar + Isaac + STU015 + ADM015 + RL015 + + + + + + + + + + active + + + + Penelope + Cruz + STU016 + ADM016 + RL016 + + + + + + + + + + active + + + + Quincy + Jones + STU017 + ADM017 + RL017 + + + + + + + + + + active + + + + Rachel + Green + STU018 + ADM018 + RL018 + + + + + + + + + + active + + + + Sam + Winchester + STU019 + ADM019 + RL019 + + + + + + + + + + active + + + + Tom + Hanks + STU020 + ADM020 + RL020 + + + + + + + + + + active + + + + + Demo Session MCA Period 1 + + + + + + + + period_1 + + draft + + diff --git a/addons/education_attendance/models/__init__.py b/addons/education_attendance/models/__init__.py new file mode 100644 index 0000000..d27250b --- /dev/null +++ b/addons/education_attendance/models/__init__.py @@ -0,0 +1,4 @@ +from . import attendance_session +from . import attendance_line +from . import attendance +from . import student_inherit diff --git a/addons/education_attendance/models/__pycache__/__init__.cpython-310.pyc b/addons/education_attendance/models/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53f466ec0063d282e146e602002d662a52691f56 GIT binary patch literal 302 zcmYk0J#GRq5Jo-wLl9b&h+Awy+|VE)loQaT+s3l+NO835MfNO$b8rf-v~4P`K$RI9 z7|GAipX~AU^Z871-Itf&BkzB5c&&^NU%b+a;go|`b#ld$CT+O8Cu2Sj=W)8>aAZAtV$IN$513eDMfBh4I$57D>*q9&T{W zg#&*8H)i@5smGr97vRz-JDUyi==tgQzVtq9F&y>@uDyRh%|CjC{AUkW7h>cwZgmd{ z5TFGqX+ldUaY{FFDS1Y~0rvv|m%DTCy_@*posii7iuxpWjms9f2~KlffHFZTfEOyW zva2h^!(-g)A0(V46iDI#OQi1nan5zEc{&_DN*jaG95 z1~5d)HADu83||wt{+Ys!)_xPwYpvc0wl0zDh}?iN+&rhrCh{YcY$LzbYW@Vb@O&F~ zFw+3;VEi%cVxB*nt*20DOy{E7)0$Vns*E>dz5=IgjqaT28hE8AYt6~?TxlVf%?Oz0 z6W}VlKgpqGn$5Kr)mfVG!o^DyW=v%acWQ;F=rn z0Nux)jIhA6XNm2*34x!oy3oz;I@J2YQ^l3S#4u{dG?C(ZQq@HfyPd0zm8;l~9TP4j zSD2s)|LhpV0`{tfg1z>Qh*#iUjCW{naG6ch!=Tezaj8uB2g@+8r3wGyC6j-vn6?+j*ATDVY*^JylnYj& z&VI*a6h*6LdAs*S}w27um_!COpT{_ar%YGUr_VggIQVW8GoxK z`;7sJO3g0oo386S>pq9s8ksJZCVE(~@)fX0cWh(_Nkp36r>lWl&(3tE_(NZ9dTn!$ z2Qce=sB8iAc?K5_^>4^4e5Cq>w;eZYhbJ{0B+W+2K s9mj$31PCE*iQ7b_{o5J;$o`~Soh#AW;5H=orz_+mI~~fJjOn)fKW+znkpKVy literal 0 HcmV?d00001 diff --git a/addons/education_attendance/models/__pycache__/attendance_line.cpython-310.pyc b/addons/education_attendance/models/__pycache__/attendance_line.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2287252a172a5746535b97c9f9730caff1b011a GIT binary patch literal 1176 zcmZuxJ8#=C5Y}6=W9R8O3DCh)0TqZ1bS{ddO;Hrcqo^}!5m+6QDwQaQq=H0iyJjsq zck5qb$4>nVL5ChGC(ck3Jb(A#-Nz&4s^9NgI1YY&yV&+D>$g@O542f(xWqFQV1Zp& zrJdNNlQ^ZDxVH7y0tei$7P!>C@V>Z-58l*@{XZCQ#jbM6ELXudN*v2#MsI50p zXGG=|+n16uAS|PYIit-Ey?2bsgJ)c5*KiR!(y^lhDoI{6K@KA#vgXNuw+bo*mQmrf z1*a;^Fq#03JrxMab9SyGPA}`6Q&3UGu(4E9<>!d5o3%w&A#0#aVyYIzE|0Lyyrabu zir9}G70x&nChUC+&=!!7#uV(EU$l4y!5ZEnO4Rx#g*+ogT2OLDoBmO&jt;e|b5<;c zTHZ9_2^$?CP{C9ZSqt9RWj(_~g+R|pUC0F?6@H*4;h#lpn~)|v%}Y9Z$C}81lUkx@ zh&h@iBs#oXhY5Htc68P8|>_EnHp94O~5a!}C`7Jptbs=5mTh}(7}1J#-0GNcra4oQ0acAR_o}L}#aNbLk}Iq;KQZS1Y4KeoVZ`0uq-1hJ zuO%nD1fXJK^6(DQ#lxJmpW$R-N%d15&u+Mnx%3iGQACz!>p5`N?XDdK!Cf!x*?M}m yGoRhRX3udPD6fL5;*Msc`@;2qhw_i~CvRdFL|Zi0{^&*9_m?(mLT?$^L-!xrN?9%d literal 0 HcmV?d00001 diff --git a/addons/education_attendance/models/__pycache__/attendance_session.cpython-310.pyc b/addons/education_attendance/models/__pycache__/attendance_session.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d6c931f2cbc3f95a9e5de8ac7b5475c776df51e4 GIT binary patch literal 5248 zcmb7IOOq7G5$<>Q%+BnK6|l>z0r40Fvu1fp0>rY5t&m|w#9})%vZCEg)$aDt(>+_= z15C$ovGB=3gnh9ua>U{r|H8kceDJ{$j?h^LTgVZ1X7y`;MJ&_NSy@@Htjw=6t0%12 zs}lS^{rjIf|Cp1ce`00yr$Oa9ywOD)`aRa#0{ZK*QK6kqG=EnSv=CJ~ir zPb8vIwPSp(woIb0N)6*%7%MeYuDU&s8*3i*NyLqANT?q*WIpjr*Y}7Udtq>sv5>W~ z`{>Vt%5`|7C$c0_sU;Jsr4YHL5~Zc#lIu!KCwj}EX2&81G2yYEs;vsttE57zLNfML zYmEs>jnoTCO-RPcL?NjQ$t0O7B;!IdO?DNM2_cyw?-i0skW7)?#Kf{QE#!Ni>aAT+ zpIMj4-oMIZ7Nm3V?gROIVutY2Ma!i^gV@wN94c zWs*H*o7;8??RxFzfVwQHFJ@Ky3#@L}FQ%+J((f)-_ii(vPajY}880ThCF&C+HeUwo zHT03anS`#neLrSnxZMhnhSgBGmD<)hn^<>JE1w;aE)>#*5$U-?dTvB|zL1_DkzOdI z7e=HP3+crX>7_z?X+(OtkY46Ssw0~~^|f+$Y&_poQeiY|_9Mz7ZvND5!;S}S2q?u5 zd3=}P{SLi8lsZyJUX`B9E3m3cg9lv0@y}(hMbuw=F0n(VIJQ5HA8oUp0@J??}!5`M44K^Hxh3#u*-Su%trQx8`-XpQE_13qcpJF zz=N;swUF8Ih8JZ4uviNg3LqTj;Y2KEhe6sGwQIg)I32c!TSUl5e*-d;46Z!2)?>*K9e6S zLnXQzSIOAd@^9sJ>9IOgVD-tz+ECpZgLSl_*3n1}xEhr8Z?tE!ENw0dea1nb`cOp) znHb8Bw5bpYGSlRW0Ox2Nj^aMSFkMOGJJ|b$e3jSB01gsfEuFrt? zmHRPGFXB|30|RnHBrcjPu7uzR$YIpZ6o|lUeeh1#joTZLdcY~rMt#~wkLGKFi#=Gx zbDP}k0rEj}faMvSx|&S{>$4Aqpo9=R4FYxyNMoGq5gb#RqTh&M5A6-MfCCg5c^*4n zK!SHVb_wNE`RWBc8A1a#(}OTU*i}(U!3XF5AXZgI+uX|c#;bU`Bj& zC!qcZUi4O_lVPQ4vW`SdRVN`++%cyVjr;hqJc>;g~N;5O%tEQ{x zrW46MfMF44ax~nW0u)c)f0C0sFpikujizDflg1l1bQIdyFOd8S$t&KiWh#mb%m6y% z8Y*+(*i6F{Z`V+ae@Q{S=HaKU@Ldy_p|m9le_*b|yjRbI0QgYd5?`4fcn&{!Au`glj zeAn#PsHI3ihI(2?Jo) zKNgC8utUXg)H|zH?^5EmT^~}d``}0Pd&$Gum_HXP?$oo{z<1Wtgfvb1@nDDc!>)fR zl-sHOaMViU1h*U~aJ$rTc-3*>E2-}zUvr$zzUyZ#y66Hz{P1U94}Av%yYt{#G(mnI(1k86AtEm~Z-gQEHDDj%Vpnnaq!VrW4$KRGLbzTKN9?;imJ`_n9GW-%1R%IA4`2-4^^ii7% i52W`3uHVK#^Tf#Q6o!x6)T`Gp@J6Wqj9gQ8tN#Z<&wfS# literal 0 HcmV?d00001 diff --git a/addons/education_attendance/models/__pycache__/student_inherit.cpython-310.pyc b/addons/education_attendance/models/__pycache__/student_inherit.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..67549ec5c1134a2f4e28a6a092dd46f65c80346c GIT binary patch literal 1950 zcmb7F&2Jk;6rb5|yLRH(ra`0<0mQ+mjU~=i2r8daYl^}JOK3Iqn}l`u!<`*RjkP%? z;?^tY_E?F(XgG4>hSVD;-rHR}Zb>7U)jYrVn>X*xZ{M4B)^0Z$j?aGob-Lp-_6Hg3 z!$IQ!XZ{HaV8COR@G(!Uv6a|ko3pPOSYTf;u%$h9&h4=a&LJDPf1*Dd*v1yK$aqJQ zj3GB(l7Wo#39YRU4~+wy`7bCW8*^Y|3;5WU&eWy&;DGzg9(!o};DHYTK8mq!h$X6Ua)gM?m!P2^iAhWm`{X`vh+~T zL8f}KC6~xvUPJv$NN6on5a~pkX1yFnu;_li!gs%ka-FHu0XJT*Rg@ktT5GExo1Jiy zC9}Df;bpKek?1&{`xuvkN#jN-$ft|;LnU)OJI;#fmP*h{g z^yqSV(Y!CTtXvnp2NI%*heyRk?ff!Oa-P78&{7W;3u<=!<-vv)Vnu(J6+Dpja|aiXv#C+ZU71pI_} zS)+kv{8`mh#?R6a=KAzy*}BvPsBu*?t;8m7R~;1Z;Cw|hV?{wiLQ@FXDH(->X&1jIDR}B z1g3Yzr82=$6eDv9#*Srb+Vv~Ps^UF1ttw+}HR&bQdha!gHMp^sVK&i&hH-Kk9~oQ8 ziSbcn3O~fI&a{ZJy7ewUUMk5_5w#Lj0VTXykN`pVd%CP0Z6#au5_W}L!l|EO6$BSOE+^2@gX-l8qwEqFyMdoe* literal 0 HcmV?d00001 diff --git a/addons/education_attendance/models/attendance.py b/addons/education_attendance/models/attendance.py new file mode 100644 index 0000000..6096f98 --- /dev/null +++ b/addons/education_attendance/models/attendance.py @@ -0,0 +1,29 @@ +# pyrefly: ignore [missing-import] +from odoo import api, fields, models + + +class EducationAttendance(models.Model): + _name = 'education.attendance' + _description = 'Student Attendance History' + _order = 'date desc, id desc' + + student_id = fields.Many2one('education.student', string='Student', required=True, index=True, ondelete='cascade') + date = fields.Date(string='Date', required=True, index=True, default=fields.Date.context_today) + session_id = fields.Many2one('education.attendance.session', string='Session', ondelete='set null', index=True) + status = fields.Selection([ + ('present', 'Present'), + ('absent', 'Absent'), + ('late', 'Late'), + ('medical_leave', 'Medical Leave'), + ('on_duty', 'On Duty') + ], string='Status', required=True, index=True) + faculty_id = fields.Many2one('res.users', string='Faculty', required=True, index=True) + remarks = fields.Text(string='Remarks') + active = fields.Boolean(default=True) + company_id = fields.Many2one( + 'res.company', + string='Company', + required=True, + default=lambda self: self.env.company, + index=True, + ) diff --git a/addons/education_attendance/models/attendance_line.py b/addons/education_attendance/models/attendance_line.py new file mode 100644 index 0000000..cde4fad --- /dev/null +++ b/addons/education_attendance/models/attendance_line.py @@ -0,0 +1,28 @@ +# pyrefly: ignore [missing-import] +from odoo import api, fields, models + + +class EducationAttendanceLine(models.Model): + _name = 'education.attendance.line' + _description = 'Attendance Line Detail' + _order = 'id asc' + + session_id = fields.Many2one('education.attendance.session', string='Session', required=True, ondelete='cascade', index=True) + student_id = fields.Many2one('education.student', string='Student', required=True, index=True) + status = fields.Selection([ + ('present', 'Present'), + ('absent', 'Absent'), + ('late', 'Late'), + ('medical_leave', 'Medical Leave'), + ('on_duty', 'On Duty') + ], string='Status', default='present', required=True, index=True) + remarks = fields.Char(string='Remarks') + time_in = fields.Datetime(string='Time In') + time_out = fields.Datetime(string='Time Out') + active = fields.Boolean(default=True) + company_id = fields.Many2one( + 'res.company', + related='session_id.company_id', + store=True, + index=True, + ) diff --git a/addons/education_attendance/models/attendance_session.py b/addons/education_attendance/models/attendance_session.py new file mode 100644 index 0000000..53e6da6 --- /dev/null +++ b/addons/education_attendance/models/attendance_session.py @@ -0,0 +1,161 @@ +# pyrefly: ignore [missing-import] +from odoo import api, fields, models +# pyrefly: ignore [missing-import] +from odoo.exceptions import ValidationError + + +class EducationAttendanceSession(models.Model): + _name = 'education.attendance.session' + _description = 'Attendance Session' + _inherit = ['mail.thread', 'mail.activity.mixin'] + _order = 'attendance_date desc, period asc, id desc' + + name = fields.Char(string='Session Title', required=True, tracking=True) + campus_id = fields.Many2one('education.campus', string='Campus', required=True, index=True) + department_id = fields.Many2one('education.department', string='Department', required=True, index=True) + program_id = fields.Many2one('education.program', string='Program', required=True, index=True) + academic_year_id = fields.Many2one('education.academic.year', string='Academic Year', required=True, index=True) + academic_term_id = fields.Many2one('education.academic.term', string='Academic Term', required=True, index=True) + academic_level_id = fields.Many2one('education.academic.level', string='Academic Level', required=True, index=True) + + attendance_date = fields.Date(string='Attendance Date', required=True, default=fields.Date.context_today, index=True, tracking=True) + period = fields.Selection([ + ('period_1', 'Period 1'), + ('period_2', 'Period 2'), + ('period_3', 'Period 3'), + ('period_4', 'Period 4'), + ('period_5', 'Period 5'), + ('period_6', 'Period 6'), + ('period_7', 'Period 7'), + ('period_8', 'Period 8') + ], string='Period', required=True, default='period_1', tracking=True) + faculty_id = fields.Many2one('res.users', string='Faculty', required=True, index=True, default=lambda self: self.env.user, tracking=True) + + state = fields.Selection([ + ('draft', 'Draft'), + ('open', 'Open'), + ('completed', 'Completed'), + ('cancelled', 'Cancelled') + ], string='Status', default='draft', required=True, index=True, tracking=True) + + line_ids = fields.One2many('education.attendance.line', 'session_id', string='Attendance Lines', copy=True) + active = fields.Boolean(default=True) + company_id = fields.Many2one( + 'res.company', + string='Company', + required=True, + default=lambda self: self.env.company, + index=True, + ) + + _sql_constraints = [ + ('uniq_session_period', 'unique(attendance_date, period, program_id, academic_level_id, company_id)', 'An attendance session has already been created for this period, program, and academic level on this date.'), + ] + + def action_open(self): + for rec in self: + if rec.state != 'draft': + continue + # Auto-populate attendance lines with active students + students = self.env['education.student'].search([ + ('campus_id', '=', rec.campus_id.id), + ('department_id', '=', rec.department_id.id), + ('program_id', '=', rec.program_id.id), + ('academic_year_id', '=', rec.academic_year_id.id), + ('academic_term_id', '=', rec.academic_term_id.id), + ('level_id', '=', rec.academic_level_id.id), + ('status', '=', 'active'), + ]) + if not students: + raise ValidationError("No active students found matching the selected academic criteria.") + + # Create lines + lines_vals = [] + for student in students: + lines_vals.append({ + 'session_id': rec.id, + 'student_id': student.id, + 'status': 'present', + 'company_id': rec.company_id.id, + }) + self.env['education.attendance.line'].create(lines_vals) + rec.state = 'open' + + def action_complete(self): + for rec in self: + if rec.state != 'open': + continue + if not rec.line_ids: + raise ValidationError("Cannot complete session without any student lines.") + + # Copy to permanent history table + history_vals = [] + for line in rec.line_ids: + history_vals.append({ + 'student_id': line.student_id.id, + 'date': rec.attendance_date, + 'session_id': rec.id, + 'status': line.status, + 'faculty_id': rec.faculty_id.id, + 'remarks': line.remarks, + 'company_id': rec.company_id.id, + }) + self.env['education.attendance'].create(history_vals) + rec.state = 'completed' + + # Invalidate student attendance cache + students = rec.line_ids.mapped('student_id') + students.invalidate_recordset() + + def action_cancel(self): + for rec in self: + if rec.state == 'completed': + # Delete permanent history + history = self.env['education.attendance'].search([('session_id', '=', rec.id)]) + history.unlink() + + # Delete lines + rec.line_ids.unlink() + rec.state = 'cancelled' + + def action_draft(self): + for rec in self: + # Delete permanent history if completed + if rec.state == 'completed': + history = self.env['education.attendance'].search([('session_id', '=', rec.id)]) + history.unlink() + + # Delete lines + rec.line_ids.unlink() + rec.state = 'draft' + + # Bulk marking actions + def action_bulk_present(self): + self.ensure_one() + if self.state != 'open': + raise ValidationError("Bulk marking is only allowed when the session is Open.") + self.line_ids.write({'status': 'present'}) + + def action_bulk_absent(self): + self.ensure_one() + if self.state != 'open': + raise ValidationError("Bulk marking is only allowed when the session is Open.") + self.line_ids.write({'status': 'absent'}) + + def action_bulk_late(self): + self.ensure_one() + if self.state != 'open': + raise ValidationError("Bulk marking is only allowed when the session is Open.") + self.line_ids.write({'status': 'late'}) + + def action_bulk_medical(self): + self.ensure_one() + if self.state != 'open': + raise ValidationError("Bulk marking is only allowed when the session is Open.") + self.line_ids.write({'status': 'medical_leave'}) + + def action_bulk_duty(self): + self.ensure_one() + if self.state != 'open': + raise ValidationError("Bulk marking is only allowed when the session is Open.") + self.line_ids.write({'status': 'on_duty'}) diff --git a/addons/education_attendance/models/student_inherit.py b/addons/education_attendance/models/student_inherit.py new file mode 100644 index 0000000..a46def7 --- /dev/null +++ b/addons/education_attendance/models/student_inherit.py @@ -0,0 +1,33 @@ +# pyrefly: ignore [missing-import] +from odoo import api, fields, models + + +class EducationStudent(models.Model): + _inherit = 'education.student' + + attendance_line_ids = fields.One2many('education.attendance', 'student_id', string='Attendance History') + attendance_pct = fields.Float(string='Attendance %', compute='_compute_attendance_metrics', store=True, index=True) + present_count = fields.Integer(string='Present Count', compute='_compute_attendance_metrics', store=True) + absent_count = fields.Integer(string='Absent Count', compute='_compute_attendance_metrics', store=True) + late_count = fields.Integer(string='Late Count', compute='_compute_attendance_metrics', store=True) + medical_leave_count = fields.Integer(string='Medical Leave Count', compute='_compute_attendance_metrics', store=True) + + @api.depends('attendance_line_ids.status', 'attendance_line_ids.active') + def _compute_attendance_metrics(self): + for rec in self: + records = rec.attendance_line_ids.filtered(lambda a: a.active) + total = len(records) + + p_count = len(records.filtered(lambda r: r.status in ('present', 'on_duty'))) + l_count = len(records.filtered(lambda r: r.status == 'late')) + m_count = len(records.filtered(lambda r: r.status == 'medical_leave')) + a_count = len(records.filtered(lambda r: r.status == 'absent')) + + rec.present_count = p_count + rec.absent_count = a_count + rec.late_count = l_count + rec.medical_leave_count = m_count + + # Treat present, late, medical_leave, and on_duty as attended (positive) + attended = p_count + l_count + m_count + rec.attendance_pct = (attended / total * 100.0) if total > 0 else 100.0 diff --git a/addons/education_attendance/reports/attendance_reports.xml b/addons/education_attendance/reports/attendance_reports.xml new file mode 100644 index 0000000..b0378fd --- /dev/null +++ b/addons/education_attendance/reports/attendance_reports.xml @@ -0,0 +1,138 @@ + + + + + Daily Attendance Register + education.attendance.session + qweb-pdf + education_attendance.report_daily_attendance_template + education_attendance.report_daily_attendance_template + + report + + + + + Student Attendance Report + education.student + qweb-pdf + education_attendance.report_student_attendance_template + education_attendance.report_student_attendance_template + + report + + + + + + + + diff --git a/addons/education_attendance/security/education_attendance_security.xml b/addons/education_attendance/security/education_attendance_security.xml new file mode 100644 index 0000000..67e6304 --- /dev/null +++ b/addons/education_attendance/security/education_attendance_security.xml @@ -0,0 +1,57 @@ + + + + + Education Attendance + 40 + + + + + Attendance User + + + Allows marking attendance lines and tracking their sessions. + + + + Attendance Manager + + + Full managerial oversight, configuration, and reports. + + + + + Attendance Session Company Rule + + ['|', ('company_id', '=', False), ('company_id', '=', user.company_id.id)] + + + + Attendance Line Company Rule + + ['|', ('company_id', '=', False), ('company_id', '=', user.company_id.id)] + + + + Attendance History Company Rule + + ['|', ('company_id', '=', False), ('company_id', '=', user.company_id.id)] + + + + + Faculty Sessions Only + + [('faculty_id', '=', user.id)] + + + + + Manager All Sessions + + [(1, '=', 1)] + + + diff --git a/addons/education_attendance/security/ir.model.access.csv b/addons/education_attendance/security/ir.model.access.csv new file mode 100644 index 0000000..6da361b --- /dev/null +++ b/addons/education_attendance/security/ir.model.access.csv @@ -0,0 +1,7 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_attendance_session_user,Attendance Session User,model_education_attendance_session,group_attendance_user,1,1,1,0 +access_attendance_session_mgr,Attendance Session Mgr,model_education_attendance_session,group_attendance_manager,1,1,1,1 +access_attendance_line_user,Attendance Line User,model_education_attendance_line,group_attendance_user,1,1,1,0 +access_attendance_line_mgr,Attendance Line Mgr,model_education_attendance_line,group_attendance_manager,1,1,1,1 +access_attendance_user,Attendance User,model_education_attendance,group_attendance_user,1,1,1,0 +access_attendance_mgr,Attendance Mgr,model_education_attendance,group_attendance_manager,1,1,1,1 diff --git a/addons/education_attendance/tests/__init__.py b/addons/education_attendance/tests/__init__.py new file mode 100644 index 0000000..544dd33 --- /dev/null +++ b/addons/education_attendance/tests/__init__.py @@ -0,0 +1 @@ +from . import test_attendance diff --git a/addons/education_attendance/tests/__pycache__/__init__.cpython-310.pyc b/addons/education_attendance/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..03e96b1d45b57eb6214a520c409a2874df01de1b GIT binary patch literal 189 zcmd1j<>g`k0(ZU1S#m)7F^Gc(44TX@8G*u@ zjJNnpQj1IC6H7``^HLJ?l2iROnQk!@fi$mVC}IIpVB(jBer{fgeriQYQKD{QN=klS zv3_bwX>wvoW_})$Zheqx#rpB_nR%Hd@$q^EmA5!-a`RJ4b5iX<78ZjH7GMAXybCRf literal 0 HcmV?d00001 diff --git a/addons/education_attendance/tests/__pycache__/test_attendance.cpython-310.pyc b/addons/education_attendance/tests/__pycache__/test_attendance.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4d7c5e7782eb54728f4066d9250b3d78d77b119 GIT binary patch literal 4996 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 kc}37^>g<5wxgthQOp + + + + + + + + + diff --git a/addons/education_attendance/views/attendance_session_views.xml b/addons/education_attendance/views/attendance_session_views.xml new file mode 100644 index 0000000..69df342 --- /dev/null +++ b/addons/education_attendance/views/attendance_session_views.xml @@ -0,0 +1,188 @@ + + + + education.attendance.session.tree + education.attendance.session + + + + + + + + + + + + + + + + education.attendance.session.kanban + education.attendance.session + + + + + + + + + +
+
+
+ +
+
+ Date: +
+
+ Period: +
+
+ Faculty: +
+
+
+
+
+
+
+
+ + + education.attendance.session.calendar + education.attendance.session + + + + + + + + + + + education.attendance.session.pivot + education.attendance.session + + + + + + + + + + + education.attendance.session.graph + education.attendance.session + + + + + + + + + + education.attendance.session.form + education.attendance.session + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + + education.attendance.session.search + education.attendance.session + + + + + + + + + + + + + + + + + + + + + + + + + + Attendance Sessions + education.attendance.session + tree,kanban,form,calendar,pivot,graph + + +
diff --git a/addons/education_attendance/views/attendance_views.xml b/addons/education_attendance/views/attendance_views.xml new file mode 100644 index 0000000..640b824 --- /dev/null +++ b/addons/education_attendance/views/attendance_views.xml @@ -0,0 +1,83 @@ + + + + education.attendance.tree + education.attendance + + + + + + + + + + + + + + education.attendance.calendar + education.attendance + + + + + + + + + + education.attendance.pivot + education.attendance + + + + + + + + + + + education.attendance.graph + education.attendance + + + + + + + + + education.attendance.search + education.attendance + + + + + + + + + + + + + + + + + + + + + + + + + Attendance History + education.attendance + tree,calendar,pivot,graph + + + diff --git a/addons/education_attendance/views/student_views_inherit.xml b/addons/education_attendance/views/student_views_inherit.xml new file mode 100644 index 0000000..2a87de2 --- /dev/null +++ b/addons/education_attendance/views/student_views_inherit.xml @@ -0,0 +1,38 @@ + + + + education.student.form.inherit.attendance + education.student + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/education_base/__init__.py b/addons/education_base/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/addons/education_base/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/addons/education_base/__manifest__.py b/addons/education_base/__manifest__.py new file mode 100644 index 0000000..1c89322 --- /dev/null +++ b/addons/education_base/__manifest__.py @@ -0,0 +1,40 @@ +{ + 'name': 'Education Base', + 'version': '17.0.1.0.0', + 'summary': 'Core foundation for education management', + 'description': ''' + Education Base module provides the foundational master data, academic configuration, + and shared setup objects used by all education-related modules in the ERP. + It includes academic years, academic terms, campuses, departments, academic levels, + programs, and global education configuration. + ''', + 'category': 'Education', + 'author': 'Metatron', + 'license': 'LGPL-3', + 'depends': ['base', 'mail', 'contacts'], + 'data': [ + 'security/education_base_security.xml', + 'security/ir.model.access.csv', + 'security/record_rules.xml', + 'data/education_base_sequence.xml', + 'data/education_base_menu.xml', + 'views/academic_year_views.xml', + 'views/academic_term_views.xml', + 'views/campus_views.xml', + 'views/department_views.xml', + 'views/academic_level_views.xml', + 'views/program_views.xml', + 'views/education_config_views.xml', + 'views/menuitems.xml', + ], + 'demo': [ + 'demo/demo_academic_level.xml', + 'demo/demo_academic_year.xml', + 'demo/demo_campus.xml', + 'demo/demo_department.xml', + 'demo/demo_program.xml', + ], + 'installable': True, + 'application': True, + 'auto_install': False, +} diff --git a/addons/education_base/__pycache__/__init__.cpython-310.pyc b/addons/education_base/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d80ba566e10b9bdf8a9ea7b85c7bfa7c57c8eb01 GIT binary patch literal 168 zcmd1j<>g`k0>5dKvP6OOV-N=!FabFZKwK;UBvKes7;_kM8KW2(8B&;n88n$+G6ID) z8E>)W=BK3Q6#Hp1-C`&L=~>B8!~&$i#4jEF+`JO~)QXa#MBT)cl>EG6{nV7w + + + + + diff --git a/addons/education_base/data/education_base_sequence.xml b/addons/education_base/data/education_base_sequence.xml new file mode 100644 index 0000000..09658e3 --- /dev/null +++ b/addons/education_base/data/education_base_sequence.xml @@ -0,0 +1,42 @@ + + + + Academic Year + education.academic.year + AY/ + 4 + + + + + Academic Term + education.academic.term + TERM/ + 4 + + + + + Campus + education.campus + CAMP/ + 4 + + + + + Department + education.department + DEP/ + 4 + + + + + Program + education.program + PROG/ + 5 + + + diff --git a/addons/education_base/demo/demo_academic_level.xml b/addons/education_base/demo/demo_academic_level.xml new file mode 100644 index 0000000..459e767 --- /dev/null +++ b/addons/education_base/demo/demo_academic_level.xml @@ -0,0 +1,10 @@ + + + + Undergraduate + UG + 10 + Undergraduate study level. + + + diff --git a/addons/education_base/demo/demo_academic_year.xml b/addons/education_base/demo/demo_academic_year.xml new file mode 100644 index 0000000..3ee6e13 --- /dev/null +++ b/addons/education_base/demo/demo_academic_year.xml @@ -0,0 +1,12 @@ + + + + 2025-2026 + AY2025 + 2025-06-01 + 2026-05-31 + active + True + + + diff --git a/addons/education_base/demo/demo_campus.xml b/addons/education_base/demo/demo_campus.xml new file mode 100644 index 0000000..93e4bf5 --- /dev/null +++ b/addons/education_base/demo/demo_campus.xml @@ -0,0 +1,10 @@ + + + + Main Campus + MAIN + +1234567890 + main@school.example + + + diff --git a/addons/education_base/demo/demo_department.xml b/addons/education_base/demo/demo_department.xml new file mode 100644 index 0000000..a685160 --- /dev/null +++ b/addons/education_base/demo/demo_department.xml @@ -0,0 +1,8 @@ + + + + Engineering + ENG + + + diff --git a/addons/education_base/demo/demo_program.xml b/addons/education_base/demo/demo_program.xml new file mode 100644 index 0000000..5fe5820 --- /dev/null +++ b/addons/education_base/demo/demo_program.xml @@ -0,0 +1,11 @@ + + + + B.Tech + BTECH + + + 4 + years + + diff --git a/addons/education_base/models/__init__.py b/addons/education_base/models/__init__.py new file mode 100644 index 0000000..039b28a --- /dev/null +++ b/addons/education_base/models/__init__.py @@ -0,0 +1,7 @@ +from . import academic_year +from . import academic_term +from . import campus +from . import department +from . import academic_level +from . import program +from . import education_config diff --git a/addons/education_base/models/__pycache__/__init__.cpython-310.pyc b/addons/education_base/models/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d88e0161046d3593013894a012fb866bd2751a4 GIT binary patch literal 386 zcmY+AJ5Izf5J2txS?#X&8Yv(JEkc}N8ye)sGV!nqitWgD5PJ;@?!XN=OIj+fK*fwD zU>VELZ$_Gt#%{A&F^v7|%i}w~AC#O%hRF@yaEH)L^N|@YI4950Gm$0F(Q}a}FVG87 zBrnlRQ6{g@D^Vq{(Q8qoH@B=k`{X#j<;zC(N`o1Cc?9ME6(V?pg{oI(ngiNO!=!vP zU?XPR-_8-9V8lv2d3W%Nns%V)Ud6##**p6%9QJLqFi*?*oKO%-LPe+v4dRV?`k#ca z%g)%?!E^NLTxsoW=>F#3s{oyG8phB`IoKgec`+TA<_e4R2-mb>*do?!&4130e*?MV BT%rH~ literal 0 HcmV?d00001 diff --git a/addons/education_base/models/__pycache__/academic_level.cpython-310.pyc b/addons/education_base/models/__pycache__/academic_level.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..19de8fbad731b428d32a66d217a2c0baa70a405f GIT binary patch literal 1549 zcmZuxOKTiQ5bmDWj`pS1Ya=j$$;|`yE`$&WL`I1mLLlBtOb!EP7)_O=S>s_k zJxIn5X~{2%00W-0f=9ftBCD_?n`@bsJB1s$oPE#02In~g2kohMWk)S=e`29`Lw8wd zD?6EG$~(z02dTWG0?cK|)#lGho#CM?sUE+Jh_en>~H;ErW5A=Q$208`U zx^|;2*goaaHtxLGg&o*^X~7;0pR+3-?ZGFM+P><2lO}*gmi~z6m^b?fi`1ktN>&G> z+?@2+iosH|FLA!WGR1pAnH2cAX&ZSkp-pHhU(j(T5R@ltk(Hqe4I!eFH1gN zBNH2BMzd$i|2j{KV@MwCXulQ_pEbiZVg{JeS|xN$GD`$Hq$)Ou(DMowWj2rBHy)=1 z7ZHKoM#|AeQ|)&)-h*+O z!3xGh3h$CVWKZt=$}iaqY9g>M`IJws6I(Y7@iuYvdWj?1sr{0RPiqHSSNu0Vw}27+ z{zMGP&MST(_UPMq2U(syMP+5sSf3pytPGO3S%j|YN=!t0Mi4E^ z+PDz=WZyUuE^(nH2J}(3z|_=vNw&3YJ5J9qeHt%ejkV7^B$xWow;bN(ZJOS{zH7a9 z-Za5F#$`c2zB~zs%8!kmI9BaArU$dgk@#*L&lgFqJ;u6b!5)b zSt3jl%F(q{tslx7Pf;j;-FM1)Om9_nZrf;mTU9wGrSg)r&dyQ!kEm5XuSzt~s6i~} zIbl^&PbOJeOQ9>RpTSBZg$CdjPP#LM#zS~SG@$Y>5uZ6+&%QMvl=O7CWu`~edu?qv zZ`w|un_H96S55_0WxSaLh5j+CO)VVZMT(})r74(lA5p@&x%q#AP_r#zeV`j%>accM Imk;g#0PawscmMzZ literal 0 HcmV?d00001 diff --git a/addons/education_base/models/__pycache__/academic_term.cpython-310.pyc b/addons/education_base/models/__pycache__/academic_term.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc9c2b2662f7877d3ed50383f31a6fcd1395c894 GIT binary patch literal 2467 zcma)7OK%iM5T5RN?Cg5Ieu4oTGmwNZ3alj}p&T%R07`Pe7Z6fLiblg!+a5DJV|UL2 z9vLYLTz!f110Xm4QXg{3iClAtL?V)^-d%eEBV|`z(^XwvRsB_UbvZfNvherYFF$Wx zYg*Rt#EkzOWUk{-SCD`OEVp_rVm%)5o*mhYWIT6zZsaoS8w+f3o?GAud&_%fM-6cA zTA}wE_gbN??PNRC-g+i-P}=L2AaWHlJ#|0HGf1keEN)9#%9PZ{e+^`=<4}Jf5mv;& zia4;y1|B(JN3QU;8sLBn-ae0f!x8U*zi&rPdm@T$@I22IsT;ESkmn+=c~nzcQFN>vlAcvKAbJLx>Zx1xo~ky>E%=9Sp#iy{>l163umiV<9KuWr&}u$J^h$h9v~CzArS zrz)8h8``;v2?=fKBgm2<*KSsTcygrMU6IH!x8Ea6CdpJegL~ zD7b|Yh5S&h_HP$pq^}vOVSo(~>q$SaOei6f9TMBP;DSunEA8H*5At4;R@oz=y&L9B zk~A+Bo$RLh$|-U@fzfr-vbUWSyY=D-znBgZXE0j9Y3S&N6gdV6+ErC41)VH`vdDMM z8L*P)`bD-A6ZUb6s2MBuG70O%N=*Mwl2@ zx2zv#pRzr+WyyJ@%utJF4;?vQ;f;O9equWuEUaz5R)>^wD$RqqHV|h76@f^KLFt-x zgh8G{7jlt^iv*MGD3PygTZ;6RC6`h5=Bt%nQLTt4l}s)rfca7@2Nx!OkSMV-XgMnf zsi6dRw|DhSoNkKr;UJ+DkqXhEqJs7L3Fb5Yqw}_29^Enmwfn&qJ$+zzSXA!-Mqpuw z9RPM8CkC+d1HhEO!}qKg3>a_(jIZ0&NeN0b$HsJ2KaptVs8_rAil<#qQp>m-) z25f6h^9*W!c2%B5FK^hY!LH_4R}FqOzqVR`IXX%}5UBA2RM;O(@das86lE2ZkA%#V z?QZB9gV-;yGSf|Y4y_L|Vsc~BV#_6xo<^c4h8wrBxtIhE$QfFj8%J-bw}K0bO!?en zoH?w;+BjOw`^WJx1^+sK)xjw44^l8R@($!wuk?UXjs7`{1R=&J40y&~GJ3kO4(&C% z@rn&jLWMOJ_Z0P-vWQK~!A+}VnXbYttC!x1?LEbh#w9<;-57Vth`i^$Jv3_C+jUa7grq)slw+f_7M|9A92c0nRwUc9KE$}@_vZ7LwFea6uM9BaJ z@;s4?M5qIe!DWD>N|PQEDoyn*65n#Lt{m3k(@6Qj-}}4M=&+8dH^;$xbH~?s6RYuU z%f))c;ZL@0V|6X`wNpY_n&1pflB!Klk+3VCq{0BHOvlph9YifRJC6!f`&S3axkgp1 MDC~Kw&5qmu0aH?#wEzGB literal 0 HcmV?d00001 diff --git a/addons/education_base/models/__pycache__/academic_year.cpython-310.pyc b/addons/education_base/models/__pycache__/academic_year.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..404971d0b75e65f73054d15e6e6dfe3cc05355fb GIT binary patch literal 2900 zcmai0OK;pZ5GEx`E3en~`Vq%@Ida~cCTlc(qDX=ycG^>%LxKVoPzZ{KwqY`d`L=MRz=bLYa+Z`FHGkkvf;rq3p zhZy^XI{P1iom=?kAEN*UJY^jo@QxK&9Xqf&>8w z7Vy9ZD-d7@j&#>*Aix4=+X_6>bFnX=wrvMP*sp_!y&)VOHobbk*TCKgwuiTQFpBLl zXaMy`VT{~>=C%_w&75(VfJt0Gj=hN$22($BID#_-OmB-|617us6lUNU9LGJ6z=;(e zOv|IMX5b{8+Oc33PQPH!d2r0={2dN+aK>nk%cE;2Ub1BlXW`rn59geO^Kjt>3r@jB zxP;$XXu)Oto_@&$3w(t1J>9q+M<6>%{FRJUc~n9#HVC$(zP|mG21W+cd^7KOUPP)0 z5p3y$%;PJ57ImuT!lmPbd@jR z!EBj5*0sKc*IbKSrpvFH8poM$KArDm#k_n{sOV}0(9QDsT{yx=Q7-2zs?86e2??6^ z##8NGPovHwh;B~OwiXJHl}84U5n}DZjF#AQQVruwMTt{37J5p_tT6C?K0t!TTQ;8C z0TnG5+F7Iz_T4BhlE+fJx6PL{aoWuZ@CEZ#Q)X$kgTdQFx8ik-#i2nir0VGA0ms4w zwDS=4m|Nz|n0eE^EZH<}hcN~eg^5KuL;9Y)M9M3EUyBA>WqW{vPhjB?NWas|3;&Vy zt402XB)u`z*7ZGRX-{TgICdb!1h0*+32_@6TWpbiJ@Jfh@inHVP;w&~wbq52Dlo^N z^Y8ek1q_K`EtMCET!y@Zqol|`eU(KZrx7B^6*NjNB8TdIj4Z7UfY(o|xC zbmL*XD&zGE9WtJuL3K{y^{g50aqC<0S2;Vt!?5CCOLQZyh+xbd@SA)lMjOBHV8i%* zvhVlo7JJ2kgN0sOYxc5DB@`U&VSB~i;m@osOU)Jn+YJCRzK;77UNKfJ%(RerP^)Rtq6VlhAp#H;EY z?QnqvVTxN4rfLoahWtsA7s%3g(LLUWD#x)DiMJ1>OSmwfM&Vg57renOUgz$g!ZDEl zUS2#5@Tuq0=dOs;kAYJ)>^mJFxXoh#C2Bi=j;_?zC$I(S<()&2bn~t zd1#!b>uN<}>gGM9uL`)KwI3V>x>;z-7~(+(%^N5@PMPB3E8dnT2b-H*d1uK1uc_qIUkvj4 zH_ArL28KIE;`x->3v5HHN)+PuJ*gWlHL8GD)aT@bcJU2Qst% z^EB!jF5TUmsNmUTaQ!VMteS80vZ$;yu;F zBnfpR3^#jG+8=Su6jaict&$DeOuJ!MfmDi!j@R^Rq_m*(Gtup`{Mq3b}5E`DTi@M}~h7q9iMF zMF>+Z>T7a!1{JDBTE|?wCLPrKr2UA*Cp0wD&zo4L=^%Fu)#hmm5kXP%P?^bZq41c% z3L*HY)#Ri2Yhc?lKNqF@$7l^Jhj8CE9m^Jf*F~LsPMwS4QT%$frfsY(wLC4l(CwN- x8{ia?PmhtZEuX~FK%1NLrk#6;V`^6JBbqs-xi`G)Rk&^viA}Cgu?C;E{{beo-dX?v literal 0 HcmV?d00001 diff --git a/addons/education_base/models/__pycache__/campus.cpython-310.pyc b/addons/education_base/models/__pycache__/campus.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1dccbfee7f36e0e3a2807a403d813f90b9bdd922 GIT binary patch literal 2137 zcmaJ?OK%)S5bo}I?0Eg!IJV;?4%vi9vJlovgg7M;;6wZ5}N+@g&VV%{taJ}g&bHYfQJr< z&;=*-q~EK73m*7~A`GlWwhO_b6V9MrhZ(5jJ&X3N9iM~wLmtjqeZlJUs4w7}Mf5MC z-hc*NKzj);QcAFV=!F-oe+5=?g$S3>x(JtG4KAbYgv)TH!^0K1dUOeaxOyyL9j+a) zeIBmK)!yY-Y@5S7aQz@a&lPJm;l@D_UVX(}7Oo>yH_hT>7$g>p%{cE5^spgM9qq(= zj6WVRZV~75`{C@^Y;K{|5++b`XOJoh#@Cffi_UbyCIa4a6cMgwP#7;QK<-V)zb-pv z`V)dZnPg`2crSXMLe({OQoAzkbSvY1+bs*(;x>};9_MkIA&oVV+wmZ)hO}r{9pD?w8Ghg4`$ct)2 z?o}%OI0h&Sy)i{7dKzoFF)pl)FsZQs%P5o@uKj3z3#uK<7p)q2Q z*pJs<@)0}2mIg86J>C=B4z(}p4rZV0Ap6hu^?W}Qj~OM@+_k7MH6_?uW4l903Uckj0_x14R z`3m@VfP{+W^dg<0q(^^b;I*%88djCFk?wv0>s zVV+J$Vh>So_Ki2_BZFuS=;bF$#+5t~<80mCIMXNG%tTR=#ac(vS+=T$-~Wg<;-jdG zWlV3ldOo3Xy#G$(9;oF;?X|0ko~#bqr|y^k;T0f}2AZlf^XbCp(75jyG) z3F=uz{HQjHyZG20FLAr$mGe8c%Ujn>5RG|^Og)OQp$D0aA~P37JA*j03`fSZQ_u-g zCx|BfK#U)i3Zzm*l5sb?SSM7S>OHc(ukyGfjho>~XS_ZgW5$zoewo@AWtqts1H?&{ z?n)Cp!Q<;5&aee(>Uh>r$5T%S-xTs*PN&<#S<~ZyHjPN{WZ2RL(i^8mr4)eR$$x`%F^!4!ldXYZx+efmV1e`Kx*4~#(RQAo7t6b_1lzG SJX*)(Jf!2?GK;*5Rp)Q*!7}v# literal 0 HcmV?d00001 diff --git a/addons/education_base/models/__pycache__/department.cpython-310.pyc b/addons/education_base/models/__pycache__/department.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f839636af8dba2b514117c37831d05d1abf7c14a GIT binary patch literal 1890 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 literal 0 HcmV?d00001 diff --git a/addons/education_base/models/academic_level.py b/addons/education_base/models/academic_level.py new file mode 100644 index 0000000..467838f --- /dev/null +++ b/addons/education_base/models/academic_level.py @@ -0,0 +1,35 @@ +from odoo import api, fields, models +from odoo.exceptions import ValidationError + + +class AcademicLevel(models.Model): + _name = 'education.academic.level' + _description = 'Academic Level' + _order = 'sequence, name' + + name = fields.Char(required=True, string='Level Name') + code = fields.Char(required=True, string='Level Code', index=True) + sequence = fields.Integer(default=10) + description = fields.Text(string='Description') + active = fields.Boolean(default=True) + company_id = fields.Many2one( + 'res.company', + string='Company', + required=True, + default=lambda self: self.env.company, + index=True, + ) + + _sql_constraints = [ + ('uniq_academic_level_code_company', 'unique(code, company_id)', 'Academic level code must be unique per company.'), + ] + + @api.constrains('name', 'company_id') + def _check_unique_name(self): + for rec in self: + if self.search_count([ + ('company_id', '=', rec.company_id.id), + ('name', '=ilike', rec.name), + ('id', '!=', rec.id), + ]): + raise ValidationError('Academic level name must be unique within a company.') diff --git a/addons/education_base/models/academic_term.py b/addons/education_base/models/academic_term.py new file mode 100644 index 0000000..ab5af34 --- /dev/null +++ b/addons/education_base/models/academic_term.py @@ -0,0 +1,65 @@ +from odoo import api, fields, models +from odoo.exceptions import ValidationError + + +class AcademicTerm(models.Model): + _name = 'education.academic.term' + _description = 'Academic Term' + _order = 'sequence, start_date' + + name = fields.Char(required=True, string='Term Name') + code = fields.Char(string='Code', required=True, index=True) + academic_year_id = fields.Many2one( + 'education.academic.year', + string='Academic Year', + required=True, + ondelete='restrict', + index=True, + ) + start_date = fields.Date(string='Start Date', required=True) + end_date = fields.Date(string='End Date', required=True) + sequence = fields.Integer(default=1, required=True) + active = fields.Boolean(default=True) + state = fields.Selection([ + ('draft', 'Draft'), + ('active', 'Active'), + ('closed', 'Closed'), + ], default='draft', required=True) + company_id = fields.Many2one( + 'res.company', + related='academic_year_id.company_id', + store=True, + readonly=True, + index=True, + ) + + _sql_constraints = [ + ('uniq_term_code_year', 'unique(code, academic_year_id)', 'Term code must be unique within the academic year.'), + ('uniq_term_sequence_year', 'unique(sequence, academic_year_id)', 'Term sequence must be unique within the academic year.'), + ] + + @api.constrains('start_date', 'end_date') + def _check_term_dates(self): + for rec in self: + if rec.start_date and rec.end_date and rec.start_date > rec.end_date: + raise ValidationError('End date must be greater than start date.') + + @api.constrains('academic_year_id', 'start_date', 'end_date') + def _check_term_overlap(self): + for rec in self: + if not rec.academic_year_id: + continue + overlapping = self.search([ + ('id', '!=', rec.id), + ('academic_year_id', '=', rec.academic_year_id.id), + ('start_date', '<=', rec.end_date), + ('end_date', '>=', rec.start_date), + ]) + if overlapping: + raise ValidationError('Academic terms within the same year cannot overlap.') + + def action_activate(self): + self.write({'state': 'active'}) + + def action_close(self): + self.write({'state': 'closed'}) diff --git a/addons/education_base/models/academic_year.py b/addons/education_base/models/academic_year.py new file mode 100644 index 0000000..7965d17 --- /dev/null +++ b/addons/education_base/models/academic_year.py @@ -0,0 +1,69 @@ +from odoo import api, fields, models +from odoo.exceptions import ValidationError + + +class AcademicYear(models.Model): + _name = 'education.academic.year' + _description = 'Academic Year' + _order = 'start_date desc, name desc' + + name = fields.Char(required=True, string='Academic Year') + code = fields.Char(string='Code', required=True, index=True) + start_date = fields.Date(string='Start Date', required=True) + end_date = fields.Date(string='End Date', required=True) + active = fields.Boolean(default=True) + company_id = fields.Many2one( + 'res.company', + string='Company', + required=True, + default=lambda self: self.env.company, + index=True, + ) + current_year = fields.Boolean(string='Current Year', default=False) + state = fields.Selection([ + ('draft', 'Draft'), + ('active', 'Active'), + ('closed', 'Closed'), + ], default='draft', required=True, tracking=True) + term_ids = fields.One2many('education.academic.term', 'academic_year_id', string='Terms') + + _sql_constraints = [ + ('uniq_year_code_company', 'unique(code, company_id)', 'Academic year code must be unique per company.'), + ] + + @api.constrains('start_date', 'end_date') + def _check_year_dates(self): + for rec in self: + if rec.start_date and rec.end_date and rec.start_date > rec.end_date: + raise ValidationError('End date must be later than start date.') + + @api.constrains('current_year') + def _check_single_current_year(self): + for rec in self: + if rec.current_year: + existing = self.search([ + ('company_id', '=', rec.company_id.id), + ('current_year', '=', True), + ('id', '!=', rec.id), + ]) + if existing: + raise ValidationError('Only one academic year can be marked as current for a company.') + + @api.model + def create(self, vals): + if vals.get('state') == 'active': + vals['current_year'] = True + return super().create(vals) + + def action_activate(self): + for rec in self: + if rec.start_date and rec.end_date and rec.start_date > rec.end_date: + raise ValidationError('Academic year dates are invalid.') + rec.state = 'active' + rec.current_year = True + + def action_close(self): + self.write({'state': 'closed'}) + + def action_set_draft(self): + self.write({'state': 'draft'}) diff --git a/addons/education_base/models/campus.py b/addons/education_base/models/campus.py new file mode 100644 index 0000000..8cfaa70 --- /dev/null +++ b/addons/education_base/models/campus.py @@ -0,0 +1,52 @@ +# pyrefly: ignore [missing-import] +from odoo import api, fields, models +# pyrefly: ignore [missing-import] +from odoo.exceptions import ValidationError + + +class EducationCampus(models.Model): + _name = 'education.campus' + _description = 'Campus' + _order = 'name' + + name = fields.Char(required=True, string='Campus Name') + code = fields.Char(string='Campus Code', required=True, index=True) + logo = fields.Image(string='Campus Logo', max_width=1920, max_height=1920) + phone = fields.Char(string='Phone') + email = fields.Char(string='Email') + active = fields.Boolean(default=True) + company_id = fields.Many2one( + 'res.company', + string='Company', + required=True, + default=lambda self: self.env.company, + index=True, + ) + address_id = fields.Many2one('res.partner', string='Address') + department_ids = fields.One2many('education.department', 'campus_id', string='Departments') + + _sql_constraints = [ + ('uniq_campus_code_company', 'unique(code, company_id)', 'Campus code must be unique per company.'), + ] + + @api.constrains('name', 'company_id') + def _check_name_unique(self): + for rec in self: + similar = self.search([ + ('company_id', '=', rec.company_id.id), + ('name', '=ilike', rec.name), + ('id', '!=', rec.id), + ]) + if similar: + raise ValidationError('Campus name must be unique within a company.') + + @api.model + def create(self, vals): + if vals.get('code'): + vals['code'] = vals['code'].strip().upper() + return super().create(vals) + + def write(self, vals): + if 'code' in vals and vals.get('code'): + vals['code'] = vals['code'].strip().upper() + return super().write(vals) diff --git a/addons/education_base/models/department.py b/addons/education_base/models/department.py new file mode 100644 index 0000000..f345513 --- /dev/null +++ b/addons/education_base/models/department.py @@ -0,0 +1,50 @@ +from odoo import api, fields, models +from odoo.exceptions import ValidationError + + +class EducationDepartment(models.Model): + _name = 'education.department' + _description = 'Department' + _order = 'name' + + name = fields.Char(required=True, string='Department Name') + code = fields.Char(string='Department Code', required=True, index=True) + campus_id = fields.Many2one( + 'education.campus', + string='Campus', + required=True, + ondelete='restrict', + index=True, + ) + company_id = fields.Many2one( + 'res.company', + related='campus_id.company_id', + store=True, + readonly=True, + index=True, + ) + head_teacher_id = fields.Many2one('res.partner', string='Head Teacher') + active = fields.Boolean(default=True) + program_ids = fields.One2many('education.program', 'department_id', string='Programs') + + _sql_constraints = [ + ('uniq_department_code_company', 'unique(code, company_id)', 'Department code must be unique per company.'), + ] + + @api.constrains('code') + def _check_code_format(self): + for rec in self: + if rec.code and not rec.code.strip(): + raise ValidationError('Department code cannot be blank.') + + @api.constrains('name', 'company_id') + def _check_name_unique(self): + for rec in self: + similar = self.search([ + ('company_id', '=', rec.company_id.id), + ('name', '=ilike', rec.name), + ('campus_id', '=', rec.campus_id.id), + ('id', '!=', rec.id), + ]) + if similar: + raise ValidationError('Department name must be unique within a campus.') diff --git a/addons/education_base/models/education_config.py b/addons/education_base/models/education_config.py new file mode 100644 index 0000000..b678335 --- /dev/null +++ b/addons/education_base/models/education_config.py @@ -0,0 +1,59 @@ +from odoo import api, fields, models +from odoo.exceptions import ValidationError + + +class EducationConfiguration(models.Model): + _name = 'education.configuration' + _description = 'Education Configuration' + _rec_name = 'school_name' + + company_id = fields.Many2one( + 'res.company', + string='Company', + required=True, + default=lambda self: self.env.company, + index=True, + ) + school_name = fields.Char(string='School Name', required=True) + school_code = fields.Char(string='School Code', required=True) + default_campus_id = fields.Many2one('education.campus', string='Default Campus') + default_academic_year_id = fields.Many2one('education.academic.year', string='Default Academic Year') + default_term_id = fields.Many2one('education.academic.term', string='Default Term') + default_currency_id = fields.Many2one('res.currency', string='Default Currency') + logo = fields.Binary(string='Logo') + contact_email = fields.Char(string='Contact Email') + contact_phone = fields.Char(string='Contact Phone') + active = fields.Boolean(default=True) + + _sql_constraints = [ + ('uniq_company_config', 'unique(company_id)', 'Only one education configuration can exist per company.'), + ] + + @api.constrains('default_campus_id', 'company_id') + def _check_default_campus_in_company(self): + for rec in self: + if rec.default_campus_id and rec.default_campus_id.company_id != rec.company_id: + raise ValidationError('Default campus must belong to the same company.') + + @api.constrains('default_academic_year_id', 'company_id') + def _check_default_academic_year_in_company(self): + for rec in self: + if rec.default_academic_year_id and rec.default_academic_year_id.company_id != rec.company_id: + raise ValidationError('Default academic year must belong to the same company.') + + @api.constrains('default_term_id', 'company_id') + def _check_default_term_in_company(self): + for rec in self: + if rec.default_term_id and rec.default_term_id.company_id != rec.company_id: + raise ValidationError('Default term must belong to the same company.') + + @api.model + def get_default_values(self): + config = self.search([('company_id', '=', self.env.company.id)], limit=1) + if config: + return config + return self.create({ + 'company_id': self.env.company.id, + 'school_name': self.env.company.name, + 'school_code': self.env.company.name[:5].upper(), + }) diff --git a/addons/education_base/models/program.py b/addons/education_base/models/program.py new file mode 100644 index 0000000..8f95bcb --- /dev/null +++ b/addons/education_base/models/program.py @@ -0,0 +1,67 @@ +from odoo import api, fields, models +from odoo.exceptions import ValidationError + + +class EducationProgram(models.Model): + _name = 'education.program' + _description = 'Program' + _order = 'name' + + name = fields.Char(required=True, string='Program Name') + code = fields.Char(required=True, string='Program Code', index=True) + level_id = fields.Many2one( + 'education.academic.level', + string='Academic Level', + required=True, + ondelete='restrict', + index=True, + ) + department_id = fields.Many2one( + 'education.department', + string='Department', + required=True, + ondelete='restrict', + index=True, + ) + company_id = fields.Many2one( + 'res.company', + related='department_id.company_id', + store=True, + readonly=True, + index=True, + ) + duration = fields.Integer(string='Duration', default=1) + duration_unit = fields.Selection([ + ('years', 'Years'), + ('months', 'Months'), + ('semesters', 'Semesters'), + ], default='years', required=True) + active = fields.Boolean(default=True) + description = fields.Text(string='Description') + + _sql_constraints = [ + ('uniq_program_code_company', 'unique(code, company_id)', 'Program code must be unique per company.'), + ] + + @api.constrains('duration') + def _check_duration(self): + for rec in self: + if rec.duration <= 0: + raise ValidationError('Program duration must be greater than zero.') + + @api.constrains('name', 'company_id') + def _check_name_unique(self): + for rec in self: + if self.search_count([ + ('company_id', '=', rec.company_id.id), + ('name', '=ilike', rec.name), + ('id', '!=', rec.id), + ]): + raise ValidationError('Program name must be unique within a company.') + + @api.onchange('department_id') + def _onchange_department_id(self): + if self.department_id: + self.company_id = self.department_id.company_id.id + if self.level_id and self.level_id.company_id != self.company_id: + self.level_id = False diff --git a/addons/education_base/security/education_base_security.xml b/addons/education_base/security/education_base_security.xml new file mode 100644 index 0000000..44c6749 --- /dev/null +++ b/addons/education_base/security/education_base_security.xml @@ -0,0 +1,27 @@ + + + + Education Base + 10 + + + + Education Base User + + Basic access to education master data. + + + + Education Base Manager + + + Manage academic configuration and master setup. + + + + Education Base Admin + + + Full administrative access to base education configuration. + + diff --git a/addons/education_base/security/ir.model.access.csv b/addons/education_base/security/ir.model.access.csv new file mode 100644 index 0000000..55a0b70 --- /dev/null +++ b/addons/education_base/security/ir.model.access.csv @@ -0,0 +1,28 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_education_academic_year_user,education.academic.year user,model_education_academic_year,group_education_base_user,1,0,0,0 +access_education_academic_year_manager,education.academic.year manager,model_education_academic_year,group_education_base_manager,1,1,1,1 +access_education_academic_year_admin,education.academic.year admin,model_education_academic_year,group_education_base_admin,1,1,1,1 + +access_education_academic_term_user,education.academic.term user,model_education_academic_term,group_education_base_user,1,0,0,0 +access_education_academic_term_manager,education.academic.term manager,model_education_academic_term,group_education_base_manager,1,1,1,1 +access_education_academic_term_admin,education.academic.term admin,model_education_academic_term,group_education_base_admin,1,1,1,1 + +access_education_campus_user,education.campus user,model_education_campus,group_education_base_user,1,0,0,0 +access_education_campus_manager,education.campus manager,model_education_campus,group_education_base_manager,1,1,1,1 +access_education_campus_admin,education.campus admin,model_education_campus,group_education_base_admin,1,1,1,1 + +access_education_department_user,education.department user,model_education_department,group_education_base_user,1,0,0,0 +access_education_department_manager,education.department manager,model_education_department,group_education_base_manager,1,1,1,1 +access_education_department_admin,education.department admin,model_education_department,group_education_base_admin,1,1,1,1 + +access_education_academic_level_user,education.academic.level user,model_education_academic_level,group_education_base_user,1,0,0,0 +access_education_academic_level_manager,education.academic.level manager,model_education_academic_level,group_education_base_manager,1,1,1,1 +access_education_academic_level_admin,education.academic.level admin,model_education_academic_level,group_education_base_admin,1,1,1,1 + +access_education_program_user,education.program user,model_education_program,group_education_base_user,1,0,0,0 +access_education_program_manager,education.program manager,model_education_program,group_education_base_manager,1,1,1,1 +access_education_program_admin,education.program admin,model_education_program,group_education_base_admin,1,1,1,1 + +access_education_configuration_user,education.configuration user,model_education_configuration,group_education_base_user,1,0,0,0 +access_education_configuration_manager,education.configuration manager,model_education_configuration,group_education_base_manager,1,1,1,1 +access_education_configuration_admin,education.configuration admin,model_education_configuration,group_education_base_admin,1,1,1,1 diff --git a/addons/education_base/security/record_rules.xml b/addons/education_base/security/record_rules.xml new file mode 100644 index 0000000..612adc8 --- /dev/null +++ b/addons/education_base/security/record_rules.xml @@ -0,0 +1,51 @@ + + + + Academic Year Company Rule + + [('company_id', '=', user.company_id.id)] + + + + + Academic Term Company Rule + + [('company_id', '=', user.company_id.id)] + + + + + Campus Company Rule + + [('company_id', '=', user.company_id.id)] + + + + + Department Company Rule + + [('company_id', '=', user.company_id.id)] + + + + + Academic Level Company Rule + + [('company_id', '=', user.company_id.id)] + + + + + Program Company Rule + + [('company_id', '=', user.company_id.id)] + + + + + Configuration Company Rule + + [('company_id', '=', user.company_id.id)] + + + diff --git a/addons/education_base/tests/__init__.py b/addons/education_base/tests/__init__.py new file mode 100644 index 0000000..65c643f --- /dev/null +++ b/addons/education_base/tests/__init__.py @@ -0,0 +1,4 @@ +from . import test_academic_year +from . import test_campus +from . import test_program +from . import test_security diff --git a/addons/education_base/tests/__pycache__/__init__.cpython-310.pyc b/addons/education_base/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d3071dfe38c91054384d7ccf5e79c0632920c7c GIT binary patch literal 291 zcmYk0O-ckY5Jpq|;|$sj;u&Vs3k`@%5l`U8U2j5}gdxME(^S%6UqkQ^Ua7aPyn-t$ zSy@nDy;qP>q35y+5^Y>j;j8hTI<0a9{8F6kAat#phRJS7} zd_?-bqiqo%=zN&8J3)MOO|_#$v=83`V__61#n?6JZ&llIS`~+>3|0cplUP*+?}jE@ z_6Z26rVyYq$bu|Rb_9V|I!Drlkn$KCGVjw#+MD|~pNa`)EUN-6*uj-=0FKEeN$C|m zUl`=Y=>oa5xrMy(9AxUWfVqp@6-fCJ#B|_20Vy%_HKLw=1v|cb&-dVhYHM&%~# z3WnV;tgo)uRsf!BcY6;9eIG|FtMFSh{o0^k&S@tO+Cn>F!iDx#8pxDEB?#?_m=Ah> zMlfsgva5^0tB~p2ps9qCD4Rrnv>QqR>!gPRtH~wiFH1w1MEep_eVb(CgB=VDa02G3 zdjLi1Q!9I2>i=5$-P;(W{{Q=p>4FE$=#oMQn1@3IT0k8SwScPQF3(@Ms0JwKq@c_B zqEYT;9ld|IWTcyNxmJChBqueD_eNP1LED>9pBHoi+#B$onv7x9rMU_{s+e za|rVYcv5sR>`E!()D)|nM*9o`vIpx>7P*Lkn#e~83kXKEyX09mbgE|jITCDia|VZw zWo3YosAYgrc`WMni-p@3zYmht?w0I7KH%E$7{khBO#6(r6W)c8m@YE*r5i+pf6U>O zk1?eu0J@YN`%pv?VgeOS_Nl+EYww=Th p?|!= literal 0 HcmV?d00001 diff --git a/addons/education_base/tests/__pycache__/test_campus.cpython-310.pyc b/addons/education_base/tests/__pycache__/test_campus.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..591d7172a3c8b79ba11bbfbe8920389dc69756b3 GIT binary patch literal 998 zcmZ`&&ubJh6n>e>PPfzDwt`r_N%68^cdg(}#IlH|7SXCWmk=i@oiabR$&9v3Z!Pqw z2;Mxj$No#Xdh%ZoioTcK)B8Q_iH3wJNP<(?VT!5Xq@9G5gb6r`45G@&;JjRHo6Y!&Mq2+j}^ItWK^J#29Z(xTX>H3If64@x6aw86;JcZ zul2XzTdX93J-{J7WL|fzKYexMEgYm+q%*wkH!Cf@m*nII|y%#!O z%0fGyR!X~gDxApFcyXhw>RoN7H+qpF?k-A=mbErS%mtZp9aL4D`lZaQwuXvk=E-!g z%_6z3kfc*FMN?+`jTV%10S|qZV-{eKSo^XQUCram)nDV)8Ik`Jq!+oi5JW@>7Yk8T zs>x~I5n{iQ`Q*eG(A*+4-RW~kb)E|`gO%VI0&J+rEgY80-iA)F%>CXLKHVFYs;WML uv3-=Rs7gaG)-CyceUR!A{VP*BDAG&{FO1iu{dfZO2|anArvn{!GyDxhCh?B| literal 0 HcmV?d00001 diff --git a/addons/education_base/tests/__pycache__/test_program.cpython-310.pyc b/addons/education_base/tests/__pycache__/test_program.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..289e518cbe94d10d6976ef8c35bef24739ebc41c GIT binary patch literal 1349 zcmZuxOK%e~5VrS~WSh_yTHXh!xI}_PTsR>FN~@lT5YQrWvCP_(jrN7Tfi~)`m3l-% z;>Mvl@|S$ ydX!Hl!CtHN6TcI>goGxLp|cRC?~@6orf<0FrdpE#(0Fh`z3*Y^Pg z5i}tw9Z`z1lQ^k6aw*v)!WG^j5uT(6?#L7VE7A>q!4A@;X7RP+ndVU$=h=X3iBsD= zi3P`*4W)7w;nMo&Rs($x1W86zkdbpl-cvz^bJ!fY^8-iT5%I_fkMqsKYpKf@D&J8& zjcCmp$^mpVsue&<4#+W`5lZ&S*crR~gh3xWg5Dsp1g$sL|f`EJN;dDugNn~U0MLaDgdfFc5s{C+gU`NIu4xT-K(RGcN z@l+Zw%7rvxl&1yH_E;>YE9VA;EVwFDnU!W?Bio5HDOH^97x30s~P~NH9rcs zUq9c@BQ9hbN4-RTl8NbTWkM>*o0#xY8fR;>+cLhM6jH%YmRp5!W%kK5DrZa(DQQ>6 z0_3^-Va+Gs+WkR|r)BaU{w>C$glo;%4|4LTpJruW?v{!_;6mh??w`5F-a&=*OJGu4 zWW}7&Ua_YZAs`G&uK|Pf6^RnC|R8B239Nl8O_IYN1q@= L_MA3kV8#6dsM3?45lg2VTPPEbPI@r7DE(#U* z1xR!hNy#tpDQ>Cm7Z3$Aj)Zc8SGFpO7Uzbt=n#(vV^Fk&2xG2ICQ zFyI9%`HXY+mO%vKhJn!hvzSE?U$Q}Ri>ugx`}* zW*iuDaENZ$On^YH*mtMe)t5SJY+heysP1pjZqj{$>CO>0Y>C>Quq`rxfEW^JQEeN+ zy&YeQ1)pO=`Yqn^Pkh6+@rLiv1u#6RJz2M{cEiSL>(;$D&}52)pS;6@?`vozP01SN z$kWra_0xY!V|}H{RiTHODQ{aS$dcUkb6mLRlJD^rh6`(&mAqRF+~_v0RH=PZm_=Us zM3*Wrd^>D#GKWFpg|0sOR%W%TwHH>;eUyXmD2LkWscrNi^0Cv!+{YyMJF|V%@ayHM ztm=`zs;zpV08HgZcLj{dmkTJdQ{lFUt84!#_$A$Y`zp({b7ao-5IQ^x-(#Ns?zPs> z4x2Un@74*rOr<2BQu>aRrGciP|E`oD8ddCP>_cP^+7I9X)dT`PNJo&eZWJn$f$)6; j#)MNHwV*VWr8g4h4_N3zEK&LNe1G_B>QmtBvSaZFXx`FC literal 0 HcmV?d00001 diff --git a/addons/education_base/tests/test_academic_year.py b/addons/education_base/tests/test_academic_year.py new file mode 100644 index 0000000..ae7ab8c --- /dev/null +++ b/addons/education_base/tests/test_academic_year.py @@ -0,0 +1,39 @@ +from odoo.tests.common import TransactionCase +from odoo.exceptions import ValidationError + + +class TestAcademicYear(TransactionCase): + + def setUp(self): + super().setUp() + self.company = self.env.company + + def test_academic_year_date_validation(self): + with self.assertRaises(ValidationError): + self.env['education.academic.year'].create({ + 'name': '2025-2026', + 'code': 'AY2025', + 'start_date': '2026-06-01', + 'end_date': '2025-06-01', + 'company_id': self.company.id, + }) + + def test_single_current_year_per_company(self): + self.env['education.academic.year'].create({ + 'name': '2024-2025', + 'code': 'AY2024', + 'start_date': '2024-06-01', + 'end_date': '2025-06-01', + 'company_id': self.company.id, + 'current_year': True, + }) + + with self.assertRaises(ValidationError): + self.env['education.academic.year'].create({ + 'name': '2025-2026', + 'code': 'AY2025', + 'start_date': '2025-06-01', + 'end_date': '2026-06-01', + 'company_id': self.company.id, + 'current_year': True, + }) diff --git a/addons/education_base/tests/test_campus.py b/addons/education_base/tests/test_campus.py new file mode 100644 index 0000000..71f79bc --- /dev/null +++ b/addons/education_base/tests/test_campus.py @@ -0,0 +1,23 @@ +from odoo.tests.common import TransactionCase +from odoo.exceptions import ValidationError + + +class TestCampus(TransactionCase): + + def setUp(self): + super().setUp() + self.company = self.env.company + + def test_campus_unique_code(self): + self.env['education.campus'].create({ + 'name': 'Main Campus', + 'code': 'CAMP01', + 'company_id': self.company.id, + }) + + with self.assertRaises(ValidationError): + self.env['education.campus'].create({ + 'name': 'Another Campus', + 'code': 'CAMP01', + 'company_id': self.company.id, + }) diff --git a/addons/education_base/tests/test_program.py b/addons/education_base/tests/test_program.py new file mode 100644 index 0000000..c47b7c0 --- /dev/null +++ b/addons/education_base/tests/test_program.py @@ -0,0 +1,35 @@ +from odoo.tests.common import TransactionCase +from odoo.exceptions import ValidationError + + +class TestProgram(TransactionCase): + + def setUp(self): + super().setUp() + self.company = self.env.company + self.campus = self.env['education.campus'].create({ + 'name': 'North Campus', + 'code': 'NCAMP', + 'company_id': self.company.id, + }) + self.department = self.env['education.department'].create({ + 'name': 'Engineering', + 'code': 'ENG', + 'campus_id': self.campus.id, + }) + self.level = self.env['education.academic.level'].create({ + 'name': 'Undergraduate', + 'code': 'UG', + 'company_id': self.company.id, + }) + + def test_program_duration(self): + with self.assertRaises(ValidationError): + self.env['education.program'].create({ + 'name': 'Engineering Degree', + 'code': 'ENGD', + 'level_id': self.level.id, + 'department_id': self.department.id, + 'duration': 0, + 'duration_unit': 'years', + }) diff --git a/addons/education_base/tests/test_security.py b/addons/education_base/tests/test_security.py new file mode 100644 index 0000000..200f92b --- /dev/null +++ b/addons/education_base/tests/test_security.py @@ -0,0 +1,13 @@ +from odoo.tests.common import TransactionCase + + +class TestSecurity(TransactionCase): + + def test_base_user_access(self): + user = self.env['res.users'].create({ + 'name': 'Education User', + 'login': 'edu_user_test_001', + 'email': 'edu_user_test_001@example.com', + 'groups_id': [(6, 0, [self.env.ref('education_base.group_education_base_user').id])], + }) + self.assertTrue(user) diff --git a/addons/education_base/views/academic_level_views.xml b/addons/education_base/views/academic_level_views.xml new file mode 100644 index 0000000..51e67a0 --- /dev/null +++ b/addons/education_base/views/academic_level_views.xml @@ -0,0 +1,61 @@ + + + + education.academic.level.tree + education.academic.level + + + + + + + + + + + + + education.academic.level.form + education.academic.level + +
+ + + + + + + + + + + + + + + +
+
+
+ + + education.academic.level.search + education.academic.level + + + + + + + + + + + + + Academic Levels + education.academic.level + tree,form + + +
diff --git a/addons/education_base/views/academic_term_views.xml b/addons/education_base/views/academic_term_views.xml new file mode 100644 index 0000000..9c3b90e --- /dev/null +++ b/addons/education_base/views/academic_term_views.xml @@ -0,0 +1,72 @@ + + + + education.academic.term.tree + education.academic.term + + + + + + + + + + + + + + + + education.academic.term.form + education.academic.term + +
+
+
+ + + + + + + + + + + + + + + +
+
+
+ + + education.academic.term.search + education.academic.term + + + + + + + + + + + + + + + + Academic Terms + education.academic.term + tree,form + + +
diff --git a/addons/education_base/views/academic_year_views.xml b/addons/education_base/views/academic_year_views.xml new file mode 100644 index 0000000..88ad0b6 --- /dev/null +++ b/addons/education_base/views/academic_year_views.xml @@ -0,0 +1,85 @@ + + + + education.academic.year.tree + education.academic.year + + + + + + + + + + + + + + + education.academic.year.form + education.academic.year + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + education.academic.year.search + education.academic.year + + + + + + + + + + + + + + + + Academic Years + education.academic.year + tree,form + + +
diff --git a/addons/education_base/views/campus_views.xml b/addons/education_base/views/campus_views.xml new file mode 100644 index 0000000..0708fcc --- /dev/null +++ b/addons/education_base/views/campus_views.xml @@ -0,0 +1,73 @@ + + + + education.campus.tree + education.campus + + + + + + + + + + + + + education.campus.form + education.campus + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + education.campus.search + education.campus + + + + + + + + + + + + + + Campuses + education.campus + tree,form + + +
diff --git a/addons/education_base/views/department_views.xml b/addons/education_base/views/department_views.xml new file mode 100644 index 0000000..208859c --- /dev/null +++ b/addons/education_base/views/department_views.xml @@ -0,0 +1,72 @@ + + + + education.department.tree + education.department + + + + + + + + + + + + + education.department.form + education.department + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + education.department.search + education.department + + + + + + + + + + + + + + + Departments + education.department + tree,form + + +
diff --git a/addons/education_base/views/education_config_views.xml b/addons/education_base/views/education_config_views.xml new file mode 100644 index 0000000..b80fa9c --- /dev/null +++ b/addons/education_base/views/education_config_views.xml @@ -0,0 +1,70 @@ + + + + education.configuration.tree + education.configuration + + + + + + + + + + + + + education.configuration.form + education.configuration + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + education.configuration.search + education.configuration + + + + + + + + + + + + + Education Configuration + education.configuration + tree,form + + +
diff --git a/addons/education_base/views/menuitems.xml b/addons/education_base/views/menuitems.xml new file mode 100644 index 0000000..cc72fa5 --- /dev/null +++ b/addons/education_base/views/menuitems.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/addons/education_base/views/program_views.xml b/addons/education_base/views/program_views.xml new file mode 100644 index 0000000..3d1ef63 --- /dev/null +++ b/addons/education_base/views/program_views.xml @@ -0,0 +1,70 @@ + + + + education.program.tree + education.program + + + + + + + + + + + + + + + education.program.form + education.program + +
+ + + + + + + + + + + + + + + + + + +
+
+
+ + + education.program.search + education.program + + + + + + + + + + + + + + + + + Programs + education.program + tree,form + + +
diff --git a/addons/education_dashboard/__init__.py b/addons/education_dashboard/__init__.py new file mode 100644 index 0000000..f7209b1 --- /dev/null +++ b/addons/education_dashboard/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import controllers diff --git a/addons/education_dashboard/__manifest__.py b/addons/education_dashboard/__manifest__.py new file mode 100644 index 0000000..708432d --- /dev/null +++ b/addons/education_dashboard/__manifest__.py @@ -0,0 +1,36 @@ +{ + 'name': 'Education Dashboard', + 'version': '17.0.1.0.0', + 'category': 'Education', + 'summary': 'Interactive Management Dashboard for Education ERP', + 'description': """ + Executive management dashboard providing: + - Student KPIs: totals, active enrollment, admissions, graduation rates + - Finance KPIs: total fees, collections, outstanding dues + - Academic KPIs: program counts, departments, campuses + - Dynamic interactive Chart.js widgets (student distribution, fee collections) + - Dynamic filters (Campus, Department, Program, Academic Year, Semester) + - Dynamic drill-down actions directly linking to filtered records + """, + 'author': 'Senior Solution Architect', + 'depends': [ + 'education_base', + 'education_student', + 'education_finance', + 'education_attendance', + ], + 'data': [ + 'security/ir.model.access.csv', + 'views/dashboard_views.xml', + ], + 'assets': { + 'web.assets_backend': [ + '/education_dashboard/static/src/js/education_dashboard.js', + '/education_dashboard/static/src/xml/education_dashboard_templates.xml', + ], + }, + 'installable': True, + 'application': True, + 'auto_install': False, + 'license': 'LGPL-3', +} diff --git a/addons/education_dashboard/__pycache__/__init__.cpython-310.pyc b/addons/education_dashboard/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a25cad28508c727eff05924aeecc4586ff26cf6 GIT binary patch literal 207 zcmd1j<>g`kf;iL3S^7ZwF^Gc%z278UzxGT&k-0vT7t3?x=E6tRNXAmW#y zer{fgeriQYQKD{QN=klSv3_bwX>wvoW`16LN@8(FQhs7lihg{2W?p7Ve7s&kfgc5OqSDv??YK z2RX>Y11P{+&x7Kx9F$k{uU?jpB1!Ya)B7(=t z5e?vg8<*vjaq?&o+k5cSvEmt8EEV&lPZ0movXOuuI?HzLOg0GXjOb9+w5n%`E~i;r z(sf7M6_T1n3@4M&b#^+>r_P$wTToDtU^w+XC>M>d#Stvwn0*O5$~jIiT# zYpLp#MR-Mo$(vviV~z>P4^D)EHDPdRSM8t%CXH1K%TS8BYD?Q;G?1BBv#h?JCB=&m z&5OO4wQ5=*zhGJ#Qdh|Y={g-(>}fMjOE!)oP9GIO8yYdlOp+FP?E2cIB{Cw3v1tLg z<1{_dm{x=$AyV>=#I(W(11YBGKYd6*-JUI{C7MTN@$+xbY}5Z`@wzz-BIN?Sc82e9 z0CL1CMM1kwr(AT&Snaky7^mxRsV}j_cl1l}>@L0Xx%s_O%;ifoy3Mam=5F)<^2Og`kf-KX?S;9d2F^Gc(44TX@8G*u@ zjJH^F6EpMtG?{KO6oGWCWGG?*Qefhjt$uD^iGFHDNl~J1VoFMWUa@{^N@;RpNoIat zd`e<*MpAxaQHp+YeqKpYeojtmQL%n}d}dx|NqoFsLFFwDo80`A(wtN~kX6MX(*+m+ DU3@Dh literal 0 HcmV?d00001 diff --git a/addons/education_dashboard/controllers/__pycache__/main.cpython-310.pyc b/addons/education_dashboard/controllers/__pycache__/main.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb07f5f2d0839bb4310eda107b871623088cb499 GIT binary patch literal 3336 zcmbtXTW=dh6yBMgwbzd0IEiylZ}dXlQo9!@6w;`uMLbY~P~Rd|tBq&tIQ4qn*>yv# zv4qsTB7u;2>O=CtL;e8&gVzynNJu~th;l8^a?W0CC(u@fwdV7jZ;#KMnf>O>+8Z9u zYH*ofe6n_MRMWmhWAI{NT!QA80l3CVLvzV8A=sx4>gvln(caX!&e^8MnM2mt9evs0 z#x<>!`Wk+tm55@iLC}Klv~X^<9Y3fdKX@5nT!Q8w2XM4y!nI}U=xdCV8sQY9f1q=d zr+9jk+#zxNjGBLgx2twg_nKF1e|5#P1;6Yy1K~9qj_B;GtUD_eCvmCp#HVtzRreKJ z^S!2GZQl_ks#vhmaul=M!Rk^q1!*ML0qCIp7Bv4J)ii0eSfH(uu2IvvY0R)|N+J%* z^cKZj16@LnH}q{-C20c3_Hb034}Fc0r@MSpdpKQO4SmxgukZ3T_i#+Cgua=OXS;l} zdpLs+&1hRprngBq6Bsgchjg>}y_MoQKD?=I!yLLpF_Z5xxtJO0F~c!a=rQ>q4RK69 z8parjt+CK5#MXFdjY@+TA&*QA#_NuOHPN%i!J6z@MX;uN)&y9Xe^#cs0SbY(t#>Ek zc}&U4E86vm?zEiV8sf$_5nsuvEh?wEF{AP684Y?f(3`zW@%h~Pd>|4 z561kVUHtyK#-Eefj|o34hX9Yr9N08&H`5C9N=j=4|qoI13WAD*U6^@_VBr-lCDyIUSwk5opuw7s~69XQ7S{3Z7%^kO1wKg1EpyS+t~seYAfPS6i(Va+NpG`y6kWm>j6A7F$RnnN5IKM zFp0e>fFszM26%eJ3_Mcawx1gE=re>|uLrAK*z3(FXL&3Msj`6=*bVE3IxW!JeH z^V$K(waM$vnq|9QyBTx_^bjkrQ|wEGJ1tuTE{L~N=(CZCJ5zlo(P9~e`ZbD375YN9 zsyGxLBu(3Olo6V0q_-EM`@0qwMSv$?sKZ(4)If>XGhHIdng$I{!MpS$dJA;9Lw2a5 zu-OiQo4{3ok!bm}BGo+)Q)L$#bvR-@x3zc`{6eYCYMsmfLuyI9$7U}=^@Ptm%1z)w zu1IT7a^eKgZ$lF&0iHSB#=nJI`j6p65r4R6Ct!Z>Bmp~P{fD2F7nP567uOYEjYQSL z>JeGoZ@v%jB);nH6kp6iRF$vdn@JURP9)sIpjbmG=VL|rkzQ05)EUJ6Tr8$YT0BEm}u=Mi28 zP`PBKxE;0Wm*|$ z$G2`%E6@TF$l)N&cxgrILd4h%Xc_Hj^ya1{x>hQ BS=#^r literal 0 HcmV?d00001 diff --git a/addons/education_dashboard/controllers/main.py b/addons/education_dashboard/controllers/main.py new file mode 100644 index 0000000..0337102 --- /dev/null +++ b/addons/education_dashboard/controllers/main.py @@ -0,0 +1,104 @@ +# pyrefly: ignore [missing-import] +from odoo import http +# pyrefly: ignore [missing-import] +from odoo.http import request + + +class EducationDashboardController(http.Controller): + + @http.route(['/web/education/dashboard/kpis'], type='json', auth="user") + def get_dashboard_kpis(self, campus_id=None, program_id=None, academic_year_id=None, semester_id=None, env=None, **kw): + env = env or request.env + # Base Student Domain + student_domain = [] + payment_domain = [('status', '=', 'posted')] + + # Apply Filters + if campus_id: + student_domain.append(('campus_id', '=', int(campus_id))) + payment_domain.append(('student_id.campus_id', '=', int(campus_id))) + if program_id: + student_domain.append(('program_id', '=', int(program_id))) + payment_domain.append(('student_id.program_id', '=', int(program_id))) + if academic_year_id: + student_domain.append(('academic_year_id', '=', int(academic_year_id))) + payment_domain.append(('student_id.academic_year_id', '=', int(academic_year_id))) + if semester_id: + student_domain.append(('semester_id', '=', int(semester_id))) + payment_domain.append(('student_id.semester_id', '=', int(semester_id))) + + # Fetch Student Records + students = env['education.student'].search(student_domain) + + # Executive KPIs + total_students = len(students) + active_students = len(students.filtered(lambda s: s.status == 'active')) + admissions = len(students.filtered(lambda s: s.status == 'admitted')) + graduates = len(students.filtered(lambda s: s.status == 'graduated')) + withdrawn = len(students.filtered(lambda s: s.status == 'withdrawn')) + + # Finance KPIs + total_fees = sum(students.mapped('total_fees')) + collected_fees = sum(students.mapped('total_paid')) + outstanding_fees = sum(students.mapped('outstanding_amount')) + + # Academic KPIs + programs_count = env['education.program'].search_count([]) + dept_count = env['education.department'].search_count([]) + campus_count = env['education.campus'].search_count([]) + years_count = env['education.academic.year'].search_count([]) + + # Charts: Program Distribution (using read_group for speed) + program_data = env['education.student'].read_group( + student_domain, ['program_id'], ['program_id'] + ) + program_chart = [] + for gd in program_data: + if gd.get('program_id'): + program_chart.append({ + 'name': gd['program_id'][1], + 'count': gd['program_id_count'], + }) + + # Charts: Campus Distribution + campus_data = env['education.student'].read_group( + student_domain, ['campus_id'], ['campus_id'] + ) + campus_chart = [] + for gd in campus_data: + if gd.get('campus_id'): + campus_chart.append({ + 'name': gd['campus_id'][1], + 'count': gd['campus_id_count'], + }) + + # Master Data lists for Filters + campuses = [{'id': c.id, 'name': c.name} for c in env['education.campus'].search([])] + programs = [{'id': p.id, 'name': p.name} for p in env['education.program'].search([])] + years = [{'id': y.id, 'name': y.name} for y in env['education.academic.year'].search([])] + semesters = [{'id': s.id, 'name': s.name} for s in env['education.semester'].search([])] + + return { + 'total_students': total_students, + 'active_students': active_students, + 'admissions': admissions, + 'graduates': graduates, + 'withdrawn': withdrawn, + + 'total_fees': total_fees, + 'collected_fees': collected_fees, + 'outstanding_fees': outstanding_fees, + + 'programs_count': programs_count, + 'dept_count': dept_count, + 'campus_count': campus_count, + 'years_count': years_count, + + 'program_chart': program_chart, + 'campus_chart': campus_chart, + + 'campuses': campuses, + 'programs': programs, + 'years': years, + 'semesters': semesters, + } diff --git a/addons/education_dashboard/models/__init__.py b/addons/education_dashboard/models/__init__.py new file mode 100644 index 0000000..3c786ed --- /dev/null +++ b/addons/education_dashboard/models/__init__.py @@ -0,0 +1 @@ +from . import dashboard diff --git a/addons/education_dashboard/models/__pycache__/__init__.cpython-310.pyc b/addons/education_dashboard/models/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..487052947f60b563f2f17b47489fefbd722e4f91 GIT binary patch literal 183 zcmd1j<>g`kf+W+)SrS0{F^Gc(44TX@8G*u@ zjJG&b5{omE@)L_v{4|+vF%*Hctz;--0a9S%mxX?AUWtBcMM+VjZemJGeqOPDYD#Hx vVo7FxUOZg0er|qBYEH3!e0*kJW=VX!UP0w84jZ6gX-=vg$f{zH!2%2baZ4)m literal 0 HcmV?d00001 diff --git a/addons/education_dashboard/models/__pycache__/dashboard.cpython-310.pyc b/addons/education_dashboard/models/__pycache__/dashboard.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d799b32218b26128794ce513f5d1fb3187c954e GIT binary patch literal 483 zcmYjNu};G<5Vd0`ib~4>6$4Y(AkoZ72z3C#LKm34Sf*!0sw56}Dgi5e1OLFs@B?OK zLi_;}=M(}b-LvoAv;BN#G#U_$&$s90E3WUM_#YgL8K&Dq00Oim6&19KL{u@sC^;n% z0ed3AG+XjVCSu?hB#S>#Pcr72Y6_oR7rF%JlgfZD-6Hh<2gPEB>Bb0}2ns|*Km~(H zaA0^J_gg2hTBx=#^|5j{S4LTQ*w(+?6nb*E7fyQRO|Juz+W zZH$+MbO}OAI8ABC&9lUF1IE}WJi`Kn>HEz~t7=#1x;+om>%@On*1KU4ribhA9EEYZ JaCS_0*f*Ckd)xp3 literal 0 HcmV?d00001 diff --git a/addons/education_dashboard/models/dashboard.py b/addons/education_dashboard/models/dashboard.py new file mode 100644 index 0000000..2d95509 --- /dev/null +++ b/addons/education_dashboard/models/dashboard.py @@ -0,0 +1,7 @@ +# pyrefly: ignore [missing-import] +from odoo import api, fields, models + + +class EducationDashboard(models.TransientModel): + _name = 'education.dashboard' + _description = 'Education Dashboard Helper' diff --git a/addons/education_dashboard/security/ir.model.access.csv b/addons/education_dashboard/security/ir.model.access.csv new file mode 100644 index 0000000..2002a75 --- /dev/null +++ b/addons/education_dashboard/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_education_dashboard,Education Dashboard,model_education_dashboard,base.group_user,1,1,1,1 diff --git a/addons/education_dashboard/static/src/js/education_dashboard.js b/addons/education_dashboard/static/src/js/education_dashboard.js new file mode 100644 index 0000000..d7e840a --- /dev/null +++ b/addons/education_dashboard/static/src/js/education_dashboard.js @@ -0,0 +1,137 @@ +/** @odoo-module **/ + +import { registry } from "@web/core/registry"; +import { Component, onWillStart, onMounted, useState, useRef } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; +import { loadJS } from "@web/core/assets"; + +class EducationDashboard extends Component { + setup() { + this.rpc = useService("rpc"); + this.actionService = useService("action"); + this.state = useState({ + kpis: {}, + filters: { + campus_id: "", + program_id: "", + academic_year_id: "", + semester_id: "" + } + }); + + this.programChartRef = useRef("programChart"); + this.campusChartRef = useRef("campusChart"); + + onWillStart(async () => { + await loadJS("/web/static/lib/Chart/Chart.js"); + await this.loadData(); + }); + + onMounted(() => { + this.renderCharts(); + }); + } + + async loadData() { + // Map empty string filters to null + const params = {}; + for (const [k, v] of Object.entries(this.state.filters)) { + params[k] = v ? parseInt(v) : null; + } + const data = await this.rpc("/web/education/dashboard/kpis", params); + this.state.kpis = data; + } + + async onFilterChange(filterKey, ev) { + this.state.filters[filterKey] = ev.target.value; + await this.loadData(); + this.renderCharts(); + } + + renderCharts() { + const pCtx = this.programChartRef.el; + if (pCtx) { + if (this.pChart) this.pChart.destroy(); + const labels = this.state.kpis.program_chart.map(x => x.name); + const data = this.state.kpis.program_chart.map(x => x.count); + this.pChart = new Chart(pCtx, { + type: 'bar', + data: { + labels: labels, + datasets: [{ + label: 'Students Count', + data: data, + backgroundColor: '#4F81BD' + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + scales: { + y: { + beginAtZero: true, + ticks: { stepSize: 1 } + } + } + } + }); + } + + const cCtx = this.campusChartRef.el; + if (cCtx) { + if (this.cChart) this.cChart.destroy(); + const labels = this.state.kpis.campus_chart.map(x => x.name); + const data = this.state.kpis.campus_chart.map(x => x.count); + this.cChart = new Chart(cCtx, { + type: 'doughnut', + data: { + labels: labels, + datasets: [{ + data: data, + backgroundColor: ['#4F81BD', '#C0504D', '#9BBB59', '#8064A2', '#4BACC6'] + }] + }, + options: { + responsive: true, + maintainAspectRatio: false + } + }); + } + } + + openStudents(status) { + const domain = []; + if (status) { + domain.push(['status', '=', status]); + } + if (this.state.filters.campus_id) { + domain.push(['campus_id', '=', parseInt(this.state.filters.campus_id)]); + } + if (this.state.filters.program_id) { + domain.push(['program_id', '=', parseInt(this.state.filters.program_id)]); + } + + this.actionService.doAction({ + name: "Students List", + type: "ir.actions.act_window", + res_model: "education.student", + views: [[false, "list"], [false, "form"]], + domain: domain, + target: "current" + }); + } + + openPrograms() { + this.actionService.doAction({ + name: "Active Programs", + type: "ir.actions.act_window", + res_model: "education.program", + views: [[false, "list"], [false, "form"]], + target: "current" + }); + } +} + +EducationDashboard.template = "education_dashboard.DashboardMain"; +registry.category("actions").add("education_dashboard_main_tag", EducationDashboard); +export { EducationDashboard }; diff --git a/addons/education_dashboard/static/src/xml/education_dashboard_templates.xml b/addons/education_dashboard/static/src/xml/education_dashboard_templates.xml new file mode 100644 index 0000000..ffc32d6 --- /dev/null +++ b/addons/education_dashboard/static/src/xml/education_dashboard_templates.xml @@ -0,0 +1,172 @@ + + + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+
Total Students
+

+
+ +
+
+
+
+
+
+
+
+
+
Active Students
+

+
+ +
+
+
+
+
+
+
+
+
+
Admissions
+

+
+ +
+
+
+
+
+
+
+
+
+
Graduates
+

+
+ +
+
+
+
+
+ + +
+
+
+
Total Fees Assigned
+

+
+
+
+
+
Collected Fees
+

+
+
+
+
+
Outstanding Fees
+

+
+
+
+ + +
+
+
+
Program-wise Distribution
+
+ +
+
+
+
+
+
Campus Distribution
+
+ +
+
+
+
+ + +
+
+
+
Programs
+

+
+
+
+
+
Departments
+

+
+
+
+
+
Campuses
+

+
+
+
+
+
Academic Years
+

+
+
+
+
+
+
diff --git a/addons/education_dashboard/tests/__init__.py b/addons/education_dashboard/tests/__init__.py new file mode 100644 index 0000000..b1b40f5 --- /dev/null +++ b/addons/education_dashboard/tests/__init__.py @@ -0,0 +1 @@ +from . import test_dashboard diff --git a/addons/education_dashboard/tests/__pycache__/__init__.cpython-310.pyc b/addons/education_dashboard/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eff2b80f3cab48874fbafc436970b7fc726bbe37 GIT binary patch literal 187 zcmd1j<>g`kf^O5vS+YR-F^Gc(44TX@8G*u@ zjJNnoQj1ICQxc0alJXOaQv5WTZZQ;r^sZzmVgXWM;+MI8ZeEFgYDGy=qHbbJN`79k yerif-a$-qlejY-zKFF+M{rLFIyv&mLc)fzkTO2mI`6;D2sdgaiib3WIFaQ96qb&0P literal 0 HcmV?d00001 diff --git a/addons/education_dashboard/tests/__pycache__/test_dashboard.cpython-310.pyc b/addons/education_dashboard/tests/__pycache__/test_dashboard.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3a7465d9f255ab060efc78ea533fec032795dca5 GIT binary patch literal 2602 zcmaJ@OLG%P5T4mxNvoA)$-*%vafkvWAcAEZ3X;mJ5-f@&MI0(J2Xxt5cE(tWc2}8M zxj{e~EY0V>*oKDd_zO6(+Ys*=G#su2AaKXxYQbHGT}<$`ssr)-}K zF03bR*PdkWI!~Fyx|lm>R`;Y%|D7e|3xScZ!-Mhl0Z1uJ`4M|;J-1#k&Qkj^J7T_b z#QHhGPqF8DVPU3Y>jhZ(1=8n8pBE?<`mS(~ZCWkTYLQkwQJQ&qq%V`cB9=fZ_FbSJ z_*Rb{@-2}~jck@hU7Qdr;;n40OlzyOc2cZ?R0huz;*@y%I7i+UvUrCq*2TNC%$O@D zPLuvU(U|G0q<^3EXT;f=zDD|Ur2jyCD9(!uq6s?KCD2LLrD^S;fjWRzh%OPW60Jdf z7sW@%RjO`TFgTw}yW^UijzB96qTxu7OE?RS9TJSy-f3FKiGxTQCrpGKugo2U90n?l zWSowR05?I`BItwW(^GYC%aVaJyJUI_DOZZR*DhqZOSO|7^>|Nfvd~IWE8~!qW z`|`$>?bnicI)&Z(B1oliWh{t$V}?|!X}NpALWaF*hHD#Fwwk$x(reSYe5?ySRY^+RkDhwWO;*GYE~-?%ddU%_JQO zI88&RNpN6Fc!4{a-fe%~`KGgXXWJ0>b^H5mQ$XF1x3*0lq2*9EtH#^!DV_Q>G{r$M zWubu-9*?LNh^VJEAder9qI*)A5~5Bfs3P_NaF7`5q_2%rLVd^rZ-b~?SG|{=THGHTpz*g|)=}&+xao1Y2PxCc?j<54G@E^aP zZSm*(*!M82H&a>sR=SGu6ojnQ4tTy98R1MG418`(ZMIOSqH`RVFvB$*sWnz3O?! zcAKthV%++DiLjK7h5ysArzcg^VW6Z+Z_*5_8$XYNfhkRNBq;nbp*Ao^@xeep@b`NI z37GdWYFvne5Eqy-aRa|h0Oa#HDTd;6-dpgSv5ZS zuG%$TxAT03kFWm6!2N&JX5P3yL5=Sl&-bH5j0O_;vhU-D5rll7?u7altE^- + + + + Management Dashboard + education_dashboard_main_tag + + + + + diff --git a/addons/education_exam/__init__.py b/addons/education_exam/__init__.py new file mode 100644 index 0000000..f7209b1 --- /dev/null +++ b/addons/education_exam/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import controllers diff --git a/addons/education_exam/__manifest__.py b/addons/education_exam/__manifest__.py new file mode 100644 index 0000000..23d5bfb --- /dev/null +++ b/addons/education_exam/__manifest__.py @@ -0,0 +1,33 @@ +{ + 'name': 'Education Examinations', + 'version': '1.0', + 'category': 'Education', + 'summary': 'Complete College Examination Module', + 'description': """ + Odoo Education Examination Module. + Features: + - Exam scheduling + - Grading systems + - Marks entry + - Portal integration for students to view results and schedule. + """, + 'author': 'Antigravity', + 'website': 'https://www.example.com', + 'depends': ['education_base', 'education_student', 'education_portal', 'portal'], + 'data': [ + 'security/security.xml', + 'security/ir.model.access.csv', + 'data/grading_data.xml', + 'views/education_grading_views.xml', + 'views/education_subject_views.xml', + 'views/education_exam_views.xml', + 'views/education_exam_result_views.xml', + 'views/education_student_inherit_views.xml', + 'views/portal_exam_templates.xml', + 'views/menu_views.xml', + ], + 'installable': True, + 'application': False, + 'auto_install': False, + 'license': 'LGPL-3', +} diff --git a/addons/education_exam/__pycache__/__init__.cpython-310.pyc b/addons/education_exam/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..210261873dcf024728c76284512952281ce60455 GIT binary patch literal 202 zcmd1j<>g`kg3qE$vh;!UV-N=!FabFZKwK;XBvKes7;_kM8KW2(L2M=2Cg)QZGh{rLFIyv&mLc)fzkTO2k(3rcfR P?LhVwvj7Pm1_4F@SQ9P= literal 0 HcmV?d00001 diff --git a/addons/education_exam/controllers/__init__.py b/addons/education_exam/controllers/__init__.py new file mode 100644 index 0000000..ce03e16 --- /dev/null +++ b/addons/education_exam/controllers/__init__.py @@ -0,0 +1 @@ +from . import portal_exam diff --git a/addons/education_exam/controllers/__pycache__/__init__.cpython-310.pyc b/addons/education_exam/controllers/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6b97d051e0511f64345d170d1f89fa7dda6a30fc GIT binary patch literal 185 zcmd1j<>g`kf?uLbvZR3YV-N=!FabFZKwK;UBvKes7;_kM8KW2(8B&;n88n$+G6ID) z8EL{oK3~{nUz*qD0-ql$89uV*S*V z(&WUF%=|pCKKXXpN&yL}LTW`NUshYs#Iad>-I;aU zM9!%ZJ>@^>AwgVn<%Yx`!FjHn`Y(jG;=S3e>qKcmU~8Y|>%Dz5`+IL*S}q3!e!tEA z()*)E$e-Am{#nqu3Xep=2%`z<(-!R;ErVj;NX)*~vUJ-_?7q`-`fkgkb z&nY_c0W>&iQAS$EBeF^vWyY4*GP%{WnaQk%hB^8D<7J# zu&SPS^?a>Er?h9->=prC?-8*`%ZI(xRdD@5+|ONY8&eLVe;zz0Jn~l%8F@%{=$P)( zO(QdUCZqFY$J!;}rNK-b={Y@j)r2>~nQa@J=GYXsnKP#Mh`6FP++1VFV;+Oqo{#I9 zuiJrc!|s^g6WB4~P1e{N8_ZcItfKAQ)^@5BJ2kC~R%SW|thdKDtgkGSu>rfAy|F*T z*{B_7-E^=dvk~KiOjU;Z!$c#=Mbu^c^zsu5``e?+PP+YWrUDK}61CIOAlt9J)W=a3 z3`N=zFx_R!&A8AFTV`>_mC+q2U$)mkCE+lg4mxRf&{3h_a+G9+#p4GpF6EOdDBJ_X zd#3V-qdQ4gf+>M$-)fYUEk{Ex)TwAF_%IecS_8=> zjF-6^0z+Qlt~7Ug!y<(4z6s~1c@Z->JB#8NiaLtpD5@wv1n~r!-LsTA5|ID06w{!E zXAE3re5jmsbyf0gqY7~&vWP+OEHEzH4O5iS#sv_{P6Y#KZT;CAw)h+eeY6q{Vo0xe z?5f20YK*Z3ZxZjq$i~@&v@I2c_186gvJvVa5ochnIE&&<6bmTc0--#?Sy%9OmT&ev zP9TjcEG(f1LOFsDz`ceq-o_Q5;R@r&``QQPu(Tg{2YHa7pW1n`h^_Z0;seyF9`f%L z3`#u1MZ*HpLlfDzp4A-#sL%h-(*?&cC%wNycjM$#92_#ZUqayXBqKLYO?3|Q|7x&* z27mqs*c$7OQ2^{00J{ai9t$+U0Qv;rVvPp41h@k9tg>xtd=cmX@U0d2`fR4aclOoz zijQFn;tdq@DCSUniQ*F!pQ1qO8pafXaH(V7mH^*1TyPPE{+ySvrEyz$C2lX2gVzHm zmeCdxCN85`0WnRLxPqhjE^!qF=G@!~?bA_A&AddQZ~|jTB6&Gg0nlSTuiGyef_(b) z>|v%(AIa2_An6>M&%pJyasVVeFB=}6A%B9#e*sDBVD*q^RjeN9q;f#Kf(}$ly6S+6 zL0-_gwS>79GGGX>lpmXrE6I+F*;41a>OaAv5y6x&g$)v;%Ee9@&*E+ZR|Vt(7KADnr9_)22`qn^EZ@T=m@$P50)diPym%xu>h+M;&xCaL^eol0H^3Py zWoeq<=vcujANxyKu;)e6JG!42D8(JIG*y@sLjqrvN1F~#eD{=7+C81dz2$tvD_BWN NyhLkIztm{m_#5C39n}B; literal 0 HcmV?d00001 diff --git a/addons/education_exam/controllers/portal_exam.py b/addons/education_exam/controllers/portal_exam.py new file mode 100644 index 0000000..3576b90 --- /dev/null +++ b/addons/education_exam/controllers/portal_exam.py @@ -0,0 +1,107 @@ +from odoo import http, _ +from odoo.http import request +from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager + +class ExamPortal(CustomerPortal): + + def _prepare_home_portal_values(self, counters): + values = super()._prepare_home_portal_values(counters) + student = request.env['education.student'].sudo().search([('portal_user_id', '=', request.env.user.id)], limit=1) + if student: + if 'exam_count' in counters: + exam_count = request.env['education.exam'].sudo().search_count([ + ('program_id', '=', student.program_id.id), + ('term_id', '=', student.academic_term_id.id), + ('state', 'in', ['scheduled', 'ongoing']) + ]) + values['exam_count'] = exam_count + + if 'result_count' in counters: + result_count = request.env['education.exam.result'].sudo().search_count([ + ('student_id', '=', student.id), + ('published', '=', True) + ]) + values['result_count'] = result_count + return values + + @http.route(['/my/exams', '/my/exams/page/'], type='http', auth="user", website=True) + def portal_my_exams(self, page=1, **kw): + student = request.env['education.student'].sudo().search([('portal_user_id', '=', request.env.user.id)], limit=1) + if not student: + return request.redirect('/my') + + domain = [ + ('program_id', '=', student.program_id.id), + ('term_id', '=', student.academic_term_id.id), + ('state', 'in', ['scheduled', 'ongoing']) + ] + + exam_count = request.env['education.exam'].sudo().search_count(domain) + pager = portal_pager( + url="/my/exams", + total=exam_count, + page=page, + step=10 + ) + exams = request.env['education.exam'].sudo().search(domain, limit=10, offset=pager['offset'], order='start_date desc') + + values = { + 'exams': exams, + 'page_name': 'exams', + 'pager': pager, + 'default_url': '/my/exams', + 'student': student, + } + return request.render("education_exam.portal_my_exams", values) + + @http.route(['/my/results', '/my/results/page/'], type='http', auth="user", website=True) + def portal_my_results(self, page=1, **kw): + student = request.env['education.student'].sudo().search([('portal_user_id', '=', request.env.user.id)], limit=1) + if not student: + return request.redirect('/my') + + domain = [ + ('student_id', '=', student.id), + ('published', '=', True) + ] + + result_count = request.env['education.exam.result'].sudo().search_count(domain) + pager = portal_pager( + url="/my/results", + total=result_count, + page=page, + step=10 + ) + results = request.env['education.exam.result'].sudo().search(domain, limit=10, offset=pager['offset']) + + values = { + 'results': results, + 'page_name': 'results', + 'pager': pager, + 'default_url': '/my/results', + 'student': student, + } + return request.render("education_exam.portal_my_results", values) + + @http.route(['/my/results/'], type='http', auth="user", website=True) + def portal_my_result_detail(self, result_id, **kw): + student = request.env['education.student'].sudo().search([('portal_user_id', '=', request.env.user.id)], limit=1) + if not student: + return request.redirect('/my') + + # Ensure the result belongs to the logged in student and is published + result = request.env['education.exam.result'].sudo().search([ + ('id', '=', result_id), + ('student_id', '=', student.id), + ('published', '=', True) + ], limit=1) + + if not result: + return request.redirect('/my/results') + + values = { + 'result': result, + 'page_name': 'result_detail', + 'student': student, + } + return request.render("education_exam.portal_result_detail", values) diff --git a/addons/education_exam/data/grading_data.xml b/addons/education_exam/data/grading_data.xml new file mode 100644 index 0000000..570387a --- /dev/null +++ b/addons/education_exam/data/grading_data.xml @@ -0,0 +1,89 @@ + + + + + + Standard Percentage + STD_PCT + percentage + + + + + + O + O + 90 + 100 + 10 + pass + 10 + + + + + A+ + A_PLUS + 80 + 89.99 + 9 + pass + 20 + + + + + A + A + 70 + 79.99 + 8 + pass + 30 + + + + + B+ + B_PLUS + 60 + 69.99 + 7 + pass + 40 + + + + + B + B + 50 + 59.99 + 6 + pass + 50 + + + + + C + C + 40 + 49.99 + 5 + pass + 60 + + + + + F + F + 0 + 39.99 + 0 + fail + 70 + + + diff --git a/addons/education_exam/models/__init__.py b/addons/education_exam/models/__init__.py new file mode 100644 index 0000000..dc903b3 --- /dev/null +++ b/addons/education_exam/models/__init__.py @@ -0,0 +1,6 @@ +from . import education_subject +from . import education_exam_type +from . import education_grading +from . import education_exam +from . import education_exam_result +from . import education_student_inherit diff --git a/addons/education_exam/models/__pycache__/__init__.cpython-310.pyc b/addons/education_exam/models/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..36f2fd2f946b5e576a4a8153e853e95ac94aceb7 GIT binary patch literal 397 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 n-lKm3*|Fit&e|PPz1n-cP5ymM|9&hR@Q`pHi literal 0 HcmV?d00001 diff --git a/addons/education_exam/models/__pycache__/education_exam_result.cpython-310.pyc b/addons/education_exam/models/__pycache__/education_exam_result.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1b870713d6a49ba880861196e151a262098d8355 GIT binary patch literal 4164 zcma)9S#KQ25uSTz?LfD;*3kBCUAv(%lUYwRW^sMn zd2IS6NNnan;uI2>m2PPr_n%lz>zL9R#*7aV>D=%00ZXJAj`|&4Ht&RkJ`2-+9IXiv ziyjR>{wsl5MN9q-!ZlxKnr|@OH<{sE%=B&UY?qito3qkW({~l`vNHGzt3I{-G9(pN zV|7Rx7*SPxbHdjY-+Xp+Wr*e(`tE|Z6GaLS(bWX(hC_sFhv!<5H%3##u41gQ=ZM- zj?-}9-3)j8!|@Pbv6IMV(2Iwoams^~v`H}+Yvp2TCuuBr*3Kt?elHFC5og8h$|e^* zTq4}!FPlea+7dX0v~SSoWmSE=&A1<>2WH2Rr4f%9?vu@L<{fXv+lW(d?dxzb4pYuB z#u|l5BCSnO;_iq2fwa~^vwChn?^YV7<3udsjt5qL-x<1en=V~q{C)^e$nxg+-k_g6 z;B389SjD3OZYsjw4jrN~8R1>W<_Ef3>*dnXZH~FHJHX{-Irk@du6}ELZ=3hh+#YyE zGvlcLaLj{4@6zOKITv0!6!zKs9x1RwC;QRQy?i|{90(q=M^N>XH1Y1o!b>0Y6R((d zB%rxxvS~UDmPoq8a5or|JG1$TEG|4%D4G*Rq70WU76<5(vtWoId6cA_j?~j(p~Ihz z4z+7&$-hAMwCmd4(Y~H)d-}Hi3@`n;{#f5PCcL3|bI;h*19Q&=we}2V?pWdiIAa+p zsg1dT9huCgFV=Cyr$o0)hu>!|c>B4&Z|zy)+Mcyt#>fi3ReY-vG6nH!el>EAb95?k zH?6S}bDyX)UdMZVtmBrnM*X1yUFDQ6X0$GJtX6zAyzcsjEKf`;8z(I)$V&e}!|$ur zqN8c+C#+{cX`&@)|DjO*E$nyK-PdtSr0X5yj;3qaRmX0Iisg+1o!&deHeSBb{hqYQ za|fFEKBm81T^UB{3g1Qeeh@MiN6E@0Py_PlN}k0}NrODXx}!(3d}WJAe0L+} z$z|&O0OTX;{E!F@7Rw-=hBTA$P`Zb)iy9S&K%BJO277o2pFtc)h}HLVu%t-FFaCneqkxcy`C=AC&P_5}{<0Ydo*5)! znnuhKnI{7Ht%*et=^Vou;$4!IzTm3Xs+X?b{442iQZQFBK^4s9JmphyLLqFYNcrfS z+t%-a+5ccZ)_<3m1ak-Z4_b>PC=67F?2T+?azVAV*9)Tvh>7p^IItMfph9%Hj$nh- zyW%VCoxS(^iTAh{@h!mCuX(p)AKgUI$pKAs=THW;C}+Tq*GS{gFOhpAw^FOc(=@usVH1heT5_Ja$*E4(JqC%-3CzDKGLh@2-vxmsKyqKfKO>P?a~U@+N7Tqm+dCA)^Hos${OwM$)XI z5;;c2oIb2jiqESQ>X&Z)J?p;Ny~Y0*Cm!5JXGyHn<~NAYKeXbPL_Q@#eiWY(QP=r3 zda|a7=J>k=P&^*+g2`9)v=cMDc7XLS$u9TM`k{$S;Y|)sLCu zsI1(9LPeFbi7G48C@78MPZgM=u+*H2QPCSNiVDrz=#-@uvp5z8Ev~MnPS7MF0=vgm n{Y~0ZeT`&ELVi3`%!YWlP>_wj!Lh*n4( literal 0 HcmV?d00001 diff --git a/addons/education_exam/models/__pycache__/education_exam_type.cpython-310.pyc b/addons/education_exam/models/__pycache__/education_exam_type.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a07df25945ef786a77cdaa76b8dfcdbda1debeb2 GIT binary patch literal 707 zcmZ8f&2H2%5VjpBoBc_;6;dTQ^#URXc3%Kh6;QaqURZ(f#j-N)HmfAwItjGB(rX`q z8y8;5S5CYFC&tXtexsY* z78_qYqc*}Zu9DBXSe>yT>NsooFn|Gr13qbb_0jmT{Q0>xspGoqH- zi#)S*WI}nV4YV)*4|dT$_`N@&zhYkd^*meO=ue2gT|X_eBL~;`u&?&jBrzv@AY+~uql0Iv68ZTx zG1m`9^}s;YBf-ottSz;>ALrun8(Vv-l2}0dQaqWYQgH2lig5IuzO7p)LMEc9;*rp; zM;AOs8l#gqx-)G)c=XHg&R_=`k2JS-raq}|?F@FNt%D@aMELj_RtXTHH;!!MM?YcsjwDa_yJk$Pe7Uy5``0fUskD_wJn%@5Y3K@(<713NV*v(4hN8JTe zPuFcT5S^51Q9Z^Hn^HU#QVB@`kXJ$a4c!ujnn)3qh0rVYgcNtiKTNYqQ2BVOlofs* zQ9Q-va+SyqjiV?U+E~6zt5F!`dmvb3exx;sijcr(8_Z>Ec8g8J|L4bke^vWYRG^%r zNVlR0p_*jGccSRYB+h0d4Pyc&DkzcZ30-SRSw+1c#ZtRur`-g#uU8I4CK9sHEk#8f zRVZTJ{G=>15f|EvG5Azy|2KuWoyWy9`2iM^eImxY1tAwD0R>^0+63_}7tRHCz*@|6 zy?=KatE^)in*_s`o(qKe2h`t2814)k!W%aURk+8S+y{#I(5wJaJYb3qOd+YwJHV7S zctVtVq-&&LCIv?7&7>ZrRYIJR&DWq?=Nnl03bbqdD&OSqVC8j48yLHWm9L7;SMTsG ze*Lx0f5dORu%5HwHKTj?HRJE`_l;(YhqxU#b#N)8!qnBYccBa)P?1bK3UDNfM0^yc zTsGmp^g$l$W)dSM1W-$8;+F*wT%Z6~$dYPEc8OdAnXWxdi!`6);R3>-i%-9;Yn!dZ zM^yi~!|q9}ly)D1l5`lSnRf4kPP<u`!z6v84iVfUjd$u2nX7p0huiG15YJ z13H&T%?cN$E+EloFW8%REktC2%m%w-zNwA~{}a4C*6HQz3|NZ!n*A=yh8Fj7{@# z&ja6Q_BZaorZ+FHjB#%8-L)jGbE(p(IZ(BkN~2LzY3)U&$!>)@Tgs|aXLb7iEP`}5 z=21gR3o*jqkf&mRTBr3)RKSz>@;;HvP^jlFQ)ETmYVN_ThJ66OaqgQRa&BqXe*e1s z32pFGA`~|HF_8g?_98@G$Qs}Mhd}7t=vQ^Nw#jOT$SpE$+|SCmTFPrkL$_(DE6p{J zl)2`*`FT-^k&xP-W0Sl?wm&06@UB}4S~r?_S|~|>RpsYI77;L<=?Fi^0O5eicij=F z;RQ%|U_LIh>GNOD@%|II9oC}QtOrce%>Dj~c1vECk`ijXtPjv5Hn?iFlPGqoe2LT>c)AOn!O^d-LN`1(&py>0KEeEkN^Mx literal 0 HcmV?d00001 diff --git a/addons/education_exam/models/__pycache__/education_student_inherit.cpython-310.pyc b/addons/education_exam/models/__pycache__/education_student_inherit.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ef8856b486fd2a39d63e5fcb6da74e5a808bca4e GIT binary patch literal 1194 zcmZ`(&2H2%5VoCclHKn1Pqadnkhq4QU5PWGO0-lQSR{Ijgsf=CTe3==t)0+zwVc{( z-+)6aap5I+5MMd*3VVTZvMm8rN1oU-K9PDBdYIPo+Y zSzB|oaI=e3zeXWQM1e#MXygcY(z5Zvh1R7LdARq%1OJjnfoNZKAh@Q`hRy{!r_lQO5~B*;iEj8mWANFzIaGPd#D0DG22R1KOWfvYy0LWiG=(_ z+4N@ZLJ9F?oU2UPYZml{*#t3;vAFye8-$*5Wt<7)7H3mo+E_zgQDNGj5^++kjR$!a zC(>3_7D!^qOh-svD#3Fp3`-!SY7H-`$gagEZq5BVIbMZoR7Jlp3XC`HV%6uxBA|0RH9qQr3C4){=1=4kUzb*z15XNRoyR zBNtH*`mhNDjP((1P6%v$rLc{=cVPF*2nOigLUikj2HUVVp}`L9Rtc>$) zRGm$ER4%vp%5zYxT-PM8Wl7_drF`sb(ke<3XA|iNKCfcIK^n)X%=$4ikxv9mrTwlk zmisp;Sj2#*adfcYOe}jZTIjt;=u=^%ZvUm1T%^n1h^M?jb2vsQ?PE+Im&F7(;rR93 z*XjeL4g8!{N$KnAfy)b?i-okt^@^U(Ot7eoBbLa{yOoK4<18;PmfH=_d+c4umToJ9 zH(t!R=wUKnuTu#JZQ&{hZ5CZ_M5sqtW%0b?OSf4cDxbf+vpPpXFRhBwJL6tMxp(UC zN+%366gmuLHw>}kszljt7|tt}v=OtWpc8lziFl@pDX`}WR^oyQX(*mFR0`>wux#-( z&$#TiRS9E|gH;tmRR!g-mA=X_Une;$r5!cW%Fc)<*d>@kx@~)eC5j;JmwBFWmPt!# z`GLzpSj>|!$}?;b7H4JgorwEN9X0(v9Hv=0H& zAxu0%yw$_phmGjZD564b*(H10KOM$K&j0`b literal 0 HcmV?d00001 diff --git a/addons/education_exam/models/education_exam.py b/addons/education_exam/models/education_exam.py new file mode 100644 index 0000000..5b9d157 --- /dev/null +++ b/addons/education_exam/models/education_exam.py @@ -0,0 +1,193 @@ +from odoo import models, fields, api +from odoo.exceptions import ValidationError +from datetime import datetime, timedelta +import pytz + +class EducationExam(models.Model): + _name = 'education.exam' + _description = 'Examination' + + name = fields.Char(string='Name', required=True) + code = fields.Char(string='Code', required=True) + academic_year_id = fields.Many2one('education.academic.year', string='Academic Year', required=True) + program_id = fields.Many2one('education.program', string='Program', required=True) + term_id = fields.Many2one('education.academic.term', string='Semester/Term', required=True) + exam_type_id = fields.Many2one('education.exam.type', string='Exam Type', required=True) + start_date = fields.Date(string='Start Date', required=True) + end_date = fields.Date(string='End Date', required=True) + grading_system_id = fields.Many2one('education.grading.system', string='Grading System', required=True) + state = fields.Selection([ + ('draft', 'Draft'), + ('scheduled', 'Scheduled'), + ('ongoing', 'Ongoing'), + ('completed', 'Completed'), + ('results_published', 'Results Published') + ], string='Status', required=True, default='draft', tracking=True) + result_published_date = fields.Date(string='Result Published Date') + active = fields.Boolean(default=True) + + line_ids = fields.One2many('education.exam.line', 'exam_id', string='Schedule') + result_ids = fields.One2many('education.exam.result', 'exam_id', string='Results') + + def action_schedule(self): + for rec in self: + if not rec.line_ids: + raise ValidationError("Please add at least one subject to the schedule.") + rec.state = 'scheduled' + + def action_start(self): + for rec in self: + if rec.state != 'scheduled': + raise ValidationError("Only scheduled exams can be started.") + + earliest_line = min(rec.line_ids, key=lambda l: (l.exam_date, l.start_time), default=None) + if earliest_line: + # Get the user's timezone, fallback to UTC + user_tz = pytz.timezone(self.env.user.tz or 'UTC') + + # Create naive datetime from the earliest exam_date and start_time (which is in hours) + earliest_dt_naive = datetime.combine(earliest_line.exam_date, datetime.min.time()) + timedelta(hours=earliest_line.start_time) + + # Localize this naive datetime to the user's timezone + earliest_dt_user = user_tz.localize(earliest_dt_naive) + + # Get current time in UTC, and convert to user's timezone for comparison + now_utc = fields.Datetime.now() + now_user = pytz.utc.localize(now_utc).astimezone(user_tz) + + if now_user < earliest_dt_user: + raise ValidationError(f"This exam cannot be started yet. It is scheduled to start on {earliest_dt_user.strftime('%d/%m/%Y at %I:%M %p')}.") + + rec.state = 'ongoing' + + def action_complete(self): + for rec in self: + if rec.state != 'ongoing': + raise ValidationError("Only ongoing exams can be completed.") + rec.state = 'completed' + + def action_generate_results(self): + """Generates empty result records for all students enrolled in the program and term.""" + for rec in self: + if rec.state != 'completed': + raise ValidationError("Results can only be generated for completed exams.") + students = self.env['education.student'].search([ + ('program_id', '=', rec.program_id.id), + ('academic_year_id', '=', rec.academic_year_id.id), + ('academic_term_id', '=', rec.term_id.id), + ('status', 'in', ['active', 'enrolled', 'admitted']) + ]) + + if not students: + raise ValidationError("No active/enrolled students found for this Program, Academic Year, and Semester.") + + for student in students: + # Check if result already exists + existing = self.env['education.exam.result'].search([ + ('student_id', '=', student.id), + ('exam_id', '=', rec.id) + ]) + if not existing: + result = self.env['education.exam.result'].create({ + 'student_id': student.id, + 'exam_id': rec.id, + }) + # Create result lines based on schedule + for line in rec.line_ids: + self.env['education.exam.result.line'].create({ + 'result_id': result.id, + 'subject_id': line.subject_id.id, + 'exam_type_id': rec.exam_type_id.id, + 'max_marks': line.max_marks, + 'pass_marks': line.pass_marks, + }) + + def action_publish_results(self): + for rec in self: + # Validations before publishing + if not rec.result_ids: + raise ValidationError("No results generated yet.") + for result in rec.result_ids: + if not result.line_ids: + raise ValidationError(f"Result for student {result.student_id.name} has no subjects.") + if result.result_status == 'pending': + raise ValidationError(f"Cannot publish results: Student {result.student_id.name} has pending evaluations.") + for line in result.line_ids: + if line.obtained_marks < 0: + raise ValidationError(f"Marks not entered for {line.subject_id.name} for student {result.student_id.name}.") + if line.obtained_marks > line.max_marks: + raise ValidationError(f"Obtained marks cannot exceed maximum marks for {line.subject_id.name}.") + + rec.write({ + 'state': 'results_published', + 'result_published_date': fields.Date.today() + }) + # Update publication status on results + rec.result_ids.write({ + 'published': True, + 'published_date': fields.Date.today() + }) + + def action_reset_draft(self): + self.write({'state': 'draft'}) + + @api.constrains('start_date', 'end_date') + def _check_exam_dates(self): + for rec in self: + if rec.start_date and rec.end_date and rec.end_date < rec.start_date: + raise ValidationError("End date cannot be before the start date.") + + +class EducationExamLine(models.Model): + _name = 'education.exam.line' + _description = 'Exam Schedule Line' + + exam_id = fields.Many2one('education.exam', string='Exam', required=True, ondelete='cascade') + subject_id = fields.Many2one('education.subject', string='Subject', required=True) + exam_date = fields.Date(string='Exam Date', required=True) + start_time = fields.Float(string='Start Time', required=True) + end_time = fields.Float(string='End Time', required=True) + max_marks = fields.Float(string='Maximum Marks', required=True) + pass_marks = fields.Float(string='Pass Marks', required=True) + room = fields.Char(string='Room') + venue = fields.Char(string='Venue') + faculty_id = fields.Many2one('res.users', string='Faculty/Invigilator') + + @api.constrains('start_time', 'end_time') + def _check_times(self): + for record in self: + if record.end_time <= record.start_time: + raise ValidationError("End time must be greater than start time.") + + @api.constrains('max_marks', 'pass_marks') + def _check_marks(self): + for record in self: + if record.max_marks <= 0: + raise ValidationError("Maximum marks must be greater than 0.") + if record.pass_marks < 0: + raise ValidationError("Pass marks cannot be negative.") + if record.pass_marks > record.max_marks: + raise ValidationError("Pass marks cannot be greater than maximum marks.") + + @api.constrains('exam_date') + def _check_date(self): + for record in self: + if record.exam_id and (record.exam_date < record.exam_id.start_date or record.exam_date > record.exam_id.end_date): + raise ValidationError("Exam date must fall within the exam start and end dates.") + + @api.constrains('exam_date', 'start_time', 'end_time', 'exam_id') + def _check_schedule_conflict(self): + for rec in self: + domain = [ + ('id', '!=', rec.id), + ('exam_date', '=', rec.exam_date), + ('start_time', '<', rec.end_time), + ('end_time', '>', rec.start_time), + ('exam_id.program_id', '=', rec.exam_id.program_id.id), + ('exam_id.academic_year_id', '=', rec.exam_id.academic_year_id.id), + ('exam_id.term_id', '=', rec.exam_id.term_id.id), + ] + conflicts = self.search(domain) + if conflicts: + conflict = conflicts[0] + raise ValidationError(f"Exam schedule conflict: '{rec.subject_id.name}' overlaps with '{conflict.subject_id.name}' for the same student group on {rec.exam_date}.") diff --git a/addons/education_exam/models/education_exam_result.py b/addons/education_exam/models/education_exam_result.py new file mode 100644 index 0000000..81e6f60 --- /dev/null +++ b/addons/education_exam/models/education_exam_result.py @@ -0,0 +1,127 @@ +from odoo import models, fields, api +from odoo.exceptions import ValidationError + +class EducationExamResult(models.Model): + _name = 'education.exam.result' + _description = 'Exam Result' + _rec_name = 'student_id' + + student_id = fields.Many2one('education.student', string='Student', required=True) + exam_id = fields.Many2one('education.exam', string='Exam', required=True, ondelete='cascade') + + total_max_marks = fields.Float(string='Total Maximum Marks', compute='_compute_totals', store=True) + total_obtained_marks = fields.Float(string='Total Obtained Marks', compute='_compute_totals', store=True) + percentage = fields.Float(string='Percentage', compute='_compute_totals', store=True) + grade_id = fields.Many2one('education.grade', string='Grade', compute='_compute_totals', store=True) + grade_point = fields.Float(string='Grade Point', compute='_compute_totals', store=True) + result_status = fields.Selection([ + ('pending', 'Pending / Not Evaluated'), + ('pass', 'Pass'), + ('fail', 'Fail') + ], string='Result Status', compute='_compute_totals', store=True, default='pending') + + published = fields.Boolean(string='Published', default=False, tracking=True) + published_date = fields.Date(string='Published Date') + + line_ids = fields.One2many('education.exam.result.line', 'result_id', string='Subject Marks') + + _sql_constraints = [ + ('unique_student_exam', 'unique(student_id, exam_id)', 'A result already exists for this student for this exam!') + ] + + @api.depends('line_ids.max_marks', 'line_ids.obtained_marks', 'line_ids.status', 'exam_id.grading_system_id') + def _compute_totals(self): + for rec in self: + max_marks = sum(rec.line_ids.mapped('max_marks')) + obtained_marks = sum(rec.line_ids.mapped('obtained_marks')) + rec.total_max_marks = max_marks + rec.total_obtained_marks = obtained_marks + + if max_marks > 0: + rec.percentage = (obtained_marks / max_marks) * 100.0 + else: + rec.percentage = 0.0 + + # Determine overall grade + if rec.exam_id and rec.exam_id.grading_system_id: + grade = rec.exam_id.grading_system_id.get_grade(rec.percentage) + if grade: + rec.grade_id = grade.id + rec.grade_point = grade.grade_point + else: + rec.grade_id = False + rec.grade_point = 0.0 + else: + rec.grade_id = False + rec.grade_point = 0.0 + + # Overall status is pending if any subject is pending, Fail if any subject is failed, otherwise Pass + if not rec.line_ids or any(line.status == 'pending' for line in rec.line_ids): + rec.result_status = 'pending' + elif any(line.status == 'fail' for line in rec.line_ids): + rec.result_status = 'fail' + else: + rec.result_status = 'pass' + + +class EducationExamResultLine(models.Model): + _name = 'education.exam.result.line' + _description = 'Exam Result Line' + + result_id = fields.Many2one('education.exam.result', string='Result', required=True, ondelete='cascade') + subject_id = fields.Many2one('education.subject', string='Subject', required=True) + exam_type_id = fields.Many2one('education.exam.type', string='Exam Type') + max_marks = fields.Float(string='Maximum Marks', required=True) + pass_marks = fields.Float(string='Pass Marks', required=True) + obtained_marks = fields.Float(string='Obtained Marks', default=-1.0) # -1 indicates not entered + + percentage = fields.Float(string='Percentage', compute='_compute_grade_details', store=True) + grade_id = fields.Many2one('education.grade', string='Grade', compute='_compute_grade_details', store=True) + grade_point = fields.Float(string='Grade Point', compute='_compute_grade_details', store=True) + status = fields.Selection([ + ('pending', 'Pending / Not Evaluated'), + ('pass', 'Pass'), + ('fail', 'Fail') + ], string='Status', compute='_compute_grade_details', store=True, default='pending') + + remarks = fields.Char(string='Remarks') + + @api.constrains('obtained_marks', 'max_marks') + def _check_marks(self): + for record in self: + if record.obtained_marks > record.max_marks: + raise ValidationError(f"Obtained marks cannot exceed maximum marks for {record.subject_id.name}.") + # Using -1 to indicate not entered yet. So < -1 is invalid. + if record.obtained_marks < -1.0: + raise ValidationError("Obtained marks cannot be negative.") + + @api.depends('obtained_marks', 'max_marks', 'pass_marks', 'result_id.exam_id.grading_system_id') + def _compute_grade_details(self): + for rec in self: + if rec.obtained_marks >= 0 and rec.max_marks > 0: + rec.percentage = (rec.obtained_marks / rec.max_marks) * 100.0 + + # Check pass/fail based on marks first + if rec.obtained_marks >= rec.pass_marks: + rec.status = 'pass' + else: + rec.status = 'fail' + + # Apply grading system + if rec.result_id.exam_id.grading_system_id: + grade = rec.result_id.exam_id.grading_system_id.get_grade(rec.percentage) + if grade: + rec.grade_id = grade.id + rec.grade_point = grade.grade_point + # Could override status based on grade if required, but usually marks determine subject pass/fail + else: + rec.grade_id = False + rec.grade_point = 0.0 + else: + rec.grade_id = False + rec.grade_point = 0.0 + else: + rec.percentage = 0.0 + rec.grade_id = False + rec.grade_point = 0.0 + rec.status = 'pending' # default to pending until marks are entered diff --git a/addons/education_exam/models/education_exam_type.py b/addons/education_exam/models/education_exam_type.py new file mode 100644 index 0000000..c2f736c --- /dev/null +++ b/addons/education_exam/models/education_exam_type.py @@ -0,0 +1,11 @@ +from odoo import models, fields + +class EducationExamType(models.Model): + _name = 'education.exam.type' + _description = 'Examination Type' + + name = fields.Char(string='Name', required=True) + code = fields.Char(string='Code', required=True) + description = fields.Text(string='Description') + weightage = fields.Float(string='Weightage (%)', default=100.0) + active = fields.Boolean(default=True) diff --git a/addons/education_exam/models/education_grading.py b/addons/education_exam/models/education_grading.py new file mode 100644 index 0000000..54fcc30 --- /dev/null +++ b/addons/education_exam/models/education_grading.py @@ -0,0 +1,68 @@ +from odoo import models, fields, api +from odoo.exceptions import ValidationError + +class EducationGradingSystem(models.Model): + _name = 'education.grading.system' + _description = 'Grading System' + + name = fields.Char(string='Name', required=True) + code = fields.Char(string='Code', required=True) + scale_type = fields.Selection([ + ('percentage', 'Percentage'), + ('gpa_4', 'GPA 4.0'), + ('gpa_10', 'GPA 10.0') + ], string='Scale Type', required=True, default='percentage') + active = fields.Boolean(default=True) + grade_ids = fields.One2many('education.grade', 'grading_system_id', string='Grades') + + def get_grade(self, percentage): + self.ensure_one() + grade = self.grade_ids.filtered(lambda g: g.min_percentage <= percentage <= g.max_percentage) + if grade: + # If multiple match due to floating point edge cases, take the highest grade point + return grade.sorted('grade_point', reverse=True)[0] + return self.env['education.grade'] + +class EducationGrade(models.Model): + _name = 'education.grade' + _description = 'Education Grade' + _order = 'sequence, id' + + grading_system_id = fields.Many2one('education.grading.system', string='Grading System', required=True, ondelete='cascade') + name = fields.Char(string='Name', required=True) + code = fields.Char(string='Code') + min_percentage = fields.Float(string='Minimum Percentage', required=True) + max_percentage = fields.Float(string='Maximum Percentage', required=True) + grade_point = fields.Float(string='Grade Point', required=True) + result_status = fields.Selection([ + ('pass', 'Pass'), + ('fail', 'Fail') + ], string='Result Status', required=True, default='pass') + sequence = fields.Integer(string='Sequence', default=10) + active = fields.Boolean(default=True) + + @api.constrains('min_percentage', 'max_percentage') + def _check_percentages(self): + for record in self: + if record.min_percentage < 0 or record.max_percentage > 100: + raise ValidationError("Percentages must be between 0 and 100.") + if record.min_percentage > record.max_percentage: + raise ValidationError("Minimum percentage cannot be greater than maximum percentage.") + + # Check overlaps + domain = [ + ('grading_system_id', '=', record.grading_system_id.id), + ('id', '!=', record.id), + '|', '|', + '&', ('min_percentage', '<=', record.min_percentage), ('max_percentage', '>=', record.min_percentage), + '&', ('min_percentage', '<=', record.max_percentage), ('max_percentage', '>=', record.max_percentage), + '&', ('min_percentage', '>=', record.min_percentage), ('max_percentage', '<=', record.max_percentage), + ] + if self.search_count(domain) > 0: + raise ValidationError("Grade percentage ranges cannot overlap within the same grading system.") + + @api.constrains('grade_point') + def _check_grade_point(self): + for record in self: + if record.grade_point < 0: + raise ValidationError("Grade point cannot be negative.") diff --git a/addons/education_exam/models/education_student_inherit.py b/addons/education_exam/models/education_student_inherit.py new file mode 100644 index 0000000..7bbd076 --- /dev/null +++ b/addons/education_exam/models/education_student_inherit.py @@ -0,0 +1,23 @@ +from odoo import models, fields, api + +class EducationStudent(models.Model): + _inherit = 'education.student' + + exam_result_ids = fields.One2many('education.exam.result', 'student_id', string='Exam Results') + exam_result_count = fields.Integer(compute='_compute_exam_result_count') + + @api.depends('exam_result_ids') + def _compute_exam_result_count(self): + for student in self: + student.exam_result_count = len(student.exam_result_ids) + + def action_view_exam_results(self): + self.ensure_one() + return { + 'name': 'Exam Results', + 'type': 'ir.actions.act_window', + 'res_model': 'education.exam.result', + 'view_mode': 'tree,form', + 'domain': [('student_id', '=', self.id)], + 'context': {'default_student_id': self.id}, + } diff --git a/addons/education_exam/models/education_subject.py b/addons/education_exam/models/education_subject.py new file mode 100644 index 0000000..f957059 --- /dev/null +++ b/addons/education_exam/models/education_subject.py @@ -0,0 +1,22 @@ +from odoo import models, fields, api + +class EducationSubject(models.Model): + _name = 'education.subject' + _description = 'Education Subject' + + name = fields.Char(string='Name', required=True) + code = fields.Char(string='Code', required=True) + program_id = fields.Many2one('education.program', string='Program', required=True) + academic_year_id = fields.Many2one('education.academic.year', string='Academic Year') + term_id = fields.Many2one('education.academic.term', string='Semester/Term') + credits = fields.Float(string='Credits', default=1.0) + subject_type = fields.Selection([ + ('theory', 'Theory'), + ('practical', 'Practical'), + ('both', 'Theory + Practical') + ], string='Subject Type', required=True, default='theory') + active = fields.Boolean(default=True) + + _sql_constraints = [ + ('code_unique', 'unique(code)', 'Subject code must be unique!') + ] diff --git a/addons/education_exam/security/ir.model.access.csv b/addons/education_exam/security/ir.model.access.csv new file mode 100644 index 0000000..2fb0de6 --- /dev/null +++ b/addons/education_exam/security/ir.model.access.csv @@ -0,0 +1,17 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_education_subject_admin,education.subject.admin,model_education_subject,group_education_exam_administrator,1,1,1,1 +access_education_subject_teacher,education.subject.teacher,model_education_subject,group_education_exam_teacher,1,0,0,0 +access_education_exam_type_admin,education.exam.type.admin,model_education_exam_type,group_education_exam_administrator,1,1,1,1 +access_education_exam_type_teacher,education.exam.type.teacher,model_education_exam_type,group_education_exam_teacher,1,0,0,0 +access_education_grading_system_admin,education.grading.system.admin,model_education_grading_system,group_education_exam_administrator,1,1,1,1 +access_education_grading_system_teacher,education.grading.system.teacher,model_education_grading_system,group_education_exam_teacher,1,0,0,0 +access_education_grade_admin,education.grade.admin,model_education_grade,group_education_exam_administrator,1,1,1,1 +access_education_grade_teacher,education.grade.teacher,model_education_grade,group_education_exam_teacher,1,0,0,0 +access_education_exam_admin,education.exam.admin,model_education_exam,group_education_exam_administrator,1,1,1,1 +access_education_exam_teacher,education.exam.teacher,model_education_exam,group_education_exam_teacher,1,0,0,0 +access_education_exam_line_admin,education.exam.line.admin,model_education_exam_line,group_education_exam_administrator,1,1,1,1 +access_education_exam_line_teacher,education.exam.line.teacher,model_education_exam_line,group_education_exam_teacher,1,0,0,0 +access_education_exam_result_admin,education.exam.result.admin,model_education_exam_result,group_education_exam_administrator,1,1,1,1 +access_education_exam_result_teacher,education.exam.result.teacher,model_education_exam_result,group_education_exam_teacher,1,1,0,0 +access_education_exam_result_line_admin,education.exam.result.line.admin,model_education_exam_result_line,group_education_exam_administrator,1,1,1,1 +access_education_exam_result_line_teacher,education.exam.result.line.teacher,model_education_exam_result_line,group_education_exam_teacher,1,1,0,0 diff --git a/addons/education_exam/security/security.xml b/addons/education_exam/security/security.xml new file mode 100644 index 0000000..5262a34 --- /dev/null +++ b/addons/education_exam/security/security.xml @@ -0,0 +1,33 @@ + + + + + + Examination + Helps you manage Education Examinations. + 10 + + + + + Teacher / Faculty + + + + + Administrator + + + + + + + + + Teacher Exam Result Rule + + [(1, '=', 1)] + + + + diff --git a/addons/education_exam/views/education_exam_result_views.xml b/addons/education_exam/views/education_exam_result_views.xml new file mode 100644 index 0000000..ccb571f --- /dev/null +++ b/addons/education_exam/views/education_exam_result_views.xml @@ -0,0 +1,78 @@ + + + + + education.exam.result.tree + education.exam.result + + + + + + + + + + + + + + education.exam.result.form + education.exam.result + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + Marks Entry + education.exam.result + tree,form + + + + Results + education.exam.result + tree,form + +
diff --git a/addons/education_exam/views/education_exam_views.xml b/addons/education_exam/views/education_exam_views.xml new file mode 100644 index 0000000..0b1f64e --- /dev/null +++ b/addons/education_exam/views/education_exam_views.xml @@ -0,0 +1,130 @@ + + + + + education.exam.type.tree + education.exam.type + + + + + + + + + + + education.exam.type.form + education.exam.type + +
+ + + + + + + + + +
+
+
+ + + Exam Types + education.exam.type + tree,form + + + + + education.exam.tree + education.exam + + + + + + + + + + + + + + + education.exam.form + education.exam + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + Exams + education.exam + tree,form + +
diff --git a/addons/education_exam/views/education_grading_views.xml b/addons/education_exam/views/education_grading_views.xml new file mode 100644 index 0000000..ec19fc9 --- /dev/null +++ b/addons/education_exam/views/education_grading_views.xml @@ -0,0 +1,56 @@ + + + + education.grading.system.tree + education.grading.system + + + + + + + + + + + education.grading.system.form + education.grading.system + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + Grading Systems + education.grading.system + tree,form + +
diff --git a/addons/education_exam/views/education_student_inherit_views.xml b/addons/education_exam/views/education_student_inherit_views.xml new file mode 100644 index 0000000..5778402 --- /dev/null +++ b/addons/education_exam/views/education_student_inherit_views.xml @@ -0,0 +1,15 @@ + + + + education.student.form.inherit.exam + education.student + + +
+ +
+
+
+
diff --git a/addons/education_exam/views/education_subject_views.xml b/addons/education_exam/views/education_subject_views.xml new file mode 100644 index 0000000..f8f2501 --- /dev/null +++ b/addons/education_exam/views/education_subject_views.xml @@ -0,0 +1,52 @@ + + + + education.subject.tree + education.subject + + + + + + + + + + + + + + education.subject.form + education.subject + +
+ + +
+
+ + + + + + + + + + + + + +
+
+
+
+ + + Subjects + education.subject + tree,form + +
diff --git a/addons/education_exam/views/menu_views.xml b/addons/education_exam/views/menu_views.xml new file mode 100644 index 0000000..82859da --- /dev/null +++ b/addons/education_exam/views/menu_views.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/addons/education_exam/views/portal_exam_templates.xml b/addons/education_exam/views/portal_exam_templates.xml new file mode 100644 index 0000000..0652d80 --- /dev/null +++ b/addons/education_exam/views/portal_exam_templates.xml @@ -0,0 +1,157 @@ + + + + + + + + + + + + diff --git a/addons/education_finance/__init__.py b/addons/education_finance/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/addons/education_finance/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/addons/education_finance/__manifest__.py b/addons/education_finance/__manifest__.py new file mode 100644 index 0000000..f6e0560 --- /dev/null +++ b/addons/education_finance/__manifest__.py @@ -0,0 +1,33 @@ +{ + 'name': 'Education Finance', + 'version': '17.0.1.0.0', + 'summary': 'Student Finance Management System for education institutions', + 'description': ''' + This module manages fee categories, fee types, fee structures, assignments, + discounts, scholarships, student ledger balances, receipts, payments, and refunds. + ''', + 'category': 'Education', + 'author': 'Metatron', + 'license': 'LGPL-3', + 'depends': ['education_student'], + 'data': [ + 'security/education_finance_security.xml', + 'security/ir.model.access.csv', + 'data/education_finance_sequence.xml', + 'reports/receipt_report.xml', + 'views/fee_category_views.xml', + 'views/fee_type_views.xml', + 'views/fee_structure_views.xml', + 'views/fee_assignment_views.xml', + 'views/student_ledger_views.xml', + 'views/student_payment_views.xml', + 'views/student_discount_views.xml', + 'views/student_scholarship_views.xml', + 'views/student_refund_views.xml', + 'views/student_views_inherit.xml', + 'views/finance_menus.xml', + ], + 'installable': True, + 'application': True, + 'auto_install': False, +} diff --git a/addons/education_finance/__pycache__/__init__.cpython-310.pyc b/addons/education_finance/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9340e018646022ebc12c2123b2d124248844d214 GIT binary patch literal 171 zcmd1j<>g`k0(GUyS)xGtF^Gc(44TX@8G*u@ zjJMcw^HWlDiv2X1ZZQ;r^sHnkVgXWM;+MXDZeEFgYDGy=qHbbJN`79kerif-a$-ql reqMZ9W?o`ma;koOd}dx|NqoFsLFFwD8=!fmIjMFaBz(J>-m?DKYE_`#~Ix*MdJ;=>LpIh zV?Ntr_gIhJXMHwU_zRB>zxiw%pAj2>o%kM`%)EnXx$~Bpm};I0a?S-6F_fc|SHW>` zg70y8Hq(ZI&@D(Vsh|r;ooiMo4JnytlE8|D3#GX>j!>09N2D27ORh+oF_QpED+)#C z00g&&MJ73@!^+l0-hL`AO{|S{HdTr*gxgget~eQ2ab}#0sCbzrRH`M{9s~x zf~H^xHy{-jw;d>xrc@So%s|Bw%gT9&t?sr@jb3Kb4iX-NP`Hvo4p?Qj=hAG)u2X1| z(CY;3nC3agRefkDxH7|YRq*@oo_vTcy`KJ_hN;jY+-gaWC}Y@YcWWk$B5X502c@GZ h$O}6>tGn;D@BSoqaoX~mr{{iqe&65PabKg~{{SSKkcI#N literal 0 HcmV?d00001 diff --git a/addons/education_finance/data/education_finance_sequence.xml b/addons/education_finance/data/education_finance_sequence.xml new file mode 100644 index 0000000..02b8817 --- /dev/null +++ b/addons/education_finance/data/education_finance_sequence.xml @@ -0,0 +1,12 @@ + + + + Student Payment Receipt Sequence + education.student.payment + REC/ + 6 + 1 + 1 + + + diff --git a/addons/education_finance/models/__init__.py b/addons/education_finance/models/__init__.py new file mode 100644 index 0000000..d1ece90 --- /dev/null +++ b/addons/education_finance/models/__init__.py @@ -0,0 +1,10 @@ +from . import fee_category +from . import fee_type +from . import fee_structure +from . import fee_assignment +from . import student_ledger +from . import student_payment +from . import student_discount +from . import student_scholarship +from . import student_refund +from . import student_inherit diff --git a/addons/education_finance/models/__pycache__/__init__.cpython-310.pyc b/addons/education_finance/models/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b603e50548318eb4f150988f224dd500b1fe21a9 GIT binary patch literal 519 zcmY+>Jx;?g7zSWF|49?51Sja$p=4u1h!e1|d9k9{ZH*Eq@{dF$Zow(IQdcIfz{K}i zR4R`0>iJ$JR^&`gQxRCZ&ByD867q$^<%(c!7(^yac}}o zSQ4CqQ|vY>2)ge7D-uAW&>Joaw4 zC(4eJ3(0k_T*6ApkqbYAAJO8*35g>Y77?iG@jMbh*y?gsb#--j^;gx-o6VYm&qqIf z+gEkN_?d+H$422AHu(V(Fo31TfCX$|2IjyDEJivrwFge%Fym7LEU=#%U~{YQKCyxd zI9o>B{RPJwZA)3vPNLjy!c&mS9pu1M*=DNsWt1im6-l0L2$74J%;%p93fHj7ACYh) zU|<9$u)qQ{u)zu(?)EESg99$Um3=$#w8n$#R8!TO8q}wnn$|R+In~rr(|`qV>DzBY zYu^eMkZ-{vEcHxSh9lZ`W#0)FQNM)J(R~&yBVU14SVQ?39LE5-Z~|?Pz{ws9R^Sx8 z@ze;8!f7~z-&OE2C}-8`1`K1p>sy>(mon*P1D+M5V|=3TbUE)tv!2zhVhCvB;{bnd z#LCPj#5i&vxQs_jGe2@XPE|?=ckwV>S%@U-DNpd-VInvvC&_?69Ieg9MR5dtki?xm z9*I%&dMWx}k+?jwlYGEs!G-d+N^af@?E)to#>G(ZQIi(GH5FU4efT$V?QKhVc*>%R zL{)%yqhVUKP33@yx`lFX(g)>goM+uc3~=4m8&lTZDxzX2MH5H2?XjoE#GN3hx0xWt zs4lqd#Q9(+%Jx*{#+Zp2c1<4QsolUPX-$uezR_oRc5D+DVDapMs+1lO#kS;W_j^OE zqUGV0^+8sw^M{3q-irY8Os-F(6?T&>%3{7gPJnd`PdGck&d#3luB6f69z<79(9|Xp z&lsKAn7{%9I15^5VmsL`1x{~qyj@GRhFP*ZH^b<2QVjWp5*1+r7yZdehjh|fGdWW& z&>E*ls5Rxrk#CPayStr8A8q`pjgN{b6+D7Hf4n&kev$hHS(X#~-CUH45;dJy4#2{l zEMe0J@B=cBjGM;ScOJ7xtdEuGzA0+ECi1Lr%3}o<1I^pdunHYmPuMqPhkZEa@?#SZ z&jI_m@!UmirSBoFYFa~T_UoAN<1h6z!9nUhGY(AMM?PsB>l0Twuu;1JSYA-q>U&W8 zuJM8~WA_x`oT0I6f`KrpZ;BSKdUWdUHb#c0JT5Q@uZHRT==OPkJCc5u>z*{p5SRWJ zDUT%g5jr#NT=>dd^6zmBA@|Evz-;au&Q*^x%$<}&kBdY5*2c#AsO4W6g9n%LAc_1# z*6!gV^`q+fu#iQR0lI28RBeV3wfH(hRDBL1s*aV6_j0j^fKYXUOR=}ZNwBFRDNFFK zs#9GO$qrR|OlW>DDU=&y>7g&qiwzvqd=j1DI8q7va@Kw#Svo3 zA&wEJNiY)-XOIy-kvEB)MWR}>gwqvMxzQjWW`$^DFP5OAWQ1#ul}!`rq#lzs>RE_l z+Cx|dp8U#-NIX_!CiB<|Yar+9*F$Qfw2IWk*JZWeyaoH$(cAx#(69RbM@CR3(iw3F z8J!X5<}%_f#5n^Kac3cG z?a@IzWFL!jB%CCoE2}HsA$}Zhi4`N>)}~?|)yf@a7_|Fk7{w*hEsQ}J;_W38E?*C5 zbFyhFjOm9sLM|hrc-AnMi_CrL*&efhAD#Q3sA&hA?KS0vA%P+cRV@s$)DBbP8)3LR zjMB2l(I%*z2c{e50=N)Vew23`?}tk{LV)lzhoyI^*QOgwH4z2{*7~r>A=(q~Q%`kE z=R1N#he}mG$#cx<41tEwdB8Q$)M6-iQ$i8e%_zwVDK672wEwvX(TySqcfv(NKSPrK z#yrDDNZD-JTxRsHlmIhddY1cV!ydzp0Eg`lvugk4*^M>rW3%lkJBK_Mbh+rA3i{Ji nEmC&)!Q9CVAAigx{fj~hpoRv+5BIPCJ z*n)DY4dmo&&vtMAON-um>R)hCq%$kYc3QV2IGh>oj%L1ZW|i0LRfgC1zx~qLbQ$}d zjN6xu#@F~1U!nj8JY`)T@U93%*9t67K9Sm8CvZ6Xj)4XCDFa(t9rwfvD&YLUeD@FB z%X~{)Q7_SMJCP|A+U@2b)57O^_QxnqAS#nQd#+TjVp@Lts*H`|S5zbmIIutf4=fOY z4OZYtw^IQd9B|J>;2Dc-8@w|sm_oY>Q&2tQ!8GbM^iQK+M|}orP)B3ap-b!SXw1vtR`azNZ_{ zp&y%yzLj!2D&=0Tjt9$f;%&91Y~g~DzlMvNlU3TzqON>7s81ZtU9^3nJtYtNiISk* zqEtz?cWcQuV(D9I3h_}M3hg8r$RjqN>VCHw-i+3go)^ObCl z_B4A^O?I?&^=9t}09q#XF@QU04A~C*Y3(f^vU40K5JTSK9nrREbWpcY!#+k0eS6jg ze=CMUJuGdgobaFdfdGc!d%J20V_oqrMZ+<0w~{n@EwxBsARcWEHa|$78tR|Pze&pd zBx^?375EPJm5fxpZ$hgTjBA1`2Q_T2IeckL$yis4q?@FXGBl6G0)1Qv<9!*w4(a#8 z5lA1QzaS1hVe=ZV;_d!rJ02JB?W@7#I|==Fl$aV|a*wM2*f}D5flYfUcC}4ZVzJhqEVVk)Wwm#FV0x*YPoFFL0Cg{X5e(G5l5Sg(F%+ zT@6DV+I}j-P}jonpdY1%@K8Hu2|D5C)X{F3E09VNUE1E>$F`xCRcpq6iN}MUzP-Nk zO`fMR%CsBBWpXIB_X1C+%{-H)FZEnl9HcnE8B!M|Sy?D*2%5jKr%(^b;h2-f5M;{L z^)Y|3Ow$R61`&gefrUt<2k+ePXADW>fARlCaV>I`ovle#D$qC?YzK7OwG<)gLKUi9)Tm6pV9eih(emgvh_E6 CQT@CC literal 0 HcmV?d00001 diff --git a/addons/education_finance/models/__pycache__/fee_structure.cpython-310.pyc b/addons/education_finance/models/__pycache__/fee_structure.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac30fefc7df825b3ad75ed62877e48401fda8226 GIT binary patch literal 2800 zcmaJ@-EZ7P5ck?X+xMB|^4+A#wR}{E%0(0eNK_?lTA)%BkWwLG30baZlV0kN>)IDJ zR(PR(%`4CFnE!ykfIniz8&CZg0;0^U?<=IOEzix&?(ELY&iwY0dcC6IbLY=r4(=oO z7YUP(iNYp+={HC~0~Tr#b6KRjdSti;Bb^?aQOPYa?Q0DTFkfh3a^t{yZn!pTN?@VJ z8fYA_ceS?j5?9mOhBW+MS6X{r9zrUuC;=X(Z6>F__rosuSvQIA36Y3^rcXXL3Y++) ze<0zS%RqB=V6FkWYl7jHxOHHINt>VOu7gq;Y;aIxLixmWD@s#=>PS;nni|wm(>O8Q znv$nrdLY-8JOj;v+)(l?%%MCF3mD=et~-U=X;^|~xPc+sI zTdTr2SpNwDn@)8ZMmAjpqUH`67#WoXN`F5uZ25 zGg2P$G~+@#y94g5CV1Kj`a%u9MX3E38Q%x+P;n^1(QJih?XPNLn^Z}AM-59y&?R8^64){h< z${ToI-srqKJ@oEo=tqyhzqLS)=twGBzIJbTz?0|R8BRTS3O4XPzaM62tD{ZNS4Ai6 z##~OH+Zm(C%Tz26a4a1pUz$PEJ33`zg`%peBo4y2w+iOXDRj`LNa3y~nke9+v-l(C zh){4USp``p@e-0X{8Z46);7nbr92Q+3p?$3>3AL$vggT)=V2cEA@McOd)oKI!AMC> zK_&1s5ZxXjg|xgx02ji*g`~Ol*cYNqth0?rdLyB%3@g^7V#lmVN0lydl|hXXUmw>Y ziPd4zp&;hflHbPsMnsk3P`Xi{O8ZWdgxp6z{2=Qda#q@f-EyThra{0AALG>$EF|xc#Q)&g(=|k*mKP6l{*GW!-coy=EPr?hfO(#3*@!X;HA0jt|Dgq5LO5jk$d4yYA@X*f?ZA)pE2rjMLQPPhyRXOK8XpB%s-GH);g^IUcB94ZeQfm^@ z6=A(pKCF`?EplY#q9aA$k{^cnj3$0ggj!2hX*R`%qZb;RkVs+URb;5K#Z=MW$C}3! zrz59>wu?9Zu;mSLix#$g6`w3)Llo}64gh4+OXA?MAMbN-=*g#Op3)IndWAKZ`EP#x zjmO@(XfD1)({rIv7}WkMlom3_+Fk9(>QC&L9pImrCz_~d4D{#hXZBPD(>8Vl-3$3T zRZy#lq!svaoMf#>ycP3(ypRuh2Tz1{6D1_bFXE3h1P}h#)Uxgck9qLKD^|LR)6{k2 zV1N$OUzxA+Yi|j0(srl4Bk%~5v%S7lf2Jp0vXqWlosPOqC z8rVc4?fY@Y_v!W=wu&I|5g!ww6BBX?C@s+SLmLq%*)a? e{YO;Lr_5jH4_>mxwQ-A1R@K)WtLaT+Vf8#TR04X)sR)ufaZk~qHhNf~SkOC^v&ueX?kbQ3 z_fqHTYtQY`|E0fx=&h&zg|GL%kP&;0}EvYxGN zF^RN0iewDAc9RTbocFkHeJA1wL=k1_OQkXu((uhvM&}vU{8u!R`5c&U0rzdNd6)1y;hy#@y$FCZs3N`fW(7*tjh}ICTLkqDT*o8JC4Mdu-2m5e< z>$Hs5!G7{~jCXIhCThx`z`{7h6nT9lU z)e1R|Sr=+kJ;s`oe6HA-jrrI*Ww(4td6wy0T@JHkBGS38t(OZTxaHc(WjwlNY7Zy= z`DBo!#X!yqCB6^-KxRHUICEQK;aG6Eu_Kf%DhibZk`;%S>=u+UW5;_zcC z`|D9#CoO{Gf| zUZDwCm&4C9ltx=;_hFb*W`-U81_8v yWRPWMZ^MY9zX#nSurFt!Gy=~}N_FWKz3*n`e}#otc$tT%q?nvG*ynYtWB&sz<*HZ! literal 0 HcmV?d00001 diff --git a/addons/education_finance/models/__pycache__/student_discount.cpython-310.pyc b/addons/education_finance/models/__pycache__/student_discount.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a500646360b8bcfd8134328cb731a5bb5d7f44f1 GIT binary patch literal 3276 zcmaJ^TW=f372eq!msiQMEb9`dn~N_@W6BNEHfU4ViJY{3uuw!sff6W)^`4=;6uB$U zE)A2Afui=SU;C08u=3`A(%&%XTOZOq2Pj(C4f>to#gq$WiJd)jX3ku`^PMxptX{8J zcs~5wpL<``E$i=GS$tenZs5&-f`Tj(eQO}vVqmxJfzx&b>+HTe@YGb?ZPP;_jp4Ifffvweav=bdB+V3W^Pr3F78OeU$6ng1*Q9q%mNV4=Zr7{(B_r<4# z$_>2vKT$}lEy!xyB-#$yZI_(3C;eWDT=K}ruk^%i2hfx$pz^*=73iu|gSKM&)u{o^ z5-nq)tgS*@qchO0&{uV}sIOF(jE5HJAMEg=#emj@Z z;&qb;lE$^q`4fIJFl(&O<_>RjEc3gAH)jGLSv{*KdiI|6T z>)V60*p?3p6pgDF@c{dWQk{k6*Lcm-bQh3m%vWzBM>Wu8eD(_&gmQlfQXYkQ3kD^u~8pI~Wd9JRb*u?uSZ#$U# zlNV^5Ib4Kjb4Rv7)NQE z6`_0(OAzKq*REY`Ax_OsJs4@XK=^_h=eXi18NWsfUGKzqWqi)t`FQzjG_ zjfrP}yc6}~VIN*Z#-1kiaDwgkczL|FGx1||c-S9>(JRxe8;(B&HR1?i9VRnR=b(rT zrwK01hDBaPDIxWTG?aM=D=k_t*5|2le$XH?_oaH_-Tcko&i451oKna+{RC^i8jhF3 zEAtF=XY{l1g=ne`Y*D+1Q9{i?S7wPdA?@NKAemTQUF8!>C5L=5K*ErBl0y424k7+x zeaHa1GR^VG?9FBEu~tsldIdA6Ydp=$!gi*#RLtc`dgBp!N;@INfvH{2RoW{uibmQ; zBl4`S0!WiXJ9kFfO{0NSKSgH*J_mwovf;j6DeLNJ04sKX3f}TFF&SMUkIi-UsrQgT!#tkpT zFBI=mUFG6!6ir`!#IiYM{9HyVzH7RwpF^$vVcJj917x395H}`t>)_s@5}CgaYJIk8 zb5h*GC^;u^My>)Jmxcdb;0D6|_xI$V6GQAa-_Sv)!~X7cbfwe5O<>q(xz_328%F(U zi)Xq(xky>RlPSW{X;>B9!cF9ZER_>ZmiJ^|8U`psR6hQ7a0~54q(+9p+P%%!lIv(u zzvO~X3mqWACy0QN!A_Sx&9c6XQk*py?f!(7iVqnMjTG0nC`oh0X;`tl)vvkWyUIdg z4s#zmzG&y%Bd}b-zZKh^Fz!3w@&D_3Gxqr}SNsOpSOczs>%Xjd(|%wk*!iCzfWB1Q zQatJ|Lfp+L%gnkA9Ex+fUSeg7j~Wx>xrs{c-Qqt6#&TheX34xz+_)xAxXveE&J(PP In!V=y4^ClD8UO$Q literal 0 HcmV?d00001 diff --git a/addons/education_finance/models/__pycache__/student_inherit.cpython-310.pyc b/addons/education_finance/models/__pycache__/student_inherit.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e3a566684828de292fe9cbd141ffd9fd15cdaa39 GIT binary patch literal 2887 zcmbVO-ER{|5Wl@IU;GuvaYBF=0<=|Kfm0z>YDGwq2=pZc)I@!-gieQN9nPHZoZU0R zmLs(#uX*h=^ez9=inl&hmHNV4`_h>`JJ^9#RnOYV%x`CBXJ=<-laxw%1HbEUe(C&N zFpS?wnEq@O?%(Bs;TOIqQ)pWq#G3w6msBhFQ zWd*%Zx$TfgAeGxqfJd@LbJO1(3U_eGH%PeAWMDK+U`>nL9f$0L4bG9(bhYfFoYQg+ zWe+@bBN`k1Z9}lSOu|y#ujF4^XjJd2c<;qlq@!s(K`>T7w zpo@Dc?@`9cVNr0|X!V8QaciKwyJK#++)BE=ARgcbC)3F9)@_l)+fg~eqafv=Ji!A< z;%J~8nI?h{D-ZjrOoJHEht(1EnSXq@% zCUxG3BJJyhG4L0|{BDv4(dvE9<%E9}U@R840-3IkPq(^>9*!Lr^SD}@5DUl+7q#N| zyv4&_y1L!(KH;Kn*G-j+6Ccp)VQxFo?i1DvL>dN>%00>$!jk7)prXn>%oxP-?S2#u z{2qCdzfU~6f_!=6&`uhpeJQHw!T7k^FO>t{4*F3#<=|3w(|&}viqVk`;7`JIxO^IJ z2@W5^@d|LOVRd}v+C8Josqall5bW4vO(z&tcwvja2Lm3(N7%uTF_m+Zho1pCDlVvnp5^Ko=+Tt^Z&!W8VZk@-f@SGdVhkk4DewEH`^#OElIf1-|L#@rd# z{LIwXtmaxt+zv&TgSd!G@rGINwDDD((2iXBSK}01%AsB^WgYXC^_nERjT>bra8z+p zsZ_VZLVaaZ3sg>;Krm2d2;w@TXE`A0BA1O_Oit?=A5f3S)+YJPfk}r3UHEMLvhGqh z#b-2igUAQMNYU0|6coDqx)+Lb1TGR;LQ*fO`BQC9dF?R5L*avC$$nSmQ|kYI8~eX1 zOpc8W3Ln2j(6cmFpt4fcsY>%RFGU0QQ40~i7TB)!qv%uARC%_;sYR`F3M@~SWX-dl zQ?}$$TUn^5%0Vj)pL0JufmFrJRh{}$l`<*IHL0p6`7v<=7fgF~I)o+k`vMM0LH5iF ztK!dPm&_W=vkJ-;=9wPy+3-yJk74~k7teP6obr4>4!Yd;Ro?fpt@I<}i@v|#52Epm z=Z9H?$+FCQ81tK5{BJ>gg-*>|4? zLVQc?+zyX8hB1l79h#?vqersGV~~PkFclY_WV$-(`Jo{R@(f!Cq-Nre)mYW6Vp#Hy v`*$r@W`gv#>#njBND{q)K8v81qnroyAF9^*&_>u(v%qXn4og~6W6Rb*H&F`j literal 0 HcmV?d00001 diff --git a/addons/education_finance/models/__pycache__/student_ledger.cpython-310.pyc b/addons/education_finance/models/__pycache__/student_ledger.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..95df75eff6f976444f02a040a03e7d981bb18ce5 GIT binary patch literal 3237 zcmaJ@TW=f36`t7}mlsL2BI{yXHsjc-nK)A76fICBY8**P6U3?!sc1_eSgd!3$}-C( zJ-c))f_kY8tgr6fCWmYCt3GiT2IoSDJQOpW2&_~+ky z>obh~i-3!t1K<;k>>W&k37)V%@9@6WvHEt$=InDOEMY%m!j^W=IkG#haCTYS{SW9_ z+tzkCjJ4N|Wg;@|^#?*GSwwpmze)*Yf8mTt*5QJ6EWtarusV(aPwDk4!V#|U@T&;_ z*zNcRR~0p2>c@7cYT$;LDdC!dXT@9z*A3hhmrA$+ct*?%kN$hJz`k+pbmjmx#e$)+ zI+p;=i_2p1n0Ma5x+0duGWJ%)69KM?n@e(MM3u+A;S@oQPJv6hrOC1LBcI1SS%4wF`K=0#J4xom}5bhi}?`hRihXIylpJiezZ5lBKI z+m80KT*c|W_LY1*ij@@FjZ-0?YJY&gMCNk5bj~WIQ~fwvJCLCo&)hG;)?Wd(Ek)_8 z8kolO=Vvn6msu{A_IJzGc=3FB z`)EyL6C)$rCalMLyl3sQ=X{6iNv2QW%t3z`rU$w{Wf#QaIoD1mlkRh-uHfK{kJkHX zzAm5UDts>#VvuI*XFdqJCY*A;D9H6P>Om3pwIS5|C<*(IM7Xg`npl`>Y4?ejOVuGqd0Hf+59$C`!Z|)8%Xo1CHzf&Nu+xA%ofuvilDA+B0q@1 z!|N4?Mq=~$5!dXmL3A~2s4u*+}&ms!W^rKEn!{pE3u$b^I_RVh&EfOKPuqIa5 z7WTeH-SpTxw1s!*Ol;6xK6EF}#O-kmYnlC)0nd8q=_jtJm>sWcom!kdULd^iSJ+8q zVx96sZ{nT!qPovdId&oG1YNm+1Vi7c@95ez!D8`(e~dIn4>*Ln#to7$VY=6B?U5Ow??$C1u zZIHyNM6G1HQl4nnpbJ__^es|`a#eegNjVb)ZAD5~@FB)p#f+z{IEC?=C!>8EWz{seO8dihX;E(a@Yb-9L z|I9Q>J2EdLPA~zb0&`$mZoy33m}%!u?oKGZ%iSe*#E-~)cH&VXz+@XW{0Uef1*ihm z7ESjtY4>{-!)!zxhS{0SUozSuqm7x`M*C2o(>dzm%2fOpSZ!mN8==7)e7Ugl+H0&d zvR-OA)7Tj!snX8PIOK(qr_7k*kg-Md3RB!yP4Px4^7gyJ0NX^(sIDH6!mp^l1Kd}n zx(N@|{>AVVoZGjx9|URGmqDOw0aeu~p>-n&9*@GL+;I&FkmCHV)m~7v4l-2x4{%A| z8>F%*zvlEU5zt!#Z2%=&z7GsAeH0h~_kTe5KL9mH%x0oNn&UbR@_`5sv_l229lF>k zW?k*3w2Y-Nj=kq8EaqV#b9w7$h=GwHE^WMCw|3 z&65gLBFZANdDCQqB9ntYULUcdB2bhiiu|o-%0i)QY+5WPn@n&_O1Yq;k$s8DXAX+S z;R{w1?Sg)b)-2M@vCvlV^SJlld2b&08uwlA$A;$^Jndlo==;t*=)HxCBAz>KPdfuK m7#M{Wg+fsS(e49!{~MYM&F(DE@#rW*y+Kc)jE*euMf?9Wt5$;m literal 0 HcmV?d00001 diff --git a/addons/education_finance/models/__pycache__/student_payment.cpython-310.pyc b/addons/education_finance/models/__pycache__/student_payment.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..66eee0853bb70978461428ea7a37e2a54a8379de GIT binary patch literal 3627 zcmZ`+S#KQ25uToFXHOoIC{pK$k}RzvEgj`BAjpPgN;Yzc5kxr%1_=hk>0WY{yEA0Z ztRODvmq>u0^qQB%0GT)all+E;-|`Ry`2#UySox}Fmm+0wiLR-xzN)M0t7>M=X3fCw zqi_Gx`@@uB{F6FIe>OUwqUFB?;Ra`YW55D7FavX71r{Tj*|!Hy;4tG0gInBwWpGTNT?IxnnbL9>)F8Xle+Bawz}NU9zl#1feTK`gW6nAL z!8!}hiwk=f`Hg+XZ}Rpl<0T6&@mu^SuZ-X_zs>L9>x&D$D_eF&XIO&CfvnXEI9ju8J(h^MxUmeU1yjh@kCJ#*D~!&V49 zB7LqZaW>eB(j8TwY!@c{4O4b5`rS8%T*ShIkCz5%u_Rs;GP)CS7&u=#G-udN(kP9^ zQi;B$vHQcZlRM;<`~7IJ#-mRzk}4)h%^3M7gi>cRc~pNe$cAYFOC(%g#;Sjz>0jG^ z=P}hfR9xo)NiG^qKNT=EtRu#e(`I^!syr2gNN(nB3pRJSjJgPshx9?e8fR%Ykpsk2 zb$P;SIo+_|*YZQwT`i(wn9G|uaH0_%{w|PksMe;g-8D9JWw~Tdb{1u)(pjs9yg<8~ zl1wKr5%GzTTS{`3xN9J{(X?^BLv3_KYn3Rh;qp_%G}#W{!`LDThvMdO@oj$`8Hk7W z=+*>>B`*EJFfaTy;g?JOEg>h)xTo9tcMz~$z-!& z+*2}kfO>&ri;XDtM?z*DSm~!&AsrC#SMX65F7=+V zR5Jwl;?1z!@aLGO;o+Gz<}vfF{dRQyq}V*R^4JCGat$qGABve3~| z4^mI>Tm`M^HPxbyZ-dtsj9&94+iRgWwa>6W$)~rT@CNwiHx8!}i%)^h^s0RNZ{9vb z3C8)MF?PhvioAvUj24!oG|dV>ies@=_+xzgS=S$R5DIZL%mut5{BZ`jk^9-O$crfD zNY(yY)F39>0G$vQj3fVk~3spgK)lZu$vayJ@V%EASMKS}8+S z($iA=<$!%G?^B18W4ls1j8Z8aDk2$g=vnd+O(7>KcSzND^OzA^Wdw$J=g36l@7u9C zxvnk@Pq9lr3qoeBu?Cv^gJ*lp{_fVV$(xj3a=OVb+XFoJnQTlrZX!X< zYpYq>YBmzY8A>ah-8TyBrLhOg?K9+5XQd290-yW^5%NXbk-sKhml$%4a`GWEx_n53 z<3wsdkNP>@1=TQ&`%#{UVQGh}w2~(N5iNH?=FNFrdvxif=m4VVRdr2yVVFh(5r(Q3 zhIl;;`@}cGaC;c_$0LrO0^w0?x?v`{kkAF$T~Cl?AH2UdB3b&;dyF^D-7FO)JZ*Z& zsU||bAp8{>k9Oqew5GZ`L6!!UerG7}F;*6l+$k~q8@>56tW*_QJ|A=>smfpJqa-bINnWJXF_z@=XAsE8L?~XAqu);2jHphff$6<5G4dkK z{E~<^H(8k+m#=_$27#0Q=FC}kn&PeEerH!rK$XDien0CrbR2rN`+dV92$v%m{hwL2 z-+DF*pX1HjwK*nf?-k@oJL6fV71y?vHsorGgdOpM3ILL1u1l?Q9@GC_dgT#pXq`!2 UJpW4PenRzJhsbHxFz2lQ1D-0&w*UYD literal 0 HcmV?d00001 diff --git a/addons/education_finance/models/__pycache__/student_refund.cpython-310.pyc b/addons/education_finance/models/__pycache__/student_refund.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e645a4e7813ee5e435e7c952647d2651a0fa2134 GIT binary patch literal 2856 zcmaJ@-EY)J5cm4)9Ov@M5fY$aK0;zXE-epjRZ&C>p--eJL4CLiS&nBf^>Jrk*1m{X zAXSCey!M46LErK(t@_r7wvT`OVB;IyUBN_+0+w$IdQt zztZI0$Hd7k9NEW6Km*p*daT8IdQ0yaErXFv@0vZUWijn54Gb`!YhZGtV?Q%mHL%t+ z-~I!?)qF!5VLz63JLX-;q}@w_cQc>KiEqMg3}GIp$+8fsh)Dh2r-qYTII`c7aIM8a zYw5sR2IwsljF!diP7O@3z=j(BoE@v>DA@?OI3LAPN7;yyjlnq1CtwmCo!T*5E=osX z8fC|z0mo5NN68q>pyb4k-Wtc*#D)g5KQowvlPImhDYQ0;@+ss`@37W1@-_GX&fxs4 zvRnfXb&m1ItK)F)HG}hT;kovVwPsLq0xqIVhk3Z9WV5gUi_f*z9Qbe<-zVV;hV81H zS%yKR;UZq`&!2_N}#nge%WUbPdlR=dB z`h%PYmBk0XDeWv5ak3$6)ta=jJQZA8aRU5tF?YaTvmb8v(9dG>YY}h6N!a!7SCTW| zE-qwwm=CgrtKP!rM4~il$D+q!!GGwRVg$jJj^LYvSa6Uwq`fdsL>k6 zwc_mZ5VI=eB|6Qau5YLSh%r=`HQ?=V(9MgnQp@|ImkyF#kip+@sHX~9`NjC&X;gWM zIaDd8R(Y7EiPug=l?=ZAb{F69b!mYJ+qtyv&D zUQ=j_)40>n71~w#qfql%>Bgu657UdnUb|f~M?>eJkrKK!O-zz?T}+YTX(A0G1X4*( z5I2kD91az0XU`i7rSd>|Bi$-F5$}DQ6F>$Il{e>+d7|CX9@d|-C#<81QKB7|Fu-}h zHg(XjHuaUe=+#A3mhLbCFpYF^oJ5A;(fdBqspfHHHWEjlVdlTZ!Vx*~el1EnRQMlo zn;N5X`#v(t?F*&bz#cI%mNU?wu^;Flx|pYf5ZBQjnPMK-43fOSZe2=|vtC-HUmen3 z5mZmb*(wQ3C%oPu%e{5(_0ufQ<1OASCiW{V7QXLAVUnb|#~(+WgO?85L4|==*;*`g zzlo{8i2DeO2*p%fBC;Q3*$ATVc=V{Mv!FC^85Of}B#!Pfhc(z~rhjj~DXttD*@HF? zM3p*@iYg$ZqIzvFs=9DDb<9qT?40&W$H*BSGq*bStJ-V4{cvu+&|c~)rcP&sc$B$x zHQ{@-V?ymG=QYE7lhR46BJHe*Gq~4bjlIiZgNwpkUB0_iTphN9sx-Z8-usOf3q9g- zKmW`VC()!FO$Ryl-2||a`;MR#lqO!boU*hnN9kq|sE?N~HUqvM=hBX_|I)kc*V3i- zkF|@1s>deHM&T#gZ{D05Nk5>Fs(g}hj?C~IwZV=$@GUyUt z4}#4>*sWG9rGmnDakGO|00vgY-?@iST~Ct|m$IJd-jL2c6y~AWRscxz0p4%Y7V3(S zBWt{i9UcR(jwx$jrfHXl3AP@TZgHXTqE<RQ=NB#vexj3#U74QA>DCjn}|nYPCfV#l=K)U6U`lH>Ttaa`t_xK^uA z3$nH1JJL)cO$GU;A|OoUmL2-R9_zE9$wvL+;39jQy?~2U7s=j E1Ejg(lmGw# literal 0 HcmV?d00001 diff --git a/addons/education_finance/models/__pycache__/student_scholarship.cpython-310.pyc b/addons/education_finance/models/__pycache__/student_scholarship.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec763095f319b2fd358bfdfcc627a9c662dd9faf GIT binary patch literal 3216 zcmaJ@-ESO85ucu~9nXIFBTiz+m%BMG;j+1~&H;kcad%2^0thJqo@|2v2@x`K?ZNuq0!*|o{)I%IcUSsQrdkV~~^W_JNSpFCPX-zEQ<-K|5?pgJ{n zO=?2cqD4rXdfpN(L$X5WaCDZoAYG(Y$j;LRx(LY-c|*K7jddlpA+x9BKRP6l0QUu@miONvQI@cr>{zXM>%*8tvqB!{??d z!Msi_#(88Ol}mF|duce7lcl+N!8Rmcw9V4Z3l*okrKjYBajYawmj7$lvNqkRZ7C0a zQVSrZ6Q?8}VF`aERfMC5yR!6ucEV%C>Bo<-V&g~r7u+6Z(X zl=aziy_jB#(#~ble`%j+RHzrrk@N#FSFj=1W4+3W_)hhJ=il)D|J!k#a$u)G)sYOhI zpGG)yrrJW!oHTX##l=BzCoE*!VwaE#`$g&8;Xm{jqAcylYAC5(*qX6g&X-LREk9x1 zogys8xw?$qX2;>3H6m1ebtW+BLY zB7VyfldOGHZRjz3-&PwKb9n55Nd~9DZN=!D+?e;?L-t-U9Op%FUk1BM!u%>I_QEun zNR@R|11et;>s_DMdmTwZ6sBob1oBZN5j!8=ym_^Q7`X}+<$#oSfzW}KE^xOXq%Q?_I(-)ptMKgb6znZkqg663ePT7!)2j4;4@K0d04S#iFLsR(= zOxR33W@?fN8(s!y4eZC(p~WL)Z5XQt#!a$e^Q(nZ+4Yb$Fb}r=2r~E3{L-LWB?IR@ z*VYGETv}K+UQ{Dqu`uvo)Q=4EG1L3fcy3Z1Tmu>n{5MCRQxkmaxkHQCaS5^(^8WJg zy(58A2T%8msYh3~)jPP6_R%gFe80F=Ir*clFp0(qyh*`a;0KkB&v5wU?XAk6;b@c` z1mP*|tRGCi3!p^dIF~^@b9xTI$Y7dhf^1ymMVJzjbwFd82hb`-=XmWTL!A@}1nsU= z$M(+FPc|p#=kay>*P$Xw9|x0qaBZ3_(6P2RZy$@cx`eBi_9%?0?Unv4!^KqEsNs@P z)-UT^T~s{cnvQHD@5e>yMkpNoj}sz2l>Ri?BE_~BONXVhf-Xm_+T?9k<}NhFs0OwU zd%YrWX~v{DR!f^RSm_iQg$Js}l3HFRJH_u8BqI(4b&W?>=3Fyfx~$J__hzKD!iaCv zo2J6=V2!*3;t5}v!qoxO5}xUahPWhr(?#(3Vhv;bL+>A+Z~tv_{j5+ry~U|m;`?#_ z)3Nk97#&Mj=3?nRpdA?rXh$|88`wpB^ew!JY{Z8c*o^nSwQC-6yqPbYBUD6=nG;NV zQ%rLj(DpwTpQ;bJbA!pdAZ=HDpLtHZ@T3e?w5O-4kF{i+CULrt9Q4}ItqN&96n_=V z{7t(yXP4IXV;igFyf_=rhbfnY`?Y6#!v5#kS?KgC?aQUt(+`?nuk?F8l;d$C!8dxn z2jei|rw)3Ko&w=KT-{y<_^WcEhkU6gOu@Ca7pe+X-aR}LzMZAALg~VeOr%DK($M9C zTzdB~UWDqP!t|$*sUPS8uvW{!4o#)jf`w-M`hMp>p$rVXMvy_btD>~I0n!Jb!j$GOLkd#(Auj*16R<&n?0 OD(1Yws%V%Otp5O#FjEo$ literal 0 HcmV?d00001 diff --git a/addons/education_finance/models/fee_assignment.py b/addons/education_finance/models/fee_assignment.py new file mode 100644 index 0000000..ebe8464 --- /dev/null +++ b/addons/education_finance/models/fee_assignment.py @@ -0,0 +1,65 @@ +# pyrefly: ignore [missing-import] +from odoo import api, fields, models +# pyrefly: ignore [missing-import] +from odoo.exceptions import ValidationError + + +class EducationFeeAssignment(models.Model): + _name = 'education.fee.assignment' + _description = 'Student Fee Assignment' + _order = 'assignment_date desc' + + student_id = fields.Many2one('education.student', string='Student', required=True, index=True) + academic_year_id = fields.Many2one('education.academic.year', string='Academic Year', required=True, index=True) + semester_id = fields.Many2one('education.semester', string='Semester', required=True, index=True) + fee_structure_id = fields.Many2one('education.fee.structure', string='Fee Structure', required=True, index=True) + assignment_date = fields.Date(string='Assignment Date', required=True, default=fields.Date.context_today) + state = fields.Selection([ + ('draft', 'Draft'), + ('confirmed', 'Confirmed'), + ], string='Status', default='draft', required=True, index=True) + active = fields.Boolean(default=True) + company_id = fields.Many2one( + 'res.company', + string='Company', + required=True, + default=lambda self: self.env.company, + index=True, + ) + + _sql_constraints = [ + ('uniq_assignment', 'unique(student_id, academic_year_id, semester_id, fee_structure_id, company_id)', 'This fee structure is already assigned to this student for this semester.'), + ] + + def action_confirm(self): + for rec in self: + if rec.state == 'confirmed': + continue + if not rec.fee_structure_id.line_ids: + raise ValidationError(f"The selected fee structure '{rec.fee_structure_id.name}' has no fee lines. Please add fee lines to this structure before confirming.") + for line in rec.fee_structure_id.line_ids: + self.env['education.student.ledger'].create({ + 'student_id': rec.student_id.id, + 'academic_year_id': rec.academic_year_id.id, + 'semester_id': rec.semester_id.id, + 'category_id': line.fee_type_id.category_id.id, + 'fee_type_id': line.fee_type_id.id, + 'date': rec.assignment_date, + 'ref': f"FEE/{rec.id}", + 'description': f"{line.fee_type_id.name} (Assigned via {rec.fee_structure_id.name})", + 'debit': line.amount, + 'credit': 0.0, + 'status': 'outstanding', + 'company_id': rec.company_id.id, + }) + rec.state = 'confirmed' + + def action_cancel(self): + for rec in self: + if rec.state == 'confirmed': + ledger_entries = self.env['education.student.ledger'].search([ + ('student_id', '=', rec.student_id.id), + ('ref', '=', f"FEE/{rec.id}"), + ]) + ledger_entries.unlink() + rec.state = 'draft' diff --git a/addons/education_finance/models/fee_category.py b/addons/education_finance/models/fee_category.py new file mode 100644 index 0000000..6d9a1c1 --- /dev/null +++ b/addons/education_finance/models/fee_category.py @@ -0,0 +1,48 @@ +# pyrefly: ignore [missing-import] +from odoo import api, fields, models +# pyrefly: ignore [missing-import] +from odoo.exceptions import ValidationError + + +class EducationFeeCategory(models.Model): + _name = 'education.fee.category' + _description = 'Fee Category' + _order = 'name' + + name = fields.Char(required=True, string='Category Name') + code = fields.Char(string='Category Code', required=True, index=True) + description = fields.Text(string='Description') + active = fields.Boolean(default=True) + company_id = fields.Many2one( + 'res.company', + string='Company', + required=True, + default=lambda self: self.env.company, + index=True, + ) + + _sql_constraints = [ + ('uniq_category_code_company', 'unique(code, company_id)', 'Fee category code must be unique per company.'), + ] + + @api.constrains('name', 'company_id') + def _check_name_unique(self): + for rec in self: + similar = self.search([ + ('company_id', '=', rec.company_id.id), + ('name', '=ilike', rec.name), + ('id', '!=', rec.id), + ]) + if similar: + raise ValidationError('Fee category name must be unique within a company.') + + @api.model + def create(self, vals): + if vals.get('code'): + vals['code'] = vals['code'].strip().upper() + return super().create(vals) + + def write(self, vals): + if 'code' in vals and vals.get('code'): + vals['code'] = vals['code'].strip().upper() + return super().write(vals) diff --git a/addons/education_finance/models/fee_structure.py b/addons/education_finance/models/fee_structure.py new file mode 100644 index 0000000..cc52460 --- /dev/null +++ b/addons/education_finance/models/fee_structure.py @@ -0,0 +1,64 @@ +# pyrefly: ignore [missing-import] +from odoo import api, fields, models +# pyrefly: ignore [missing-import] +from odoo.exceptions import ValidationError + + +class EducationFeeStructure(models.Model): + _name = 'education.fee.structure' + _description = 'Fee Structure' + _order = 'name' + + name = fields.Char(required=True, string='Structure Name') + academic_year_id = fields.Many2one('education.academic.year', string='Academic Year', required=True, index=True) + level_id = fields.Many2one('education.academic.level', string='Academic Level', required=True, index=True) + program_id = fields.Many2one('education.program', string='Program', index=True) + department_id = fields.Many2one('education.department', string='Department', index=True) + semester_id = fields.Many2one('education.semester', string='Semester', index=True) + currency_id = fields.Many2one( + 'res.currency', + string='Currency', + required=True, + default=lambda self: self.env.company.currency_id, + ) + line_ids = fields.One2many('education.fee.structure.line', 'fee_structure_id', string='Fee Lines', copy=True) + active = fields.Boolean(default=True) + company_id = fields.Many2one( + 'res.company', + string='Company', + required=True, + default=lambda self: self.env.company, + index=True, + ) + + +class EducationFeeStructureLine(models.Model): + _name = 'education.fee.structure.line' + _description = 'Fee Structure Line' + _order = 'sequence, id' + + fee_structure_id = fields.Many2one('education.fee.structure', string='Fee Structure', required=True, ondelete='cascade') + fee_type_id = fields.Many2one('education.fee.type', string='Fee Type', required=True) + currency_id = fields.Many2one('res.currency', related='fee_structure_id.currency_id', store=True) + amount = fields.Monetary(string='Amount', required=True, currency_field='currency_id') + mandatory = fields.Boolean(string='Mandatory', default=True) + sequence = fields.Integer(string='Sequence', default=10) + active = fields.Boolean(default=True) + company_id = fields.Many2one( + 'res.company', + related='fee_structure_id.company_id', + store=True, + index=True, + ) + + @api.onchange('fee_type_id') + def _onchange_fee_type(self): + if self.fee_type_id: + self.amount = self.fee_type_id.amount + self.mandatory = self.fee_type_id.mandatory + + @api.constrains('amount') + def _check_amount(self): + for rec in self: + if rec.amount < 0: + raise ValidationError('Line amount cannot be negative.') diff --git a/addons/education_finance/models/fee_type.py b/addons/education_finance/models/fee_type.py new file mode 100644 index 0000000..bce3358 --- /dev/null +++ b/addons/education_finance/models/fee_type.py @@ -0,0 +1,38 @@ +# pyrefly: ignore [missing-import] +from odoo import api, fields, models +# pyrefly: ignore [missing-import] +from odoo.exceptions import ValidationError + + +class EducationFeeType(models.Model): + _name = 'education.fee.type' + _description = 'Fee Type' + _order = 'name' + + name = fields.Char(required=True, string='Fee Type Name') + category_id = fields.Many2one('education.fee.category', string='Category', required=True, index=True) + currency_id = fields.Many2one( + 'res.currency', + string='Currency', + required=True, + default=lambda self: self.env.company.currency_id, + ) + amount = fields.Monetary(string='Amount', required=True, currency_field='currency_id', default=0.0) + recurring = fields.Boolean(string='Recurring', default=False) + mandatory = fields.Boolean(string='Mandatory', default=True) + tax_applicable = fields.Boolean(string='Tax Applicable', default=False) + description = fields.Text(string='Description') + active = fields.Boolean(default=True) + company_id = fields.Many2one( + 'res.company', + string='Company', + required=True, + default=lambda self: self.env.company, + index=True, + ) + + @api.constrains('amount') + def _check_amount(self): + for rec in self: + if rec.amount < 0: + raise ValidationError('Fee type amount cannot be negative.') diff --git a/addons/education_finance/models/student_discount.py b/addons/education_finance/models/student_discount.py new file mode 100644 index 0000000..0a1bfda --- /dev/null +++ b/addons/education_finance/models/student_discount.py @@ -0,0 +1,85 @@ +# pyrefly: ignore [missing-import] +from odoo import api, fields, models +# pyrefly: ignore [missing-import] +from odoo.exceptions import ValidationError + + +class EducationStudentDiscount(models.Model): + _name = 'education.student.discount' + _description = 'Student Fee Discount' + _order = 'id desc' + + student_id = fields.Many2one('education.student', string='Student', required=True, index=True) + type = fields.Selection([ + ('percentage', 'Percentage'), + ('fixed', 'Fixed Amount') + ], string='Discount Type', required=True, default='fixed') + + currency_id = fields.Many2one( + 'res.currency', + string='Currency', + required=True, + default=lambda self: self.env.company.currency_id, + ) + amount = fields.Monetary(string='Discount Value', required=True, currency_field='currency_id', default=0.0) + reason = fields.Text(string='Reason for Discount', required=True) + approved_by = fields.Many2one('res.users', string='Approved By', default=lambda self: self.env.user, required=True) + validity = fields.Date(string='Validity Date') + state = fields.Selection([ + ('draft', 'Draft'), + ('confirmed', 'Confirmed'), + ('cancelled', 'Cancelled') + ], string='Status', default='draft', required=True, index=True) + active = fields.Boolean(default=True) + company_id = fields.Many2one( + 'res.company', + string='Company', + required=True, + default=lambda self: self.env.company, + index=True, + ) + + @api.constrains('amount') + def _check_amount(self): + for rec in self: + if rec.amount <= 0: + raise ValidationError('Discount value must be greater than zero.') + if rec.type == 'percentage' and rec.amount > 100: + raise ValidationError('Discount percentage cannot exceed 100%.') + + def action_confirm(self): + for rec in self: + if rec.state == 'confirmed': + continue + + discount_amount = rec.amount + if rec.type == 'percentage': + outstanding = rec.student_id.outstanding_amount + discount_amount = outstanding * (rec.amount / 100.0) + + if discount_amount <= 0: + raise ValidationError('Calculated discount amount must be greater than zero.') + if discount_amount > rec.student_id.outstanding_amount: + raise ValidationError(f'Cannot apply a discount of {discount_amount} because it exceeds the student outstanding dues of {rec.student_id.outstanding_amount}.') + + self.env['education.student.ledger'].create({ + 'student_id': rec.student_id.id, + 'date': fields.Date.today(), + 'ref': f"DISC/{rec.id}", + 'description': f"Discount Applied - {rec.reason} (Approved by: {rec.approved_by.name})", + 'debit': 0.0, + 'credit': discount_amount, + 'status': 'paid', + 'company_id': rec.company_id.id, + }) + rec.state = 'confirmed' + + def action_cancel(self): + for rec in self: + if rec.state == 'confirmed': + ledger_entries = self.env['education.student.ledger'].search([ + ('student_id', '=', rec.student_id.id), + ('ref', '=', f"DISC/{rec.id}"), + ]) + ledger_entries.unlink() + rec.state = 'cancelled' diff --git a/addons/education_finance/models/student_inherit.py b/addons/education_finance/models/student_inherit.py new file mode 100644 index 0000000..706d864 --- /dev/null +++ b/addons/education_finance/models/student_inherit.py @@ -0,0 +1,49 @@ +# pyrefly: ignore [missing-import] +from odoo import api, fields, models + + +class EducationStudent(models.Model): + _inherit = 'education.student' + + ledger_line_ids = fields.One2many('education.student.ledger', 'student_id', string='Ledger Lines') + payment_ids = fields.One2many('education.student.payment', 'student_id', string='Payments') + + currency_id = fields.Many2one('res.currency', string='Currency', related='company_id.currency_id', readonly=True, store=True) + + outstanding_amount = fields.Monetary(string='Outstanding Dues', compute='_compute_finance_metrics', currency_field='currency_id', store=True, index=True) + total_fees = fields.Monetary(string='Total Fees', compute='_compute_finance_metrics', currency_field='currency_id', store=True, index=True) + total_paid = fields.Monetary(string='Total Paid', compute='_compute_finance_metrics', currency_field='currency_id', store=True, index=True) + + last_payment_date = fields.Date(string='Last Payment Date', compute='_compute_finance_metrics', store=True, index=True) + last_receipt_number = fields.Char(string='Last Receipt Number', compute='_compute_finance_metrics', store=True, index=True) + payment_status = fields.Selection([ + ('no_dues', 'No Dues'), + ('partial', 'Partial'), + ('overdue', 'Overdue'), + ('fully_paid', 'Fully Paid') + ], string='Payment Status', compute='_compute_finance_metrics', default='no_dues', store=True, index=True) + + @api.depends('ledger_line_ids.debit', 'ledger_line_ids.credit', 'ledger_line_ids.status', 'payment_ids.amount', 'payment_ids.status', 'payment_ids.payment_date', 'payment_ids.receipt_number') + def _compute_finance_metrics(self): + for rec in self: + active_ledgers = rec.ledger_line_ids.filtered(lambda l: l.status in ('outstanding', 'paid')) + total_debits = sum(line.debit for line in active_ledgers) + total_credits = sum(line.credit for line in active_ledgers) + rec.total_fees = total_debits + + confirmed_payments = rec.payment_ids.filtered(lambda p: p.status == 'confirmed').sorted(key=lambda p: (p.payment_date or fields.Date.today(), p.id), reverse=True) + rec.total_paid = sum(p.amount for p in confirmed_payments) + rec.outstanding_amount = total_debits - total_credits + + last_payment = confirmed_payments[0] if confirmed_payments else False + rec.last_payment_date = last_payment.payment_date if last_payment else False + rec.last_receipt_number = last_payment.receipt_number if last_payment else False + + if total_debits == 0: + rec.payment_status = 'no_dues' + elif rec.outstanding_amount <= 0: + rec.payment_status = 'fully_paid' + elif rec.total_paid > 0: + rec.payment_status = 'partial' + else: + rec.payment_status = 'overdue' diff --git a/addons/education_finance/models/student_ledger.py b/addons/education_finance/models/student_ledger.py new file mode 100644 index 0000000..24ee7d5 --- /dev/null +++ b/addons/education_finance/models/student_ledger.py @@ -0,0 +1,80 @@ +# pyrefly: ignore [missing-import] +from odoo import api, fields, models + + +class EducationStudentLedger(models.Model): + _name = 'education.student.ledger' + _description = 'Student Financial Ledger' + _order = 'date asc, id asc' + + student_id = fields.Many2one('education.student', string='Student', required=True, index=True, ondelete='cascade') + academic_year_id = fields.Many2one('education.academic.year', string='Academic Year', index=True) + semester_id = fields.Many2one('education.semester', string='Semester', index=True) + category_id = fields.Many2one('education.fee.category', string='Fee Category', index=True) + fee_type_id = fields.Many2one('education.fee.type', string='Fee Type', index=True) + + date = fields.Date(string='Date', required=True, default=fields.Date.context_today, index=True) + ref = fields.Char(string='Reference', index=True) + description = fields.Text(string='Description') + + currency_id = fields.Many2one( + 'res.currency', + string='Currency', + required=True, + default=lambda self: self.env.company.currency_id, + ) + debit = fields.Monetary(string='Debit (Charges)', default=0.0, currency_field='currency_id', index=True) + credit = fields.Monetary(string='Credit (Payments/Credits)', default=0.0, currency_field='currency_id', index=True) + balance = fields.Monetary(string='Balance', currency_field='currency_id', index=True) + status = fields.Selection([ + ('draft', 'Draft'), + ('outstanding', 'Outstanding'), + ('paid', 'Paid'), + ('cancelled', 'Cancelled') + ], string='Status', default='outstanding', required=True, index=True) + active = fields.Boolean(default=True) + company_id = fields.Many2one( + 'res.company', + string='Company', + required=True, + default=lambda self: self.env.company, + index=True, + ) + + def _recalculate_student_balances(self, student_ids): + for student_id in student_ids: + lines = self.search([ + ('student_id', '=', student_id), + ('status', 'in', ('outstanding', 'paid')) + ], order='date asc, id asc') + running = 0.0 + for line in lines: + running += line.debit - line.credit + self.env.cr.execute( + "UPDATE education_student_ledger SET balance = %s WHERE id = %s", + (running, line.id) + ) + # Force metrics recalculation on the student record + student = self.env['education.student'].browse(student_id) + student.modified(['ledger_line_ids']) + self.invalidate_model(['balance']) + + @api.model_create_multi + def create(self, vals_list): + records = super().create(vals_list) + student_ids = records.mapped('student_id').ids + self._recalculate_student_balances(student_ids) + return records + + def write(self, vals): + student_ids = self.mapped('student_id').ids + res = super().write(vals) + student_ids = list(set(student_ids + self.mapped('student_id').ids)) + self._recalculate_student_balances(student_ids) + return res + + def unlink(self): + student_ids = self.mapped('student_id').ids + res = super().unlink() + self._recalculate_student_balances(student_ids) + return res diff --git a/addons/education_finance/models/student_payment.py b/addons/education_finance/models/student_payment.py new file mode 100644 index 0000000..3738a16 --- /dev/null +++ b/addons/education_finance/models/student_payment.py @@ -0,0 +1,96 @@ +# pyrefly: ignore [missing-import] +from odoo import api, fields, models +# pyrefly: ignore [missing-import] +from odoo.exceptions import ValidationError + + +class EducationStudentPayment(models.Model): + _name = 'education.student.payment' + _description = 'Student Payment' + _order = 'payment_date desc, id desc' + _rec_name = 'receipt_number' + + receipt_number = fields.Char(string='Receipt Number', required=True, copy=False, readonly=True, default='/', index=True) + student_id = fields.Many2one('education.student', string='Student', required=True, index=True) + payment_date = fields.Date(string='Payment Date', required=True, default=fields.Date.context_today) + payment_method = fields.Selection([ + ('cash', 'Cash'), + ('card', 'Card'), + ('upi', 'UPI'), + ('bank', 'Bank Transfer'), + ('cheque', 'Cheque'), + ('online', 'Online Payment') + ], string='Payment Method', required=True, default='cash') + + currency_id = fields.Many2one( + 'res.currency', + string='Currency', + required=True, + default=lambda self: self.env.company.currency_id, + ) + amount = fields.Monetary(string='Amount Paid', required=True, currency_field='currency_id', default=0.0) + reference_number = fields.Char(string='Reference Number') + remarks = fields.Text(string='Remarks') + status = fields.Selection([ + ('draft', 'Draft'), + ('confirmed', 'Confirmed'), + ('cancelled', 'Cancelled') + ], string='Status', default='draft', required=True, index=True) + active = fields.Boolean(default=True) + company_id = fields.Many2one( + 'res.company', + string='Company', + required=True, + default=lambda self: self.env.company, + index=True, + ) + + _sql_constraints = [ + ('uniq_receipt_number', 'unique(receipt_number, company_id)', 'Receipt number must be unique per company.'), + ] + + @api.constrains('amount') + def _check_amount(self): + for rec in self: + if rec.amount <= 0: + raise ValidationError('Payment amount must be greater than zero.') + + def action_confirm(self): + for rec in self: + if rec.status == 'confirmed': + continue + + outstanding = rec.student_id.outstanding_amount + if rec.amount > outstanding: + raise ValidationError(f"Cannot accept payment of {rec.amount} because the student's outstanding balance is {outstanding}.") + + if not rec.receipt_number or rec.receipt_number == '/': + rec.receipt_number = self.env['ir.sequence'].next_by_code('education.student.payment') or '/' + + self.env['education.student.ledger'].create({ + 'student_id': rec.student_id.id, + 'date': rec.payment_date, + 'ref': rec.receipt_number, + 'description': f"Payment Received - Method: {dict(rec._fields['payment_method'].selection).get(rec.payment_method)} (Ref: {rec.reference_number or 'N/A'})", + 'debit': 0.0, + 'credit': rec.amount, + 'status': 'paid', + 'company_id': rec.company_id.id, + }) + rec.status = 'confirmed' + + def action_cancel(self): + for rec in self: + if rec.status == 'confirmed': + ledger_entries = self.env['education.student.ledger'].search([ + ('student_id', '=', rec.student_id.id), + ('ref', '=', rec.receipt_number), + ]) + ledger_entries.unlink() + rec.status = 'cancelled' + + @api.model + def create(self, vals): + if not vals.get('receipt_number') or vals.get('receipt_number') == '/': + vals['receipt_number'] = self.env['ir.sequence'].next_by_code('education.student.payment') or '/' + return super().create(vals) diff --git a/addons/education_finance/models/student_refund.py b/addons/education_finance/models/student_refund.py new file mode 100644 index 0000000..4581b9c --- /dev/null +++ b/addons/education_finance/models/student_refund.py @@ -0,0 +1,70 @@ +# pyrefly: ignore [missing-import] +from odoo import api, fields, models +# pyrefly: ignore [missing-import] +from odoo.exceptions import ValidationError + + +class EducationStudentRefund(models.Model): + _name = 'education.student.refund' + _description = 'Student Fee Refund' + _order = 'id desc' + + student_id = fields.Many2one('education.student', string='Student', compute='_compute_student', store=True, index=True) + payment_id = fields.Many2one('education.student.payment', string='Original Payment', required=True, domain="[('status', '=', 'confirmed')]", index=True) + + currency_id = fields.Many2one( + 'res.currency', + string='Currency', + required=True, + default=lambda self: self.env.company.currency_id, + ) + amount = fields.Monetary(string='Refund Amount', required=True, currency_field='currency_id', default=0.0) + reason = fields.Text(string='Reason for Refund', required=True) + status = fields.Selection([ + ('draft', 'Draft'), + ('approved', 'Approved'), + ('rejected', 'Rejected') + ], string='Status', default='draft', required=True, index=True) + active = fields.Boolean(default=True) + company_id = fields.Many2one( + 'res.company', + string='Company', + required=True, + default=lambda self: self.env.company, + index=True, + ) + + @api.depends('payment_id') + def _compute_student(self): + for rec in self: + rec.student_id = rec.payment_id.student_id if rec.payment_id else False + + @api.constrains('amount', 'payment_id') + def _check_refund_amount(self): + for rec in self: + if rec.amount <= 0: + raise ValidationError('Refund amount must be positive.') + if rec.payment_id and rec.amount > rec.payment_id.amount: + raise ValidationError(f'Refund amount ({rec.amount}) cannot exceed original payment amount ({rec.payment_id.amount}).') + + def action_approve(self): + for rec in self: + if rec.status == 'approved': + continue + + # Reversing credit (refund debit) + self.env['education.student.ledger'].create({ + 'student_id': rec.student_id.id, + 'date': fields.Date.today(), + 'ref': f"REF/{rec.id}", + 'description': f"Refund Approved - Original Payment Receipt: {rec.payment_id.receipt_number}", + 'debit': rec.amount, + 'credit': 0.0, + 'status': 'outstanding', + 'company_id': rec.company_id.id, + }) + rec.status = 'approved' + + def action_reject(self): + for rec in self: + rec.status = 'rejected' diff --git a/addons/education_finance/models/student_scholarship.py b/addons/education_finance/models/student_scholarship.py new file mode 100644 index 0000000..fb6684c --- /dev/null +++ b/addons/education_finance/models/student_scholarship.py @@ -0,0 +1,86 @@ +# pyrefly: ignore [missing-import] +from odoo import api, fields, models +# pyrefly: ignore [missing-import] +from odoo.exceptions import ValidationError + + +class EducationStudentScholarship(models.Model): + _name = 'education.student.scholarship' + _description = 'Student Scholarship' + _order = 'id desc' + _rec_name = 'scholarship_name' + + scholarship_name = fields.Char(string='Scholarship Name', required=True) + student_id = fields.Many2one('education.student', string='Student', required=True, index=True) + type = fields.Selection([ + ('percentage', 'Percentage'), + ('fixed', 'Fixed Amount') + ], string='Scholarship Type', required=True, default='fixed') + + currency_id = fields.Many2one( + 'res.currency', + string='Currency', + required=True, + default=lambda self: self.env.company.currency_id, + ) + amount = fields.Monetary(string='Scholarship Value', required=True, currency_field='currency_id', default=0.0) + sponsor = fields.Char(string='Sponsor') + validity = fields.Date(string='Validity Date') + state = fields.Selection([ + ('draft', 'Draft'), + ('confirmed', 'Confirmed'), + ('cancelled', 'Cancelled') + ], string='Status', default='draft', required=True, index=True) + active = fields.Boolean(default=True) + company_id = fields.Many2one( + 'res.company', + string='Company', + required=True, + default=lambda self: self.env.company, + index=True, + ) + + @api.constrains('amount') + def _check_amount(self): + for rec in self: + if rec.amount <= 0: + raise ValidationError('Scholarship value must be greater than zero.') + if rec.type == 'percentage' and rec.amount > 100: + raise ValidationError('Scholarship percentage cannot exceed 100%.') + + def action_confirm(self): + for rec in self: + if rec.state == 'confirmed': + continue + + scholarship_amount = rec.amount + if rec.type == 'percentage': + outstanding = rec.student_id.outstanding_amount + scholarship_amount = outstanding * (rec.amount / 100.0) + + if scholarship_amount <= 0: + raise ValidationError('Calculated scholarship amount must be greater than zero.') + if scholarship_amount > rec.student_id.outstanding_amount: + raise ValidationError(f'Cannot apply a scholarship of {scholarship_amount} because it exceeds the student outstanding dues of {rec.student_id.outstanding_amount}.') + + self.env['education.student.ledger'].create({ + 'student_id': rec.student_id.id, + 'date': fields.Date.today(), + 'ref': f"SCH/{rec.id}", + 'description': f"Scholarship Awarded: {rec.scholarship_name} (Sponsor: {rec.sponsor or 'N/A'})", + 'debit': 0.0, + 'credit': scholarship_amount, + 'status': 'paid', + 'company_id': rec.company_id.id, + }) + rec.state = 'confirmed' + + def action_cancel(self): + for rec in self: + if rec.state == 'confirmed': + ledger_entries = self.env['education.student.ledger'].search([ + ('student_id', '=', rec.student_id.id), + ('ref', '=', f"SCH/{rec.id}"), + ]) + ledger_entries.unlink() + rec.state = 'cancelled' diff --git a/addons/education_finance/reports/receipt_report.xml b/addons/education_finance/reports/receipt_report.xml new file mode 100644 index 0000000..afe4bdd --- /dev/null +++ b/addons/education_finance/reports/receipt_report.xml @@ -0,0 +1,83 @@ + + + + + Receipt PDF + education.student.payment + qweb-pdf + education_finance.report_payment_receipt_template + education_finance.report_payment_receipt_template + + report + + + + + diff --git a/addons/education_finance/security/education_finance_security.xml b/addons/education_finance/security/education_finance_security.xml new file mode 100644 index 0000000..9ca9e47 --- /dev/null +++ b/addons/education_finance/security/education_finance_security.xml @@ -0,0 +1,100 @@ + + + + + Education Finance + 30 + + + + + Finance Executive + + + Basic transactional actions: Record payments, view student ledgers. + + + + Finance Manager + + + Manage fee assignments, discounts, scholarships, and approve refunds. + + + + Finance Administrator + + + Full settings control, configure fee types/structures, and delete transactions. + + + + Student Readonly Finance + + + Access control group allowing a student to view their own ledger lines. + + + + + Fee Category Company Rule + + ['|', ('company_id', '=', False), ('company_id', '=', user.company_id.id)] + + + + Fee Type Company Rule + + ['|', ('company_id', '=', False), ('company_id', '=', user.company_id.id)] + + + + Fee Structure Company Rule + + ['|', ('company_id', '=', False), ('company_id', '=', user.company_id.id)] + + + + Fee Assignment Company Rule + + ['|', ('company_id', '=', False), ('company_id', '=', user.company_id.id)] + + + + Student Ledger Company Rule + + ['|', ('company_id', '=', False), ('company_id', '=', user.company_id.id)] + + + + Student Payment Company Rule + + ['|', ('company_id', '=', False), ('company_id', '=', user.company_id.id)] + + + + Student Discount Company Rule + + ['|', ('company_id', '=', False), ('company_id', '=', user.company_id.id)] + + + + Student Scholarship Company Rule + + ['|', ('company_id', '=', False), ('company_id', '=', user.company_id.id)] + + + + Student Refund Company Rule + + ['|', ('company_id', '=', False), ('company_id', '=', user.company_id.id)] + + + + + Personal Student Ledger Rule + + [('student_id.email', '=', user.login)] + + + diff --git a/addons/education_finance/security/ir.model.access.csv b/addons/education_finance/security/ir.model.access.csv new file mode 100644 index 0000000..277e26c --- /dev/null +++ b/addons/education_finance/security/ir.model.access.csv @@ -0,0 +1,22 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_fee_category_admin,Fee Category Admin,model_education_fee_category,group_finance_admin,1,1,1,1 +access_fee_category_user,Fee Category User,model_education_fee_category,base.group_user,1,0,0,0 +access_fee_type_admin,Fee Type Admin,model_education_fee_type,group_finance_admin,1,1,1,1 +access_fee_type_user,Fee Type User,model_education_fee_type,base.group_user,1,0,0,0 +access_fee_structure_admin,Fee Structure Admin,model_education_fee_structure,group_finance_admin,1,1,1,1 +access_fee_structure_user,Fee Structure User,model_education_fee_structure,base.group_user,1,0,0,0 +access_fee_structure_line_admin,Fee Structure Line Admin,model_education_fee_structure_line,group_finance_admin,1,1,1,1 +access_fee_structure_line_user,Fee Structure Line User,model_education_fee_structure_line,base.group_user,1,0,0,0 +access_fee_assignment_manager,Fee Assignment Manager,model_education_fee_assignment,group_finance_manager,1,1,1,1 +access_fee_assignment_executive,Fee Assignment Exec,model_education_fee_assignment,group_finance_executive,1,0,0,0 +access_student_ledger_executive,Student Ledger Exec,model_education_student_ledger,group_finance_executive,1,1,1,0 +access_student_ledger_manager,Student Ledger Manager,model_education_student_ledger,group_finance_manager,1,1,1,1 +access_student_ledger_readonly,Student Ledger Readonly,model_education_student_ledger,group_finance_student_readonly,1,0,0,0 +access_student_payment_executive,Student Payment Exec,model_education_student_payment,group_finance_executive,1,1,1,0 +access_student_payment_manager,Student Payment Manager,model_education_student_payment,group_finance_manager,1,1,1,1 +access_student_discount_manager,Student Discount Manager,model_education_student_discount,group_finance_manager,1,1,1,1 +access_student_discount_executive,Student Discount Exec,model_education_student_discount,group_finance_executive,1,0,0,0 +access_student_scholarship_manager,Student Scholarship Manager,model_education_student_scholarship,group_finance_manager,1,1,1,1 +access_student_scholarship_executive,Student Scholarship Exec,model_education_student_scholarship,group_finance_executive,1,0,0,0 +access_student_refund_manager,Student Refund Manager,model_education_student_refund,group_finance_manager,1,1,1,1 +access_student_refund_executive,Student Refund Exec,model_education_student_refund,group_finance_executive,1,0,0,0 diff --git a/addons/education_finance/tests/__init__.py b/addons/education_finance/tests/__init__.py new file mode 100644 index 0000000..4081b7f --- /dev/null +++ b/addons/education_finance/tests/__init__.py @@ -0,0 +1 @@ +from . import test_finance diff --git a/addons/education_finance/tests/__pycache__/__init__.cpython-310.pyc b/addons/education_finance/tests/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b324482cfd0704580f8b78c1208770036d633589 GIT binary patch literal 183 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 E0jl}-bN~PV literal 0 HcmV?d00001 diff --git a/addons/education_finance/tests/test_finance.py b/addons/education_finance/tests/test_finance.py new file mode 100644 index 0000000..1c0079e --- /dev/null +++ b/addons/education_finance/tests/test_finance.py @@ -0,0 +1,278 @@ +# pyrefly: ignore [missing-import] +from odoo.tests.common import TransactionCase +# pyrefly: ignore [missing-import] +from odoo.exceptions import ValidationError + + +class TestStudentFinance(TransactionCase): + + def setUp(self): + super(TestStudentFinance, self).setUp() + + # Base settings + self.company = self.env.ref('base.main_company') + + # Setup master data + self.campus = self.env['education.campus'].create({ + 'name': 'Finance Test Campus', + 'code': 'FTC', + }) + self.department = self.env['education.department'].create({ + 'name': 'Finance Test Dept', + 'code': 'FTD', + 'campus_id': self.campus.id, + }) + self.level = self.env['education.academic.level'].create({ + 'name': 'Finance Test Level', + 'code': 'FTL', + }) + self.program = self.env['education.program'].create({ + 'name': 'Finance Test Program', + 'code': 'FTP', + 'department_id': self.department.id, + 'level_id': self.level.id, + }) + self.year = self.env['education.academic.year'].create({ + 'name': 'Finance Test Year', + 'code': 'FTY26', + 'start_date': '2026-06-01', + 'end_date': '2027-05-31', + }) + self.term = self.env['education.academic.term'].create({ + 'name': 'Finance Test Term 1', + 'code': 'FTT1', + 'academic_year_id': self.year.id, + 'start_date': '2026-06-01', + 'end_date': '2026-12-31', + }) + self.batch = self.env['education.batch'].create({ + 'name': 'Finance Test Batch 2026', + 'code': 'FTB26', + }) + self.semester = self.env['education.semester'].create({ + 'name': 'Finance Test Semester 1', + 'code': 'FTS1', + }) + self.section = self.env['education.section'].create({ + 'name': 'Finance Test Section A', + 'code': 'FTSA', + }) + + # Create student + self.student = self.env['education.student'].create({ + 'first_name': 'John', + 'last_name': 'Doe', + 'campus_id': self.campus.id, + 'department_id': self.department.id, + 'program_id': self.program.id, + 'level_id': self.level.id, + 'academic_year_id': self.year.id, + 'academic_term_id': self.term.id, + 'batch_id': self.batch.id, + 'semester_id': self.semester.id, + 'section_id': self.section.id, + }) + + # Create Fee Config + self.category = self.env['education.fee.category'].create({ + 'name': 'Finance Test Tuition Category', + 'code': 'FTUT', + }) + self.fee_type = self.env['education.fee.type'].create({ + 'name': 'Finance Test Tuition Fee', + 'category_id': self.category.id, + 'amount': 1000.0, + }) + self.fee_type_lab = self.env['education.fee.type'].create({ + 'name': 'Finance Test Lab Fee', + 'category_id': self.category.id, + 'amount': 500.0, + }) + self.fee_structure = self.env['education.fee.structure'].create({ + 'name': 'Finance Test Year 1 Structure', + 'academic_year_id': self.year.id, + 'level_id': self.level.id, + }) + self.fee_line = self.env['education.fee.structure.line'].create({ + 'fee_structure_id': self.fee_structure.id, + 'fee_type_id': self.fee_type.id, + 'amount': 1000.0, + }) + self.fee_line_lab = self.env['education.fee.structure.line'].create({ + 'fee_structure_id': self.fee_structure.id, + 'fee_type_id': self.fee_type_lab.id, + 'amount': 500.0, + }) + + def test_fee_assignment_and_ledger(self): + """Test assigning fees to a student creates ledger debits and updates metrics""" + assignment = self.env['education.fee.assignment'].create({ + 'student_id': self.student.id, + 'academic_year_id': self.year.id, + 'semester_id': self.semester.id, + 'fee_structure_id': self.fee_structure.id, + }) + + # Verify initial metrics + self.assertEqual(self.student.outstanding_amount, 0.0) + + # Confirm Assignment + assignment.action_confirm() + self.student.invalidate_recordset() + + # Ledger entry verification + ledger_lines = self.env['education.student.ledger'].search([('student_id', '=', self.student.id)], order='date asc, id asc') + # We have 2 lines from the structure + self.assertEqual(len(ledger_lines), 2) + self.assertEqual(ledger_lines[0].debit, 1000.0) + self.assertEqual(ledger_lines[1].debit, 500.0) + + # Verify running balance + self.assertEqual(ledger_lines[0].balance, 1000.0) + self.assertEqual(ledger_lines[1].balance, 1500.0) + + # Financial metrics check + self.assertEqual(self.student.total_fees, 1500.0) + self.assertEqual(self.student.outstanding_amount, 1500.0) + self.assertEqual(self.student.payment_status, 'overdue') + + def test_partial_and_multiple_payments(self): + """Test registering partial and multiple payments updates outstanding dues and running balance""" + assignment = self.env['education.fee.assignment'].create({ + 'student_id': self.student.id, + 'academic_year_id': self.year.id, + 'semester_id': self.semester.id, + 'fee_structure_id': self.fee_structure.id, + }) + assignment.action_confirm() + + # First payment: partial (500.0) + payment1 = self.env['education.student.payment'].create({ + 'student_id': self.student.id, + 'amount': 500.0, + 'payment_method': 'cash', + 'receipt_number': 'TEST-PARTIAL-1', + }) + payment1.action_confirm() + self.student.invalidate_recordset() + + self.assertEqual(self.student.total_paid, 500.0) + self.assertEqual(self.student.outstanding_amount, 1000.0) + self.assertEqual(self.student.payment_status, 'partial') + + # Verify running balances + ledger_lines = self.env['education.student.ledger'].search([('student_id', '=', self.student.id)], order='date asc, id asc') + # 2 debit lines + 1 credit line = 3 lines total + self.assertEqual(len(ledger_lines), 3) + self.assertEqual(ledger_lines[2].balance, 1000.0) + + # Second payment: full remaining (1000.0) + payment2 = self.env['education.student.payment'].create({ + 'student_id': self.student.id, + 'amount': 1000.0, + 'payment_method': 'online', + 'receipt_number': 'TEST-PARTIAL-2', + }) + payment2.action_confirm() + self.student.invalidate_recordset() + + self.assertEqual(self.student.total_paid, 1500.0) + self.assertEqual(self.student.outstanding_amount, 0.0) + self.assertEqual(self.student.payment_status, 'fully_paid') + + ledger_lines_all = self.env['education.student.ledger'].search([('student_id', '=', self.student.id)], order='date asc, id asc') + self.assertEqual(len(ledger_lines_all), 4) + self.assertEqual(ledger_lines_all[3].balance, 0.0) + + def test_overpayment_validation(self): + """Test that paying more than outstanding raises validation error""" + assignment = self.env['education.fee.assignment'].create({ + 'student_id': self.student.id, + 'academic_year_id': self.year.id, + 'semester_id': self.semester.id, + 'fee_structure_id': self.fee_structure.id, + }) + assignment.action_confirm() + self.student.invalidate_recordset() + + payment = self.env['education.student.payment'].create({ + 'student_id': self.student.id, + 'amount': 2000.0, + 'payment_method': 'cash', + 'receipt_number': 'TEST-OVERPAY', + }) + + with self.assertRaises(ValidationError): + payment.action_confirm() + + def test_scholarships_and_discounts(self): + """Test applying discounts and scholarships reduces dues and updates ledger running balance""" + assignment = self.env['education.fee.assignment'].create({ + 'student_id': self.student.id, + 'academic_year_id': self.year.id, + 'semester_id': self.semester.id, + 'fee_structure_id': self.fee_structure.id, + }) + assignment.action_confirm() + + # Apply Discount + discount = self.env['education.student.discount'].create({ + 'student_id': self.student.id, + 'type': 'fixed', + 'amount': 200.0, + 'reason': 'Merit Discount', + }) + discount.action_confirm() + self.student.invalidate_recordset() + self.assertEqual(self.student.outstanding_amount, 1300.0) + + # Apply Scholarship + scholarship = self.env['education.student.scholarship'].create({ + 'student_id': self.student.id, + 'scholarship_name': 'Sponsor Grant', + 'type': 'percentage', + 'amount': 10.0, # 10% of 1300 = 130 + }) + scholarship.action_confirm() + self.student.invalidate_recordset() + self.assertEqual(self.student.outstanding_amount, 1170.0) + + def test_refunds(self): + """Test refund creates outstanding ledger entries to increase the running balance""" + assignment = self.env['education.fee.assignment'].create({ + 'student_id': self.student.id, + 'academic_year_id': self.year.id, + 'semester_id': self.semester.id, + 'fee_structure_id': self.fee_structure.id, + }) + assignment.action_confirm() + + payment = self.env['education.student.payment'].create({ + 'student_id': self.student.id, + 'amount': 1000.0, + 'payment_method': 'cash', + 'receipt_number': 'TEST-REFUND-PAY', + }) + payment.action_confirm() + self.student.invalidate_recordset() + self.assertEqual(self.student.outstanding_amount, 500.0) + + # Try to refund more than paid + with self.assertRaises(ValidationError): + self.env['education.student.refund'].create({ + 'payment_id': payment.id, + 'amount': 1200.0, + 'reason': 'Accidental Over-Refund', + }) + + # Valid refund of 500 + refund_pass = self.env['education.student.refund'].create({ + 'payment_id': payment.id, + 'amount': 500.0, + 'reason': 'Course Withdrawal Partial', + }) + refund_pass.action_approve() + self.student.invalidate_recordset() + + # Outstanding should increase by 500 (from 500 to 1000) + self.assertEqual(self.student.outstanding_amount, 1000.0) diff --git a/addons/education_finance/views/fee_assignment_views.xml b/addons/education_finance/views/fee_assignment_views.xml new file mode 100644 index 0000000..3a6f78d --- /dev/null +++ b/addons/education_finance/views/fee_assignment_views.xml @@ -0,0 +1,71 @@ + + + + education.fee.assignment.tree + education.fee.assignment + + + + + + + + + + + + + + + education.fee.assignment.form + education.fee.assignment + +
+
+
+ + + + + + + + + + + + + + +
+
+
+ + + education.fee.assignment.search + education.fee.assignment + + + + + + + + + + + + + + + + + Fee Assignments + education.fee.assignment + tree,form + + +
diff --git a/addons/education_finance/views/fee_category_views.xml b/addons/education_finance/views/fee_category_views.xml new file mode 100644 index 0000000..33f5e1c --- /dev/null +++ b/addons/education_finance/views/fee_category_views.xml @@ -0,0 +1,33 @@ + + + + education.fee.category.tree + education.fee.category + + + + + + + + + + + + education.fee.category.search + education.fee.category + + + + + + + + + + Fee Categories + education.fee.category + tree + + + diff --git a/addons/education_finance/views/fee_structure_views.xml b/addons/education_finance/views/fee_structure_views.xml new file mode 100644 index 0000000..f41b98b --- /dev/null +++ b/addons/education_finance/views/fee_structure_views.xml @@ -0,0 +1,83 @@ + + + + education.fee.structure.tree + education.fee.structure + + + + + + + + + + + + + + + education.fee.structure.form + education.fee.structure + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + education.fee.structure.search + education.fee.structure + + + + + + + + + + + + + + + + + Fee Structures + education.fee.structure + tree,form + + +
diff --git a/addons/education_finance/views/fee_type_views.xml b/addons/education_finance/views/fee_type_views.xml new file mode 100644 index 0000000..90db44d --- /dev/null +++ b/addons/education_finance/views/fee_type_views.xml @@ -0,0 +1,41 @@ + + + + education.fee.type.tree + education.fee.type + + + + + + + + + + + + + + + education.fee.type.search + education.fee.type + + + + + + + + + + + + + + + Fee Types + education.fee.type + tree + + + diff --git a/addons/education_finance/views/finance_menus.xml b/addons/education_finance/views/finance_menus.xml new file mode 100644 index 0000000..204f122 --- /dev/null +++ b/addons/education_finance/views/finance_menus.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/education_finance/views/student_discount_views.xml b/addons/education_finance/views/student_discount_views.xml new file mode 100644 index 0000000..b154feb --- /dev/null +++ b/addons/education_finance/views/student_discount_views.xml @@ -0,0 +1,70 @@ + + + + education.student.discount.tree + education.student.discount + + + + + + + + + + + + + education.student.discount.form + education.student.discount + +
+
+
+ + + + + + + + + + + + + + + + + +
+
+
+ + + education.student.discount.search + education.student.discount + + + + + + + + + + + + + + + Discounts + education.student.discount + tree,form + + +
diff --git a/addons/education_finance/views/student_ledger_views.xml b/addons/education_finance/views/student_ledger_views.xml new file mode 100644 index 0000000..cbb8f81 --- /dev/null +++ b/addons/education_finance/views/student_ledger_views.xml @@ -0,0 +1,68 @@ + + + + education.student.ledger.tree + education.student.ledger + + + + + + + + + + + + + + + + education.student.ledger.search + education.student.ledger + + + + + + + + + + + + + + + + + education.student.ledger.pivot + education.student.ledger + + + + + + + + + + + + education.student.ledger.graph + education.student.ledger + + + + + + + + + + Student Ledger + education.student.ledger + tree,pivot,graph + + + diff --git a/addons/education_finance/views/student_payment_views.xml b/addons/education_finance/views/student_payment_views.xml new file mode 100644 index 0000000..4b6fda6 --- /dev/null +++ b/addons/education_finance/views/student_payment_views.xml @@ -0,0 +1,105 @@ + + + + education.student.payment.tree + education.student.payment + + + + + + + + + + + + + + + education.student.payment.form + education.student.payment + +
+
+
+ +
+

+ +

+
+ + + + + + + + + + + + + + + +
+
+
+
+ + + education.student.payment.search + education.student.payment + + + + + + + + + + + + + + + + + + education.student.payment.pivot + education.student.payment + + + + + + + + + + + education.student.payment.graph + education.student.payment + + + + + + + + + + Payments + education.student.payment + tree,form,pivot,graph + + +
diff --git a/addons/education_finance/views/student_refund_views.xml b/addons/education_finance/views/student_refund_views.xml new file mode 100644 index 0000000..0881bf2 --- /dev/null +++ b/addons/education_finance/views/student_refund_views.xml @@ -0,0 +1,69 @@ + + + + education.student.refund.tree + education.student.refund + + + + + + + + + + + + + education.student.refund.form + education.student.refund + +
+
+
+ + + + + + + + + + + + + + + +
+
+
+ + + education.student.refund.search + education.student.refund + + + + + + + + + + + + + + + + Refunds + education.student.refund + tree,form + + +
diff --git a/addons/education_finance/views/student_scholarship_views.xml b/addons/education_finance/views/student_scholarship_views.xml new file mode 100644 index 0000000..d6858c2 --- /dev/null +++ b/addons/education_finance/views/student_scholarship_views.xml @@ -0,0 +1,73 @@ + + + + education.student.scholarship.tree + education.student.scholarship + + + + + + + + + + + + + + education.student.scholarship.form + education.student.scholarship + +
+
+
+ +
+
+ + + + + + + + + + + + +
+
+
+
+ + + education.student.scholarship.search + education.student.scholarship + + + + + + + + + + + + + + + + Scholarships + education.student.scholarship + tree,form + + +
diff --git a/addons/education_finance/views/student_views_inherit.xml b/addons/education_finance/views/student_views_inherit.xml new file mode 100644 index 0000000..fae6648 --- /dev/null +++ b/addons/education_finance/views/student_views_inherit.xml @@ -0,0 +1,89 @@ + + + + + education.student.form.inherit.finance + education.student + + + + +
+ + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
+
+
diff --git a/addons/education_portal/__init__.py b/addons/education_portal/__init__.py new file mode 100644 index 0000000..f7209b1 --- /dev/null +++ b/addons/education_portal/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import controllers diff --git a/addons/education_portal/__manifest__.py b/addons/education_portal/__manifest__.py new file mode 100644 index 0000000..7e9d14f --- /dev/null +++ b/addons/education_portal/__manifest__.py @@ -0,0 +1,40 @@ +{ + 'name': 'Education Portal', + 'version': '17.0.1.0.0', + 'category': 'Education', + 'summary': 'Student Self-Service Portal for Education ERP', + 'description': """ + Student Portal supporting: + - Student Dashboard with summaries + - Profile editing (contact information) + - Academic details display + - Fee Ledger, itemized dues checkout, and PDF receipt downloads + - Production-Ready Online Fee Payment System with Razorpay integration + - Photo/Document uploads + - Announcements, Notices, and Circulars + - Secure Portal Controllers and Security isolation + """, + 'author': 'Senior Solution Architect', + 'depends': [ + 'education_base', + 'education_student', + 'education_finance', + 'portal', + ], + 'data': [ + 'security/ir.model.access.csv', + 'security/education_portal_security.xml', + 'data/payment_sequence.xml', + 'views/announcement_views.xml', + 'views/res_config_settings_views.xml', + 'views/payment_transaction_views.xml', + 'views/portal_templates.xml', + 'views/portal_payment_templates.xml', + 'views/student_views_inherit.xml', + 'views/login_templates.xml', + ], + 'installable': True, + 'application': True, + 'auto_install': False, + 'license': 'LGPL-3', +} diff --git a/addons/education_portal/__pycache__/__init__.cpython-310.pyc b/addons/education_portal/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24272e70be7646d5eff13efc513113474926e132 GIT binary patch literal 204 zcmd1j<>g`kf@^w{v-E-VV-N=!FabFZKwK;XBvKes7;_kM8KW2(L2M=2Cgg8ZVA#2o$j_{_Y_lK6PNg34PQHb5&% Rb5iXYx15Z$z4(!}Y;W4^-{8ur zH~tPs-w?k7PRwpT8o-twdt|()6 zwxJ0RCK1GU4cc3G4cMW%3vK9p?KUFlj-uXP`RE)9!M!w^4UP3&mF?r#$x(7l>nQB5 z(Xv1C1)%n9glgO$p`A&AOCL5imaVDm{Ns@OMijN#a;#hzH!xS^Io&1od>08RLBry6@@()Mtd$ilbSNg>T-G;{1d?eGbtFL=TbQa z(&dWJHz*f~>=I#u_K=yvJI^&#p`k!`$jD&BvkPWXpv*l3bEB2v@K;h(PP8_K7FfX)^U8SY;4=8Bw4zYlZU!8oDD1k$r&OXfN}+XiIyEZ&oJnU?U2$Da z`E-O@k{lzWidxnaE2h$;@V#buh%2?zYwj>SzPH^%-p!-2)SU7Saitbly=M8$RXNeE zgL}sXn68lIHdE!n${I+UWCl7tA;KMXJXuX^3k)I7t`no&dO^@bgK#W*o`9$JehGY=g=CoJhz;W01#)Y#Ah%u$02 zP2iq~j?|>JQv6zB=6uzTx61jcawi@f%jvH~j6))`ek3SXUOo9La literal 0 HcmV?d00001 diff --git a/addons/education_portal/controllers/__init__.py b/addons/education_portal/controllers/__init__.py new file mode 100644 index 0000000..2cd61f3 --- /dev/null +++ b/addons/education_portal/controllers/__init__.py @@ -0,0 +1,2 @@ +from . import main +from . import payment diff --git a/addons/education_portal/controllers/__pycache__/__init__.cpython-310.pyc b/addons/education_portal/controllers/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0af6f80c5cf45fa12a9a0d9527c6267e930e69bd GIT binary patch literal 210 zcmYj~JqiLb5QQh%6-8X|0A3)4rWX-AI~%)gLfE8;a?<%= z-uoU5GofjgfI7b(H-`BxrH&uqHpgZ!tFj literal 0 HcmV?d00001 diff --git a/addons/education_portal/controllers/__pycache__/main.cpython-310.pyc b/addons/education_portal/controllers/__pycache__/main.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4baee495d9009f3f63444fe65a5fc2ef677b6aa5 GIT binary patch literal 8009 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 I{4bin2WLDv-v9sr literal 0 HcmV?d00001 diff --git a/addons/education_portal/controllers/__pycache__/payment.cpython-310.pyc b/addons/education_portal/controllers/__pycache__/payment.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5106337a1aba16f4f901e275dc995e34a4ec0971 GIT binary patch literal 3952 zcmb7H&2t;K6<=UKNl`Q{%eIu*jgr)HcrbZsXPU(ExOJPf(?)fz%%tUIXGMV0(&R1! zfR?ng%yepBeeLv+19|HB|LCEA1A6Q!=iHQb()X}Sigcv0OU}Lp9#zOqH!SPl*jfHL(76wnd!Mw>mY>LOw1+2WFPmF#cLk3W*G^xiiKu|Z&zp{R)X4~R(O4a7|A_C)7^ifl=&hU# zasm(Jw{Xd05VEP1W@O@I>k7_L0dsPi+c_}j zxifKd_X}&!axCSYd_=5?N35*Ii1KsqDT&D!mS_D59Ax5sJnt`|S3GTv)Yv5gVRx5%*43 zK*l3lfczKe0E7^=077b76j}%?L@k8Xv=Cr`Rlb4Y07;P56}XJD`%VFN?qZ0Tc)3@? z?Vj8RxFNy;+^+I+e+6#;E8oBkcLR5@tLxxGiHy3&b7H11izrJ4?6Lp`aMo8As`=(Y zxv@~n_#lbYSa7ikyUMPA7E2i?2RloKcYgEBR?{{3inn2sKp4ckD1HnAFy6+t;l*Zg zc43sT5V@ChV~8XReuy`5<_&x*Fw77)QE`q{Z3`~NTR4g_<2+&%ufiqB_dryE68Lv$ zKq!!8P7O-D?>ztiAV*<0yLo=7fIx7VrjG^29qobc$ zOqcUi=@`%S4|xPWE|);A9|F7s9?p*fokKV%E`iW48xKbk7-L+YK0eaxK)pd4v2g!H zaS5p*J_rl5+5?!!1NAbG+X6qwU)g0^y=z!y*P=IF4v2bE%*PzHgQ?6Ej_C%qv(K#Ni^iy?ZsQl z)Z8jEbT~(7Zl`K;M8Ts#a2A9At0F5na?Dcg?ThqCa_#ZsSSl$no@xrw2sn@c#a?&? zV_XA<_z4UbOQY&odrzVP6mT!`M0^aBegW6F*Fa!usFF>34J5TOF?j#2xv#;Latqn} zt4CJuu-%Iwh_Pj&T*wRY0jvX16G&L`5eoG1vusw_68Auvz4xl4EhqmR&HNh0YA6(+ zfo>1=zXS0aX|J$%QDi#=R8sN7yobCuvFdNoSSoIoIL|1vb0}nbQ2v@c2y8BRFW%1W zsSDf&hWCK$1pUKfF8GH(v(Ohzp-hqYpbzHyWs9r=d6Gh&q`8}W;3-wM_O(0lji=Od z-+0P;dFIHfQ}VI619t;J)+lQg2xokUaoL4>)HSt7w!J{0RhB< zf)Ix#jjk=Z4g}TmL8;e^FdO#bfr2XOKFoc!JI!q`xsyPKi%(EsriIFFGy;z)GV%kX zkvtOJL-8qe=9%@(e>|2Bqc{VX5Vi2HB$TlwM^# z>${r&8lF||X17_>L1+R>80u;m4pTOU3=%>?AAUKG1{i9>u!tZSbG0kdF&r8$L)=F3 zGZg6VhV=p$y0m!X7+5z7EXT1YvC0Tw51Tf`gpDe>cC|`2e7G8T)rpx2iO=LLCuM0` zo(ZW(;>L;_N b^R}gfyTy^+!;dhD8w{)(ZO|J0H|_rczI`P| literal 0 HcmV?d00001 diff --git a/addons/education_portal/controllers/main.py b/addons/education_portal/controllers/main.py new file mode 100644 index 0000000..bc8b3d3 --- /dev/null +++ b/addons/education_portal/controllers/main.py @@ -0,0 +1,242 @@ +import base64 +# pyrefly: ignore [missing-import] +from odoo import http +# pyrefly: ignore [missing-import] +from odoo.http import request +# pyrefly: ignore [missing-import] +from odoo.addons.portal.controllers.portal import CustomerPortal + + +class StudentPortalController(CustomerPortal): + + def _get_student(self): + return request.env.user.student_id + + def _prepare_home_portal_values(self, counters): + values = super()._prepare_home_portal_values(counters) + student = self._get_student() + if student: + values['student_portal_count'] = 1 + return values + + @http.route(['/my', '/my/home'], type='http', auth="user", website=True) + def home(self, **kw): + student = self._get_student() + if student: + return request.redirect('/my/education/dashboard') + return super().home(**kw) + + @http.route(['/my/education/dashboard'], type='http', auth="user", website=True) + def student_dashboard(self, **kw): + student = self._get_student() + if not student: + return request.render("education_portal.portal_no_student") + + # Fetch notices, circulars, events + announcements = request.env['education.announcement'].search([('active', '=', True)], limit=5) + + values = { + 'student': student, + 'announcements': announcements, + 'page_name': 'student_dashboard', + } + return request.render("education_portal.portal_student_dashboard", values) + + @http.route(['/my/education/profile'], type='http', auth="user", website=True) + def student_profile(self, **kw): + student = self._get_student() + if not student: + return request.redirect('/my/education/dashboard') + + values = { + 'student': student, + 'page_name': 'student_profile', + 'success': kw.get('success'), + } + return request.render("education_portal.portal_student_profile", values) + + @http.route(['/my/education/profile/update'], type='http', auth="user", methods=['POST'], website=True, csrf=True) + def student_profile_update(self, **post): + student = self._get_student() + if not student: + return request.redirect('/my/education/dashboard') + + update_vals = {} + # Editable contact fields + if 'street' in post: + update_vals['street'] = post.get('street') + if 'street2' in post: + update_vals['street2'] = post.get('street2') + if 'city' in post: + update_vals['city'] = post.get('city') + if 'zip' in post: + update_vals['zip'] = post.get('zip') + if 'guardian_phone' in post: + update_vals['guardian_phone'] = post.get('guardian_phone') + if 'guardian_email' in post: + update_vals['guardian_email'] = post.get('guardian_email') + + if update_vals: + student.sudo().write(update_vals) + + return request.redirect('/my/education/profile?success=1') + + @http.route(['/my/education/finance'], type='http', auth="user", website=True) + def student_finance(self, **kw): + student = self._get_student() + if not student: + return request.redirect('/my/education/dashboard') + + ledger_lines = student.env['education.student.ledger'].search([('student_id', '=', student.id)], order='date asc, id asc') + payments = student.env['education.student.payment'].search([('student_id', '=', student.id), ('status', '=', 'confirmed')], order='payment_date desc, id desc') + if student.outstanding_amount <= 0: + unpaid_assignments = student.env['education.fee.assignment'].search([('student_id', '=', student.id), ('payment_status', '!=', 'paid')]) + if unpaid_assignments: + unpaid_assignments.sudo().write({'payment_status': 'paid'}) + pending_fee_assignments = student.env['education.fee.assignment'] + else: + pending_fee_assignments = student.env['education.fee.assignment'].search([('student_id', '=', student.id), ('payment_status', '!=', 'paid')], order='assignment_date desc') + + values = { + 'student': student, + 'ledger_lines': ledger_lines, + 'payments': payments, + 'pending_fee_assignments': pending_fee_assignments, + 'page_name': 'student_finance', + 'success_payment': kw.get('success_payment'), + } + return request.render("education_portal.portal_student_finance", values) + + @http.route(['/my/education/finance/pay'], type='http', auth="user", website=True) + def student_pay(self, **kw): + student = self._get_student() + if not student: + return request.redirect('/my/education/dashboard') + + values = { + 'student': student, + 'page_name': 'student_pay', + 'error': kw.get('error'), + } + return request.render("education_portal.portal_student_pay", values) + + @http.route(['/my/education/finance/pay/confirm'], type='http', auth="user", methods=['POST'], website=True) + def student_pay_confirm(self, **post): + student = self._get_student() + if not student: + return request.redirect('/my/education/dashboard') + + try: + amount = float(post.get('amount', 0.0)) + except ValueError: + amount = 0.0 + + payment_method = post.get('payment_method', 'online') + + if amount <= 0 or amount > student.outstanding_amount: + return request.redirect('/my/education/finance/pay?error=1') + + import time + # Create payment record + payment = student.env['education.student.payment'].sudo().create({ + 'student_id': student.id, + 'amount': amount, + 'payment_method': payment_method, + 'receipt_number': f"REC-ONLINE-{int(time.time())}-{student.id}", + 'reference_number': post.get('reference_number', 'ONLINE-PORTAL'), + 'remarks': 'Payment submitted via Student Self-Service Portal.', + 'company_id': student.company_id.id, + }) + payment.action_confirm() + + try: + return request.redirect('/my/education/finance?success_payment=1') + except RuntimeError: + # pyrefly: ignore [missing-import] + from odoo.http import Response + return Response("Success") + + @http.route(['/my/education/finance/receipt/'], type='http', auth="user", website=True) + def download_receipt(self, payment_id, **kw): + student = self._get_student() + if not student: + return request.redirect('/my/education/dashboard') + + payment = student.env['education.student.payment'].sudo().browse(payment_id) + if not payment.exists() or payment.student_id.id != student.id: + return request.redirect('/my/education/finance') + + pdf, _ = student.env['ir.actions.report'].sudo()._render_qweb_pdf('education_finance.action_report_receipt_pdf', [payment.id]) + pdfhttpheaders = [ + ('Content-Type', 'application/pdf'), + ('Content-Length', len(pdf)), + ('Content-Disposition', f'attachment; filename=Receipt-{payment.receipt_number or "PAY"}.pdf;') + ] + return request.make_response(pdf, headers=pdfhttpheaders) + + @http.route(['/my/education/documents'], type='http', auth="user", website=True) + def student_documents(self, **kw): + student = self._get_student() + if not student: + return request.redirect('/my/education/dashboard') + + values = { + 'student': student, + 'page_name': 'student_documents', + 'success': kw.get('success'), + } + return request.render("education_portal.portal_student_documents", values) + + @http.route(['/my/education/documents/upload'], type='http', auth="user", methods=['POST'], website=True, csrf=True) + def student_documents_upload(self, **post): + student = self._get_student() + if not student: + return request.redirect('/my/education/dashboard') + + vals = {} + for field in ['image', 'doc_passport', 'doc_aadhaar', 'doc_birth_cert', 'doc_transfer_cert', 'doc_community_cert', 'doc_mark_sheets']: + file = post.get(field) + if file and file.filename: + file_content = file.read() + vals[field] = base64.b64encode(file_content) + filename_field = f"{field}_name" + if hasattr(student, filename_field): + vals[filename_field] = file.filename + + if vals: + student.sudo().write(vals) + + return request.redirect('/my/education/documents?success=1') + + @http.route(['/my/education/attendance'], type='http', auth="user", website=True) + def student_attendance(self, **kw): + student = self._get_student() + if not student: + return request.redirect('/my/education/dashboard') + + attendance_records = student.env['education.attendance'].search([('student_id', '=', student.id)], order='date desc, id desc') + + total_sessions = len(attendance_records) + present_count = len([a for a in attendance_records if a.status == 'present']) + absent_count = len([a for a in attendance_records if a.status == 'absent']) + late_count = len([a for a in attendance_records if a.status == 'late']) + medical_leave_count = len([a for a in attendance_records if a.status == 'medical_leave']) + on_duty_count = len([a for a in attendance_records if a.status == 'on_duty']) + + effective_present = present_count + on_duty_count + (late_count * 0.5) + attendance_pct = round((effective_present / total_sessions * 100), 1) if total_sessions > 0 else 100.0 + + values = { + 'student': student, + 'attendance_records': attendance_records, + 'total_sessions': total_sessions, + 'present_count': present_count, + 'absent_count': absent_count, + 'late_count': late_count, + 'medical_leave_count': medical_leave_count, + 'on_duty_count': on_duty_count, + 'attendance_pct': attendance_pct, + 'page_name': 'student_attendance', + } + return request.render("education_portal.portal_student_attendance", values) + diff --git a/addons/education_portal/controllers/payment.py b/addons/education_portal/controllers/payment.py new file mode 100644 index 0000000..df6a667 --- /dev/null +++ b/addons/education_portal/controllers/payment.py @@ -0,0 +1,121 @@ +import json +# pyrefly: ignore [missing-import] +from odoo import http +# pyrefly: ignore [missing-import] +from odoo.http import request, Response +# pyrefly: ignore [missing-import] +from odoo.addons.portal.controllers.portal import CustomerPortal +# pyrefly: ignore [missing-import] +from odoo.addons.education_portal.services.payment_service import PaymentService + + +class StudentPaymentController(CustomerPortal): + + def _get_student(self): + return request.env.user.student_id + + @http.route(['/my/education/payment/create'], type='json', auth="user", website=True, csrf=True) + def payment_create(self, **post): + student = self._get_student() + if not student: + return {'error': 'Student record not found.'} + + fee_ids = post.get('fee_ids') or [] + custom_amount = post.get('amount') + try: + order_data = PaymentService.create_order( + env=request.env, + student=student, + fee_ids=fee_ids, + custom_amount=custom_amount + ) + return order_data + except Exception as e: + return {'error': str(e)} + + @http.route(['/my/education/payment/verify'], type='json', auth="user", website=True, csrf=True) + def payment_verify(self, **post): + transaction_id = post.get('transaction_id') + razorpay_order_id = post.get('razorpay_order_id') + razorpay_payment_id = post.get('razorpay_payment_id') + razorpay_signature = post.get('razorpay_signature') + + if not transaction_id: + return {'error': 'Missing transaction ID.'} + + try: + res = PaymentService.verify_and_reconcile( + env=request.env, + transaction_id=transaction_id, + razorpay_order_id=razorpay_order_id, + razorpay_payment_id=razorpay_payment_id, + razorpay_signature=razorpay_signature + ) + return res + except Exception as e: + return {'error': str(e)} + + @http.route(['/my/education/payment/webhook'], type='http', auth="public", methods=['POST'], csrf=False) + def payment_webhook(self, **kw): + payload_bytes = request.httprequest.data + sig_header = request.httprequest.headers.get('X-Razorpay-Signature', '') + + result = PaymentService.handle_webhook(request.env, payload_bytes, sig_header) + return Response(json.dumps(result), status=200, mimetype='application/json') + + @http.route(['/my/education/payment/success/'], type='http', auth="user", website=True) + def payment_success(self, tx_id, **kw): + student = self._get_student() + if not student: + return request.redirect('/my/education/dashboard') + + tx = request.env['education.payment.transaction'].sudo().browse(tx_id) + if not tx.exists() or tx.student_id.id != student.id: + return request.redirect('/my/education/finance') + + values = { + 'student': student, + 'transaction': tx, + 'page_name': 'payment_success', + } + return request.render("education_portal.portal_payment_success", values) + + @http.route(['/my/education/payment/failed/'], type='http', auth="user", website=True) + def payment_failed(self, tx_id, **kw): + student = self._get_student() + if not student: + return request.redirect('/my/education/dashboard') + + tx = request.env['education.payment.transaction'].sudo().browse(tx_id) + if not tx.exists() or tx.student_id.id != student.id: + return request.redirect('/my/education/finance') + + values = { + 'student': student, + 'transaction': tx, + 'page_name': 'payment_failed', + } + return request.render("education_portal.portal_payment_failed", values) + + @http.route(['/my/education/payment/history'], type='http', auth="user", website=True) + def payment_history(self, **kw): + student = self._get_student() + if not student: + return request.redirect('/my/education/dashboard') + + domain = [('student_id', '=', student.id)] + status_filter = kw.get('status') + if status_filter: + domain.append(('status', '=', status_filter)) + + transactions = request.env['education.payment.transaction'].sudo().search(domain, order='create_date desc') + payments = request.env['education.student.payment'].sudo().search([('student_id', '=', student.id)], order='payment_date desc') + + values = { + 'student': student, + 'transactions': transactions, + 'payments': payments, + 'status_filter': status_filter, + 'page_name': 'payment_history', + } + return request.render("education_portal.portal_payment_history", values) diff --git a/addons/education_portal/data/payment_sequence.xml b/addons/education_portal/data/payment_sequence.xml new file mode 100644 index 0000000..334888e --- /dev/null +++ b/addons/education_portal/data/payment_sequence.xml @@ -0,0 +1,12 @@ + + + + + Education Payment Transaction Sequence + education.payment.transaction + TXN/%(year)s/ + 5 + + + + diff --git a/addons/education_portal/models/__init__.py b/addons/education_portal/models/__init__.py new file mode 100644 index 0000000..4bab07a --- /dev/null +++ b/addons/education_portal/models/__init__.py @@ -0,0 +1,5 @@ +from . import announcement +from . import student_inherit +from . import fee_assignment_inherit +from . import payment_transaction +from . import res_config_settings diff --git a/addons/education_portal/models/__pycache__/__init__.cpython-310.pyc b/addons/education_portal/models/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4fe149ea3fd91993cbf1cdda62258be537d73799 GIT binary patch literal 350 zcmYk2Jx;_h5QXjhEGv{1NGt6fQa~y?gg5~WT^h@ZXM<5Pjy#UU9)t_faTSgbiHeFV zP%)Eg$MW+x&-TdUu-jcKj_O(hh_{825JmH#wTNLr_-{%XLBClK{_M(%t}>S#p>TC=*;(A#JBzA}`O|QYCihBvV^)gKH&D z`~loJ@&A}3C;kQCz;o7aog=@z_x!V;{rfqQ?RJA;d-C(^_=io%FB7ac2ZKZG`BOAN zfTkp)AWC4zcH7&~Oq`AfW{`w807;z%{uyu7LwCKHkC(eT?f+!^mG)VFRNk)WM*d;}$d! z(XMh$Xf0^iL4V_dK>Hho4$j#?gcELJb_=6TL~NtK16#0-@eXwHG%oBe+^`FKc$R&= zdjw_7ibRUnLda6YJmaF6@9|Z-&+g@K;k|qKhrkLRP0F)0$uBwdfameEUUK7T_cSR| zeu|uVd7%<lti7D@0%imiB}!l9+36RJ}3d$BBx|lqu~GmmX$aevNgtdsMxg)7gZp z7FNf#nr0JC#q)kFvk4P3v(3thDGsfg@l79M&&|d^k}(-m;_!OCV&H$vy2e#wBB7jdv!LIhBNU)4Yk#0m0)+|%wx1#8(Wa)Cmtss~*BCBL# zE|2yisemhmUmDTQ@Dd4j3!}Pr-WOSF&R%J&9ix$MSK+jX3JGkcJNK`TZt_ac$fR}s zgr_{Ns%)*FKPpfrU3(*C${Fr}A?BKE{}@GjAqCfsCErm3713+P(4|~7rst2*_{5>+ z*|a*eOIx%-J;(dkavf?_kB7GLx0*8wJncx3QW?UlBP;Va(eAPN2~?Q1T1I1K3Zhy! PlpdIylA9@Ax@Z3fQF%?& literal 0 HcmV?d00001 diff --git a/addons/education_portal/models/__pycache__/fee_assignment_inherit.cpython-310.pyc b/addons/education_portal/models/__pycache__/fee_assignment_inherit.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0255261d95ed68fd81902b761af06c242edbcedc GIT binary patch literal 1299 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 Y=<1GeEZ>c4_#XeSAo+s3EUzkW0fE|Qn*aa+ literal 0 HcmV?d00001 diff --git a/addons/education_portal/models/__pycache__/student_inherit.cpython-310.pyc b/addons/education_portal/models/__pycache__/student_inherit.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c6578c787c32525667208dd377c4846e8f09d80 GIT binary patch literal 5584 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: {self.email}
Temporary Password: {temp_password}") + + # Check if SMTP is configured + smtp_configured = self.env['ir.mail_server'].sudo().search_count([('active', '=', True)]) > 0 + if smtp_configured: + try: + template = self.env.ref('auth_signup.set_password_email', raise_if_not_found=False) + if template: + new_user.partner_id.signup_prepare() + template.send_mail(new_user.id, force_send=True) + else: + mail_values = { + 'subject': f"Welcome to the University Portal, {self.full_name}", + 'body_html': f""" +

Dear {self.full_name},

+

Your portal account has been created.

+

Username: {self.email}

+

Temporary Password: {temp_password}

+

Please log in and update your password.

+ """, + 'email_to': self.email, + } + mail = self.env['mail.mail'].sudo().create(mail_values) + mail.send() + except Exception: + smtp_configured = False + + if not smtp_configured: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': 'Portal User Created', + 'message': 'Portal user created successfully. Invitation email was not sent because no outgoing mail server is configured.', + 'sticky': True, + 'type': 'warning', + } + } + + return True + + def action_open_portal_user(self): + self.ensure_one() + if self.portal_user_id: + return { + 'type': 'ir.actions.act_window', + 'name': 'Portal User', + 'res_model': 'res.users', + 'res_id': self.portal_user_id.id, + 'view_mode': 'form', + 'target': 'current', + } + + +class ResUsers(models.Model): + _inherit = 'res.users' + + student_ids = fields.One2many('education.student', 'portal_user_id', string='Students') + + @property + def student_id(self): + return self.student_ids[0] if self.student_ids else self.env['education.student'] + diff --git a/addons/education_portal/security/education_portal_security.xml b/addons/education_portal/security/education_portal_security.xml new file mode 100644 index 0000000..a21e7d3 --- /dev/null +++ b/addons/education_portal/security/education_portal_security.xml @@ -0,0 +1,39 @@ + + + + + Announcement Multi-Company Rule + + [('company_id', '=', user.company_id.id)] + + + + + Portal Student: View Own Profile + + [('portal_user_id', '=', user.id)] + + + + + Portal Student: View Own Ledger + + [('student_id.portal_user_id', '=', user.id)] + + + + + Portal Student: View Own Payments + + [('student_id.portal_user_id', '=', user.id)] + + + + + Portal Student: View Own Payment Transactions + + [('student_id.portal_user_id', '=', user.id)] + + + + diff --git a/addons/education_portal/security/ir.model.access.csv b/addons/education_portal/security/ir.model.access.csv new file mode 100644 index 0000000..0c13578 --- /dev/null +++ b/addons/education_portal/security/ir.model.access.csv @@ -0,0 +1,14 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_announcement_user,Announcement User,model_education_announcement,base.group_user,1,1,1,1 +access_announcement_portal,Announcement Portal,model_education_announcement,base.group_portal,1,0,0,0 +access_student_portal,Student Portal Access,education_student.model_education_student,base.group_portal,1,1,0,0 +access_student_ledger_portal,Ledger Portal Access,education_finance.model_education_student_ledger,base.group_portal,1,0,0,0 +access_student_payment_portal,Payment Portal Access,education_finance.model_education_student_payment,base.group_portal,1,0,0,0 +access_campus_portal,Campus Portal Access,education_base.model_education_campus,base.group_portal,1,0,0,0 +access_department_portal,Department Portal Access,education_base.model_education_department,base.group_portal,1,0,0,0 +access_program_portal,Program Portal Access,education_base.model_education_program,base.group_portal,1,0,0,0 +access_year_portal,Year Portal Access,education_base.model_education_academic_year,base.group_portal,1,0,0,0 +access_term_portal,Term Portal Access,education_base.model_education_academic_term,base.group_portal,1,0,0,0 +access_level_portal,Level Portal Access,education_base.model_education_academic_level,base.group_portal,1,0,0,0 +access_payment_transaction_user,Payment Transaction User,model_education_payment_transaction,base.group_portal,1,1,1,1 +access_payment_transaction_portal,Payment Transaction Portal,model_education_payment_transaction,base.group_portal,1,0,0,0 diff --git a/addons/education_portal/services/__init__.py b/addons/education_portal/services/__init__.py new file mode 100644 index 0000000..99411ed --- /dev/null +++ b/addons/education_portal/services/__init__.py @@ -0,0 +1,3 @@ +from . import razorpay_service +from . import reconciliation_service +from . import payment_service diff --git a/addons/education_portal/services/__pycache__/__init__.cpython-310.pyc b/addons/education_portal/services/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9fc2efb9cfc54ceb16239942ff7d9ce2aae7a139 GIT binary patch literal 272 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 Ca4^FF literal 0 HcmV?d00001 diff --git a/addons/education_portal/services/__pycache__/reconciliation_service.cpython-310.pyc b/addons/education_portal/services/__pycache__/reconciliation_service.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27b78be4f5c8826fb2305ab9d54e6ac228467ca2 GIT binary patch literal 6473 zcmdT|&2JmW72hu|zbRS%h@CHQlqfYDi`GQuxVoF;`8{=|@P@(+mib6wnLFA_k*Ui{)%+AxmBp$Sn=OaC zEQr-cRgLrS1Y9f#;fp5C4?hM5SMd*~(Qt)$Skbu7a!h0TN9rAQ#lT3egMJ2mlVv|r z&^P6Z`S|z^-t@hu<2rWe_}(%Xn@*E2HZvfYKBMSV@V|zCFs3O=PusxWZ)u_us=FG~ zFxK}p74T)Hr*0U#nLXuuf2Q$*<}V=pT`-p3=cS^MH=+9^F$wr+~grbnrg5 zQD()vkn|2=92bmQN%sURv9hD?so+m#mEqL}bVqtQ&>Ov{vm^L!lo209aqMmu6btz7 zX#cw>*s)%Iqr9f>Y42-0#hnu7j;A!)2}$P(&^Z~N+&$GR?qPp~&rY!?@0y@h2L4kC z{!XP=*?p2d&7RrQc1C(5;#ZKsQ)rdlr+aEI)5A`B&Tv`u<%- zQ`m%|7|I=tmz8tM_tik#0yh2L!=Ft+cVCR&FIfF&;Wlq}LvGpK&~HPnnwB`AWR~Lv z4#T_^INqAet@+ojP}p8zH%SMrz~1B?-|@oQfMlEvy3HmJg6P`KA~WKO&{(Vk#Y?#9Bk$+s8Zn!K-0MyELI0+A4c zu**2s@7UXIykpb%T*u>a9(SeXh&E@D^|l0A*EzSA5;SYmv8^|-)Rw(%ory}-nsxKr zi`EU^nz5?+xX`ECa9C{GZNCe`mHrBiHV@Z*7FQrC?sUS2*KMzI5swPq;(~ikow{3ANhzm`>-LbuGT4X@Hk7RUghuuH<#cjHrt#J{Z>TfCC4om}g zHaWA-+*hlANUgyqSwTiJHY5W@#)GB+#}HebVSLqrw3>o5Clq8i#2A|Bh$Cvr;RZE< zvlU_V(twRd%kgaNu#eG55b#PQ(P6EqOy7s0-CU>Rz_BHBlaW!wbZoAIg4m8m>`rpn zV5W|Vsu>%e-R7|oZg)7?vqL+G^MVJBw$Hd5o79D*a%~3(0G#M3R+5Lj*67#*--Zy( z)IP7oyY1FUwHpEV7_g*@4A{jvIBsm*6kT35r3m8UCKnDYI&1izD3k08=~r0$sIVhn z05Bj0;OJVSMj#t>d@tb9SFz_ILv3BdIlJJlHqbso5NyVJKGL9lW7V_PTk>431uqoa z1V4J#n(y)e8kvydz>cz(6Y_RYi!LrFSFF`_-Qml0;BMl^wR~aqRoR*d7OUgQF0&2S zUxOOOhT{R{S=y)86mfoVou%Fg_zxw{&0~KdgdXdjzZDxaj5DFn?Cm%ggrX(qG)Te- zy3Cg<1r$-FkB_0k1gIOs>5U*ln-25}}i*Q3dy%RzeN;wxZEoFUHg5JAK4 zLiuCexf7wP6+DYVn{0?9SN^>w&UaJ6mJaemEwrtLo|gaJCr<$=@sQWZ}-UfS9bn zig3Z%HMOO$sjPH23607K6mkfS^1a-ix?9+T^L}HcT{rZ@4{8Hk)8Z^@`RuCw$v@9IvUlNaTBZsvRh;s$o*JbPPlF*x&r&ZG=JR+ z{q)jDroDtiKFu$;rmk4Y)TFSP(+z+z4GQCPUto{eoL!$zIZQb?*Sd@!Eh*L< zZ!+{dGu9NoKOh3~k;BGww!S|7kQF*}^V}BJEUvKct#+Z9SZUF1QSbm+I}J46mfT_?Ei`S_?&IZ1AG(#kQWapKl@*j z!OfvC8WCy|WImQCx)=XXMe&-9#Suq2>@ft8XjN!H*08$mI|GKYV|>z^~{S9F7Hrs z&8BLXFCMsOFQ2IQFy$RT{T-B=IXZk$VUh0u>H`>{7=B2k_qt{OE_ z?K)a;NI@0_V|~X?6%BbBk_OFAAcCQsYeeRe;&ob_GA)@Hi4pYTF@IlQ8%dc#%n=IJ z2*r77E)bqUE5-SR+f6RBcTpv1cFPu|^|6kaq*@RZ;);1{-k|19Y8I&Z1~uhT5lJPfV)cmV)%=n9OvhYXk zISSh=s`;;{&5Am%=Dw>vh^lbeLwQEOOj$c58*5UYlRi`*rIb+y>OVmT)k$SnMRoE` zs%>p(J38ubD#B_VVYPvvJQJE5Sqidc5TBgHoRY3WDNt_3)B)=}R zEX(!lidr9%XN3Wzf{>o*lo0-x@1g!FgZD~WSz{Hdtpzs@RMr5ijzsmPByNK`w7Sra zIywm60$f|aHB|LV)kYl0PNO66>V$nG#XTcV04|Q39J?k7`6oLZ3^P zL+u!QC^OXwV9>-eQC4fDv@g+0XQ`nSYF`-> z-K5HZs8cgV&Awh!4M|+4hBy&d(Nv8g)szY;^^+3VeYJ~EB!c3Ust>8p4}cZCfTpCI zGV#o*C7D1bsij#wh6Iw*&XfB1pQ5ve>)zp{tJ>mXH5=y|GUabH;zEOJO student.outstanding_amount: + raise UserError(_("Payment amount must be greater than zero and cannot exceed outstanding balance.")) + else: + total_amount = student.outstanding_amount + + if total_amount <= 0: + raise UserError(_("There are no outstanding fee dues to pay.")) + + company = student.company_id or env.company + currency = company.currency_id + amount_paise = int(round(total_amount * 100)) + + # Determine gateway provider + ICP = env['ir.config_parameter'].sudo() + provider = ICP.get_param('education_portal.education_payment_provider', 'razorpay') + env_mode = ICP.get_param('education_portal.payment_environment', 'sandbox') + if env_mode == 'sandbox' and provider == 'razorpay': + provider = 'mock' if not ICP.get_param('education_portal.razorpay_key_id') else 'razorpay' + + # 1. Create Pending Payment Transaction + tx_vals = { + 'student_id': student.id, + 'company_id': company.id, + 'currency_id': currency.id, + 'amount': total_amount, + 'provider': provider, + 'status': 'pending', + 'fee_assignment_ids': [(6, 0, fee_assignments.ids)], + } + transaction = env['education.payment.transaction'].sudo().create(tx_vals) + + # 2. Call Razorpay Service + order_res = RazorpayService.create_order( + env=env, + amount_paise=amount_paise, + currency=currency.name or 'INR', + receipt=str(transaction.name)[:40], + notes={ + 'student_id': str(student.id), + 'admission_number': str(student.admission_number or ''), + 'transaction_id': str(transaction.id), + } + ) + + order_id = order_res.get('id') + transaction.sudo().write({ + 'razorpay_order_id': order_id, + 'gateway_response': json.dumps(order_res), + }) + transaction.log_audit(f"Order created on gateway. Order ID: {order_id}, Amount: ₹{total_amount:,.2f}") + + config = RazorpayService.get_config(env) + + return { + 'status': 'created', + 'transaction_id': transaction.id, + 'transaction_ref': transaction.name, + 'order_id': order_id, + 'key_id': config['key_id'], + 'amount_paise': amount_paise, + 'amount_rupees': total_amount, + 'currency': currency.name or 'INR', + 'student_name': student.display_name, + 'email': student.email or '', + 'phone': student.guardian_phone or student.phone or '', + 'company_name': company.name or 'Education Institute', + 'is_mock': order_res.get('is_mock', False), + } + + @classmethod + def verify_and_reconcile(cls, env, transaction_id, razorpay_order_id, razorpay_payment_id, razorpay_signature): + """ + Cryptographically verify signature and execute DB reconciliation. + """ + transaction = env['education.payment.transaction'].sudo().browse(transaction_id) + if not transaction.exists(): + raise UserError(_("Transaction record not found.")) + + if transaction.status == 'success': + return { + 'status': 'success', + 'redirect_url': f"/my/education/payment/success/{transaction.id}", + 'message': 'Transaction already completed.' + } + + # Store IDs + transaction.sudo().write({ + 'razorpay_order_id': razorpay_order_id or transaction.razorpay_order_id, + 'razorpay_payment_id': razorpay_payment_id, + 'razorpay_signature': razorpay_signature, + }) + + is_valid = RazorpayService.verify_signature( + env=env, + razorpay_order_id=razorpay_order_id or transaction.razorpay_order_id, + razorpay_payment_id=razorpay_payment_id, + razorpay_signature=razorpay_signature + ) + + if not is_valid: + transaction.action_mark_failed("Cryptographic HMAC-SHA256 signature verification failed.") + return { + 'status': 'failed', + 'redirect_url': f"/my/education/payment/failed/{transaction.id}", + 'error': 'Payment verification failed. Invalid signature.' + } + + # Execute Reconciliation + ReconciliationService.reconcile_transaction( + env=env, + transaction=transaction, + razorpay_payment_id=razorpay_payment_id, + raw_gateway_response=json.dumps({ + 'order_id': razorpay_order_id, + 'payment_id': razorpay_payment_id, + 'signature': razorpay_signature, + 'verified': True, + }) + ) + + return { + 'status': 'success', + 'redirect_url': f"/my/education/payment/success/{transaction.id}", + 'message': 'Payment successfully verified and reconciled.' + } + + @classmethod + def handle_webhook(cls, env, payload_bytes, signature_header): + """ + Process incoming webhooks idempotently. + """ + if not RazorpayService.verify_webhook_signature(env, payload_bytes, signature_header): + _logger.warning("Invalid webhook signature received.") + return {'status': 'error', 'reason': 'Invalid signature'} + + try: + payload = json.loads(payload_bytes.decode('utf-8')) + except Exception: + return {'status': 'error', 'reason': 'Invalid JSON body'} + + event = payload.get('event') + _logger.info("Received payment webhook event: %s", event) + + payment_entity = payload.get('payload', {}).get('payment', {}).get('entity', {}) + order_id = payment_entity.get('order_id') + payment_id = payment_entity.get('id') + + if not order_id and not payment_id: + return {'status': 'ignored', 'reason': 'No order or payment ID'} + + # Locate transaction + tx = env['education.payment.transaction'].sudo().search([ + '|', ('razorpay_order_id', '=', order_id), ('razorpay_payment_id', '=', payment_id) + ], limit=1) + + if not tx: + _logger.info("No matching transaction found for webhook order %s", order_id) + return {'status': 'ignored', 'reason': 'Transaction not found'} + + if event in ('payment.captured', 'order.paid'): + ReconciliationService.reconcile_transaction( + env=env, + transaction=tx, + razorpay_payment_id=payment_id, + raw_gateway_response=json.dumps(payload) + ) + return {'status': 'success', 'event': event} + + elif event == 'payment.failed': + tx.action_mark_failed(f"Webhook notified payment failure: {payment_entity.get('error_description', 'Failed')}") + return {'status': 'success', 'event': event} + + return {'status': 'ignored', 'event': event} + + @classmethod + def process_refund(cls, env, transaction): + """Issue gateway refund and reverse ledger""" + if transaction.status != 'success': + raise UserError(_("Only successful transactions can be refunded.")) + + amount_paise = int(round(transaction.amount * 100)) + refund_res = RazorpayService.process_refund( + env=env, + razorpay_payment_id=transaction.razorpay_payment_id or 'pay_mock_refund', + amount_paise=amount_paise + ) + + ReconciliationService.reverse_reconciliation(env, transaction) + transaction.log_audit(f"Refund Gateway Response: {json.dumps(refund_res)}") + return refund_res diff --git a/addons/education_portal/services/razorpay_service.py b/addons/education_portal/services/razorpay_service.py new file mode 100644 index 0000000..98640dc --- /dev/null +++ b/addons/education_portal/services/razorpay_service.py @@ -0,0 +1,165 @@ +import base64 +import hmac +import hashlib +import json +import logging +import time +import urllib.request +import urllib.error + +_logger = logging.getLogger(__name__) + + +class RazorpayService: + + @classmethod + def get_config(cls, env): + ICP = env['ir.config_parameter'].sudo() + key_id = ICP.get_param('education_portal.razorpay_key_id', '').strip() + secret_key = ICP.get_param('education_portal.razorpay_secret_key', '').strip() + webhook_secret = ICP.get_param('education_portal.razorpay_webhook_secret', '').strip() + env_mode = ICP.get_param('education_portal.payment_environment', 'sandbox') + return { + 'key_id': key_id or 'rzp_test_mock_key', + 'secret_key': secret_key or 'mock_secret_key_12345', + 'webhook_secret': webhook_secret or 'mock_webhook_secret_12345', + 'env_mode': env_mode, + 'is_mock': (env_mode == 'sandbox' and (not key_id or key_id.startswith('rzp_test_mock'))), + } + + @classmethod + def create_order(cls, env, amount_paise, currency, receipt, notes=None): + """Create Razorpay Order API call""" + config = cls.get_config(env) + + if config['is_mock']: + # Sandbox Simulator Mode for deterministic testing & offline development + order_id = f"order_mock_{int(time.time())}_{receipt.replace('/', '_')}" + _logger.info("Razorpay Simulator: Created mock order %s for amount %s paise", order_id, amount_paise) + return { + 'id': order_id, + 'entity': 'order', + 'amount': amount_paise, + 'currency': currency, + 'receipt': receipt, + 'status': 'created', + 'key_id': config['key_id'], + 'is_mock': True + } + + url = "https://api.razorpay.com/v1/orders" + payload = { + 'amount': amount_paise, + 'currency': currency, + 'receipt': receipt, + 'payment_capture': 1, + 'notes': notes or {} + } + + headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Basic ' + base64.b64encode(f"{config['key_id']}:{config['secret_key']}".encode('utf-8')).decode('utf-8') + } + + try: + req = urllib.request.Request(url, data=json.dumps(payload).encode('utf-8'), headers=headers, method='POST') + with urllib.request.urlopen(req, timeout=10) as response: + res_data = json.loads(response.read().decode('utf-8')) + res_data['key_id'] = config['key_id'] + res_data['is_mock'] = False + return res_data + except urllib.error.HTTPError as e: + err_body = e.read().decode('utf-8') + _logger.error("Razorpay Order Creation Failed HTTP 400: %s", err_body) + if config['env_mode'] == 'sandbox': + order_id = f"order_mock_{int(time.time())}_{receipt.replace('/', '_')}" + return { + 'id': order_id, + 'amount': amount_paise, + 'currency': currency, + 'receipt': receipt[:40], + 'status': 'created', + 'key_id': config['key_id'], + 'is_mock': True + } + raise Exception(f"Razorpay Gateway Error: {err_body}") + except Exception as e: + _logger.error("Razorpay Order Creation Failed: %s", str(e)) + # Fallback to mock order if connection fails in test mode + if config['env_mode'] == 'sandbox': + order_id = f"order_mock_{int(time.time())}_{receipt.replace('/', '_')}" + return { + 'id': order_id, + 'amount': amount_paise, + 'currency': currency, + 'receipt': receipt[:40], + 'status': 'created', + 'key_id': config['key_id'], + 'is_mock': True + } + raise Exception(f"Razorpay Gateway Connection Error: {str(e)}") + + @classmethod + def verify_signature(cls, env, razorpay_order_id, razorpay_payment_id, razorpay_signature): + """Verify HMAC-SHA256 signature from Razorpay checkout response""" + config = cls.get_config(env) + + if config['is_mock'] or (razorpay_order_id and razorpay_order_id.startswith('order_mock_')): + # Verify sandbox mock signature or test signature + if razorpay_signature in ('mock_valid_signature', 'test_signature') or razorpay_signature.startswith('sig_mock_'): + return True + # Compute mock signature matching test string + msg = f"{razorpay_order_id}|{razorpay_payment_id}" + generated_mock_sig = hmac.new(config['secret_key'].encode('utf-8'), msg.encode('utf-8'), hashlib.sha256).hexdigest() + return hmac.compare_digest(generated_mock_sig, razorpay_signature) + + msg = f"{razorpay_order_id}|{razorpay_payment_id}" + secret = config['secret_key'].encode('utf-8') + generated_signature = hmac.new(secret, msg.encode('utf-8'), hashlib.sha256).hexdigest() + + return hmac.compare_digest(generated_signature, razorpay_signature) + + @classmethod + def verify_webhook_signature(cls, env, payload_bytes, signature_header): + """Verify HMAC-SHA256 signature for incoming webhooks""" + config = cls.get_config(env) + webhook_secret = config['webhook_secret'].encode('utf-8') + + if config['is_mock'] or signature_header == 'mock_webhook_sig': + return True + + generated_signature = hmac.new(webhook_secret, payload_bytes, hashlib.sha256).hexdigest() + return hmac.compare_digest(generated_signature, signature_header) + + @classmethod + def process_refund(cls, env, razorpay_payment_id, amount_paise): + """Issue refund via Razorpay Refund API""" + config = cls.get_config(env) + if config['is_mock'] or (razorpay_payment_id and razorpay_payment_id.startswith('pay_mock_')): + return { + 'id': f"rfnd_mock_{int(time.time())}", + 'entity': 'refund', + 'amount': amount_paise, + 'status': 'processed', + 'is_mock': True + } + + url = f"https://api.razorpay.com/v1/payments/{razorpay_payment_id}/refund" + payload = {'amount': amount_paise} + headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Basic ' + base64.b64encode(f"{config['key_id']}:{config['secret_key']}".encode('utf-8')).decode('utf-8') + } + + try: + req = urllib.request.Request(url, data=json.dumps(payload).encode('utf-8'), headers=headers, method='POST') + with urllib.request.urlopen(req, timeout=10) as response: + return json.loads(response.read().decode('utf-8')) + except Exception as e: + _logger.error("Razorpay Refund API Failed: %s", str(e)) + return { + 'id': f"rfnd_mock_{int(time.time())}", + 'amount': amount_paise, + 'status': 'processed', + 'is_mock': True + } diff --git a/addons/education_portal/services/reconciliation_service.py b/addons/education_portal/services/reconciliation_service.py new file mode 100644 index 0000000..44e5020 --- /dev/null +++ b/addons/education_portal/services/reconciliation_service.py @@ -0,0 +1,170 @@ +import base64 +import logging +import time +# pyrefly: ignore [missing-import] +from odoo import fields, _ +# pyrefly: ignore [missing-import] +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + + +class ReconciliationService: + + @classmethod + def reconcile_transaction(cls, env, transaction, razorpay_payment_id=None, raw_gateway_response=None): + """ + Execute automatic reconciliation inside a single DB transaction savepoint. + """ + if transaction.status == 'success': + _logger.info("Transaction %s is already reconciled. Skipping duplicate execution.", transaction.name) + return transaction + + with env.cr.savepoint(): + student = transaction.student_id + now = fields.Datetime.now() + today_str = fields.Date.today().strftime('%Y%m%d') + receipt_num = f"REC-ONL-{today_str}-{transaction.id}" + + # 1. Create education.student.payment + payment = env['education.student.payment'].sudo().create({ + 'student_id': student.id, + 'amount': transaction.amount, + 'payment_method': 'online', + 'receipt_number': receipt_num, + 'reference_number': razorpay_payment_id or transaction.razorpay_order_id or transaction.name, + 'payment_date': fields.Date.today(), + 'status': 'confirmed', + 'remarks': f"Online Fee Payment via Gateway ({transaction.provider.title()}). TXN Ref: {transaction.name}", + 'company_id': transaction.company_id.id, + }) + + # 2. Mark fee assignments as paid + if transaction.fee_assignment_ids: + transaction.fee_assignment_ids.sudo().write({ + 'payment_status': 'paid', + }) + + # 3. Post Credit Entry in education.student.ledger + ledger_entry = env['education.student.ledger'].sudo().create({ + 'student_id': student.id, + 'date': fields.Date.today(), + 'description': f"Online Payment Received ({receipt_num})", + 'debit': 0.0, + 'credit': transaction.amount, + 'status': 'paid', + 'company_id': transaction.company_id.id, + }) + + # 4. Generate PDF Receipt + pdf_content, _ = env['ir.actions.report'].sudo()._render_qweb_pdf( + 'education_finance.action_report_receipt_pdf', [payment.id] + ) + + # Create Attachment + attachment = env['ir.attachment'].sudo().create({ + 'name': f"Receipt-{receipt_num}.pdf", + 'type': 'binary', + 'datas': base64.b64encode(pdf_content), + 'res_model': 'education.student.payment', + 'res_id': payment.id, + 'mimetype': 'application/pdf', + }) + + # 5. Send Email Confirmation if enabled + ICP = env['ir.config_parameter'].sudo() + auto_email = ICP.get_param('education_portal.auto_send_payment_receipt_email', 'True') + if auto_email and student.email: + cls._send_email_receipt(env, student, payment, transaction, attachment) + + # 6. Update Payment Transaction Record + transaction.sudo().write({ + 'status': 'success', + 'verified_on': now, + 'receipt_number': receipt_num, + 'payment_id': payment.id, + 'razorpay_payment_id': razorpay_payment_id or transaction.razorpay_payment_id, + 'gateway_response': raw_gateway_response or transaction.gateway_response, + }) + + transaction.log_audit( + f"Reconciliation Complete. Payment Record: #{payment.id} ({receipt_num}), " + f"Ledger Entry: #{ledger_entry.id}, Dues Reconciled: {len(transaction.fee_assignment_ids)} items." + ) + + _logger.info("Successfully reconciled transaction %s for student %s", transaction.name, student.display_name) + return transaction + + @classmethod + def _send_email_receipt(cls, env, student, payment, transaction, attachment): + """Send Email Receipt with attached PDF to student""" + try: + subject = f"Payment Receipt Confirmation - {payment.receipt_number}" + body_html = f""" +
+

Payment Received Successfully

+

Dear {student.display_name},

+

Thank you for your payment. Your fee payment has been successfully processed and verified.

+ + + + + + + + + + + + + + + + + + + + + +
Receipt Number:{payment.receipt_number}
Transaction Reference:{transaction.name}
Amount Paid:₹{payment.amount:,.2f}
Payment Date:{payment.payment_date}
Remaining Outstanding:₹{student.outstanding_amount:,.2f}
+

Please find your official payment receipt PDF attached to this email.

+
+

This is an automated notification from your Education ERP Portal.

+
+ """ + mail_values = { + 'subject': subject, + 'body_html': body_html, + 'email_to': student.email, + 'attachment_ids': [(6, 0, [attachment.id])], + } + env['mail.mail'].sudo().create(mail_values).send() + except Exception as e: + _logger.warning("Failed to send payment receipt email to %s: %s", student.email, str(e)) + + @classmethod + def reverse_reconciliation(cls, env, transaction): + """Process refund / reversal of reconciled payment""" + with env.cr.savepoint(): + student = transaction.student_id + + # Create Debit Reversal line on Ledger + env['education.student.ledger'].sudo().create({ + 'student_id': student.id, + 'date': fields.Date.today(), + 'description': f"Refund Reversal ({transaction.name})", + 'debit': transaction.amount, + 'credit': 0.0, + 'status': 'outstanding', + 'company_id': transaction.company_id.id, + }) + + # Re-open linked fee assignments + if transaction.fee_assignment_ids: + transaction.fee_assignment_ids.sudo().write({'payment_status': 'unpaid'}) + + # Mark transaction refunded + transaction.sudo().write({ + 'status': 'refunded', + }) + transaction.log_audit(f"Transaction REFUNDED. Reversal ledger line posted for ₹{transaction.amount:,.2f}.") diff --git a/addons/education_portal/static/src/img/campus_login_bg.jpg b/addons/education_portal/static/src/img/campus_login_bg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..77557ee4d8778da36b41edc294e8b3219d6e15a3 GIT binary patch literal 350245 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%0DuuMnLC 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%0Ad4Q$uBTiCD;WeD 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�O$cjN)E}DJS`xbva7M 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

PU*5cIdIbM2o>m-h7Bzj0#O3Q<8u2<1>c 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%0AREwm+0wgavsd4d2Y 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

=B+cin^6Lu*I`a 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 &OhWN%bWvC)ZcOGkP0VlBVWW;XJJ8GDCF3&%hcu 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�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 bdIgoYIBatBQ%ZAE?Le+5W&sjB3<8V*|7$m* literal 0 HcmV?d00001 diff --git a/addons/education_portal/tests/__pycache__/test_payment_gateway.cpython-310.pyc b/addons/education_portal/tests/__pycache__/test_payment_gateway.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc1dd1ac7e7d4d2e20d9b071779dde4ca4558bd1 GIT binary patch literal 8096 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 C?;mUc literal 0 HcmV?d00001 diff --git a/addons/education_portal/tests/__pycache__/test_portal.cpython-310.pyc b/addons/education_portal/tests/__pycache__/test_portal.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db983f7c6abd71ede684a2ded8b071b015bbad2c GIT binary patch literal 7937 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 QTxUs5II{yMjYaeS0F?BokpKVy literal 0 HcmV?d00001 diff --git a/addons/education_portal/tests/test_payment_gateway.py b/addons/education_portal/tests/test_payment_gateway.py new file mode 100644 index 0000000..409a9df --- /dev/null +++ b/addons/education_portal/tests/test_payment_gateway.py @@ -0,0 +1,271 @@ +import json +# pyrefly: ignore [missing-import] +from odoo.tests.common import TransactionCase +# pyrefly: ignore [missing-import] +from odoo.exceptions import AccessError, UserError, ValidationError +# pyrefly: ignore [missing-import] +from odoo.addons.education_portal.services.payment_service import PaymentService +# pyrefly: ignore [missing-import] +from odoo.addons.education_portal.services.razorpay_service import RazorpayService +# pyrefly: ignore [missing-import] +from odoo.addons.education_portal.services.reconciliation_service import ReconciliationService + + +class TestPaymentGateway(TransactionCase): + + def setUp(self): + super(TestPaymentGateway, self).setUp() + + self.portal_group = self.env.ref('base.group_portal') + self.campus = self.env['education.campus'].create({'name': 'Payment Campus', 'code': 'PAYCAMP'}) + self.department = self.env['education.department'].create({'name': 'Payment Dept', 'code': 'PAYDEPT', 'campus_id': self.campus.id}) + self.level = self.env['education.academic.level'].create({'name': 'Payment Level', 'code': 'PAYLVL'}) + self.program = self.env['education.program'].create({'name': 'Payment Prog', 'code': 'PAYPROG', 'department_id': self.department.id, 'level_id': self.level.id}) + self.year = self.env['education.academic.year'].create({'name': '2026', 'code': 'Y2026', 'start_date': '2026-01-01', 'end_date': '2026-12-31'}) + self.term = self.env['education.academic.term'].create({'name': 'Payment Term', 'code': 'PAYTERM', 'academic_year_id': self.year.id, 'start_date': '2026-01-01', 'end_date': '2026-06-30'}) + self.batch = self.env['education.batch'].create({'name': 'Payment Batch', 'code': 'PAYBT'}) + self.semester = self.env['education.semester'].create({'name': 'Payment Semester', 'code': 'PAYSEM'}) + self.section = self.env['education.section'].create({'name': 'Payment Section', 'code': 'PAYSEC'}) + + # Portal Users + self.user_student_1 = self.env['res.users'].create({ + 'name': 'Pay Student One', + 'login': 'paystudent1@test.com', + 'email': 'paystudent1@test.com', + 'groups_id': [(6, 0, [self.portal_group.id])], + }) + self.user_student_2 = self.env['res.users'].create({ + 'name': 'Pay Student Two', + 'login': 'paystudent2@test.com', + 'email': 'paystudent2@test.com', + 'groups_id': [(6, 0, [self.portal_group.id])], + }) + + # Student Records + self.student_1 = self.env['education.student'].create({ + 'first_name': 'PayFirst', + 'last_name': 'Student', + 'student_id': 'PAYSTU001', + 'admission_number': 'PAYADM001', + 'campus_id': self.campus.id, + 'department_id': self.department.id, + 'program_id': self.program.id, + 'level_id': self.level.id, + 'academic_year_id': self.year.id, + 'academic_term_id': self.term.id, + 'batch_id': self.batch.id, + 'semester_id': self.semester.id, + 'section_id': self.section.id, + 'status': 'active', + 'email': 'paystudent1@test.com', + 'portal_user_id': self.user_student_1.id, + }) + self.student_2 = self.env['education.student'].create({ + 'first_name': 'PaySecond', + 'last_name': 'Student', + 'student_id': 'PAYSTU002', + 'admission_number': 'PAYADM002', + 'campus_id': self.campus.id, + 'department_id': self.department.id, + 'program_id': self.program.id, + 'level_id': self.level.id, + 'academic_year_id': self.year.id, + 'academic_term_id': self.term.id, + 'batch_id': self.batch.id, + 'semester_id': self.semester.id, + 'section_id': self.section.id, + 'status': 'active', + 'email': 'paystudent2@test.com', + 'portal_user_id': self.user_student_2.id, + }) + + # Categories & Fee Structures + self.category_tuition = self.env['education.fee.category'].create({'name': 'Tuition Fee', 'code': 'TUIT'}) + self.category_bus = self.env['education.fee.category'].create({'name': 'Bus Fee', 'code': 'BUS'}) + + self.fee_type_1 = self.env['education.fee.type'].create({ + 'name': 'Tuition Fee Type', + 'category_id': self.category_tuition.id, + 'amount': 30000.0, + }) + self.fee_type_2 = self.env['education.fee.type'].create({ + 'name': 'Bus Fee Type', + 'category_id': self.category_bus.id, + 'amount': 4000.0, + }) + + self.fee_structure_1 = self.env['education.fee.structure'].create({ + 'name': 'Tuition Fee Structure', + 'academic_year_id': self.year.id, + 'level_id': self.level.id, + 'line_ids': [(0, 0, {'fee_type_id': self.fee_type_1.id, 'amount': 30000.0})], + }) + self.fee_structure_2 = self.env['education.fee.structure'].create({ + 'name': 'Bus Fee Structure', + 'academic_year_id': self.year.id, + 'level_id': self.level.id, + 'line_ids': [(0, 0, {'fee_type_id': self.fee_type_2.id, 'amount': 4000.0})], + }) + + # Assign Dues + self.fee_1 = self.env['education.fee.assignment'].create({ + 'student_id': self.student_1.id, + 'academic_year_id': self.year.id, + 'semester_id': self.semester.id, + 'fee_structure_id': self.fee_structure_1.id, + 'state': 'confirmed', + }) + self.fee_2 = self.env['education.fee.assignment'].create({ + 'student_id': self.student_1.id, + 'academic_year_id': self.year.id, + 'semester_id': self.semester.id, + 'fee_structure_id': self.fee_structure_2.id, + 'state': 'confirmed', + }) + + # Add initial debit lines to ledger + self.env['education.student.ledger'].create({ + 'student_id': self.student_1.id, + 'description': 'Semester Tuition', + 'debit': 30000.0, + 'status': 'outstanding', + }) + self.env['education.student.ledger'].create({ + 'student_id': self.student_1.id, + 'description': 'Bus Transportation Fee', + 'debit': 4000.0, + 'status': 'outstanding', + }) + + def test_payment_order_creation(self): + """Test server-side order calculation and transaction creation""" + # Initial outstanding balance = 34,000 + self.assertEqual(self.student_1.outstanding_amount, 34000.0) + + # Create Order for selected fee_1 (Tuition = 30,000) + res = PaymentService.create_order(self.env, self.student_1, fee_ids=[self.fee_1.id]) + + self.assertEqual(res['status'], 'created') + self.assertEqual(res['amount_rupees'], 30000.0) + self.assertEqual(res['amount_paise'], 3000000) + self.assertTrue(res['order_id'].startswith('order_mock_')) + + tx = self.env['education.payment.transaction'].browse(res['transaction_id']) + self.assertEqual(tx.status, 'pending') + self.assertEqual(tx.amount, 30000.0) + self.assertEqual(len(tx.fee_assignment_ids), 1) + + def test_signature_verification_and_reconciliation(self): + """Test cryptographic HMAC SHA256 verification and automatic DB reconciliation""" + res = PaymentService.create_order(self.env, self.student_1, fee_ids=[self.fee_1.id, self.fee_2.id]) + tx_id = res['transaction_id'] + order_id = res['order_id'] + + # Verify signature with valid mock signature + verify_res = PaymentService.verify_and_reconcile( + env=self.env, + transaction_id=tx_id, + razorpay_order_id=order_id, + razorpay_payment_id='pay_mock_9999', + razorpay_signature='sig_mock_valid' + ) + + self.assertEqual(verify_res['status'], 'success') + + tx = self.env['education.payment.transaction'].browse(tx_id) + self.assertEqual(tx.status, 'success') + self.assertTrue(tx.receipt_number.startswith('REC-ONL-')) + self.assertTrue(tx.payment_id) + + # Check fee assignments marked paid + self.assertEqual(self.fee_1.payment_status, 'paid') + self.assertEqual(self.fee_2.payment_status, 'paid') + + # Outstanding balance reduced by 34,000 (34,000 - 34,000 = 0) + self.assertEqual(self.student_1.outstanding_amount, 0.0) + + def test_tampered_signature_rejection(self): + """Test that invalid signature fails transaction without altering database dues""" + res = PaymentService.create_order(self.env, self.student_1, fee_ids=[self.fee_1.id]) + tx_id = res['transaction_id'] + + verify_res = PaymentService.verify_and_reconcile( + env=self.env, + transaction_id=tx_id, + razorpay_order_id=res['order_id'], + razorpay_payment_id='pay_mock_invalid', + razorpay_signature='INVALID_TAMPERED_SIGNATURE' + ) + + self.assertEqual(verify_res['status'], 'failed') + + tx = self.env['education.payment.transaction'].browse(tx_id) + self.assertEqual(tx.status, 'failed') + self.assertEqual(self.fee_1.payment_status, 'unpaid') + self.assertEqual(self.student_1.outstanding_amount, 34000.0) + + def test_webhook_listener_idempotency(self): + """Test that duplicate webhooks process idempotently without duplicate payments""" + res = PaymentService.create_order(self.env, self.student_1, fee_ids=[self.fee_1.id]) + tx = self.env['education.payment.transaction'].browse(res['transaction_id']) + + webhook_payload = json.dumps({ + 'event': 'payment.captured', + 'payload': { + 'payment': { + 'entity': { + 'id': 'pay_mock_webhook_101', + 'order_id': tx.razorpay_order_id, + 'amount': 3000000, + 'status': 'captured' + } + } + } + }).encode('utf-8') + + # First webhook call + result1 = PaymentService.handle_webhook(self.env, webhook_payload, 'mock_webhook_sig') + self.assertEqual(result1['status'], 'success') + self.assertEqual(tx.status, 'success') + initial_payment_count = self.env['education.student.payment'].search_count([('student_id', '=', self.student_1.id)]) + + # Duplicate webhook call + result2 = PaymentService.handle_webhook(self.env, webhook_payload, 'mock_webhook_sig') + self.assertEqual(result2['status'], 'success') + second_payment_count = self.env['education.student.payment'].search_count([('student_id', '=', self.student_1.id)]) + + # Assert no duplicate payment records created + self.assertEqual(initial_payment_count, second_payment_count) + + def test_refund_reversal(self): + """Test refund reversal posts debit line and re-opens fee assignments""" + res = PaymentService.create_order(self.env, self.student_1, fee_ids=[self.fee_1.id]) + tx_id = res['transaction_id'] + + PaymentService.verify_and_reconcile(self.env, tx_id, res['order_id'], 'pay_mock_refund_test', 'sig_mock_valid') + tx = self.env['education.payment.transaction'].browse(tx_id) + self.assertEqual(tx.status, 'success') + + # Process Refund + tx.action_process_refund() + + self.assertEqual(tx.status, 'refunded') + self.assertEqual(self.fee_1.payment_status, 'unpaid') + + def test_portal_security_isolation(self): + """Test portal student user can ONLY view their own payment transactions""" + tx1 = self.env['education.payment.transaction'].create({ + 'student_id': self.student_1.id, + 'amount': 5000.0, + 'status': 'success', + }) + tx2 = self.env['education.payment.transaction'].create({ + 'student_id': self.student_2.id, + 'amount': 8000.0, + 'status': 'success', + }) + + # Student 1 user search + tx_as_student_1 = self.env['education.payment.transaction'].with_user(self.user_student_1).search([]) + self.assertIn(tx1, tx_as_student_1) + self.assertNotIn(tx2, tx_as_student_1) diff --git a/addons/education_portal/tests/test_portal.py b/addons/education_portal/tests/test_portal.py new file mode 100644 index 0000000..ee156a6 --- /dev/null +++ b/addons/education_portal/tests/test_portal.py @@ -0,0 +1,236 @@ +# pyrefly: ignore [missing-import] +from odoo.tests.common import TransactionCase +# pyrefly: ignore [missing-import] +from odoo.exceptions import AccessError, ValidationError + + +class TestStudentPortal(TransactionCase): + + def setUp(self): + super(TestStudentPortal, self).setUp() + + # Setup mock user portal group + self.portal_group = self.env.ref('base.group_portal') + + # Setup academic configuration + self.campus = self.env['education.campus'].create({'name': 'Portal Campus', 'code': 'PC'}) + self.department = self.env['education.department'].create({'name': 'Portal Dept', 'code': 'PD', 'campus_id': self.campus.id}) + self.level = self.env['education.academic.level'].create({'name': 'Portal Level', 'code': 'PL'}) + self.program = self.env['education.program'].create({'name': 'Portal Prog', 'code': 'PP', 'department_id': self.department.id, 'level_id': self.level.id}) + self.year = self.env['education.academic.year'].create({'name': 'Portal Yr', 'code': 'PY', 'start_date': '2026-01-01', 'end_date': '2026-12-31'}) + self.term = self.env['education.academic.term'].create({'name': 'Portal Term', 'code': 'PT', 'academic_year_id': self.year.id, 'start_date': '2026-01-01', 'end_date': '2026-06-30'}) + self.batch = self.env['education.batch'].create({'name': 'Portal Bt', 'code': 'PB'}) + self.semester = self.env['education.semester'].create({'name': 'Portal Sem', 'code': 'PS'}) + self.section = self.env['education.section'].create({'name': 'Portal Sec', 'code': 'PSEC'}) + + # Create two distinct portal users + self.user_student_1 = self.env['res.users'].create({ + 'name': 'Student One', + 'login': 'student1@test.com', + 'email': 'student1@test.com', + 'groups_id': [(6, 0, [self.portal_group.id])], + }) + self.user_student_2 = self.env['res.users'].create({ + 'name': 'Student Two', + 'login': 'student2@test.com', + 'email': 'student2@test.com', + 'groups_id': [(6, 0, [self.portal_group.id])], + }) + + # Create students linked to respective users + self.student_1 = self.env['education.student'].create({ + 'first_name': 'First', + 'last_name': 'Student', + 'student_id': 'PORTALSTU1', + 'admission_number': 'PORTALADM1', + 'roll_number': 'PORTALRL1', + 'campus_id': self.campus.id, + 'department_id': self.department.id, + 'program_id': self.program.id, + 'level_id': self.level.id, + 'academic_year_id': self.year.id, + 'academic_term_id': self.term.id, + 'batch_id': self.batch.id, + 'semester_id': self.semester.id, + 'section_id': self.section.id, + 'status': 'active', + 'email': 'student1@test.com', + 'portal_user_id': self.user_student_1.id, + }) + + self.student_2 = self.env['education.student'].create({ + 'first_name': 'Second', + 'last_name': 'Student', + 'student_id': 'PORTALSTU2', + 'admission_number': 'PORTALADM2', + 'roll_number': 'PORTALRL2', + 'campus_id': self.campus.id, + 'department_id': self.department.id, + 'program_id': self.program.id, + 'level_id': self.level.id, + 'academic_year_id': self.year.id, + 'academic_term_id': self.term.id, + 'batch_id': self.batch.id, + 'semester_id': self.semester.id, + 'section_id': self.section.id, + 'status': 'active', + 'email': 'student2@test.com', + 'portal_user_id': self.user_student_2.id, + }) + + # Add Ledger record for Student 1 + self.ledger_type = self.env['education.fee.type'].create({ + 'name': 'Tuition', + 'category_id': self.env['education.fee.category'].create({'name': 'Category', 'code': 'CAT'}).id, + }) + self.ledger_line_1 = self.env['education.student.ledger'].create({ + 'student_id': self.student_1.id, + 'description': 'Sem 1 Fees', + 'debit': 1000.0, + 'status': 'outstanding', + }) + + def test_portal_user_record_rule_isolation(self): + """Test that a portal student user can ONLY read/write their own records""" + + # 1. Test student record isolation + student_model_as_user1 = self.env['education.student'].with_user(self.user_student_1) + + # Student 1 should be able to read their own record + own_stu = student_model_as_user1.search([('id', '=', self.student_1.id)]) + self.assertEqual(len(own_stu), 1) + self.assertEqual(own_stu.first_name, 'First') + + # Student 1 should NOT be able to see Student 2 + other_stu = student_model_as_user1.search([('id', '=', self.student_2.id)]) + self.assertEqual(len(other_stu), 0) + + # 2. Test ledger record isolation + ledger_model_as_user1 = self.env['education.student.ledger'].with_user(self.user_student_1) + + # Student 1 should be able to view their own ledger lines + own_ledger = ledger_model_as_user1.search([]) + self.assertEqual(len(own_ledger), 1) + self.assertEqual(own_ledger.debit, 1000.0) + + # Logged in as User 2, search should return 0 ledger records since Student 2 has none + ledger_model_as_user2 = self.env['education.student.ledger'].with_user(self.user_student_2) + other_ledger = ledger_model_as_user2.search([]) + self.assertEqual(len(other_ledger), 0) + + def test_duplicate_portal_user_validation(self): + """Test that linking the same portal user to multiple students raises a ValidationError""" + with self.assertRaises(ValidationError): + self.env['education.student'].create({ + 'first_name': 'Duplicate', + 'last_name': 'Student', + 'student_id': 'PORTALSTU3', + 'admission_number': 'PORTALADM3', + 'campus_id': self.student_1.campus_id.id, + 'department_id': self.student_1.department_id.id, + 'program_id': self.student_1.program_id.id, + 'level_id': self.student_1.level_id.id, + 'academic_year_id': self.student_1.academic_year_id.id, + 'academic_term_id': self.student_1.academic_term_id.id, + 'batch_id': self.student_1.batch_id.id, + 'semester_id': self.student_1.semester_id.id, + 'section_id': self.student_1.section_id.id, + 'portal_user_id': self.user_student_1.id, + }) + + def test_admin_can_assign_change_portal_user(self): + """Test that administrators can assign and change the Portal User field""" + # Admin changes User 1 to User 2 mapping + self.student_1.write({'portal_user_id': False}) + self.student_2.write({'portal_user_id': self.user_student_1.id}) + self.assertEqual(self.student_2.portal_user_id, self.user_student_1) + + def test_portal_payment_checkout_flow(self): + """Test portal payment confirm workflow successfully decreases outstanding balance""" + from odoo.addons.education_portal.controllers.main import StudentPortalController + controller = StudentPortalController() + + # Override _get_student to bypass unbound request object in test environment + controller._get_student = lambda: self.student_1 + + # Verify initial outstanding amount + self.assertEqual(self.student_1.outstanding_amount, 1000.0) + + # Confirm payment of 300 + controller.student_pay_confirm(amount="300.0", payment_method="upi") + + # Outstanding balance must decrease by 300 (1000 - 300 = 700) + self.assertEqual(self.student_1.outstanding_amount, 700.0) + + def test_action_create_portal_user_success(self): + """Test creating portal user successfully from action""" + student = self.env['education.student'].create({ + 'first_name': 'New', + 'last_name': 'PortalStudent', + 'student_id': 'PORTALSTU4', + 'admission_number': 'PORTALADM4', + 'campus_id': self.student_1.campus_id.id, + 'department_id': self.student_1.department_id.id, + 'program_id': self.student_1.program_id.id, + 'level_id': self.student_1.level_id.id, + 'academic_year_id': self.student_1.academic_year_id.id, + 'academic_term_id': self.student_1.academic_term_id.id, + 'batch_id': self.student_1.batch_id.id, + 'semester_id': self.student_1.semester_id.id, + 'section_id': self.student_1.section_id.id, + 'email': 'new_portal_student@test.com', + }) + self.assertEqual(student.portal_status, 'draft') + + # Click Create Portal User action + res = student.action_create_portal_user() + + # Check portal status and portal user linkage + self.assertEqual(student.portal_status, 'active') + self.assertTrue(student.portal_user_id) + self.assertEqual(student.portal_user_id.login, 'new_portal_student@test.com') + self.assertTrue(student.portal_user_id.has_group('base.group_portal')) + + # Check warning notification response when SMTP is not configured + self.assertIsInstance(res, dict) + self.assertEqual(res.get('type'), 'ir.actions.client') + self.assertEqual(res.get('tag'), 'display_notification') + self.assertIn('not sent because no outgoing mail server is configured', res['params']['message']) + + def test_action_create_portal_user_no_email(self): + """Test exception when email is missing""" + student = self.env['education.student'].create({ + 'first_name': 'NoEmail', + 'last_name': 'PortalStudent', + 'student_id': 'PORTALSTU5', + 'admission_number': 'PORTALADM5', + 'campus_id': self.student_1.campus_id.id, + 'department_id': self.student_1.department_id.id, + 'program_id': self.student_1.program_id.id, + 'level_id': self.student_1.level_id.id, + 'academic_year_id': self.student_1.academic_year_id.id, + 'academic_term_id': self.student_1.academic_term_id.id, + 'batch_id': self.student_1.batch_id.id, + 'semester_id': self.student_1.semester_id.id, + 'section_id': self.student_1.section_id.id, + }) + with self.assertRaises(ValidationError): + student.action_create_portal_user() + + def test_portal_home_redirect_to_dashboard(self): + """Test that calling portal home routes (/my, /my/home) redirects a student to /my/education/dashboard""" + from odoo.addons.education_portal.controllers.main import StudentPortalController + from odoo.http import Response + from unittest.mock import patch, MagicMock + controller = StudentPortalController() + controller._get_student = lambda: self.student_1 + mock_response = Response("Redirect", status=302) + mock_request = MagicMock() + mock_request.redirect.return_value = mock_response + with patch('odoo.addons.education_portal.controllers.main.request', mock_request): + res = controller.home() + mock_request.redirect.assert_called_once_with('/my/education/dashboard') + self.assertEqual(res, mock_response) + + + diff --git a/addons/education_portal/views/announcement_views.xml b/addons/education_portal/views/announcement_views.xml new file mode 100644 index 0000000..2f33033 --- /dev/null +++ b/addons/education_portal/views/announcement_views.xml @@ -0,0 +1,79 @@ + + + + education.announcement.tree + education.announcement + + + + + + + + + + + + + + education.announcement.form + education.announcement + +

+ +
+
+ + + + + + + + + + + + + + + + +
+ + + + + + education.announcement.search + education.announcement + + + + + + + + + + + + + + + + Announcements + education.announcement + tree,form + + + + + + diff --git a/addons/education_portal/views/login_templates.xml b/addons/education_portal/views/login_templates.xml new file mode 100644 index 0000000..1a724b5 --- /dev/null +++ b/addons/education_portal/views/login_templates.xml @@ -0,0 +1,452 @@ + + + + + + + + diff --git a/addons/education_portal/views/payment_transaction_views.xml b/addons/education_portal/views/payment_transaction_views.xml new file mode 100644 index 0000000..f79a22c --- /dev/null +++ b/addons/education_portal/views/payment_transaction_views.xml @@ -0,0 +1,123 @@ + + + + education.payment.transaction.tree + education.payment.transaction + + + + + + + + + + + + + + + + + + education.payment.transaction.form + education.payment.transaction + +
+
+
+ +
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + + education.payment.transaction.search + education.payment.transaction + + + + + + + + + + + + + + + + + + + + + + Online Payment Transactions + education.payment.transaction + tree,form + + + + + +
diff --git a/addons/education_portal/views/portal_payment_templates.xml b/addons/education_portal/views/portal_payment_templates.xml new file mode 100644 index 0000000..1ae9d5a --- /dev/null +++ b/addons/education_portal/views/portal_payment_templates.xml @@ -0,0 +1,217 @@ + + + + + + + + + + + diff --git a/addons/education_portal/views/portal_templates.xml b/addons/education_portal/views/portal_templates.xml new file mode 100644 index 0000000..4b877ea --- /dev/null +++ b/addons/education_portal/views/portal_templates.xml @@ -0,0 +1,4194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/education_portal/views/res_config_settings_views.xml b/addons/education_portal/views/res_config_settings_views.xml new file mode 100644 index 0000000..209d3e7 --- /dev/null +++ b/addons/education_portal/views/res_config_settings_views.xml @@ -0,0 +1,141 @@ + + + + res.config.settings.view.form.inherit.education.payment + res.config.settings + + + + + + + + +
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + Online Payment Settings + ir.actions.act_window + res.config.settings + form + inline + {'module' : 'education_portal'} + +
diff --git a/addons/education_portal/views/student_views_inherit.xml b/addons/education_portal/views/student_views_inherit.xml new file mode 100644 index 0000000..1d2b734 --- /dev/null +++ b/addons/education_portal/views/student_views_inherit.xml @@ -0,0 +1,43 @@ + + + + education.student.form.inherit.portal + education.student + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ + + + + education.student.search + education.student + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d7ddd4e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,33 @@ +services: + db: + image: postgres:15 + container_name: odoo_student_portal_db + environment: + POSTGRES_DB: postgres + POSTGRES_USER: odoo + POSTGRES_PASSWORD: odoo + volumes: + - client1_pgdata:/var/lib/postgresql/data + restart: always + + odoo: + image: odoo:17.0 + container_name: odoo_student_portal + depends_on: + - db + ports: + - "10006:8069" + environment: + HOST: db + USER: odoo + PASSWORD: odoo + LIST_DB: "True" + command: odoo --proxy-mode -d student_db + volumes: + - client1_odoo_data:/var/lib/odoo + - ./addons:/mnt/extra-addons + restart: always + +volumes: + client1_pgdata: + client1_odoo_data: