from odoo import models, fields, api, _ from odoo.exceptions import UserError from collections import defaultdict from datetime import timedelta, datetime from odoo.tools.safe_eval import safe_eval import json import calendar class DefaultDictroll(defaultdict): def get(self, key, default=None): if key not in self and default is not None: self[key] = default return self[key] class OfferLetter(models.Model): _name = 'offer.letter' _description = 'Employee Offer Letter' _inherit = ['mail.thread', 'mail.activity.mixin'] _order = 'create_date desc' name = fields.Char( string='Reference', required=True, default=lambda self: _('New'), copy=False ) candidate_id = fields.Many2one( 'hr.applicant', string='Candidate', required=True, ) employee_id = fields.Char( string='Employee ID', readonly=True ) position = fields.Char( string='Position', required=True ) salary = fields.Float( string='Salary', required=True ) mi = fields.Float( string='Medical Insurance', ) currency_id = fields.Many2one( 'res.currency', string='Currency', default=lambda self: self.env.company.currency_id ) joining_date = fields.Date( string='Joining Date', default=lambda self: (datetime.now() + timedelta(days=14)).strftime('%Y-%m-%d')) contract_type = fields.Selection([ ('permanent', 'Permanent'), ('contract', 'Fixed Term Contract'), ('intern', 'Internship')], string='Contract Type', default='permanent' ) probation_period = fields.Integer( string='Probation Period (months)', default=3 ) terms_conditions = fields.Text( string='Terms and Conditions', default=lambda self: self._default_terms() ) state = fields.Selection([ ('draft', 'Draft'), ('sent', 'Sent'), ('accepted', 'Accepted'), ('rejected', 'Rejected'), ('expired', 'Expired')], string='Status', default='draft', tracking=True ) sent_date = fields.Datetime(string='Sent Date') response_date = fields.Datetime(string='Response Date') pay_struct_id = fields.Many2one('hr.payroll.structure', string="Salary Structure", required=True) manager_id = fields.Many2one('hr.employee', string='Manager') @api.model def _default_terms(self): return """
1. This offer is contingent upon satisfactory reference checks.
2. You will be required to sign a confidentiality agreement.
3. The company reserves the right to modify job responsibilities.
""" @api.model def create(self, vals): if vals.get('name', _('New')) == _('New'): vals['name'] = self.env['ir.sequence'].next_by_code('offer.letter') or _('New') return super(OfferLetter, self).create(vals) def action_send_offer(self): self.ensure_one() # template = self.env.ref('offer_letters.email_template_offer_letter') self.write({'state': 'sent', 'sent_date': fields.Datetime.now()}) # template.send_mail(self.id, force_send=True) return True def action_accept_offer(self): self.ensure_one() # employee = self.env['hr.employee'].create({ # 'name': self.candidate_id.partner_name, # 'job_title': self.position, # 'department_id': self.department_id.id, # 'currency_id': self.currency_id.id, # }) self.write({ 'state': 'accepted', # 'employee_id': employee, 'response_date': fields.Datetime.now() }) return True def action_reject_offer(self): self.ensure_one() self.write({ 'state': 'rejected', 'response_date': fields.Datetime.now() }) return True @api.onchange('candidate_id') def _onchange_candidate_id(self): self.position = self.candidate_id.job_id.name def get_paydetailed_lines(self): today = fields.Date.today() first_day = today.replace(day=1) last_day = today.replace(day=calendar.monthrange(today.year, today.month)[1]) payslip = self.env['hr.payslip'].new({ 'date_from': first_day, 'date_to': last_day, }) contract = self.env['hr.contract'].new({ 'date_start': first_day, 'date_end': last_day, 'l10n_in_medical_insurance':self.mi, 'l10n_in_provident_fund': True, 'name': 'test', 'wage': self.salary / 12, }) categories_dict = {} rules_dict = {} result = {} localdict = { 'payslip': payslip, 'contract': contract, 'worked_days': {}, 'categories': defaultdict(lambda: 0), # Fixed: Changed DefaultDictroll to defaultdict 'rules': defaultdict(lambda: dict(total=0, amount=0, quantity=0)), 'result': None, 'result_qty': 1.0, 'result_rate': 100, 'result_name': False, 'inputs': {}, } blacklisted_ids = set(self.env.context.get('prevent_payslip_computation_line_ids', [])) for rule in sorted(self.pay_struct_id.rule_ids, key=lambda r: r.sequence): if rule.id in blacklisted_ids or not rule._satisfy_condition(localdict): continue qty = 1.0 rate = 100.0 amount = 0.0 try: if rule.amount_select == 'fix': amount = rule.amount_fix elif rule.amount_select == 'percentage': base = float(safe_eval(rule.amount_percentage_base or '0.0', localdict, mode='exec', nocopy=True)) amount = base * rule.amount_percentage / 100 elif rule.amount_select == 'code': safe_eval(rule.amount_python_compute or '0.0', localdict, mode='exec', nocopy=True) amount = float(localdict.get('result', 0.0)) except Exception as e: raise UserError(_("Error in rule %s: %s") % (rule.name, str(e))) total = payslip._get_payslip_line_total(amount, qty, rate, rule) rule_code = rule.code previous_amount = localdict.get(rule.code, 0.0) category_code = rule.category_id.code tot_rule = payslip._get_payslip_line_total(amount, qty, rate, rule) # Make sure _sum_salary_rule_category method exists if hasattr(rule.category_id, '_sum_salary_rule_category'): localdict = rule.category_id._sum_salary_rule_category(localdict, tot_rule - previous_amount) localdict[rule_code] = total rules_dict[rule_code] = rule categories_dict[category_code] = categories_dict.get(category_code, 0.0) + amount result[rule_code] = { 'sequence': rule.sequence, 'code': rule_code, 'name': rule.name, # Simplified name retrieval 'salary_rule_id': rule.id, 'amount': round(amount,2), 'y_amount': round((amount * 12),2), 'quantity': qty, 'rate': rate, 'total': round(total,2), } self.terms_conditions = json.dumps(list(result.values())) return { 'type': 'ir.actions.act_window', 'res_model': self._name, 'res_id': self.id, 'view_mode': 'form', 'view_type': 'form', 'target': 'current', } def generate_pdf_report(self): return self.env.ref('offer_letters.hr_offer_letters_employee_print').report_action(self)