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:
Yamen Ahmad
2026-04-10 17:26:42 +04:00
commit 907a5c0e92
331 changed files with 23511 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,55 @@
import json
import logging
from odoo import http
from odoo.http import request
from odoo.addons.encoach_api.controllers.base import (
jwt_required, _json_response, _error_response, _get_json_body, _paginate
)
_logger = logging.getLogger(__name__)
class EncoachReportsController(http.Controller):
# ------------------------------------------------------------------
# GET /api/reports/exam/<int:attempt_id>/pdf
# ------------------------------------------------------------------
@http.route('/api/reports/exam/<int:attempt_id>/pdf', type='http',
auth='none', methods=['GET'], csrf=False)
@jwt_required
def download_exam_pdf(self, attempt_id, **kw):
try:
Attempt = request.env['encoach.student.attempt'].sudo()
attempt = Attempt.browse(attempt_id)
if not attempt.exists():
return _error_response('Attempt not found', 404)
user = request.env.user.sudo()
is_owner = attempt.student_id.id == user.id
is_admin = (
user.user_type in ('admin', 'mastercorporate')
or user.has_group('base.group_system')
)
is_teacher = user.user_type == 'teacher'
if not (is_owner or is_admin or is_teacher):
return _error_response('You do not have access to this report', 403)
from odoo.addons.encoach_pdf_report.services.pdf_generator import PdfGenerator
pdf_bytes = PdfGenerator.generate_exam_report(request.env, attempt_id)
student_name = (attempt.student_id.name or 'student').replace(' ', '_')
filename = f"exam_report_{student_name}_{attempt_id}.pdf"
return request.make_response(
pdf_bytes,
headers=[
('Content-Type', 'application/pdf'),
('Content-Disposition', f'attachment; filename="{filename}"'),
('Content-Length', str(len(pdf_bytes))),
],
)
except Exception as e:
_logger.exception('download_exam_pdf failed')
return _error_response(str(e), 500)