feat(v3): restructure project + add complete frontend
- Restructure: move backend from new_project/ to backend/ - Add full React/TypeScript frontend (37 pages, 17 services, 16 type defs, 11 query hooks) - Add docs/ with SRS specs, user stories, and workflow documentation - Update .gitignore for new directory layout Workflows implemented: WF1 User Signup, WF2 Placement Test, WF3 Exam Configuration, WF4 General English Exam, WF5 Course Generation, WF6 Entity Student Onboarding, AI Course Generation, Adaptive Learning Engine UI, White-Label Branding, Score Release Made-with: Cursor
This commit is contained in:
1
custom_addons/encoach_core/__init__.py
Normal file
1
custom_addons/encoach_core/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
27
custom_addons/encoach_core/__manifest__.py
Normal file
27
custom_addons/encoach_core/__manifest__.py
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
'name': 'EnCoach Core',
|
||||
'version': '19.0.1.1',
|
||||
'category': 'Education',
|
||||
'summary': 'Core models for EnCoach: users, entities, roles, permissions',
|
||||
'author': 'EnCoach',
|
||||
'depends': ['base', 'mail', 'openeducat_core'],
|
||||
'data': [
|
||||
'security/encoach_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'data/encoach_permissions.xml',
|
||||
'views/encoach_menus.xml',
|
||||
'views/encoach_entity_views.xml',
|
||||
'views/entity_level_mapping_views.xml',
|
||||
'views/dashboard_action.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'encoach_core/static/src/js/dashboard.js',
|
||||
'encoach_core/static/src/xml/dashboard.xml',
|
||||
'encoach_core/static/src/css/dashboard.css',
|
||||
],
|
||||
},
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
468
custom_addons/encoach_core/data/encoach_permissions.xml
Normal file
468
custom_addons/encoach_core/data/encoach_permissions.xml
Normal file
@@ -0,0 +1,468 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<data noupdate="0">
|
||||
<!-- Global permissions (PermissionType from permissions.ts) -->
|
||||
<record id="perm_viewCorporate" model="encoach.permission">
|
||||
<field name="code">viewCorporate</field>
|
||||
<field name="topic">Manage Corporate</field>
|
||||
</record>
|
||||
<record id="perm_editCorporate" model="encoach.permission">
|
||||
<field name="code">editCorporate</field>
|
||||
<field name="topic">Manage Corporate</field>
|
||||
</record>
|
||||
<record id="perm_deleteCorporate" model="encoach.permission">
|
||||
<field name="code">deleteCorporate</field>
|
||||
<field name="topic">Manage Corporate</field>
|
||||
</record>
|
||||
<record id="perm_createCodeCorporate" model="encoach.permission">
|
||||
<field name="code">createCodeCorporate</field>
|
||||
<field name="topic">Manage Corporate</field>
|
||||
</record>
|
||||
<record id="perm_viewAdmin" model="encoach.permission">
|
||||
<field name="code">viewAdmin</field>
|
||||
<field name="topic">Manage Admin</field>
|
||||
</record>
|
||||
<record id="perm_editAdmin" model="encoach.permission">
|
||||
<field name="code">editAdmin</field>
|
||||
<field name="topic">Manage Admin</field>
|
||||
</record>
|
||||
<record id="perm_deleteAdmin" model="encoach.permission">
|
||||
<field name="code">deleteAdmin</field>
|
||||
<field name="topic">Manage Admin</field>
|
||||
</record>
|
||||
<record id="perm_createCodeAdmin" model="encoach.permission">
|
||||
<field name="code">createCodeAdmin</field>
|
||||
<field name="topic">Manage Admin</field>
|
||||
</record>
|
||||
<record id="perm_viewStudent" model="encoach.permission">
|
||||
<field name="code">viewStudent</field>
|
||||
<field name="topic">Manage Student</field>
|
||||
</record>
|
||||
<record id="perm_editStudent" model="encoach.permission">
|
||||
<field name="code">editStudent</field>
|
||||
<field name="topic">Manage Student</field>
|
||||
</record>
|
||||
<record id="perm_deleteStudent" model="encoach.permission">
|
||||
<field name="code">deleteStudent</field>
|
||||
<field name="topic">Manage Student</field>
|
||||
</record>
|
||||
<record id="perm_createCodeStudent" model="encoach.permission">
|
||||
<field name="code">createCodeStudent</field>
|
||||
<field name="topic">Manage Student</field>
|
||||
</record>
|
||||
<record id="perm_viewTeacher" model="encoach.permission">
|
||||
<field name="code">viewTeacher</field>
|
||||
<field name="topic">Manage Teacher</field>
|
||||
</record>
|
||||
<record id="perm_editTeacher" model="encoach.permission">
|
||||
<field name="code">editTeacher</field>
|
||||
<field name="topic">Manage Teacher</field>
|
||||
</record>
|
||||
<record id="perm_deleteTeacher" model="encoach.permission">
|
||||
<field name="code">deleteTeacher</field>
|
||||
<field name="topic">Manage Teacher</field>
|
||||
</record>
|
||||
<record id="perm_createCodeTeacher" model="encoach.permission">
|
||||
<field name="code">createCodeTeacher</field>
|
||||
<field name="topic">Manage Teacher</field>
|
||||
</record>
|
||||
<record id="perm_viewCountryManager" model="encoach.permission">
|
||||
<field name="code">viewCountryManager</field>
|
||||
<field name="topic">Manage Country Manager</field>
|
||||
</record>
|
||||
<record id="perm_editCountryManager" model="encoach.permission">
|
||||
<field name="code">editCountryManager</field>
|
||||
<field name="topic">Manage Country Manager</field>
|
||||
</record>
|
||||
<record id="perm_deleteCountryManager" model="encoach.permission">
|
||||
<field name="code">deleteCountryManager</field>
|
||||
<field name="topic">Manage Country Manager</field>
|
||||
</record>
|
||||
<record id="perm_createCodeCountryManager" model="encoach.permission">
|
||||
<field name="code">createCodeCountryManager</field>
|
||||
<field name="topic">Manage Country Manager</field>
|
||||
</record>
|
||||
<record id="perm_createReadingExam" model="encoach.permission">
|
||||
<field name="code">createReadingExam</field>
|
||||
<field name="topic">Manage Exams</field>
|
||||
</record>
|
||||
<record id="perm_createListeningExam" model="encoach.permission">
|
||||
<field name="code">createListeningExam</field>
|
||||
<field name="topic">Manage Exams</field>
|
||||
</record>
|
||||
<record id="perm_createWritingExam" model="encoach.permission">
|
||||
<field name="code">createWritingExam</field>
|
||||
<field name="topic">Manage Exams</field>
|
||||
</record>
|
||||
<record id="perm_createSpeakingExam" model="encoach.permission">
|
||||
<field name="code">createSpeakingExam</field>
|
||||
<field name="topic">Manage Exams</field>
|
||||
</record>
|
||||
<record id="perm_createLevelExam" model="encoach.permission">
|
||||
<field name="code">createLevelExam</field>
|
||||
<field name="topic">Manage Exams</field>
|
||||
</record>
|
||||
<record id="perm_viewExams" model="encoach.permission">
|
||||
<field name="code">viewExams</field>
|
||||
<field name="topic">View Pages</field>
|
||||
</record>
|
||||
<record id="perm_viewExercises" model="encoach.permission">
|
||||
<field name="code">viewExercises</field>
|
||||
<field name="topic">View Pages</field>
|
||||
</record>
|
||||
<record id="perm_viewRecords" model="encoach.permission">
|
||||
<field name="code">viewRecords</field>
|
||||
<field name="topic">View Pages</field>
|
||||
</record>
|
||||
<record id="perm_viewStats" model="encoach.permission">
|
||||
<field name="code">viewStats</field>
|
||||
<field name="topic">View Pages</field>
|
||||
</record>
|
||||
<record id="perm_viewTickets" model="encoach.permission">
|
||||
<field name="code">viewTickets</field>
|
||||
<field name="topic">View Pages</field>
|
||||
</record>
|
||||
<record id="perm_viewPaymentRecords" model="encoach.permission">
|
||||
<field name="code">viewPaymentRecords</field>
|
||||
<field name="topic">View Pages</field>
|
||||
</record>
|
||||
<record id="perm_viewGroup" model="encoach.permission">
|
||||
<field name="code">viewGroup</field>
|
||||
<field name="topic">Manage Group</field>
|
||||
</record>
|
||||
<record id="perm_editGroup" model="encoach.permission">
|
||||
<field name="code">editGroup</field>
|
||||
<field name="topic">Manage Group</field>
|
||||
</record>
|
||||
<record id="perm_deleteGroup" model="encoach.permission">
|
||||
<field name="code">deleteGroup</field>
|
||||
<field name="topic">Manage Group</field>
|
||||
</record>
|
||||
<record id="perm_createGroup" model="encoach.permission">
|
||||
<field name="code">createGroup</field>
|
||||
<field name="topic">Manage Group</field>
|
||||
</record>
|
||||
<record id="perm_viewCodes" model="encoach.permission">
|
||||
<field name="code">viewCodes</field>
|
||||
<field name="topic">Manage Codes</field>
|
||||
</record>
|
||||
<record id="perm_deleteCodes" model="encoach.permission">
|
||||
<field name="code">deleteCodes</field>
|
||||
<field name="topic">Manage Codes</field>
|
||||
</record>
|
||||
<record id="perm_createCodes" model="encoach.permission">
|
||||
<field name="code">createCodes</field>
|
||||
<field name="topic">Manage Codes</field>
|
||||
</record>
|
||||
<record id="perm_all" model="encoach.permission">
|
||||
<field name="code">all</field>
|
||||
<field name="topic">Others</field>
|
||||
</record>
|
||||
|
||||
<!-- Role-level permissions (RolePermission from entityPermissions.ts) -->
|
||||
<record id="rperm_view_students" model="encoach.permission">
|
||||
<field name="code">view_students</field>
|
||||
<field name="topic">Users</field>
|
||||
</record>
|
||||
<record id="rperm_view_teachers" model="encoach.permission">
|
||||
<field name="code">view_teachers</field>
|
||||
<field name="topic">Users</field>
|
||||
</record>
|
||||
<record id="rperm_view_corporates" model="encoach.permission">
|
||||
<field name="code">view_corporates</field>
|
||||
<field name="topic">Users</field>
|
||||
</record>
|
||||
<record id="rperm_view_mastercorporates" model="encoach.permission">
|
||||
<field name="code">view_mastercorporates</field>
|
||||
<field name="topic">Users</field>
|
||||
</record>
|
||||
<record id="rperm_edit_students" model="encoach.permission">
|
||||
<field name="code">edit_students</field>
|
||||
<field name="topic">Users</field>
|
||||
</record>
|
||||
<record id="rperm_edit_teachers" model="encoach.permission">
|
||||
<field name="code">edit_teachers</field>
|
||||
<field name="topic">Users</field>
|
||||
</record>
|
||||
<record id="rperm_edit_corporates" model="encoach.permission">
|
||||
<field name="code">edit_corporates</field>
|
||||
<field name="topic">Users</field>
|
||||
</record>
|
||||
<record id="rperm_edit_mastercorporates" model="encoach.permission">
|
||||
<field name="code">edit_mastercorporates</field>
|
||||
<field name="topic">Users</field>
|
||||
</record>
|
||||
<record id="rperm_delete_students" model="encoach.permission">
|
||||
<field name="code">delete_students</field>
|
||||
<field name="topic">Users</field>
|
||||
</record>
|
||||
<record id="rperm_delete_teachers" model="encoach.permission">
|
||||
<field name="code">delete_teachers</field>
|
||||
<field name="topic">Users</field>
|
||||
</record>
|
||||
<record id="rperm_delete_corporates" model="encoach.permission">
|
||||
<field name="code">delete_corporates</field>
|
||||
<field name="topic">Users</field>
|
||||
</record>
|
||||
<record id="rperm_delete_mastercorporates" model="encoach.permission">
|
||||
<field name="code">delete_mastercorporates</field>
|
||||
<field name="topic">Users</field>
|
||||
</record>
|
||||
<record id="rperm_generate_reading" model="encoach.permission">
|
||||
<field name="code">generate_reading</field>
|
||||
<field name="topic">Exams</field>
|
||||
</record>
|
||||
<record id="rperm_view_reading" model="encoach.permission">
|
||||
<field name="code">view_reading</field>
|
||||
<field name="topic">Exams</field>
|
||||
</record>
|
||||
<record id="rperm_delete_reading" model="encoach.permission">
|
||||
<field name="code">delete_reading</field>
|
||||
<field name="topic">Exams</field>
|
||||
</record>
|
||||
<record id="rperm_generate_listening" model="encoach.permission">
|
||||
<field name="code">generate_listening</field>
|
||||
<field name="topic">Exams</field>
|
||||
</record>
|
||||
<record id="rperm_view_listening" model="encoach.permission">
|
||||
<field name="code">view_listening</field>
|
||||
<field name="topic">Exams</field>
|
||||
</record>
|
||||
<record id="rperm_delete_listening" model="encoach.permission">
|
||||
<field name="code">delete_listening</field>
|
||||
<field name="topic">Exams</field>
|
||||
</record>
|
||||
<record id="rperm_generate_writing" model="encoach.permission">
|
||||
<field name="code">generate_writing</field>
|
||||
<field name="topic">Exams</field>
|
||||
</record>
|
||||
<record id="rperm_view_writing" model="encoach.permission">
|
||||
<field name="code">view_writing</field>
|
||||
<field name="topic">Exams</field>
|
||||
</record>
|
||||
<record id="rperm_delete_writing" model="encoach.permission">
|
||||
<field name="code">delete_writing</field>
|
||||
<field name="topic">Exams</field>
|
||||
</record>
|
||||
<record id="rperm_generate_speaking" model="encoach.permission">
|
||||
<field name="code">generate_speaking</field>
|
||||
<field name="topic">Exams</field>
|
||||
</record>
|
||||
<record id="rperm_view_speaking" model="encoach.permission">
|
||||
<field name="code">view_speaking</field>
|
||||
<field name="topic">Exams</field>
|
||||
</record>
|
||||
<record id="rperm_delete_speaking" model="encoach.permission">
|
||||
<field name="code">delete_speaking</field>
|
||||
<field name="topic">Exams</field>
|
||||
</record>
|
||||
<record id="rperm_generate_level" model="encoach.permission">
|
||||
<field name="code">generate_level</field>
|
||||
<field name="topic">Exams</field>
|
||||
</record>
|
||||
<record id="rperm_view_level" model="encoach.permission">
|
||||
<field name="code">view_level</field>
|
||||
<field name="topic">Exams</field>
|
||||
</record>
|
||||
<record id="rperm_delete_level" model="encoach.permission">
|
||||
<field name="code">delete_level</field>
|
||||
<field name="topic">Exams</field>
|
||||
</record>
|
||||
<record id="rperm_view_classrooms" model="encoach.permission">
|
||||
<field name="code">view_classrooms</field>
|
||||
<field name="topic">Classrooms</field>
|
||||
</record>
|
||||
<record id="rperm_create_classroom" model="encoach.permission">
|
||||
<field name="code">create_classroom</field>
|
||||
<field name="topic">Classrooms</field>
|
||||
</record>
|
||||
<record id="rperm_rename_classrooms" model="encoach.permission">
|
||||
<field name="code">rename_classrooms</field>
|
||||
<field name="topic">Classrooms</field>
|
||||
</record>
|
||||
<record id="rperm_add_to_classroom" model="encoach.permission">
|
||||
<field name="code">add_to_classroom</field>
|
||||
<field name="topic">Classrooms</field>
|
||||
</record>
|
||||
<record id="rperm_remove_from_classroom" model="encoach.permission">
|
||||
<field name="code">remove_from_classroom</field>
|
||||
<field name="topic">Classrooms</field>
|
||||
</record>
|
||||
<record id="rperm_delete_classroom" model="encoach.permission">
|
||||
<field name="code">delete_classroom</field>
|
||||
<field name="topic">Classrooms</field>
|
||||
</record>
|
||||
<record id="rperm_view_entities" model="encoach.permission">
|
||||
<field name="code">view_entities</field>
|
||||
<field name="topic">Entities</field>
|
||||
</record>
|
||||
<record id="rperm_rename_entity" model="encoach.permission">
|
||||
<field name="code">rename_entity</field>
|
||||
<field name="topic">Entities</field>
|
||||
</record>
|
||||
<record id="rperm_add_to_entity" model="encoach.permission">
|
||||
<field name="code">add_to_entity</field>
|
||||
<field name="topic">Entities</field>
|
||||
</record>
|
||||
<record id="rperm_remove_from_entity" model="encoach.permission">
|
||||
<field name="code">remove_from_entity</field>
|
||||
<field name="topic">Entities</field>
|
||||
</record>
|
||||
<record id="rperm_delete_entity" model="encoach.permission">
|
||||
<field name="code">delete_entity</field>
|
||||
<field name="topic">Entities</field>
|
||||
</record>
|
||||
<record id="rperm_view_entity_roles" model="encoach.permission">
|
||||
<field name="code">view_entity_roles</field>
|
||||
<field name="topic">Roles</field>
|
||||
</record>
|
||||
<record id="rperm_create_entity_role" model="encoach.permission">
|
||||
<field name="code">create_entity_role</field>
|
||||
<field name="topic">Roles</field>
|
||||
</record>
|
||||
<record id="rperm_rename_entity_role" model="encoach.permission">
|
||||
<field name="code">rename_entity_role</field>
|
||||
<field name="topic">Roles</field>
|
||||
</record>
|
||||
<record id="rperm_edit_role_permissions" model="encoach.permission">
|
||||
<field name="code">edit_role_permissions</field>
|
||||
<field name="topic">Roles</field>
|
||||
</record>
|
||||
<record id="rperm_assign_to_role" model="encoach.permission">
|
||||
<field name="code">assign_to_role</field>
|
||||
<field name="topic">Roles</field>
|
||||
</record>
|
||||
<record id="rperm_delete_entity_role" model="encoach.permission">
|
||||
<field name="code">delete_entity_role</field>
|
||||
<field name="topic">Roles</field>
|
||||
</record>
|
||||
<record id="rperm_view_assignments" model="encoach.permission">
|
||||
<field name="code">view_assignments</field>
|
||||
<field name="topic">Assignments</field>
|
||||
</record>
|
||||
<record id="rperm_create_assignment" model="encoach.permission">
|
||||
<field name="code">create_assignment</field>
|
||||
<field name="topic">Assignments</field>
|
||||
</record>
|
||||
<record id="rperm_edit_assignment" model="encoach.permission">
|
||||
<field name="code">edit_assignment</field>
|
||||
<field name="topic">Assignments</field>
|
||||
</record>
|
||||
<record id="rperm_delete_assignment" model="encoach.permission">
|
||||
<field name="code">delete_assignment</field>
|
||||
<field name="topic">Assignments</field>
|
||||
</record>
|
||||
<record id="rperm_start_assignment" model="encoach.permission">
|
||||
<field name="code">start_assignment</field>
|
||||
<field name="topic">Assignments</field>
|
||||
</record>
|
||||
<record id="rperm_archive_assignment" model="encoach.permission">
|
||||
<field name="code">archive_assignment</field>
|
||||
<field name="topic">Assignments</field>
|
||||
</record>
|
||||
<record id="rperm_view_entity_statistics" model="encoach.permission">
|
||||
<field name="code">view_entity_statistics</field>
|
||||
<field name="topic">Statistics</field>
|
||||
</record>
|
||||
<record id="rperm_create_user" model="encoach.permission">
|
||||
<field name="code">create_user</field>
|
||||
<field name="topic">Users</field>
|
||||
</record>
|
||||
<record id="rperm_create_user_batch" model="encoach.permission">
|
||||
<field name="code">create_user_batch</field>
|
||||
<field name="topic">Users</field>
|
||||
</record>
|
||||
<record id="rperm_create_code" model="encoach.permission">
|
||||
<field name="code">create_code</field>
|
||||
<field name="topic">Codes</field>
|
||||
</record>
|
||||
<record id="rperm_create_code_batch" model="encoach.permission">
|
||||
<field name="code">create_code_batch</field>
|
||||
<field name="topic">Codes</field>
|
||||
</record>
|
||||
<record id="rperm_view_code_list" model="encoach.permission">
|
||||
<field name="code">view_code_list</field>
|
||||
<field name="topic">Codes</field>
|
||||
</record>
|
||||
<record id="rperm_delete_code" model="encoach.permission">
|
||||
<field name="code">delete_code</field>
|
||||
<field name="topic">Codes</field>
|
||||
</record>
|
||||
<record id="rperm_view_statistics" model="encoach.permission">
|
||||
<field name="code">view_statistics</field>
|
||||
<field name="topic">Statistics</field>
|
||||
</record>
|
||||
<record id="rperm_download_statistics_report" model="encoach.permission">
|
||||
<field name="code">download_statistics_report</field>
|
||||
<field name="topic">Statistics</field>
|
||||
</record>
|
||||
<record id="rperm_edit_grading_system" model="encoach.permission">
|
||||
<field name="code">edit_grading_system</field>
|
||||
<field name="topic">Grading</field>
|
||||
</record>
|
||||
<record id="rperm_view_student_performance" model="encoach.permission">
|
||||
<field name="code">view_student_performance</field>
|
||||
<field name="topic">Students</field>
|
||||
</record>
|
||||
<record id="rperm_upload_classroom" model="encoach.permission">
|
||||
<field name="code">upload_classroom</field>
|
||||
<field name="topic">Classrooms</field>
|
||||
</record>
|
||||
<record id="rperm_download_user_list" model="encoach.permission">
|
||||
<field name="code">download_user_list</field>
|
||||
<field name="topic">Users</field>
|
||||
</record>
|
||||
<record id="rperm_view_student_record" model="encoach.permission">
|
||||
<field name="code">view_student_record</field>
|
||||
<field name="topic">Students</field>
|
||||
</record>
|
||||
<record id="rperm_download_student_record" model="encoach.permission">
|
||||
<field name="code">download_student_record</field>
|
||||
<field name="topic">Students</field>
|
||||
</record>
|
||||
<record id="rperm_pay_entity" model="encoach.permission">
|
||||
<field name="code">pay_entity</field>
|
||||
<field name="topic">Payments</field>
|
||||
</record>
|
||||
<record id="rperm_view_payment_record" model="encoach.permission">
|
||||
<field name="code">view_payment_record</field>
|
||||
<field name="topic">Payments</field>
|
||||
</record>
|
||||
<record id="rperm_view_approval_workflows" model="encoach.permission">
|
||||
<field name="code">view_approval_workflows</field>
|
||||
<field name="topic">Workflows</field>
|
||||
</record>
|
||||
<record id="rperm_update_exam_privacy" model="encoach.permission">
|
||||
<field name="code">update_exam_privacy</field>
|
||||
<field name="topic">Exams</field>
|
||||
</record>
|
||||
<record id="rperm_view_workflows" model="encoach.permission">
|
||||
<field name="code">view_workflows</field>
|
||||
<field name="topic">Workflows</field>
|
||||
</record>
|
||||
<record id="rperm_configure_workflows" model="encoach.permission">
|
||||
<field name="code">configure_workflows</field>
|
||||
<field name="topic">Workflows</field>
|
||||
</record>
|
||||
<record id="rperm_edit_workflow" model="encoach.permission">
|
||||
<field name="code">edit_workflow</field>
|
||||
<field name="topic">Workflows</field>
|
||||
</record>
|
||||
<record id="rperm_delete_workflow" model="encoach.permission">
|
||||
<field name="code">delete_workflow</field>
|
||||
<field name="topic">Workflows</field>
|
||||
</record>
|
||||
<record id="rperm_view_confidential_exams" model="encoach.permission">
|
||||
<field name="code">view_confidential_exams</field>
|
||||
<field name="topic">Exams</field>
|
||||
</record>
|
||||
<record id="rperm_create_confidential_exams" model="encoach.permission">
|
||||
<field name="code">create_confidential_exams</field>
|
||||
<field name="topic">Exams</field>
|
||||
</record>
|
||||
<record id="rperm_create_public_exams" model="encoach.permission">
|
||||
<field name="code">create_public_exams</field>
|
||||
<field name="topic">Exams</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
8
custom_addons/encoach_core/models/__init__.py
Normal file
8
custom_addons/encoach_core/models/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from . import encoach_user
|
||||
from . import encoach_entity
|
||||
from . import role
|
||||
from . import permission
|
||||
from . import code
|
||||
from . import invite
|
||||
from . import user_entity_rel
|
||||
from . import entity_level_mapping
|
||||
57
custom_addons/encoach_core/models/code.py
Normal file
57
custom_addons/encoach_core/models/code.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import uuid
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class EncoachCode(models.Model):
|
||||
_name = "encoach.code"
|
||||
_description = "EnCoach Registration Code"
|
||||
|
||||
code = fields.Char(required=True, index=True, default=lambda self: uuid.uuid4().hex[:8].upper())
|
||||
entity_id = fields.Many2one("encoach.entity", ondelete="set null")
|
||||
creator_id = fields.Many2one("res.users", required=True)
|
||||
expiry_date = fields.Datetime()
|
||||
code_type = fields.Selection(
|
||||
[("individual", "Individual"), ("corporate", "Corporate")],
|
||||
default="individual",
|
||||
)
|
||||
max_uses = fields.Integer(default=1)
|
||||
uses = fields.Integer(default=0)
|
||||
user_type = fields.Selection(
|
||||
[
|
||||
("student", "Student"),
|
||||
("teacher", "Teacher"),
|
||||
("corporate", "Corporate"),
|
||||
],
|
||||
default="student",
|
||||
)
|
||||
legacy_id = fields.Char(index=True)
|
||||
|
||||
@api.model
|
||||
def validate_code(self, code_str):
|
||||
rec = self.search([("code", "=", code_str)], limit=1)
|
||||
if not rec:
|
||||
return {"valid": False, "error": "Code not found"}
|
||||
if rec.expiry_date and rec.expiry_date < fields.Datetime.now():
|
||||
return {"valid": False, "error": "Code expired"}
|
||||
if rec.max_uses and rec.uses >= rec.max_uses:
|
||||
return {"valid": False, "error": "Code fully used"}
|
||||
return {
|
||||
"valid": True,
|
||||
"code": rec.to_encoach_dict(),
|
||||
}
|
||||
|
||||
def to_encoach_dict(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
"id": self.id,
|
||||
"code": self.code,
|
||||
"entity": self.entity_id.id if self.entity_id else None,
|
||||
"creator": self.creator_id.id,
|
||||
"expiryDate": (
|
||||
self.expiry_date.isoformat() if self.expiry_date else None
|
||||
),
|
||||
"type": self.code_type,
|
||||
"maxUses": self.max_uses,
|
||||
"uses": self.uses,
|
||||
"userType": self.user_type,
|
||||
}
|
||||
62
custom_addons/encoach_core/models/encoach_entity.py
Normal file
62
custom_addons/encoach_core/models/encoach_entity.py
Normal file
@@ -0,0 +1,62 @@
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class EncoachEntity(models.Model):
|
||||
_name = 'encoach.entity'
|
||||
_description = 'EnCoach Entity'
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
name = fields.Char(required=True, tracking=True)
|
||||
code = fields.Char(required=True, tracking=True)
|
||||
type = fields.Selection([
|
||||
('corporate', 'Corporate'),
|
||||
('university', 'University'),
|
||||
('school', 'School'),
|
||||
('government', 'Government'),
|
||||
('freelance', 'Freelance'),
|
||||
], tracking=True)
|
||||
logo = fields.Binary(attachment=True)
|
||||
logo_url = fields.Char(string='Logo URL')
|
||||
primary_color = fields.Char(default='#4F46E5')
|
||||
secondary_color = fields.Char(default='#7C3AED')
|
||||
background_color = fields.Char(default='#FFFFFF')
|
||||
white_label_domain = fields.Char(string='White Label Domain')
|
||||
login_title = fields.Char()
|
||||
login_description = fields.Text()
|
||||
favicon = fields.Binary(attachment=True)
|
||||
results_release_mode = fields.Selection([
|
||||
('auto', 'Auto'),
|
||||
('manual_approval', 'Manual Approval'),
|
||||
], default='auto')
|
||||
active = fields.Boolean(default=True)
|
||||
user_ids = fields.Many2many('res.users', 'encoach_entity_user_rel', 'entity_id', 'user_id', string='Users')
|
||||
role_ids = fields.One2many('encoach.role', 'entity_id', string='Roles')
|
||||
user_count = fields.Integer(compute='_compute_user_count', store=True)
|
||||
|
||||
_code_unique = models.Constraint('UNIQUE(code)', 'Entity code must be unique.')
|
||||
|
||||
@api.depends('user_ids')
|
||||
def _compute_user_count(self):
|
||||
for rec in self:
|
||||
rec.user_count = len(rec.user_ids)
|
||||
|
||||
def to_api_dict(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'code': self.code,
|
||||
'type': self.type or '',
|
||||
'logo': bool(self.logo),
|
||||
'logo_url': self.logo_url or '',
|
||||
'primary_color': self.primary_color or '#4F46E5',
|
||||
'secondary_color': self.secondary_color or '#7C3AED',
|
||||
'background_color': self.background_color or '#FFFFFF',
|
||||
'white_label_domain': self.white_label_domain or '',
|
||||
'login_title': self.login_title or '',
|
||||
'login_description': self.login_description or '',
|
||||
'has_favicon': bool(self.favicon),
|
||||
'results_release_mode': self.results_release_mode or 'auto',
|
||||
'active': self.active,
|
||||
'user_count': self.user_count,
|
||||
}
|
||||
86
custom_addons/encoach_core/models/encoach_user.py
Normal file
86
custom_addons/encoach_core/models/encoach_user.py
Normal file
@@ -0,0 +1,86 @@
|
||||
from odoo import models, fields, api
|
||||
|
||||
USER_TYPE_SELECTION = [
|
||||
('student', 'Student'),
|
||||
('teacher', 'Teacher'),
|
||||
('admin', 'Admin'),
|
||||
('corporate', 'Corporate'),
|
||||
('mastercorporate', 'Master Corporate'),
|
||||
('agent', 'Agent'),
|
||||
('developer', 'Developer'),
|
||||
]
|
||||
|
||||
|
||||
class EncoachUser(models.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
user_type = fields.Selection(USER_TYPE_SELECTION, string='User Type')
|
||||
is_verified = fields.Boolean(default=False)
|
||||
entity_ids = fields.Many2many('encoach.entity', 'encoach_entity_user_rel', 'user_id', 'entity_id', string='Entities')
|
||||
phone = fields.Char()
|
||||
birth_date = fields.Date()
|
||||
gender = fields.Selection([('m', 'Male'), ('f', 'Female'), ('o', 'Other')])
|
||||
op_student_id = fields.Many2one('op.student', string='OpenEduCat Student')
|
||||
op_faculty_id = fields.Many2one('op.faculty', string='OpenEduCat Faculty')
|
||||
encoach_avatar = fields.Binary(attachment=True, string='EnCoach Avatar')
|
||||
last_login = fields.Datetime()
|
||||
role_ids = fields.Many2many(
|
||||
'encoach.role', 'encoach_role_user_rel', 'user_id', 'role_id', string='Roles',
|
||||
)
|
||||
permission_direct_ids = fields.Many2many(
|
||||
'encoach.permission', 'encoach_user_permission_rel', 'user_id', 'permission_id',
|
||||
string='Direct Permissions',
|
||||
)
|
||||
|
||||
first_login = fields.Boolean(default=True)
|
||||
account_source = fields.Selection([
|
||||
('self_registered', 'Self Registered'),
|
||||
('entity_bulk_upload', 'Entity Bulk Upload'),
|
||||
])
|
||||
account_status = fields.Selection([
|
||||
('unactivated', 'Unactivated'),
|
||||
('activated', 'Activated'),
|
||||
('suspended', 'Suspended'),
|
||||
], default='unactivated')
|
||||
|
||||
def get_all_permissions(self):
|
||||
"""Return the union of direct permissions and permissions from all assigned roles."""
|
||||
self.ensure_one()
|
||||
role_permissions = self.role_ids.mapped('permission_ids')
|
||||
return self.permission_direct_ids | role_permissions
|
||||
|
||||
def _api_user_type(self):
|
||||
"""Frontend routes require a non-empty role; many users have no encoach.user_type set yet."""
|
||||
self.ensure_one()
|
||||
if self.user_type:
|
||||
return self.user_type
|
||||
if self.op_faculty_id:
|
||||
return 'teacher'
|
||||
if self.op_student_id:
|
||||
return 'student'
|
||||
if self.has_group('base.group_system'):
|
||||
return 'admin'
|
||||
return ''
|
||||
|
||||
def to_api_dict(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'email': self.email or '',
|
||||
'login': self.login or '',
|
||||
'user_type': self._api_user_type(),
|
||||
'is_verified': self.is_verified,
|
||||
'phone': self.phone or '',
|
||||
'birth_date': self.birth_date.isoformat() if self.birth_date else None,
|
||||
'gender': self.gender or '',
|
||||
'avatar': bool(self.encoach_avatar),
|
||||
'entities': [
|
||||
{'id': e.id, 'name': e.name, 'logo': bool(e.logo)}
|
||||
for e in self.entity_ids
|
||||
],
|
||||
'last_login': self.last_login.isoformat() if self.last_login else None,
|
||||
'first_login': self.first_login,
|
||||
'account_source': self.account_source or '',
|
||||
'account_status': self.account_status or 'unactivated',
|
||||
}
|
||||
15
custom_addons/encoach_core/models/entity_level_mapping.py
Normal file
15
custom_addons/encoach_core/models/entity_level_mapping.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class EncoachEntityLevelMapping(models.Model):
|
||||
_name = 'encoach.entity.level.mapping'
|
||||
_description = 'Entity Level Mapping'
|
||||
|
||||
entity_id = fields.Many2one('encoach.entity', required=True, ondelete='cascade', index=True)
|
||||
min_score = fields.Float(required=True)
|
||||
max_score = fields.Float(required=True)
|
||||
internal_level_name = fields.Char(size=100, required=True)
|
||||
cefr_equivalent = fields.Selection([
|
||||
('pre_a1', 'Pre-A1'), ('a1', 'A1'), ('a2', 'A2'),
|
||||
('b1', 'B1'), ('b2', 'B2'), ('c1', 'C1'), ('c2', 'C2'),
|
||||
])
|
||||
46
custom_addons/encoach_core/models/invite.py
Normal file
46
custom_addons/encoach_core/models/invite.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class EncoachInvite(models.Model):
|
||||
_name = "encoach.invite"
|
||||
_description = "EnCoach Entity Invite"
|
||||
|
||||
from_user_id = fields.Many2one("res.users", required=True, ondelete="cascade")
|
||||
to_user_id = fields.Many2one("res.users", required=True, ondelete="cascade")
|
||||
entity_id = fields.Many2one("encoach.entity", required=True, ondelete="cascade")
|
||||
status = fields.Selection(
|
||||
[
|
||||
("pending", "Pending"),
|
||||
("accepted", "Accepted"),
|
||||
("declined", "Declined"),
|
||||
],
|
||||
default="pending",
|
||||
)
|
||||
legacy_id = fields.Char(index=True)
|
||||
|
||||
def action_accept(self):
|
||||
self.ensure_one()
|
||||
self.status = "accepted"
|
||||
existing = self.env["encoach.user.entity.rel"].search([
|
||||
("user_id", "=", self.to_user_id.id),
|
||||
("entity_id", "=", self.entity_id.id),
|
||||
], limit=1)
|
||||
if not existing:
|
||||
self.env["encoach.user.entity.rel"].create({
|
||||
"user_id": self.to_user_id.id,
|
||||
"entity_id": self.entity_id.id,
|
||||
})
|
||||
|
||||
def action_decline(self):
|
||||
self.ensure_one()
|
||||
self.status = "declined"
|
||||
|
||||
def to_encoach_dict(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
"id": self.id,
|
||||
"from": self.from_user_id.id,
|
||||
"to": self.to_user_id.id,
|
||||
"entity": self.entity_id.id,
|
||||
"status": self.status,
|
||||
}
|
||||
24
custom_addons/encoach_core/models/permission.py
Normal file
24
custom_addons/encoach_core/models/permission.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class EncoachPermission(models.Model):
|
||||
_name = "encoach.permission"
|
||||
_description = "EnCoach Permission"
|
||||
|
||||
code = fields.Char(required=True, index=True)
|
||||
topic = fields.Char()
|
||||
legacy_id = fields.Char(index=True)
|
||||
|
||||
_code_unique = models.Constraint(
|
||||
"UNIQUE(code)",
|
||||
"Permission code must be unique.",
|
||||
)
|
||||
|
||||
def to_encoach_dict(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
"id": self.id,
|
||||
"type": self.code,
|
||||
"topic": self.topic or "",
|
||||
"users": [],
|
||||
}
|
||||
22
custom_addons/encoach_core/models/role.py
Normal file
22
custom_addons/encoach_core/models/role.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class EncoachRole(models.Model):
|
||||
_name = "encoach.role"
|
||||
_description = "EnCoach Role"
|
||||
|
||||
name = fields.Char(required=True)
|
||||
entity_id = fields.Many2one("encoach.entity", required=True, ondelete="cascade")
|
||||
permission_ids = fields.Many2many("encoach.permission", string="Permissions")
|
||||
is_default = fields.Boolean(default=False)
|
||||
legacy_id = fields.Char(index=True)
|
||||
|
||||
def to_encoach_dict(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
"id": self.id,
|
||||
"label": self.name,
|
||||
"entityID": self.entity_id.id,
|
||||
"permissions": [p.code for p in self.permission_ids if p.code],
|
||||
"isDefault": self.is_default,
|
||||
}
|
||||
15
custom_addons/encoach_core/models/user_entity_rel.py
Normal file
15
custom_addons/encoach_core/models/user_entity_rel.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class EncoachUserEntityRel(models.Model):
|
||||
_name = "encoach.user.entity.rel"
|
||||
_description = "EnCoach User-Entity Membership"
|
||||
|
||||
user_id = fields.Many2one("res.users", required=True, ondelete="cascade")
|
||||
entity_id = fields.Many2one("encoach.entity", required=True, ondelete="cascade")
|
||||
role_id = fields.Many2one("encoach.role", ondelete="set null")
|
||||
|
||||
_user_entity_unique = models.Constraint(
|
||||
"UNIQUE(user_id, entity_id)",
|
||||
"A user can only have one membership per entity.",
|
||||
)
|
||||
30
custom_addons/encoach_core/security/encoach_security.xml
Normal file
30
custom_addons/encoach_core/security/encoach_security.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!-- Security groups matching the 7 user types -->
|
||||
<record id="group_encoach_student" model="res.groups">
|
||||
<field name="name">EnCoach Student</field>
|
||||
</record>
|
||||
<record id="group_encoach_teacher" model="res.groups">
|
||||
<field name="name">EnCoach Teacher</field>
|
||||
</record>
|
||||
<record id="group_encoach_corporate" model="res.groups">
|
||||
<field name="name">EnCoach Corporate</field>
|
||||
</record>
|
||||
<record id="group_encoach_admin" model="res.groups">
|
||||
<field name="name">EnCoach Admin</field>
|
||||
<field name="implied_ids" eval="[(4, ref('group_encoach_teacher'))]"/>
|
||||
</record>
|
||||
<record id="group_encoach_developer" model="res.groups">
|
||||
<field name="name">EnCoach Developer</field>
|
||||
<field name="implied_ids" eval="[(4, ref('group_encoach_admin'))]"/>
|
||||
</record>
|
||||
<record id="group_encoach_agent" model="res.groups">
|
||||
<field name="name">EnCoach Agent</field>
|
||||
</record>
|
||||
<record id="group_encoach_mastercorporate" model="res.groups">
|
||||
<field name="name">EnCoach Master Corporate</field>
|
||||
<field name="implied_ids" eval="[(4, ref('group_encoach_corporate'))]"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
20
custom_addons/encoach_core/security/ir.model.access.csv
Normal file
20
custom_addons/encoach_core/security/ir.model.access.csv
Normal file
@@ -0,0 +1,20 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_encoach_entity_student,encoach.entity.student,model_encoach_entity,group_encoach_student,1,0,0,0
|
||||
access_encoach_entity_teacher,encoach.entity.teacher,model_encoach_entity,group_encoach_teacher,1,1,0,0
|
||||
access_encoach_entity_admin,encoach.entity.admin,model_encoach_entity,group_encoach_admin,1,1,1,1
|
||||
access_encoach_role_student,encoach.role.student,model_encoach_role,group_encoach_student,1,0,0,0
|
||||
access_encoach_role_teacher,encoach.role.teacher,model_encoach_role,group_encoach_teacher,1,1,0,0
|
||||
access_encoach_role_admin,encoach.role.admin,model_encoach_role,group_encoach_admin,1,1,1,1
|
||||
access_encoach_permission_student,encoach.permission.student,model_encoach_permission,group_encoach_student,1,0,0,0
|
||||
access_encoach_permission_teacher,encoach.permission.teacher,model_encoach_permission,group_encoach_teacher,1,0,0,0
|
||||
access_encoach_permission_admin,encoach.permission.admin,model_encoach_permission,group_encoach_admin,1,1,1,1
|
||||
access_encoach_invite_student,encoach.invite.student,model_encoach_invite,group_encoach_student,1,0,0,0
|
||||
access_encoach_invite_teacher,encoach.invite.teacher,model_encoach_invite,group_encoach_teacher,1,1,0,0
|
||||
access_encoach_invite_admin,encoach.invite.admin,model_encoach_invite,group_encoach_admin,1,1,1,1
|
||||
access_encoach_code_student,encoach.code.student,model_encoach_code,group_encoach_student,1,0,0,0
|
||||
access_encoach_code_teacher,encoach.code.teacher,model_encoach_code,group_encoach_teacher,1,1,0,0
|
||||
access_encoach_code_admin,encoach.code.admin,model_encoach_code,group_encoach_admin,1,1,1,1
|
||||
access_encoach_user_entity_rel_all,encoach.user.entity.rel.all,model_encoach_user_entity_rel,base.group_user,1,1,1,1
|
||||
access_encoach_level_mapping_student,encoach.entity.level.mapping.student,model_encoach_entity_level_mapping,group_encoach_student,1,0,0,0
|
||||
access_encoach_level_mapping_teacher,encoach.entity.level.mapping.teacher,model_encoach_entity_level_mapping,group_encoach_teacher,1,1,0,0
|
||||
access_encoach_level_mapping_admin,encoach.entity.level.mapping.admin,model_encoach_entity_level_mapping,group_encoach_admin,1,1,1,1
|
||||
|
16
custom_addons/encoach_core/static/src/css/dashboard.css
Normal file
16
custom_addons/encoach_core/static/src/css/dashboard.css
Normal file
@@ -0,0 +1,16 @@
|
||||
.o_encoach_dashboard .card {
|
||||
transition: transform 0.15s, box-shadow 0.15s;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
.o_encoach_dashboard .card.cursor-pointer:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
.o_encoach_dashboard h2 {
|
||||
color: #1f2937;
|
||||
font-weight: 700;
|
||||
}
|
||||
.o_encoach_dashboard .badge {
|
||||
text-transform: capitalize;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
94
custom_addons/encoach_core/static/src/js/dashboard.js
Normal file
94
custom_addons/encoach_core/static/src/js/dashboard.js
Normal file
@@ -0,0 +1,94 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { Component, onWillStart, useState } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { Layout } from "@web/search/layout";
|
||||
|
||||
class EncoachDashboard extends Component {
|
||||
static template = "encoach_core.Dashboard";
|
||||
static components = { Layout };
|
||||
static props = ["*"];
|
||||
|
||||
setup() {
|
||||
this.orm = useService("orm");
|
||||
this.action = useService("action");
|
||||
this.state = useState({
|
||||
loading: true,
|
||||
stats: {
|
||||
total_students: 0,
|
||||
total_teachers: 0,
|
||||
total_courses: 0,
|
||||
total_exams: 0,
|
||||
active_sessions: 0,
|
||||
pending_grading: 0,
|
||||
pending_approval: 0,
|
||||
total_entities: 0,
|
||||
},
|
||||
recentAttempts: [],
|
||||
});
|
||||
|
||||
onWillStart(async () => {
|
||||
await this.loadDashboardData();
|
||||
});
|
||||
}
|
||||
|
||||
async loadDashboardData() {
|
||||
try {
|
||||
const [students, teachers, courses, exams, sessions, pendingGrading, pendingApproval, entities] = await Promise.all([
|
||||
this.orm.searchCount("res.users", [["account_source", "!=", false]]),
|
||||
this.orm.searchCount("res.users", [["user_type", "=", "teacher"]]),
|
||||
this.orm.searchCount("op.course", []),
|
||||
this.orm.searchCount("encoach.exam.custom", []),
|
||||
this.orm.searchCount("encoach.student.attempt", [["status", "=", "in_progress"]]),
|
||||
this.orm.searchCount("encoach.student.attempt", [["status", "=", "scoring"]]),
|
||||
this.orm.searchCount("encoach.student.attempt", [["status", "=", "pending_approval"]]),
|
||||
this.orm.searchCount("encoach.entity", []),
|
||||
]);
|
||||
|
||||
this.state.stats = {
|
||||
total_students: students,
|
||||
total_teachers: teachers,
|
||||
total_courses: courses,
|
||||
total_exams: exams,
|
||||
active_sessions: sessions,
|
||||
pending_grading: pendingGrading,
|
||||
pending_approval: pendingApproval,
|
||||
total_entities: entities,
|
||||
};
|
||||
|
||||
const attempts = await this.orm.searchRead(
|
||||
"encoach.student.attempt",
|
||||
[],
|
||||
["student_id", "exam_id", "status", "overall_band", "started_at"],
|
||||
{ limit: 10, order: "started_at desc" },
|
||||
);
|
||||
this.state.recentAttempts = attempts;
|
||||
|
||||
} catch (e) {
|
||||
console.error("Dashboard load error:", e);
|
||||
} finally {
|
||||
this.state.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
openAction(model, name, domain = []) {
|
||||
this.action.doAction({
|
||||
type: "ir.actions.act_window",
|
||||
name: name,
|
||||
res_model: model,
|
||||
views: [[false, "list"], [false, "form"]],
|
||||
domain: domain,
|
||||
});
|
||||
}
|
||||
|
||||
openStudents() { this.openAction("res.users", "Students", [["account_source", "!=", false]]); }
|
||||
openTeachers() { this.openAction("res.users", "Teachers", [["user_type", "=", "teacher"]]); }
|
||||
openCourses() { this.openAction("op.course", "Courses"); }
|
||||
openExams() { this.openAction("encoach.exam.custom", "Exams"); }
|
||||
openGradingQueue() { this.openAction("encoach.student.attempt", "Grading Queue", [["status", "=", "scoring"]]); }
|
||||
openPendingApproval() { this.openAction("encoach.student.attempt", "Score Approval", [["status", "=", "pending_approval"]]); }
|
||||
openEntities() { this.openAction("encoach.entity", "Entities"); }
|
||||
}
|
||||
|
||||
registry.category("actions").add("encoach_dashboard", EncoachDashboard);
|
||||
162
custom_addons/encoach_core/static/src/xml/dashboard.xml
Normal file
162
custom_addons/encoach_core/static/src/xml/dashboard.xml
Normal file
@@ -0,0 +1,162 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="encoach_core.Dashboard">
|
||||
<Layout display="{ controlPanel: {} }">
|
||||
<div class="o_encoach_dashboard">
|
||||
<div class="container-fluid py-4">
|
||||
<h2 class="mb-4">EnCoach Dashboard</h2>
|
||||
|
||||
<t t-if="state.loading">
|
||||
<div class="text-center py-5">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"/>
|
||||
<p class="mt-2">Loading dashboard...</p>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-else="">
|
||||
<!-- KPI Cards -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<div class="card shadow-sm h-100 cursor-pointer" t-on-click="openStudents">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="rounded-circle bg-primary bg-opacity-10 p-3 me-3">
|
||||
<i class="fa fa-users fa-2x text-primary"/>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="mb-0" t-esc="state.stats.total_students"/>
|
||||
<small class="text-muted">Students</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<div class="card shadow-sm h-100 cursor-pointer" t-on-click="openTeachers">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="rounded-circle bg-success bg-opacity-10 p-3 me-3">
|
||||
<i class="fa fa-chalkboard-teacher fa-2x text-success"/>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="mb-0" t-esc="state.stats.total_teachers"/>
|
||||
<small class="text-muted">Teachers</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<div class="card shadow-sm h-100 cursor-pointer" t-on-click="openCourses">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="rounded-circle bg-info bg-opacity-10 p-3 me-3">
|
||||
<i class="fa fa-book fa-2x text-info"/>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="mb-0" t-esc="state.stats.total_courses"/>
|
||||
<small class="text-muted">Courses</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<div class="card shadow-sm h-100 cursor-pointer" t-on-click="openExams">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="rounded-circle bg-warning bg-opacity-10 p-3 me-3">
|
||||
<i class="fa fa-file-text fa-2x text-warning"/>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="mb-0" t-esc="state.stats.total_exams"/>
|
||||
<small class="text-muted">Exams</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Cards -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="card shadow-sm border-start border-danger border-4 cursor-pointer" t-on-click="openGradingQueue">
|
||||
<div class="card-body">
|
||||
<h5>Grading Queue</h5>
|
||||
<h2 class="text-danger" t-esc="state.stats.pending_grading"/>
|
||||
<small class="text-muted">Exams awaiting grading</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="card shadow-sm border-start border-warning border-4 cursor-pointer" t-on-click="openPendingApproval">
|
||||
<div class="card-body">
|
||||
<h5>Pending Approval</h5>
|
||||
<h2 class="text-warning" t-esc="state.stats.pending_approval"/>
|
||||
<small class="text-muted">Scores awaiting release</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="card shadow-sm border-start border-info border-4 cursor-pointer" t-on-click="openEntities">
|
||||
<div class="card-body">
|
||||
<h5>Entities</h5>
|
||||
<h2 class="text-info" t-esc="state.stats.total_entities"/>
|
||||
<small class="text-muted">Registered organizations</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Attempts Table -->
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-white">
|
||||
<h5 class="mb-0">Recent Exam Attempts</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Student</th>
|
||||
<th>Exam</th>
|
||||
<th>Status</th>
|
||||
<th>Band</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-foreach="state.recentAttempts" t-as="attempt" t-key="attempt.id">
|
||||
<tr>
|
||||
<td t-esc="attempt.student_id[1]"/>
|
||||
<td t-esc="attempt.exam_id ? attempt.exam_id[1] : '-'"/>
|
||||
<td>
|
||||
<span class="badge"
|
||||
t-att-class="{
|
||||
'bg-success': attempt.status === 'released',
|
||||
'bg-warning': attempt.status === 'pending_approval',
|
||||
'bg-info': attempt.status === 'scoring',
|
||||
'bg-secondary': attempt.status === 'in_progress',
|
||||
'bg-primary': attempt.status === 'scored'
|
||||
}"
|
||||
t-esc="attempt.status"/>
|
||||
</td>
|
||||
<td t-esc="attempt.overall_band || '-'"/>
|
||||
<td t-esc="attempt.started_at"/>
|
||||
</tr>
|
||||
</t>
|
||||
<t t-if="!state.recentAttempts.length">
|
||||
<tr>
|
||||
<td colspan="5" class="text-center text-muted py-4">No recent attempts</td>
|
||||
</tr>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
</t>
|
||||
</templates>
|
||||
13
custom_addons/encoach_core/views/dashboard_action.xml
Normal file
13
custom_addons/encoach_core/views/dashboard_action.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="action_encoach_dashboard" model="ir.actions.client">
|
||||
<field name="name">EnCoach Dashboard</field>
|
||||
<field name="tag">encoach_dashboard</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_encoach_dashboard_action"
|
||||
name="Dashboard"
|
||||
parent="menu_encoach_dashboard"
|
||||
action="action_encoach_dashboard"
|
||||
sequence="1"/>
|
||||
</odoo>
|
||||
115
custom_addons/encoach_core/views/encoach_entity_views.xml
Normal file
115
custom_addons/encoach_core/views/encoach_entity_views.xml
Normal file
@@ -0,0 +1,115 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_encoach_entity_list" model="ir.ui.view">
|
||||
<field name="name">encoach.entity.list</field>
|
||||
<field name="model">encoach.entity</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Entities">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="type"/>
|
||||
<field name="white_label_domain"/>
|
||||
<field name="user_count"/>
|
||||
<field name="results_release_mode"/>
|
||||
<field name="active" column_invisible="True"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_encoach_entity_form" model="ir.ui.view">
|
||||
<field name="name">encoach.entity.form</field>
|
||||
<field name="model">encoach.entity</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Entity">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
</div>
|
||||
<field name="logo" widget="image" class="oe_avatar"/>
|
||||
<div class="oe_title">
|
||||
<h1><field name="name" placeholder="Entity Name"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="code"/>
|
||||
<field name="type"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="white_label_domain"/>
|
||||
<field name="results_release_mode"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Branding">
|
||||
<group>
|
||||
<group>
|
||||
<field name="primary_color" widget="color"/>
|
||||
<field name="secondary_color" widget="color"/>
|
||||
<field name="background_color" widget="color"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="logo_url"/>
|
||||
<field name="login_title"/>
|
||||
<field name="favicon" widget="image"/>
|
||||
</group>
|
||||
</group>
|
||||
<field name="login_description"/>
|
||||
</page>
|
||||
<page string="Users">
|
||||
<field name="user_ids">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="login"/>
|
||||
<field name="user_type"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Roles">
|
||||
<field name="role_ids">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_encoach_entity_search" model="ir.ui.view">
|
||||
<field name="name">encoach.entity.search</field>
|
||||
<field name="model">encoach.entity</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Entities">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<filter string="Corporate" name="corporate" domain="[('type', '=', 'corporate')]"/>
|
||||
<filter string="University" name="university" domain="[('type', '=', 'university')]"/>
|
||||
<filter string="School" name="school" domain="[('type', '=', 'school')]"/>
|
||||
<separator/>
|
||||
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_encoach_entity" model="ir.actions.act_window">
|
||||
<field name="name">Entities</field>
|
||||
<field name="res_model">encoach.entity</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="search_view_id" ref="view_encoach_entity_search"/>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create your first entity
|
||||
</p>
|
||||
<p>Entities represent organizations (corporates, universities, schools) on the EnCoach platform.</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_encoach_entity_list"
|
||||
name="Entities"
|
||||
parent="menu_encoach_entity"
|
||||
action="action_encoach_entity"
|
||||
sequence="1"/>
|
||||
</odoo>
|
||||
15
custom_addons/encoach_core/views/encoach_menus.xml
Normal file
15
custom_addons/encoach_core/views/encoach_menus.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<menuitem id="menu_encoach_root" name="EnCoach" sequence="10" web_icon="encoach_core,static/description/icon.png"/>
|
||||
|
||||
<menuitem id="menu_encoach_dashboard" name="Dashboard" parent="menu_encoach_root" sequence="1"/>
|
||||
<menuitem id="menu_encoach_exams" name="Exams" parent="menu_encoach_root" sequence="10"/>
|
||||
<menuitem id="menu_encoach_courses" name="Courses" parent="menu_encoach_root" sequence="20"/>
|
||||
<menuitem id="menu_encoach_students" name="Students" parent="menu_encoach_root" sequence="30"/>
|
||||
<menuitem id="menu_encoach_lms" name="LMS" parent="menu_encoach_root" sequence="40"/>
|
||||
<menuitem id="menu_encoach_content" name="Content" parent="menu_encoach_root" sequence="50"/>
|
||||
<menuitem id="menu_encoach_entity" name="Entity Management" parent="menu_encoach_root" sequence="60"/>
|
||||
<menuitem id="menu_encoach_reports" name="Reports" parent="menu_encoach_root" sequence="70"/>
|
||||
<menuitem id="menu_encoach_config" name="Configuration" parent="menu_encoach_root" sequence="80"/>
|
||||
<menuitem id="menu_encoach_support" name="Support" parent="menu_encoach_root" sequence="90"/>
|
||||
</odoo>
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_entity_level_mapping_form" model="ir.ui.view">
|
||||
<field name="name">encoach.entity.level.mapping.form</field>
|
||||
<field name="model">encoach.entity.level.mapping</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Entity Level Mapping">
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="entity_id"/>
|
||||
<field name="internal_level_name"/>
|
||||
<field name="cefr_equivalent"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="min_score"/>
|
||||
<field name="max_score"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_entity_level_mapping_list" model="ir.ui.view">
|
||||
<field name="name">encoach.entity.level.mapping.list</field>
|
||||
<field name="model">encoach.entity.level.mapping</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Entity Level Mappings" editable="bottom">
|
||||
<field name="entity_id"/>
|
||||
<field name="internal_level_name"/>
|
||||
<field name="min_score"/>
|
||||
<field name="max_score"/>
|
||||
<field name="cefr_equivalent"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_entity_level_mapping" model="ir.actions.act_window">
|
||||
<field name="name">Entity Level Mappings</field>
|
||||
<field name="res_model">encoach.entity.level.mapping</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_entity_level_mapping"
|
||||
name="Entity Level Mappings"
|
||||
parent="encoach_core.menu_encoach_entity"
|
||||
action="encoach_core.action_entity_level_mapping"
|
||||
sequence="10"/>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user