EnCoach Odoo 19 custom modules

Full backend implementation with custom Odoo modules:
- encoach_api: Core API, user management, JWT auth
- encoach_exam: Exam generation (reading, writing, listening, speaking)
- encoach_evaluate: AI-powered evaluation (writing, speaking)
- encoach_training: Training tips and walkthrough
- encoach_storage: File storage management
- encoach_payment: Stripe, PayPal, Paymob integration
- encoach_mail: Email notifications

Made-with: Cursor
This commit is contained in:
Talal Sharabi
2026-03-14 16:46:46 +04:00
commit f5b627256f
168 changed files with 13428 additions and 0 deletions

View File

@@ -0,0 +1 @@
from . import models

View File

@@ -0,0 +1,16 @@
{
"name": "EnCoach Evaluation",
"version": "19.0.1.0.0",
"category": "Education",
"summary": "Evaluation records and AI job tracking for EnCoach",
"description": "Stores writing/speaking evaluations and background AI job state.",
"depends": ["encoach_core", "encoach_exam", "encoach_ai"],
"data": [
"security/ir.model.access.csv",
"security/rules.xml",
"views/evaluation_views.xml",
"views/ai_job_views.xml",
],
"installable": True,
"license": "LGPL-3",
}

View File

@@ -0,0 +1,2 @@
from . import evaluation
from . import ai_job

View File

@@ -0,0 +1,34 @@
from odoo import models, fields
class EncoachAIJob(models.Model):
_name = "encoach.ai.job"
_description = "EnCoach AI Background Job"
job_type = fields.Selection(
[
("writing_grading", "Writing Grading"),
("speaking_grading", "Speaking Grading"),
("video_generation", "Video Generation"),
],
string="Job Type",
)
status = fields.Selection(
[
("pending", "Pending"),
("in_progress", "In Progress"),
("completed", "Completed"),
("error", "Error"),
],
default="pending",
)
user_id = fields.Many2one("res.users", string="User", ondelete="set null")
evaluation_id = fields.Many2one(
"encoach.evaluation", string="Evaluation", ondelete="cascade",
)
input_data = fields.Json()
result_data = fields.Json()
error_message = fields.Text()
created_at = fields.Datetime(default=fields.Datetime.now)
completed_at = fields.Datetime()
retry_count = fields.Integer(default=0)

View File

@@ -0,0 +1,45 @@
from odoo import models, fields
class EncoachEvaluation(models.Model):
_name = "encoach.evaluation"
_description = "EnCoach Evaluation Record"
user_id = fields.Many2one("res.users", string="User", ondelete="cascade")
session_id = fields.Integer(string="Session Reference")
exercise_id = fields.Char(string="Exercise ID")
exercise_type = fields.Selection(
[("writing", "Writing"), ("speaking", "Speaking")],
string="Exercise Type",
)
status = fields.Selection(
[
("pending", "Pending"),
("in_progress", "In Progress"),
("completed", "Completed"),
("error", "Error"),
],
default="pending",
)
result = fields.Json()
created_at = fields.Datetime(default=fields.Datetime.now)
completed_at = fields.Datetime()
legacy_id = fields.Char(index=True)
def to_encoach_dict(self):
self.ensure_one()
return {
"id": self.id,
"userId": self.user_id.id if self.user_id else None,
"sessionId": self.session_id,
"exerciseId": self.exercise_id or "",
"exerciseType": self.exercise_type or "",
"status": self.status,
"result": self.result,
"createdAt": (
self.created_at.isoformat() if self.created_at else None
),
"completedAt": (
self.completed_at.isoformat() if self.completed_at else None
),
}

View File

@@ -0,0 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_encoach_evaluation_student,encoach.evaluation.student,model_encoach_evaluation,encoach_core.group_encoach_student,1,0,0,0
access_encoach_evaluation_admin,encoach.evaluation.admin,model_encoach_evaluation,encoach_core.group_encoach_admin,1,1,1,1
access_encoach_ai_job_admin,encoach.ai.job.admin,model_encoach_ai_job,encoach_core.group_encoach_admin,1,1,1,1
access_encoach_evaluation_sysadmin,encoach.evaluation.sysadmin,model_encoach_evaluation,base.group_system,1,1,1,1
access_encoach_ai_job_sysadmin,encoach.ai.job.sysadmin,model_encoach_ai_job,base.group_system,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_encoach_evaluation_student encoach.evaluation.student model_encoach_evaluation encoach_core.group_encoach_student 1 0 0 0
3 access_encoach_evaluation_admin encoach.evaluation.admin model_encoach_evaluation encoach_core.group_encoach_admin 1 1 1 1
4 access_encoach_ai_job_admin encoach.ai.job.admin model_encoach_ai_job encoach_core.group_encoach_admin 1 1 1 1
5 access_encoach_evaluation_sysadmin encoach.evaluation.sysadmin model_encoach_evaluation base.group_system 1 1 1 1
6 access_encoach_ai_job_sysadmin encoach.ai.job.sysadmin model_encoach_ai_job base.group_system 1 1 1 1

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data noupdate="1">
<record id="rule_evaluation_student_own" model="ir.rule">
<field name="name">Students see own evaluations</field>
<field name="model_id" ref="model_encoach_evaluation"/>
<field name="groups" eval="[(4, ref('encoach_core.group_encoach_student'))]"/>
<field name="domain_force">[('user_id', '=', user.id)]</field>
</record>
<record id="rule_evaluation_admin" model="ir.rule">
<field name="name">Admins see all evaluations</field>
<field name="model_id" ref="model_encoach_evaluation"/>
<field name="groups" eval="[(4, ref('encoach_core.group_encoach_admin'))]"/>
<field name="domain_force">[(1, '=', 1)]</field>
</record>
<record id="rule_ai_job_admin" model="ir.rule">
<field name="name">Admins see all AI jobs</field>
<field name="model_id" ref="model_encoach_ai_job"/>
<field name="groups" eval="[(4, ref('encoach_core.group_encoach_admin'))]"/>
<field name="domain_force">[(1, '=', 1)]</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="view_encoach_ai_job_list" model="ir.ui.view">
<field name="name">encoach.ai.job.list</field>
<field name="model">encoach.ai.job</field>
<field name="arch" type="xml">
<list string="AI Jobs">
<field name="job_type"/>
<field name="status"/>
<field name="user_id"/>
<field name="retry_count"/>
<field name="created_at"/>
<field name="completed_at"/>
<field name="error_message"/>
</list>
</field>
</record>
<record id="view_encoach_ai_job_form" model="ir.ui.view">
<field name="name">encoach.ai.job.form</field>
<field name="model">encoach.ai.job</field>
<field name="arch" type="xml">
<form string="AI Job">
<sheet>
<group>
<field name="job_type"/>
<field name="status"/>
<field name="user_id"/>
<field name="evaluation_id"/>
<field name="retry_count"/>
<field name="created_at"/>
<field name="completed_at"/>
<field name="error_message"/>
</group>
<group string="Data">
<field name="input_data" widget="json"/>
<field name="result_data" widget="json"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_encoach_ai_job" model="ir.actions.act_window">
<field name="name">AI Jobs</field>
<field name="res_model">encoach.ai.job</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="menu_encoach_ai_job"
name="AI Jobs"
parent="encoach_core.menu_encoach_ai"
action="action_encoach_ai_job"
sequence="20"/>
</odoo>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="view_encoach_evaluation_list" model="ir.ui.view">
<field name="name">encoach.evaluation.list</field>
<field name="model">encoach.evaluation</field>
<field name="arch" type="xml">
<list string="Evaluations">
<field name="user_id"/>
<field name="exercise_type"/>
<field name="exercise_id"/>
<field name="status"/>
<field name="created_at"/>
<field name="completed_at"/>
</list>
</field>
</record>
<record id="view_encoach_evaluation_form" model="ir.ui.view">
<field name="name">encoach.evaluation.form</field>
<field name="model">encoach.evaluation</field>
<field name="arch" type="xml">
<form string="Evaluation">
<sheet>
<field name="status" widget="statusbar" statusbar_visible="pending,in_progress,completed,error"/>
<group>
<field name="user_id"/>
<field name="exercise_type"/>
<field name="exercise_id"/>
<field name="session_id"/>
<field name="created_at"/>
<field name="completed_at"/>
<field name="result" widget="json"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_encoach_evaluation" model="ir.actions.act_window">
<field name="name">Evaluations</field>
<field name="res_model">encoach.evaluation</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="menu_encoach_evaluation"
name="Evaluations"
parent="encoach_core.menu_encoach_ai"
action="action_encoach_evaluation"
sequence="10"/>
</odoo>