feat: initial backend codebase — EnCoach v3
Complete Odoo 19 backend with 25 custom addons: - encoach_core: user/entity/role management - encoach_api: REST API + JWT auth - encoach_ai: OpenAI integration, AI settings, generation - encoach_ai_course: AI-powered English & IELTS course generation - encoach_exam_template/session: exam creation, structures, sessions - encoach_scoring: AI auto-grading + manual approval - encoach_vector: pgvector RAG integration - encoach_adaptive: adaptive learning engine - encoach_placement: placement testing - encoach_taxonomy/resources: content taxonomy & resource management - Plus 14 more modules for courses, branding, portal, etc. Includes docs: user guide, generation report, developer workflow. Made-with: Cursor
This commit is contained in:
2
custom_addons/encoach_signup/models/__init__.py
Normal file
2
custom_addons/encoach_signup/models/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import otp
|
||||
from . import student_profile
|
||||
13
custom_addons/encoach_signup/models/otp.py
Normal file
13
custom_addons/encoach_signup/models/otp.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class EncoachOtp(models.Model):
|
||||
_name = 'encoach.otp'
|
||||
_description = 'OTP Verification Record'
|
||||
|
||||
email = fields.Char(required=True, index=True)
|
||||
otp_hash = fields.Char(required=True)
|
||||
expires_at = fields.Datetime(required=True)
|
||||
used = fields.Boolean(default=False)
|
||||
resend_count = fields.Integer(default=0)
|
||||
created_at = fields.Datetime(default=fields.Datetime.now)
|
||||
52
custom_addons/encoach_signup/models/student_profile.py
Normal file
52
custom_addons/encoach_signup/models/student_profile.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import json
|
||||
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class EncoachStudentProfile(models.Model):
|
||||
_name = 'encoach.student.profile'
|
||||
_description = 'Student Onboarding Profile'
|
||||
|
||||
user_id = fields.Many2one('res.users', required=True, ondelete='cascade', index=True)
|
||||
cefr_level = fields.Selection([
|
||||
('pre_a1', 'Pre-A1'),
|
||||
('a1', 'A1'),
|
||||
('a2', 'A2'),
|
||||
('b1', 'B1'),
|
||||
('b2', 'B2'),
|
||||
('c1', 'C1'),
|
||||
('c2', 'C2'),
|
||||
])
|
||||
target_band = fields.Float()
|
||||
learning_style = fields.Text(help='JSON array of styles: visual, audio, reading, mixed')
|
||||
learning_goal = fields.Char(size=200)
|
||||
hours_per_week = fields.Integer()
|
||||
study_mode = fields.Selection([
|
||||
('self_study', 'Self Study'),
|
||||
('with_teacher', 'With Teacher'),
|
||||
])
|
||||
exam_date = fields.Date()
|
||||
placement_completed = fields.Boolean(default=False)
|
||||
placement_decision = fields.Selection([
|
||||
('take_now', 'Take Now'),
|
||||
('skip', 'Skip'),
|
||||
])
|
||||
entity_id = fields.Many2one('encoach.entity', ondelete='set null')
|
||||
|
||||
def get_learning_styles(self):
|
||||
"""Return learning_style as a Python list."""
|
||||
self.ensure_one()
|
||||
if not self.learning_style:
|
||||
return []
|
||||
try:
|
||||
return json.loads(self.learning_style)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
return [self.learning_style] if self.learning_style else []
|
||||
|
||||
def set_learning_styles(self, styles):
|
||||
"""Accept a list of styles and store as JSON."""
|
||||
self.ensure_one()
|
||||
if isinstance(styles, list):
|
||||
self.learning_style = json.dumps(styles)
|
||||
elif isinstance(styles, str):
|
||||
self.learning_style = json.dumps([styles])
|
||||
Reference in New Issue
Block a user