# -*- coding: utf-8 -*- import logging import random import base64 from io import BytesIO from odoo import api, fields, models, _, tools from odoo.exceptions import UserError, AccessError from odoo.addons.base.models.ir_mail_server import MailDeliveryException from odoo.addons.auth_signup.models.res_partner import SignupError _logger = logging.getLogger(__name__) try: import qrcode from qrcode.constants import ERROR_CORRECT_L except ImportError: qrcode = None ERROR_CORRECT_L = None class MainPin(models.Model): _name = 'main.pin' _description = 'SHS Code' _rec_name = 'custom_group_label' _inherit = ['mail.thread', 'mail.activity.mixin'] data_term = fields.Integer(string="ចំនួនប្រតិបត្ដិការ", default=1) name = fields.Char(string="ផ្សេងៗ") pin_code_id = fields.One2many('pin.spm', 'main_id', string="តារាងលេខកូដ", readonly=True) reviewer_code = fields.Many2one('res.users', string="អ្នកទទួលលេខកូដ", domain="[('active', '=', True)]") groups_id = fields.Many2many('res.groups', string="Access Rights") custom_group_label = fields.Char(string="Group Label") link_qr = fields.Binary("QR Link", compute='_generate_qrcode', store=False) file_name = fields.Char(string="ឈ្មោះឯកសារយោង") files = fields.Binary(string="ឯកសារយោង") check_con = fields.Boolean(string="បន្ដការសិក្សាថ្នាក់ឧត្ដមសិក្សា") selection_data = fields.Selection([ ('0', 'ថ្នាក់មហាវិទ្យាល័យ'), ('01', 'ថ្នាក់មត្ដេយ្យ, មធ្យម និងមធ្យមសិក្សាទុតិយភូមិ'), ('1', 'សិស្សបន្ដការសិក្សា(មកពីប្រទេសថៃ)') ], string="កម្រិតអាហារូបករណ៍", default='0') school_kind = fields.Many2one('univer.univer', string="សាលាដែលទទួលបាន", domain="[('grade_study','in',('01','1','2','3','4'))]") pass_percent = fields.Float(string="%") requester = fields.Char(string="គោលដៅ") # ✅ FIXED: Removed 'id' dependency. Empty depends() means "compute on access" @api.depends() def _generate_qrcode(self): login_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url', 'https://reg.amt.live') + '/web/login' for record in self: record.link_qr = False if not (qrcode and ERROR_CORRECT_L): continue try: qr = qrcode.QRCode(version=1, error_correction=ERROR_CORRECT_L, box_size=4, border=2) qr.add_data(login_url) qr.make(fit=True) img = qr.make_image(fill_color="black", back_color="white") buffered = BytesIO() img.save(buffered, format="PNG") record.link_qr = base64.b64encode(buffered.getvalue()) except Exception as e: _logger.error(f"QR generation failed for {record.id}: {e}") @api.onchange('data_term') def _onchange_data_term(self): default_group = self.env.ref("register_v15.groups_amt", raise_if_not_found=False) if default_group: self.groups_id = [(6, 0, [default_group.id])] self.custom_group_label = 'ផ្គូផ្គង' @api.constrains('data_term') def _check_data_term(self): for rec in self: if rec.data_term < 1: raise UserError(_("ចំនួនប្រតិបត្ដិការត្រូវតែយ៉ាងតិច 1")) if rec.data_term > 1000: raise UserError(_("ចំនួនប្រតិបត្ដិការអតិបរមាគឺ 1000")) def compute_code(self): self.ensure_one() if not self.data_term or self.data_term < 1: raise UserError(_("សូមបញ្ចូលចំនួនប្រតិបត្ដិការ")) target_group = self.env.ref("register_v15.groups_amt", raise_if_not_found=False) pin_records = [] created_users = [] failed_count = 0 for i in range(1, self.data_term + 1): pin_code = str(random.randint(100000, 999999)) identifier = f"{self.name or 'PIN'}{pin_code}{i:03d}" valid_email = f"{identifier.lower()}@reg.amt.live" pin_records.append((0, 0, { 'name': identifier, 'data_rec': self.name or '', 'main_id': self.id, })) user_vals = { 'name': identifier, 'login': identifier, 'email': valid_email, 'password': identifier, 'active': True, 'first_login': True, 'kind': self.selection_data, 'pin_code': pin_code, 'groups': self.custom_group_label or 'ប្រឡង', } # ✅ Use standard Odoo field name: groups_id if target_group: user_vals['group_ids'] = [(4, target_group.id)] elif self.groups_id: user_vals['group_ids'] = [(6, 0, self.groups_id.ids)] try: # 🔒 Prevent password reset emails from triggering user = self.env['res.users'].sudo().with_context(no_reset_password=True).create(user_vals) # 📝 Optional: Log for verification _logger.info(f"✅ Created: {user.login} | Password={pin_code} | Groups={user.group_ids.mapped('name')}") created_users.append(user) except Exception as e: failed_count += 1 _logger.error(f"❌ Failed to create '{identifier}': {e}", exc_info=True) continue if pin_records: self.pin_code_id = pin_records if failed_count > 0: return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'title': _('បញ្ហា!'), 'message': _('បានបង្កើត %d, បរាជ័យ %d។ ពិនិត្យ log!') % (len(created_users), failed_count), 'type': 'warning', 'sticky': True, } } return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'title': _('ជោគជ័យ!'), 'message': _('បានបង្កើត %d លេខកូដ និងគណនីដោយជោគជ័យ') % len(created_users), 'type': 'success', 'sticky': False, } } def get_spm_code(self): target_group = self.env.ref("register_v15.groups_amt", raise_if_not_found=False) for code in self.pin_code_id.filtered(lambda c: not c.status): user = self.env['res.users'].sudo().search([('login', '=', code.name), ('active', '=', False)], limit=1) if not user: continue update_vals = {'active': True, 'first_login': True, 'kind': self.selection_data} if target_group and target_group not in user.groups_id: update_vals['groups_id'] = [(4, target_group.id)] user.write(update_vals) code.write({'status': True}) return True class PinSPM(models.Model): _name = "pin.spm" _description = "Pin Code for Scholarship" _inherit = ['mail.thread'] name = fields.Char(index=True, default=lambda self: _('New'), string="លេខកូដ", readonly=True) data = fields.Integer(string="ចំនួនប្រតិបត្ដិការ") status = fields.Boolean(default=False, string="Used") reviewer_ids = fields.Many2many('res.users', string="អ្នកត្រួតពិនិត្យលើ") main_id = fields.Many2one('main.pin', string="Main Reference", ondelete='cascade', required=True) data_rec = fields.Char(string="ឈ្មោះខេត្ដ/ក្រុង ផ្សេងៗ") reviewer = fields.Many2one("res.users", string="អ្នកទទួលលេខកូដ") user_use_code = fields.Many2one('res.users', string="ឈ្មោះអ្នកប្រើប្រាស់", readonly=True) info_name = fields.Many2one('info.info', string="ឈ្មោះក្នុងទំរងបំពេញពាក្យ") name_info = fields.Char(related="info_name.name", store=True, readonly=False, string="Name") email = fields.Char(related="info_name.email", store=True, readonly=True, string="Email") gender = fields.Many2one(related="info_name.gender", store=True, readonly=False, string="Gender") eng_name = fields.Char(related="info_name.eng_name", store=True, readonly=False, string="English Name") dob = fields.Date(related="info_name.dob", store=True, readonly=False, string="DOB") id_card = fields.Char(related="info_name.id_card", store=True, readonly=False, string="ID Card") phone = fields.Char(related="info_name.phone", store=True, readonly=False, string="Phone") study_level = fields.Selection(related="info_name.grade_study", store=True, readonly=False, string="Study Level") create_uids = fields.Many2one(related="info_name.create_uid", store=True, readonly=False, string="Created By") group_register = fields.Char(related="info_name.groups", store=True, readonly=False, string="Registration Group") def check_use_code(self): active_ids = self.env.context.get('active_ids') if not active_ids: return False success = 0 for code in self.browse(active_ids): if code.status: continue user = self.env['res.users'].sudo().search([('login', '=', code.name), ('active', '=', False)], limit=1) if not user: continue user.write({'active': True, 'x_pin_code_spm': code.id, 'first_login': True}) info_user = self.env['info.info'].sudo().search( ['|', ('create_uid', '=', user.id), ('email', '=', user.login)], limit=1) code.write({'info_name': info_user.id if info_user else False, 'user_use_code': user.id, 'status': True}) if info_user and not info_user.x_manager and code.reviewer: info_user.x_manager = code.reviewer success += 1 return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': {'title': _('ជោគជ័យ'), 'message': _('បានផ្ទៀងផ្ទាត់ %d លេខកូដ') % success, 'type': 'success'} } if success else False class ReviewGroups(models.Model): _name = 'rev.groups' _description = 'Groups Review' name = fields.Char(string="SHS Name", required=True, translate=True) active = fields.Boolean(default=True) class ResUsers(models.Model): _name = "res.users" _inherit = ["res.users", "mail.thread", "mail.activity.mixin"] _description = "Signup User" pin_code = fields.Char(string='PIN Code', size=6) is_sending_email = fields.Boolean(string="Email Sent", default=False) count_request_pin_code = fields.Integer(string="PIN Request Count", default=0) groups = fields.Char(string="Group Label") kind = fields.Char(string="Kind/Type") first_login = fields.Boolean(default=True) x_pin_code_spm = fields.Many2one('pin.spm', string="Pin Code SPM", domain="[('name','=',login), ('status','=',False)]") x_code_name = fields.Char(related='x_pin_code_spm.name', string='Code Name', store=True, readonly=True) @api.model_create_multi def create(self, vals_list): users = super(ResUsers, self).create(vals_list) template = self.env.ref('ck_signup.sending_pin_code_template', raise_if_not_found=False) for user in users: if self._context.get('skip_email_sending') or not template or not user.email: continue pin = str(random.randint(100000, 999999)) user.write({'pin_code': pin, 'active': False, 'first_login': True, 'groups': 'ប្រឡង', 'is_sending_email': True}) self._send_pin_email(user, pin, template) return users def _send_pin_email(self, user, pin_code, template): try: company = self.env.company mail_from = company.email or tools.config.get('email_from') or 'noreply@localhost' body = f"""
{template.body_html or ''}
{_('សូមបញ្ចូលលេខកូដខាងលើ រួចចុច ផ្ទៀងផ្ទាត់!')}