from odoo import models, fields, api, _ from odoo.exceptions import ValidationError from odoo.exceptions import UserError, ValidationError class PaProgramLevel(models.Model): _name = 'pa.program.level' _description = 'Program Levels Screen' _rec_name = 'name' _order = 'code' code = fields.Char( string='Level Code', required=True, copy=False, readonly=True, index=True, default=lambda self: self.env['ir.sequence'].next_by_code('pa.program.level') or _('New'), ) name = fields.Char( string='Level Name', required=True, default=lambda self: _('Technical Tracks'), help='Enter the level name such as: cultural, cognitive, religious, developmental, preventive, therapeutic', ) type = fields.Selection([ ('route', 'Tracks'), ('activity', 'Activities'), ('medad', 'Medad'), ], string='Level Type', required=True, help='Select the type of level') description = fields.Text(string='Level Description') active = fields.Boolean(string='Level Status', default=True) track_ids = fields.One2many( 'pa.program.track', 'level_id', string='Associated Tracks', help='Tracks that belong to this level', ) # Optional: enforce unique code at Python level @api.model def create(self, vals): if 'code' in vals: existing = self.search([('code', '=', vals['code'])]) if existing: raise ValidationError(_('كود المستوى يجب أن يكون فريدًا')) return super(PaProgramLevel, self).create(vals) class PaProgramTrack(models.Model): _name = 'pa.program.track' _description = 'Program Track Screen' _rec_name = 'name' _order = 'code' code = fields.Char( string='Track Code', required=True, copy=False, readonly=True, index=True, default=lambda self: self.env['ir.sequence'].next_by_code('pa.program.track') or _('New'), ) name = fields.Char(string='Track Name', required=True) description = fields.Text(string='Track Description') # branch = fields.Many2one('age.category',string='Branch') branch = fields.Many2one("branch.settings", string='Branch', domain="[('branch_type','=','branches')]") gender = fields.Selection( [('male', 'Male'), ('female', 'Female')], string='Gender', ) age_category = fields.Many2one( 'age.category', string='Age Category', help='Filter track based on age category', ) study_category = fields.Many2one('education.level',string='Study Category') hobby = fields.Char(string='Hobby') study_status = fields.Char(string='Study Status') health_status = fields.Char(string='Health Status') education_status = fields.Char(string='Educational Status') partner_id = fields.Many2one( 'res.partner', string='Associated Partner', help='The partner linked to the track' ) active = fields.Boolean(string='Track Status', default=True) level_id = fields.Many2one( 'pa.program.level', string='Associated Level', required=True, ondelete='cascade' ) class PaProgram(models.Model): _name = 'pa.program' _description = 'Programs Screen' _rec_name = 'name' _order = 'code' code = fields.Char( string='Program Code', required=True, copy=False, readonly=True, index=True, default=lambda self: self.env['ir.sequence'].next_by_code('pa.program') or _('New'), ) name = fields.Char(string='Program Name', required=True) program_type_id = fields.Many2one( 'pa.program.type', string='Program Type', ) payment_type = fields.Selection([ ('paid', 'Paid'), ('unpaid', 'Unpaid') ], string='Payment Type', required=True) sponsor_id = fields.Many2one('takaful.sponsorship', string='Linked Sponsor', help='Sponsor is shown only if the payment type is Paid' ) sponsor_support_amount = fields.Float( string='Allocated Support Amount', related='sponsor_id.total_sponsorship_amount', readonly=True, help='Support amount as per sponsor details' ) budget = fields.Float( string='Program Budget', help='Used when payment type = Unpaid' ) location = fields.Selection([ ('inside', 'Internal'), ('outside', 'External') ], string='Program Location', required=True) description = fields.Text(string='Program Description') track_id = fields.Many2one( 'pa.program.track', string='Associated Track', help='Each program is linked to one track that contains several activities' ) analytic_account_id = fields.Many2one( 'account.analytic.account', string='Linked Analytic Account', readonly=True, help='Automatically created analytic account with the same name as the program and cannot be deleted' ) estimated_budget = fields.Float( string='Associated Estimated Budget', help='Estimated budget related to the analytic account' ) active = fields.Boolean(string='Program Status', default=True) # @api.constrains('name') # def _create_program_account(self): # for rec in self: # if not rec.name: # continue # # check if analytic account already exists # analytic = self.env["account.analytic.account"].search([("name", "=", rec.name)], limit=1) # if not analytic: # self.env["account.analytic.account"].create({ # "name": rec.name, # }) @api.constrains('payment_type', 'sponsor_id') def _check_sponsor_if_paid(self): for record in self: if record.payment_type == 'paid' and not record.sponsor_id: raise ValidationError(_('يجب اختيار الكافل في حالة نوع الدفع مدفوع')) if record.payment_type == 'unpaid' and record.sponsor_id: raise ValidationError(_('لا يجب اختيار كافل في حالة نوع الدفع غير مدفوع')) @api.model def create(self, vals): res = super().create(vals) # ✅ fix if not res.analytic_account_id: account = self.env['account.analytic.account'].create({ 'name': res.name, 'active': True, }) res.analytic_account_id = account.id return res def write(self, vals): res = super().write(vals) # ✅ same correction here for record in self: if not record.analytic_account_id: account = self.env['account.analytic.account'].create({ 'name': record.name, 'active': True, }) record.analytic_account_id = account.id return res class PaProgramActivity(models.Model): _name = 'pa.program.activity' _description = 'Program Activity' _rec_name = 'name' code = fields.Char("Activity Code", required=True, copy=False, default=lambda self: self.env['ir.sequence'].next_by_code('pa.program.activity')) name = fields.Char("Activity Name", required=True) description = fields.Text("Activity Description") program_id = fields.Many2one('pa.program', string="Related Program") location = fields.Selection([('inside', 'Internal'), ('outside', 'External')], required=True, string="Activity Location") date_start = fields.Datetime("Activity Start Date") date_end = fields.Datetime("Activity End Date") analytic_account_id = fields.Many2one('account.analytic.account', string="Analytic Account") estimated_budget = fields.Float("Estimated Budget") active = fields.Boolean("Active", default=True) @api.model def create(self, vals): res = super().create(vals) # ✅ fix if not res.analytic_account_id: account = self.env['account.analytic.account'].create({ 'name': res.name, 'active': True, }) res.analytic_account_id = account.id return res def write(self, vals): res = super().write(vals) # ✅ same correction here for record in self: if not record.analytic_account_id: account = self.env['account.analytic.account'].create({ 'name': record.name, 'active': True, }) record.analytic_account_id = account.id return res class PaProgramMedad(models.Model): _name = 'pa.program.medad' _description = 'Medad' _rec_name = 'name' code = fields.Char("Medad Code", required=True, copy=False, default=lambda self: self.env['ir.sequence'].next_by_code('pa.program.medad')) name = fields.Char("Medad Name", required=True) description = fields.Text("Medad Description") medad_type = fields.Selection([ ('skills', 'Skills'), ('training', 'Training'), ('innovation', 'Innovation Support') ], string="Medad Type", required=True) activity_id = fields.Many2one('pa.program.activity', string="Associated Activity") analytic_account_id = fields.Many2one('account.analytic.account', string="Associated Analytic Account") estimated_budget = fields.Float("Estimated Budget") active = fields.Boolean("Active", default=True) @api.model def create(self, vals): res = super().create(vals) # ✅ fix if not res.analytic_account_id: account = self.env['account.analytic.account'].create({ 'name': res.name, 'active': True, }) res.analytic_account_id = account.id return res def write(self, vals): res = super().write(vals) # ✅ same correction here for record in self: if not record.analytic_account_id: account = self.env['account.analytic.account'].create({ 'name': record.name, 'active': True, }) record.analytic_account_id = account.id return res # Example family/beneficiary for demo: you should replace with your actual model class PaProgramActivityRegistration(models.Model): _name = 'pa.program.activity.registration' _description = 'Activity Registration' _inherit = ['mail.thread'] name = fields.Char(string='Request Number', readonly=True, index=True, default=lambda self: self.env['ir.sequence'].next_by_code('pa.program.activity.registration') or 'New') request_date = fields.Datetime(string='Request Date', default=fields.Datetime.now, readonly=True) creator_id = fields.Many2one('res.users', string='Creator', default=lambda self: self.env.user, readonly=True) date_from = fields.Datetime(string='Date From') date_to = fields.Datetime(string='Date To') familye_id = fields.Many2one('grant.benefit', string='Family') beneficiary_ids = fields.Many2many( 'family.member', string='Beneficiary Name', domain="[('benefit_id', '=', familye_id)]" ) benefit_category_id = fields.Many2one(related='familye_id.benefit_category_id', string='Family Category', readonly=True) # beneficiary_ids = fields.Many2many('family.member', string='Beneficiary Name') sms_phone = fields.Char(related='familye_id.sms_phone', string='Mobile Number', readonly=True) member_ids = fields.One2many( 'family.member', 'benefit_ids', string='Family Members' ) branch_custom_id = fields.Many2one(string='Branch', related='familye_id.branch_custom_id', readonly=True) gender = fields.Selection(related='familye_id.gender', readonly=True) nationality_id = fields.Many2one(string='Nationality', related='familye_id.nationality_id', readonly=True) graduation_status = fields.Selection(string='Graduation Status', related='familye_id.graduation_status', readonly=True) health_status = fields.Selection(string='Health Status', related='familye_id.health_status', readonly=True) education_status = fields.Selection(string='Educational Status', related='familye_id.education_status', readonly=True) mother_is_life = fields.Boolean(string='Is Mother Alive?', related='familye_id.mother_is_life', readonly=True) mother_location_conf = fields.Many2one(string='Mother Location', related='familye_id.mother_location_conf', readonly=True) is_mother_work = fields.Boolean(string='Is Mother Working?', related='familye_id.is_mother_work', readonly=True) housing_type = fields.Selection(string='Housing Type', related='familye_id.housing_type', readonly=True) beneficiary_relation = fields.Selection([ ('mother', 'Mother'), ('son', 'Son'), ('daughter', 'Daughter') ], string='Beneficiary Relation') sons = fields.Integer( string="Number of Sons", compute='_compute_sons_daughters', store=False ) daughters = fields.Integer( string="Number of Daughters", compute='_compute_sons_daughters', store=False ) return_reason = fields.Text(string='Return Reason') @api.depends('familye_id', 'familye_id.member_ids', 'familye_id.member_ids.relationn', 'familye_id.member_ids.relationn.relation_type') def _compute_sons_daughters(self): for rec in self: sons = 0 daughters = 0 members = rec.familye_id.member_ids.filtered(lambda m: m.relationn) for member in members: if member.relationn.relation_type == 'son': sons += 1 elif member.relationn.relation_type == 'daughter': daughters += 1 rec.sons = sons rec.daughters = daughters level_id = fields.Many2one('pa.program.level', string='Program Levels', required=True) track_id = fields.Many2one('pa.program.track', string='Program Tracks') program_id = fields.Many2one('pa.program', string='Programs') activity_id = fields.Many2one('pa.program.activity', string='Activities') medad_id = fields.Many2one('pa.program.medad', string='Medad') want_transport = fields.Selection([('yes', 'Yes'), ('no', 'No')], string='Does the beneficiary want transportation') state = fields.Selection([ ('draft', 'Draft'), ('social_specialist', 'Social Specialist'), ('operation_manager', 'Operations Manager'), ('branch_manager', 'Branch Manager'), ('activity_head', 'Activity Head'), ('finance_manager', 'Finance Management'), ('approved', 'Approved'), ('refused', 'Refused') ], default='draft', tracking=True) rejection_reason = fields.Text(string='Rejection Reason') # To hold the last rejection note def unlink(self): for order in self: if order.state not in ['draft']: raise UserError(_('You cannot delete this record State not Draft')) return super(PaProgramActivityRegistration, self).unlink() @api.onchange('familye_id') def _onchange_familye_id(self): if self.familye_id: # Set member_ids to the current members of the selected familye_id # This links existing family.member records (no create) self.member_ids = [(6, 0, self.familye_id.member_ids.ids)] else: self.member_ids = [(5, 0, 0)] # Clear if no familye_id # Validation of required fields based on level type @api.constrains('level_id', 'track_id', 'program_id', 'activity_id', 'medad_id') def _check_required_fields_based_on_level(self): for rec in self: if not rec.level_id: continue lvl_type = rec.level_id.type if lvl_type == 'route': # مسارات if not rec.track_id or not rec.program_id: raise ValidationError(_('لحالة المستوى "مسارات"، الحقول "المسار" و "البرنامج" مطلوبان')) elif lvl_type == 'activity': # أنشطة if not rec.track_id or not rec.program_id or not rec.activity_id: raise ValidationError(_('لحالة المستوى "أنشطة"، الحقول "المسار" و "البرنامج" و "النشاط" مطلوبة')) elif lvl_type == 'medad': # مداد if not rec.track_id or not rec.program_id or not rec.activity_id or not rec.medad_id: raise ValidationError(_('لحالة المستوى "مداد"، جميع الحقول "المسار"، "البرنامج"، "النشاط"، "المداد" مطلوبة')) def action_to_social_specialist(self): self.ensure_one() self.state = 'social_specialist' def action_approve(self): self.ensure_one() transition_map = { 'social_specialist': 'operation_manager', 'operation_manager': 'branch_manager', 'branch_manager': 'activity_head', 'activity_head': 'finance_manager', 'finance_manager': 'approved', } next_state = transition_map.get(self.state) if next_state: self.state = next_state def action_refuse(self): self.ensure_one() self.state = 'refused' def action_reset_to_draft(self): self.ensure_one() self.state = 'draft' def action_return_to_specialist(self): self.ensure_one() self.state = 'social_specialist' def action_return_to_draft(self): # Open wizard to collect return reason - opens form view on 'empowerment.return.reason.wizard' return { 'type': 'ir.actions.act_window', 'name': _('سبب الإرجاع'), 'view_mode': 'form', 'res_model': 'pa.return.reason.wizard', 'target': 'new', 'context': {'default_registration_id': self.id} } class PaReturnReasonWizard(models.TransientModel): _name = 'pa.return.reason.wizard' _description = 'Return Reason Wizard' reason = fields.Text(string='Return Reason', required=True) registration_id = fields.Many2one('pa.program.activity.registration', string='Registration Request') def action_confirm_return(self): if self.registration_id: self.registration_id.rejection_reason = self.reason # move back to draft or social_specialist depending on the context/state if self.registration_id.state == 'operation_manager': self.registration_id.state = 'social_specialist' self.registration_id.return_reason = self.reason else: self.registration_id.state = 'draft' self.registration_id.return_reason = self.reason return {'type': 'ir.actions.act_window_close'} class PaProgram(models.Model): _name = 'pa.family' class PaProgramType(models.Model): _name = 'pa.program.type' pg_type =fields.Char(string='Name') class FamilyMember(models.Model): _inherit = 'family.member' benefit_ids = fields.Many2one( 'pa.program.activity.registration', string='Benefit Registration', ondelete='cascade', index=True, ) need_trans = fields.Boolean(string='Need Transportation') need_medicin = fields.Boolean(string='Need Medicien') need_mental_dis = fields.Boolean(string='Have Mental Disorders')