"""Training library: vocabulary items, grammar rules and per-user progress. Backs the `/admin/training/vocabulary` and `/admin/training/grammar` pages (previously hard-coded mocks). """ from odoo import api, fields, models CEFR_LEVELS = [ ('A1', 'A1'), ('A2', 'A2'), ('B1', 'B1'), ('B2', 'B2'), ('C1', 'C1'), ('C2', 'C2'), ] class EncoachVocabItem(models.Model): _name = 'encoach.vocab.item' _description = 'Training Vocabulary Item' _order = 'level,word' word = fields.Char('Word', required=True, index=True) meaning = fields.Text('Meaning', required=True) example_sentence = fields.Text('Example Sentence') level = fields.Selection(CEFR_LEVELS, string='CEFR Level', default='B1', required=True) part_of_speech = fields.Selection([ ('noun', 'Noun'), ('verb', 'Verb'), ('adjective', 'Adjective'), ('adverb', 'Adverb'), ('phrase', 'Phrase'), ('other', 'Other'), ], string='Part of Speech', default='noun') category = fields.Char('Category', default='general', help='Tag such as "academic", "business", "daily".') active = fields.Boolean('Active', default=True) progress_ids = fields.One2many('encoach.vocab.progress', 'vocab_id', 'Progress') completion_count = fields.Integer('Completions', compute='_compute_stats') learners_count = fields.Integer('Learners', compute='_compute_stats') @api.depends('progress_ids.completed') def _compute_stats(self): for rec in self: rec.learners_count = len(rec.progress_ids) rec.completion_count = len(rec.progress_ids.filtered(lambda p: p.completed)) @api.constrains('word') def _check_word_unique(self): for rec in self: if not rec.word: continue dup = self.sudo().search([ ('id', '!=', rec.id), ('word', '=ilike', rec.word.strip()), ], limit=1) if dup: from odoo.exceptions import ValidationError raise ValidationError(f"Word '{rec.word}' already exists.") class EncoachVocabProgress(models.Model): _name = 'encoach.vocab.progress' _description = 'Vocabulary Progress' _order = 'last_reviewed desc' user_id = fields.Many2one('res.users', required=True, default=lambda self: self.env.user.id, ondelete='cascade') vocab_id = fields.Many2one('encoach.vocab.item', required=True, ondelete='cascade') completed = fields.Boolean('Completed', default=False) mastery = fields.Selection([ ('learning', 'Learning'), ('familiar', 'Familiar'), ('mastered', 'Mastered'), ], default='learning') last_reviewed = fields.Datetime('Last Reviewed') review_count = fields.Integer('Review Count', default=0) @api.constrains('user_id', 'vocab_id') def _check_unique_user_vocab(self): for rec in self: dup = self.sudo().search([ ('id', '!=', rec.id), ('user_id', '=', rec.user_id.id), ('vocab_id', '=', rec.vocab_id.id), ], limit=1) if dup: from odoo.exceptions import ValidationError raise ValidationError("Progress already exists for this user + vocabulary item.") class EncoachGrammarRule(models.Model): _name = 'encoach.grammar.rule' _description = 'Training Grammar Rule' _order = 'level,name' name = fields.Char('Rule', required=True, index=True) description = fields.Text('Description', required=True) example = fields.Text('Example') level = fields.Selection(CEFR_LEVELS, string='CEFR Level', default='B1', required=True) category = fields.Char('Category', default='general', help='Tag such as "tenses", "modals", "clauses".') active = fields.Boolean('Active', default=True) progress_ids = fields.One2many('encoach.grammar.progress', 'rule_id', 'Progress') completion_count = fields.Integer('Completions', compute='_compute_stats') learners_count = fields.Integer('Learners', compute='_compute_stats') @api.depends('progress_ids.completed') def _compute_stats(self): for rec in self: rec.learners_count = len(rec.progress_ids) rec.completion_count = len(rec.progress_ids.filtered(lambda p: p.completed)) @api.constrains('name') def _check_rule_name_unique(self): for rec in self: if not rec.name: continue dup = self.sudo().search([ ('id', '!=', rec.id), ('name', '=ilike', rec.name.strip()), ], limit=1) if dup: from odoo.exceptions import ValidationError raise ValidationError(f"Grammar rule '{rec.name}' already exists.") class EncoachGrammarProgress(models.Model): _name = 'encoach.grammar.progress' _description = 'Grammar Rule Progress' _order = 'last_reviewed desc' user_id = fields.Many2one('res.users', required=True, default=lambda self: self.env.user.id, ondelete='cascade') rule_id = fields.Many2one('encoach.grammar.rule', required=True, ondelete='cascade') completed = fields.Boolean('Completed', default=False) last_reviewed = fields.Datetime('Last Reviewed') review_count = fields.Integer('Review Count', default=0) @api.constrains('user_id', 'rule_id') def _check_unique_user_rule(self): for rec in self: dup = self.sudo().search([ ('id', '!=', rec.id), ('user_id', '=', rec.user_id.id), ('rule_id', '=', rec.rule_id.id), ], limit=1) if dup: from odoo.exceptions import ValidationError raise ValidationError("Progress already exists for this user + grammar rule.")