diff --git a/addons_extensions/hr_employee_extended/__manifest__.py b/addons_extensions/hr_employee_extended/__manifest__.py
index b821a5a04..ee072e92c 100644
--- a/addons_extensions/hr_employee_extended/__manifest__.py
+++ b/addons_extensions/hr_employee_extended/__manifest__.py
@@ -28,6 +28,9 @@
'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}
+
+
+
+ | Date |
+ Check In |
+ Check Out |
+ Hours Worked |
+
+
+ {rows}{t}
+
+
+
+ """
+ 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