diff --git a/encoach_api/controllers/generation.py b/encoach_api/controllers/generation.py index eb1dcfc..e59a21d 100644 --- a/encoach_api/controllers/generation.py +++ b/encoach_api/controllers/generation.py @@ -9,6 +9,13 @@ from .base import EncoachMixin _logger = logging.getLogger(__name__) +def _get_gen_service(): + from odoo.addons.encoach_ai_generation.services.generation_service import ( + EncoachGenerationService, + ) + return EncoachGenerationService(request.env) + + class GenerationController(EncoachMixin, http.Controller): # ------------------------------------------------- reading passage @@ -28,9 +35,9 @@ class GenerationController(EncoachMixin, http.Controller): topic = kwargs.get('topic') word_count = int(kwargs.get('word_count', 500)) - GenService = request.env['encoach.ai.generation'].sudo() - result = GenService.generate_reading_passage( - passage=passage, + service = _get_gen_service() + result = service.generate_reading_passage( + passage_num=passage, topic=topic, word_count=word_count, ) @@ -57,10 +64,10 @@ class GenerationController(EncoachMixin, http.Controller): if not text or not exercises: return self._error_response('text and exercises are required', 400) - GenService = request.env['encoach.ai.generation'].sudo() - result = GenService.generate_reading_exercises( + service = _get_gen_service() + result = service.generate_reading_exercises( text=text, - exercises=exercises, + exercises_config=exercises, difficulty=difficulty, ) @@ -86,8 +93,8 @@ class GenerationController(EncoachMixin, http.Controller): topic = kwargs.get('topic') difficulty = kwargs.get('difficulty') - GenService = request.env['encoach.ai.generation'].sudo() - result = GenService.generate_listening_dialog( + service = _get_gen_service() + result = service.generate_listening_dialog( section=section, topic=topic, difficulty=difficulty, @@ -115,10 +122,10 @@ class GenerationController(EncoachMixin, http.Controller): if not text or not exercises: return self._error_response('text and exercises are required', 400) - GenService = request.env['encoach.ai.generation'].sudo() - result = GenService.generate_listening_exercises( + service = _get_gen_service() + result = service.generate_listening_exercises( text=text, - exercises=exercises, + exercises_config=exercises, difficulty=difficulty, ) @@ -144,8 +151,8 @@ class GenerationController(EncoachMixin, http.Controller): topic = kwargs.get('topic') difficulty = kwargs.get('difficulty') - GenService = request.env['encoach.ai.generation'].sudo() - result = GenService.generate_writing_task( + service = _get_gen_service() + result = service.generate_writing_task( task=task, topic=topic, difficulty=difficulty, @@ -175,9 +182,9 @@ class GenerationController(EncoachMixin, http.Controller): second_topic = kwargs.get('second_topic') difficulty = kwargs.get('difficulty') - GenService = request.env['encoach.ai.generation'].sudo() - result = GenService.generate_speaking_task( - task=task, + service = _get_gen_service() + result = service.generate_speaking_task( + part=task, topic=topic, first_topic=first_topic, second_topic=second_topic, @@ -189,8 +196,8 @@ class GenerationController(EncoachMixin, http.Controller): _logger.exception("Generate speaking task error") return self._error_response('Internal server error', 500) - # --------------------------------------------- level exercises - @http.route('/api/exam/level/', type='http', auth='public', methods=['POST', 'OPTIONS'], csrf=False, cors='*') + # --------------------------------------------- level exercises (POST) + @http.route('/api/exam/level/generate', type='http', auth='public', methods=['POST', 'OPTIONS'], csrf=False, cors='*') def generate_level_exercises(self, **kwargs): """Generate level-test exercises using GPT-4o.""" try: @@ -205,9 +212,9 @@ class GenerationController(EncoachMixin, http.Controller): if not exercises: return self._error_response('exercises specification is required', 400) - GenService = request.env['encoach.ai.generation'].sudo() - result = GenService.generate_level_exercises( - exercises=exercises, + service = _get_gen_service() + result = service.generate_level_exercises( + exercises_config=exercises, difficulty=difficulty, ) @@ -242,11 +249,9 @@ class GenerationController(EncoachMixin, http.Controller): image_data = base64.b64encode(image_file.read()).decode('utf-8') mime_type = image_file.content_type or 'image/png' - GenService = request.env['encoach.ai.generation'].sudo() - result = GenService.generate_writing_with_image( + service = _get_gen_service() + result = service.generate_writing_task( task=task, - image_b64=image_data, - mime_type=mime_type, difficulty=difficulty, ) @@ -255,8 +260,8 @@ class GenerationController(EncoachMixin, http.Controller): _logger.exception("Generate writing attachment error") return self._error_response('Internal server error', 500) - # -------------------------------------------- pre-built level exam - @http.route('/api/exam/level/', type='http', auth='public', methods=['GET', 'OPTIONS'], csrf=False, cors='*') + # -------------------------------------------- pre-built level exam (GET) + @http.route('/api/exam/level', type='http', auth='public', methods=['GET', 'OPTIONS'], csrf=False, cors='*') def get_level_exam(self, **kwargs): """Return a pre-built level exam (no AI generation).""" try: @@ -296,7 +301,7 @@ class GenerationController(EncoachMixin, http.Controller): return self._error_response('Internal server error', 500) # -------------------------------------------- level exam import - @http.route('/api/exam/level/import/', type='http', auth='public', methods=['POST', 'OPTIONS'], csrf=False, cors='*') + @http.route('/api/exam/level/import', type='http', auth='public', methods=['POST', 'OPTIONS'], csrf=False, cors='*') def import_level_exam(self, **kwargs): """Import level exam from uploaded file.""" try: @@ -309,9 +314,8 @@ class GenerationController(EncoachMixin, http.Controller): if not exercises_file: return self._error_response('exercises file is required', 400) - import json as _json content = exercises_file.read().decode('utf-8') - data = _json.loads(content) + data = json.loads(content) exam = request.env['encoach.exam'].sudo().create({ 'module': 'level', @@ -325,7 +329,7 @@ class GenerationController(EncoachMixin, http.Controller): return self._error_response('Internal server error', 500) # -------------------------------------------- custom level exam - @http.route('/api/exam/level/custom/', type='http', auth='public', methods=['POST', 'OPTIONS'], csrf=False, cors='*') + @http.route('/api/exam/level/custom', type='http', auth='public', methods=['POST', 'OPTIONS'], csrf=False, cors='*') def generate_custom_level(self, **kwargs): """Generate custom level exam from JSON specification.""" try: @@ -336,9 +340,9 @@ class GenerationController(EncoachMixin, http.Controller): body = self._get_json_body() spec = body.get('spec', body) - GenService = request.env['encoach.ai.generation'].sudo() - result = GenService.generate_level_exercises( - exercises=spec.get('exercises', []), + service = _get_gen_service() + result = service.generate_level_exercises( + exercises_config=spec.get('exercises', []), difficulty=spec.get('difficulty', ['B1', 'B2']), ) return self._json_response({'exercises': result}) diff --git a/encoach_api/controllers/subscriptions.py b/encoach_api/controllers/subscriptions.py index e5d7800..461bac7 100644 --- a/encoach_api/controllers/subscriptions.py +++ b/encoach_api/controllers/subscriptions.py @@ -19,10 +19,7 @@ class SubscriptionsController(EncoachMixin, http.Controller): if not user: return self._error_response('Unauthorized', 401) - domain = [('is_active', '=', True)] - entity_id = kwargs.get('entityID') - if entity_id: - domain.append(('entity_id', '=', int(entity_id))) + domain = [] packages = request.env['encoach.package'].sudo().search(domain, order='price asc') return self._json_response([p.to_encoach_dict() for p in packages]) diff --git a/encoach_api/controllers/tickets.py b/encoach_api/controllers/tickets.py index 6898bd6..37ae462 100644 --- a/encoach_api/controllers/tickets.py +++ b/encoach_api/controllers/tickets.py @@ -19,7 +19,7 @@ class TicketsController(EncoachMixin, http.Controller): return self._error_response('Unauthorized', 401) domain = [] - user_type = getattr(user, 'encoach_user_type', 'student') + user_type = getattr(user, 'encoach_type', 'student') if user_type not in ('admin', 'developer'): domain.append(('user_id', '=', user.id)) @@ -33,7 +33,7 @@ class TicketsController(EncoachMixin, http.Controller): records = Ticket.search(domain, offset=offset, limit=limit, order='create_date desc') return self._json_response({ - 'tickets': [self._serialize_ticket(t) for t in records], + 'tickets': [self._serialize(t) for t in records], 'total': total, 'page': page, 'limit': limit, @@ -67,7 +67,7 @@ class TicketsController(EncoachMixin, http.Controller): vals['category'] = body['category'] ticket = request.env['encoach.ticket'].sudo().create(vals) - return self._json_response(self._serialize_ticket(ticket), status=201) + return self._json_response(self._serialize(ticket), status=201) except Exception: _logger.exception("Create ticket error") return self._error_response('Internal server error', 500) @@ -101,7 +101,7 @@ class TicketsController(EncoachMixin, http.Controller): if vals: ticket.write(vals) - return self._json_response(self._serialize_ticket(ticket)) + return self._json_response(self._serialize(ticket)) except Exception: _logger.exception("Update ticket error") return self._error_response('Internal server error', 500) diff --git a/encoach_api/controllers/training.py b/encoach_api/controllers/training.py index 91051bd..90a0bdf 100644 --- a/encoach_api/controllers/training.py +++ b/encoach_api/controllers/training.py @@ -105,10 +105,10 @@ class TrainingController(EncoachMixin, http.Controller): data = [{ '_id': str(w.id), 'id': w.id, - 'title': getattr(w, 'title', ''), - 'description': getattr(w, 'description', ''), - 'steps': json.loads(w.steps) if getattr(w, 'steps', None) else [], - 'module': getattr(w, 'module', ''), + 'title': w.name or '', + 'description': '', + 'steps': w.content if isinstance(w.content, list) else (json.loads(w.content) if isinstance(w.content, str) else []), + 'module': w.module or '', } for w in records] return self._json_response(data) diff --git a/encoach_api/controllers/users.py b/encoach_api/controllers/users.py index dbe9442..8916188 100644 --- a/encoach_api/controllers/users.py +++ b/encoach_api/controllers/users.py @@ -78,11 +78,11 @@ class UsersController(EncoachMixin, http.Controller): user_type = kwargs.get('type') if user_type: - domain.append(('encoach_user_type', '=', user_type)) + domain.append(('encoach_type', '=', user_type)) entity_id = kwargs.get('entityID') if entity_id: - domain.append(('encoach_entity_id', '=', int(entity_id))) + domain.append(('entity_rel_ids.entity_id', '=', int(entity_id))) Users = request.env['res.users'].sudo() total = Users.search_count(domain) diff --git a/encoach_ticket/models/ticket.py b/encoach_ticket/models/ticket.py index 1d46eef..fa7969b 100644 --- a/encoach_ticket/models/ticket.py +++ b/encoach_ticket/models/ticket.py @@ -5,10 +5,18 @@ class EncoachTicket(models.Model): _name = "encoach.ticket" _description = "EnCoach Support Ticket" + user_id = fields.Many2one( + "res.users", string="Reporter", ondelete="set null", index=True, + ) reporter_name = fields.Char() reporter_email = fields.Char() subject = fields.Char(required=True) description = fields.Text() + priority = fields.Selection( + [("low", "Low"), ("medium", "Medium"), ("high", "High")], + default="medium", + ) + category = fields.Char() ticket_type = fields.Selection( [ ("feedback", "Feedback"), @@ -37,11 +45,15 @@ class EncoachTicket(models.Model): self.ensure_one() return { "id": self.id, + "_id": str(self.id), + "userId": str(self.user_id.id) if self.user_id else None, "reporterName": self.reporter_name or "", "reporterEmail": self.reporter_email or "", "subject": self.subject or "", "description": self.description or "", "type": self.ticket_type or "", + "priority": self.priority or "medium", + "category": self.category or "", "status": self.status, "examInformation": self.exam_information, "assignedTo": ( diff --git a/encoach_training/models/walkthrough.py b/encoach_training/models/walkthrough.py index 3b89f33..6eb6218 100644 --- a/encoach_training/models/walkthrough.py +++ b/encoach_training/models/walkthrough.py @@ -6,6 +6,7 @@ class EncoachWalkthrough(models.Model): _description = "EnCoach Walkthrough" name = fields.Char() + sequence = fields.Integer(default=10) content = fields.Json() module = fields.Selection( [