diff --git a/addons_extensions/hr_employee_extended/__manifest__.py b/addons_extensions/hr_employee_extended/__manifest__.py index ad173df00..ee072e92c 100644 --- a/addons_extensions/hr_employee_extended/__manifest__.py +++ b/addons_extensions/hr_employee_extended/__manifest__.py @@ -18,12 +18,19 @@ 'version': '0.1', # any module necessary for this one to work correctly + 'depends': ['base','hr','account','mail','hr_skills', 'hr_contract'], + + + # always loaded 'data': [ 'security/security.xml', 'security/ir.model.access.csv', + 'data/cron.xml', + 'data/mail.xml', + 'views/res_config_settings.xml', 'views/hr_employee.xml', 'views/bank_details.xml', 'wizards/work_location_wizard.xml' diff --git a/addons_extensions/hr_employee_extended/data/cron.xml b/addons_extensions/hr_employee_extended/data/cron.xml new file mode 100644 index 000000000..9903b9986 --- /dev/null +++ b/addons_extensions/hr_employee_extended/data/cron.xml @@ -0,0 +1,15 @@ + + + + + Employee: Event Remainder + + code + model.hr_employee_event_remainder() + 1 + days + + + + + \ No newline at end of file diff --git a/addons_extensions/hr_employee_extended/data/mail.xml b/addons_extensions/hr_employee_extended/data/mail.xml new file mode 100644 index 000000000..6a22e6914 --- /dev/null +++ b/addons_extensions/hr_employee_extended/data/mail.xml @@ -0,0 +1,59 @@ + + + + + + Employee Birthday & Anniversary Reminder + + {{ user.email_formatted }} + hr@ftprotech.com + Upcoming Employee Events: Birthdays & Anniversaries + Notify HR of employees with upcoming birthdays or anniversaries. + + + +

+ Dear HR Team, +
+
+ + Here are the upcoming Employee Events: + +
+
+ + 🎂 Birthdays: +
+

    + +
  • + + — + +
  • +
    +
+
+ + + 💍 Marriage Anniversaries:
+
    + +
  • + — + +
  • +
    +
+
+ +
+ Regards, +
+ +

+
+
+ +
+
\ No newline at end of file diff --git a/addons_extensions/hr_employee_extended/models/__init__.py b/addons_extensions/hr_employee_extended/models/__init__.py index 315b0c858..c60cd7877 100644 --- a/addons_extensions/hr_employee_extended/models/__init__.py +++ b/addons_extensions/hr_employee_extended/models/__init__.py @@ -1,3 +1,4 @@ +from . import res_config_settings from . import hr_employee from . import work_location_history from . import education_history diff --git a/addons_extensions/hr_employee_extended/models/hr_employee.py b/addons_extensions/hr_employee_extended/models/hr_employee.py index 0d980fa55..e23e2c68f 100644 --- a/addons_extensions/hr_employee_extended/models/hr_employee.py +++ b/addons_extensions/hr_employee_extended/models/hr_employee.py @@ -38,7 +38,7 @@ class HrEmployeeBase(models.AbstractModel): @api.constrains('identification_id') def _check_identification_id(self): for record in self: - if not record.identification_id.isdigit(): + if record.identification_id and not record.identification_id.isdigit(): raise ValidationError("Identification ID should only contain numbers.") @api.depends('doj') @@ -156,4 +156,67 @@ class HrEmployee(models.Model): passport_issued_location = fields.Char(string='Passport Issued Location') previous_company_pf_no = fields.Char(string='Previous Company PF No') - previous_company_uan_no = fields.Char(string='Previous Company UAN No') \ No newline at end of file + previous_company_uan_no = fields.Char(string='Previous Company UAN No') + + def hr_employee_event_remainder(self): + self.send_birthday_reminders() + today = fields.Date.today() + tomorrow = today + timedelta(days=1) + day_after = today + timedelta(days=2) + + tomorrow_md = tomorrow.strftime('-%m-%d') + day_after_md = day_after.strftime('-%m-%d') + + if today.weekday() == 4: # Friday + birthday_domain = ['|', ('birthday', 'like', f'%{tomorrow_md}'), ('birthday', 'like', f'%{day_after_md}')] + anniversary_domain = [('marital', '=', 'married'), '|', + ('marriage_anniversary_date','like', f'%{tomorrow_md}'), + ('marriage_anniversary_date', 'like', f'%{day_after_md}')] + else: + birthday_domain = [('birthday','like', f'%{tomorrow_md}')] + anniversary_domain = [('marital', '=', 'married'), + ('marriage_anniversary_date','like', f'%{tomorrow_md}')] + + birthday_emps = self.search(birthday_domain) + anniversary_emps = self.search(anniversary_domain) + + birthday_list = [{'name': emp.name, 'birthday': emp.birthday} for emp in birthday_emps] + anniversary_list = [{'name': emp.name, 'marriage_anniversary_date': emp.marriage_anniversary_date} for emp in anniversary_emps] + context = { + 'birthday_list': birthday_list, + 'anniversary_list': anniversary_list, + } + email_values = {'auto_delete': True} + template = self.env.ref('hr_employee_extended.hr_employee_event_reminder_template') + if template and (birthday_list or anniversary_list): + hr_id = self.env['ir.config_parameter'].sudo().get_param('hr_employee_extended.emp_hr_id') + if hr_id: + emp_hr = self.env['res.users'].sudo().browse(int(hr_id)) if hr_id else False + email_values['email_to'] = emp_hr.email + else: + emp_hr = self.env['res.users'].sudo().search([('groups_id', 'in', self.env.ref('hr.group_hr_manager').id)],limit=1) + + # Use `self[0]` as dummy record for sending + template.with_context(**context).send_mail( + emp_hr.employee_id.id, force_send=True, email_values=email_values, + ) + + @api.model + def send_birthday_reminders(self): + today = datetime.today().strftime('%m-%d') + employees = self.search([('birthday', '!=', False)]) + + birthday_emps = employees.filtered( + lambda emp: emp.birthday.strftime('%m-%d') == today + ) + + if birthday_emps: + channel = self.env['discuss.channel'].search([('name', 'ilike', 'General')], limit=1) + if channel: + for emp in birthday_emps: + message = f"🎉 Happy Birthday {emp.name}! 🎂 Wishing you a fantastic day! 🥳" + channel.message_post( + body=message, + message_type='comment', + subtype_xmlid='mail.mt_comment' + ) diff --git a/addons_extensions/hr_employee_extended/models/res_config_settings.py b/addons_extensions/hr_employee_extended/models/res_config_settings.py new file mode 100644 index 000000000..3167c8558 --- /dev/null +++ b/addons_extensions/hr_employee_extended/models/res_config_settings.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models, api, _ + + +class ResConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + emp_hr_id = fields.Many2one('res.users',config_parameter='hr_employee_extended.emp_hr_id', string='Employee HR', + domain=lambda self: [ + ('groups_id', 'in', self.env.ref('hr.group_hr_manager').id)]) + diff --git a/addons_extensions/hr_employee_extended/views/res_config_settings.xml b/addons_extensions/hr_employee_extended/views/res_config_settings.xml new file mode 100644 index 000000000..3a0fbb233 --- /dev/null +++ b/addons_extensions/hr_employee_extended/views/res_config_settings.xml @@ -0,0 +1,41 @@ + + + + res.config.settings.view.form.inherit.employee + res.config.settings + + + + + + + + + + + + + + + + + Settings + res.config.settings + form + inline + {'module' : 'hr_appraisal', 'bin_size': False} + + + + + + diff --git a/addons_extensions/hr_payroll/models/hr_employee.py b/addons_extensions/hr_payroll/models/hr_employee.py index 5696ba274..f2794ade3 100644 --- a/addons_extensions/hr_payroll/models/hr_employee.py +++ b/addons_extensions/hr_payroll/models/hr_employee.py @@ -69,22 +69,4 @@ class HrEmployee(models.Model): return self.env.cr.dictfetchall() - @api.model - def send_birthday_reminders(self): - today = datetime.today().strftime('%m-%d') - employees = self.search([('birthday', '!=', False)]) - birthday_emps = employees.filtered( - lambda emp: emp.birthday.strftime('%m-%d') == today - ) - - if birthday_emps: - channel = self.env['discuss.channel'].search([('name', 'ilike', 'General')], limit=1) - if channel: - for emp in birthday_emps: - message = f"🎉 Happy Birthday {emp.name}! 🎂 Wishing you a fantastic day! 🥳" - channel.message_post( - body=message, - message_type='comment', - subtype_xmlid='mail.mt_comment' - ) diff --git a/addons_extensions/weekly_attendance_report/__init__.py b/addons_extensions/weekly_attendance_report/__init__.py new file mode 100644 index 000000000..9a7e03ede --- /dev/null +++ b/addons_extensions/weekly_attendance_report/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/addons_extensions/weekly_attendance_report/__manifest__.py b/addons_extensions/weekly_attendance_report/__manifest__.py new file mode 100644 index 000000000..aaa9bcde8 --- /dev/null +++ b/addons_extensions/weekly_attendance_report/__manifest__.py @@ -0,0 +1,11 @@ +{ + 'name': 'Weekly Attendance Email Report', + 'version': '1.0', + 'category': 'Human Resources', + 'depends': ['hr_attendance', 'mail'], + 'data': [ + 'security/ir.model.access.csv', + ], + 'installable': True, + 'application': False, +} diff --git a/addons_extensions/weekly_attendance_report/models/__init__.py b/addons_extensions/weekly_attendance_report/models/__init__.py new file mode 100644 index 000000000..98cad3460 --- /dev/null +++ b/addons_extensions/weekly_attendance_report/models/__init__.py @@ -0,0 +1 @@ +from . import attendance_report \ No newline at end of file diff --git a/addons_extensions/weekly_attendance_report/models/attendance_report.py b/addons_extensions/weekly_attendance_report/models/attendance_report.py new file mode 100644 index 000000000..60026722d --- /dev/null +++ b/addons_extensions/weekly_attendance_report/models/attendance_report.py @@ -0,0 +1,131 @@ +from odoo import models, fields, api +from datetime import timedelta +import json +from collections import defaultdict +from datetime import datetime + + +class AttendanceWeeklyReport(models.Model): + _name = 'attendance.weekly.report' + _description = 'Weekly Attendance Report' + + def send_weekly_attendance_email(self): + management_emails = self.env['hr.employee'].search([ + ('department_id.name', '=', 'Administration'), + ('work_email', '!=', False) + ]).mapped('work_email') + + if not management_emails: + return + + today = fields.Date.context_today(self) + last_monday = today - timedelta(days=today.weekday() + 7) + last_sunday = last_monday + timedelta(days=6) + + attendances = self.env['hr.attendance'].search([ + ('check_in', '>=', str(last_monday)), + ('check_out', '<=', str(last_sunday + timedelta(days=1))) + ]) + + employee_data = {} + + employee_data = defaultdict(list) + grouped_attendance = defaultdict(lambda: defaultdict(list)) # {emp: {date: [attendances]}} + + for att in attendances: + emp = att.employee_id.name + date = att.check_in.date() + grouped_attendance[emp][date].append(att) + + for emp_name, dates in grouped_attendance.items(): + for date, records in dates.items(): + records = sorted(records, key=lambda a: a.check_in) + total_seconds = 0 + first_in = records[0].check_in.time().strftime('%H:%M') + last_out = 'N/A' + + for rec in records: + if rec.check_in and rec.check_out: + total_seconds += (rec.check_out - rec.check_in).total_seconds() + last_out = rec.check_out.time().strftime('%H:%M') + + employee_data[emp_name].append({ + 'date': date.strftime('%Y-%m-%d'), + 'in': first_in, + 'out': last_out, + 'hours': f'{total_seconds / 3600:.2f}', + }) + + # Inline QWeb-compatible HTML template (must be in a real view in production) + html_template = """ +
+

Dear Management,

+

+ Please find below the attendance summary for the period + %(from_date)s to %(to_date)s. +

+ + %(employee_tables)s + +

Regards,
Odoo HR System

+ For more details, visit ftprotech.in +
+ """ + + employee_tables_html = "" + if employee_data: + for emp_name, records in employee_data.items(): + rows = "" + total = 0 + for line in records: + total += float(line['hours']) if line['hours'] != False else 0 + rows += f""" + + {line['date']} + {line['in']} + {line['out']} + {line['hours']} + + """ + t =f""" + + + Total worked Hours {total:.2f} + + + """ + table = f""" +
+

{emp_name}

+ + + + + + + + + + {rows}{t} + +
DateCheck InCheck OutHours Worked
+
+ """ + employee_tables_html += table + else: + employee_tables_html = "

No attendance data available for this period.

" + + # Final HTML body + body_html = html_template % { + 'from_date': last_monday.strftime('%Y-%m-%d'), + 'to_date': last_sunday.strftime('%Y-%m-%d'), + 'employee_tables': employee_tables_html + } + + # Send email to all management emails + for email in management_emails: + self.env['mail.mail'].create({ + 'email_to': email, + 'subject': f"Weekly Attendance Report: {last_monday} to {last_sunday}", + 'body_html': body_html, + }).send() diff --git a/addons_extensions/weekly_attendance_report/security/ir.model.access.csv b/addons_extensions/weekly_attendance_report/security/ir.model.access.csv new file mode 100644 index 000000000..44a7ad01c --- /dev/null +++ b/addons_extensions/weekly_attendance_report/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id,perm_read,perm_write,perm_create,perm_unlink +access_attendance_weekly_report,access_attendance_weekly_report,model_attendance_weekly_report,,1,1,1,1