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,137 @@
import json
import logging
from odoo import http
from odoo.http import request
from .base import EncoachMixin
_logger = logging.getLogger(__name__)
class StatsController(EncoachMixin, http.Controller):
# -------------------------------------------------------------- save
@http.route('/api/stats', type='http', auth='public', methods=['POST', 'OPTIONS'], csrf=False, cors='*')
def save_stats(self, **kwargs):
try:
user = self._authenticate()
if not user:
return self._error_response('Unauthorized', 401)
body = self._get_json_body()
stat_id = body.get('_id') or body.get('id')
Stat = request.env['encoach.stat'].sudo()
vals = {}
if body.get('sessionId'):
vals['session_id'] = int(body['sessionId'])
if body.get('module'):
vals['module'] = body['module']
if 'score' in body:
vals['score'] = body['score']
if body.get('result') is not None:
vals['result'] = json.dumps(body['result']) if isinstance(body['result'], dict) else body['result']
if body.get('examId'):
vals['exam_id'] = int(body['examId'])
if body.get('assignmentId'):
vals['assignment_id'] = int(body['assignmentId'])
if stat_id:
record = Stat.browse(int(stat_id))
if record.exists():
record.write(vals)
return self._json_response(self._serialize_stat(record))
vals['user_id'] = user.id
record = Stat.create(vals)
return self._json_response(self._serialize_stat(record), status=201)
except Exception:
_logger.exception("Save stats error")
return self._error_response('Internal server error', 500)
# --------------------------------------------------------------- get
@http.route(
'/api/stats/<int:stat_id>', type='http', auth='public',
methods=['GET', 'OPTIONS'], csrf=False, cors='*',
)
def get_stat(self, stat_id, **kwargs):
try:
user = self._authenticate()
if not user:
return self._error_response('Unauthorized', 401)
stat = request.env['encoach.stat'].sudo().browse(stat_id)
if not stat.exists():
return self._error_response('Stat not found', 404)
return self._json_response(self._serialize_stat(stat))
except Exception:
_logger.exception("Get stat error")
return self._error_response('Internal server error', 500)
# --------------------------------------------------------- statistical
@http.route('/api/statistical', type='http', auth='public', methods=['POST', 'OPTIONS'], csrf=False, cors='*')
def statistical_query(self, **kwargs):
"""Run statistical queries against exam stats."""
try:
user = self._authenticate()
if not user:
return self._error_response('Unauthorized', 401)
body = self._get_json_body()
Stat = request.env['encoach.stat'].sudo()
domain = []
user_id = body.get('userId')
if user_id:
domain.append(('user_id', '=', int(user_id)))
module = body.get('module')
if module:
domain.append(('module', '=', module))
group_id = body.get('groupId')
if group_id:
domain.append(('assignment_id.group_id', '=', int(group_id)))
assignment_id = body.get('assignmentId')
if assignment_id:
domain.append(('assignment_id', '=', int(assignment_id)))
date_from = body.get('dateFrom')
if date_from:
domain.append(('create_date', '>=', date_from))
date_to = body.get('dateTo')
if date_to:
domain.append(('create_date', '<=', date_to))
stats = Stat.search(domain, order='create_date desc')
scores = [s.score for s in stats if s.score]
avg_score = sum(scores) / len(scores) if scores else 0
total = len(stats)
module_breakdown = {}
for s in stats:
mod = getattr(s, 'module', 'unknown')
if mod not in module_breakdown:
module_breakdown[mod] = {'count': 0, 'scores': []}
module_breakdown[mod]['count'] += 1
if s.score:
module_breakdown[mod]['scores'].append(s.score)
for mod, data in module_breakdown.items():
data['average'] = sum(data['scores']) / len(data['scores']) if data['scores'] else 0
del data['scores']
return self._json_response({
'total': total,
'averageScore': round(avg_score, 2),
'moduleBreakdown': module_breakdown,
'stats': [self._serialize_stat(s) for s in stats[:100]],
})
except Exception:
_logger.exception("Statistical query error")
return self._error_response('Internal server error', 500)