This commit is contained in:
esam 2025-12-16 15:18:21 -05:00
parent 4f24dd46ee
commit d9f96285a9
24 changed files with 1894 additions and 0 deletions

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import models
from . import wizard

View File

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Odex - Communications Management System.
# Copyright (C) 2019 Expert Co. Ltd. (<http://exp-sa.com>).
#
##############################################################################
{
'name': 'Timesheet Reminder Employees',
'version': '1.0',
'sequence': 10,
'author': 'Expert Co. Ltd. ---sudan team',
'category': 'Odex30-HR/Odex30-HR',
'website': 'http://exp-sa.com',
'summary': 'Timesheet Missing Reminder to Employees',
'description': """
Timesheet Missing Reminder to Employees By sending Email per Day or Week and Month
========================================""",
'website': 'http://www.exp-sa.com',
'depends': ['mail', 'hr_timesheet_sheet', 'exp_official_mission', 'hr_holidays_public', 'attendances'],
'data': [
'data/corn_timesheet_reminder.xml',
'data/timesheet_email_template.xml',
'security/ir.model.access.csv',
'views/extend_hr_employee.xml',
'views/hr_employee_reminder_history.xml',
'report/report_action.xml',
'report/report_template.xml',
'wizard/timesheet_reminder_report.xml',
'views/wizard_approve.xml',
],
'qweb': [
],
'installable': True,
'auto_install': False,
'application': False,
'test_tags': ['standard', 'at_install'],
}

View File

@ -0,0 +1,37 @@
<odoo>
<data>
<record model="ir.cron" id="day_reminder_mail">
<field name="name">Late Email Reminder Day</field>
<field name="active">True</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="model_id" ref="model_hr_timesheet_sheet" />
<field name="state">code</field>
<field name="code">model.action_send_late_email('day')</field>
</record>
<record model="ir.cron" id="week_reminder_mail">
<field name="name">Late Email Reminder Week</field>
<field name="active">True</field>
<field name="interval_number">7</field>
<field name="interval_type">days</field>
<field name="model_id" ref="model_hr_timesheet_sheet" />
<field name="state">code</field>
<field name="code">model.action_send_late_email('week')</field>
</record>
<record model="ir.cron" id="monthly_reminder_mail">
<field name="name">Late Email Reminder Month</field>
<field name="active">True</field>
<field name="interval_number">30</field>
<field name="interval_type">days</field>
<field name="model_id" ref="model_hr_timesheet_sheet" />
<field name="state">code</field>
<field name="code">model.action_send_late_email('month')</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="timesheet_daily_reminder_email_notify" model="mail.template">
<field name="name">Daily Timesheet Reminder Message</field>
<field name="model_id" ref="hr_timesheet_sheet.model_hr_employee"/>
<field name="auto_delete" eval="True"/>
<field name="email_from"><![CDATA[${user.company_id.name} <${(user.company_id.email or user.email)|safe}>]]></field>
<field name="subject"><![CDATA[ تذكير يومي بتقرير المهام اليومية ]]></field>
<field name="body_html">
<![CDATA[
<div style="text-align: right;direction:rtl">
<p style="text-align: right">السادة الفضلاء:}</p>
<p style="text-align: left">حفظهم الله،،</p>
<p style="font-size: 1.1em;text-align: right">
السلام عليكم ورحمة الله.
</p>
<p style="font-size: 1.1em;text-align: right">
الرجاء تعبئة الايام المفقوده
</p><br/>
<p style="font-size: 1em;text-align: right">
</p><br/>
<p style="font-size: 1.1em;text-align: right">
<a href="${user.company_id.website}">${user.company_id.name}</a>
</p>
</div>
]]>
</field>
</record>
<record id="timesheet_week_reminder_email_notify" model="mail.template">
<field name="name">Weekly Timesheet Reminder Message</field>
<field name="model_id" ref="hr_timesheet_sheet.model_hr_employee"/>
<field name="auto_delete" eval="True"/>
<field name="email_from"><![CDATA[${user.company_id.name} <${(user.company_id.email or user.email)|safe}>]]></field>
<field name="subject"><![CDATA[ تذكير اسبوعي بتقرير المهام اليومية ]]></field>
<field name="body_html">
<![CDATA[
<div style="text-align: right;direction:rtl">
<p style="text-align: right">السادة الفضلاء:}</p>
<p style="text-align: left">حفظهم الله،،</p>
<p style="font-size: 1.1em;text-align: right">
السلام عليكم ورحمة الله.
</p>
<p style="font-size: 1.1em;text-align: right">
الرجاء تعبئة الايام المفقوده
</p><br/>
<p style="font-size: 1em;text-align: right">
</p><br/>
<p style="font-size: 1.1em;text-align: right">
<a href="${user.company_id.website}">${user.company_id.name}</a>
</p>
</div>
]]>
</field>
</record>
<!--monthly email reminder -->
<record id="timesheet_monthly_reminder_email_notify" model="mail.template">
<field name="name">Monthly Timesheet Reminder Message</field>
<field name="model_id" ref="hr_timesheet_sheet.model_hr_employee"/>
<field name="auto_delete" eval="True"/>
<field name="email_from"><![CDATA[${user.company_id.name} <${(user.company_id.email or user.email)|safe}>]]></field>
<field name="subject"><![CDATA[ تذكير شهري بتقرير المهام اليومية ]]></field>
<field name="body_html">
<![CDATA[
<div style="text-align: right;direction:rtl">
<p style="text-align: right">السادة الفضلاء:}</p>
<p style="text-align: left">حفظهم الله،،</p>
<p style="font-size: 1.1em;text-align: right">
السلام عليكم ورحمة الله.
</p>
<p style="font-size: 1.1em;text-align: right">
الرجاء تعبئة الايام المفقوده
</p><br/>
<p style="font-size: 1em;text-align: right">
</p><br/>
<p style="font-size: 1.1em;text-align: right">
<a href="${user.company_id.website}">${user.company_id.name}</a>
</p>
</div>
]]>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,506 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * exp_timesheet_missing_reminder
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-16 14:04+0000\n"
"PO-Revision-Date: 2025-12-16 14:04+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: exp_timesheet_missing_reminder
#: model:mail.template,subject:exp_timesheet_missing_reminder.timesheet_week_reminder_email_notify
msgid " تذكير اسبوعي بتقرير المهام اليومية "
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model:mail.template,subject:exp_timesheet_missing_reminder.timesheet_monthly_reminder_email_notify
msgid " تذكير شهري بتقرير المهام اليومية "
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model:mail.template,subject:exp_timesheet_missing_reminder.timesheet_daily_reminder_email_notify
msgid " تذكير يومي بتقرير المهام اليومية "
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model:ir.actions.report,name:exp_timesheet_missing_reminder.action_report_reminded
msgid "\"Reminded Employee"
msgstr "تذكير الموظفين"
#. module: exp_timesheet_missing_reminder
#: model:mail.template,body_html:exp_timesheet_missing_reminder.timesheet_monthly_reminder_email_notify
msgid ""
"<div style=\"text-align: right;direction:rtl\">\n"
" <p style=\"text-align: right\">السادة الفضلاء:}</p>\n"
" <p style=\"text-align: left\">حفظهم الله،،</p>\n"
" <p style=\"font-size: 1.1em;text-align: right\">\n"
" السلام عليكم ورحمة الله.\n"
" </p>\n"
" <p style=\"font-size: 1.1em;text-align: right\">\n"
" الرجاء تعبئة الايام المفقوده\n"
" </p><br/>\n"
" <p style=\"font-size: 1em;text-align: right\">\n"
" </p><br/>\n"
" <p style=\"font-size: 1.1em;text-align: right\">\n"
" <a href=\"${user.company_id.website}\">${user.company_id.name}</a>\n"
" </p>\n"
" </div>\n"
" \n"
" "
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model:mail.template,body_html:exp_timesheet_missing_reminder.timesheet_week_reminder_email_notify
msgid ""
"<div>\n"
" <p>السلام عليكم ورحمة الله</p>\n"
" <br/>\n"
" <p> السيد\\ة esam alsrmaah مع التحية</p>\n"
" <br/>\n"
" <p> تقرير الأعمال اليومي الخاص بكم في النظام لم يغط الأوقات المطلوبة منكم حسب الجدول التالي:\n"
"\n"
"، نأمل إبداء الأسباب خلال \n"
"يوم واحد فقط للنظر فيها، وفي حال لم ترسل الأسباب أو أرسلت ولم تكن مقنعة نظاما، سيتم خصم ساعات الغياب من راتبك الشهري.\n"
"إن عدم تغطية الأوقات الفعلية لكم بدون أسباب مقبولة منكم هو تقصير تستحقون عليه الإنذار. </p>\n"
" </div>\n"
" <center>\n"
" <table style=\"margin-top:30px;color:#666666;border: 1px solid black\">\n"
" <thead>\n"
" <tr style=\"color:#9A6C8E; font-size:12px;border: 1px solid black\">\n"
" <th style=\"border: 1px solid gray\" text-align:right=\"\" align=\"center\">الساعات</th>\n"
" <th style=\"border: 1px solid gray\" text-align:right=\"\" align=\"center\">التاريخ</th>\n"
" <th style=\"border: 1px solid gray\" text-align:right=\"\" align=\"center\">الايام</th>\n"
" </tr>\n"
" </thead>\n"
" <tbody>\n"
" % for line in [['2025-12-08', 'Monday', 0.0, 0.0, 0.0], ['2025-12-09', 'Tuesday', 0.0, 0.0, 0.0], ['2025-12-10', 'Wednesday', 0.0, 0.0, 0.0], ['2025-12-11', 'Thursday', 0.0, 0.0, 0.0], ['2025-12-12', 'Friday', 0.0, 0.0, 0.0], ['2025-12-13', 'Saturday', 0.0, 0.0, 0.0], ['2025-12-14', 'Sunday', 0.0, 0.0, 0.0]]:\n"
" <tr style=\"border: 1px solid gray\">\n"
" <td style=\"padding: 20px;margin:5px;border: 1px solid gray\" align=\"left\">\n"
" ${line[2]}\n"
" </td>\n"
" <td style=\"padding: 20px;margin:5px;border: 1px solid gray\" align=\"left\">\n"
" ${line[0]}\n"
" </td>\n"
" <td style=\"padding: 20px;margin:5px;border: 1px solid gray\" align=\"right\">\n"
" ${line[1]}\n"
" </td>\n"
" </tr>\n"
" % endfor\n"
" </tbody>\n"
" </table>\n"
" </center>\n"
" "
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model:mail.template,body_html:exp_timesheet_missing_reminder.timesheet_daily_reminder_email_notify
msgid ""
"<div>\n"
" <p>السلام عليكم ورحمة الله</p>\n"
" <br/>\n"
" <p> السيد\\ة esam alsrmaah مع التحية</p>\n"
" <br/>\n"
" <p> تقرير الأعمال اليومي الخاص بكم في النظام لم يغط الأوقات المطلوبة منكم حسب الجدول التالي:\n"
"\n"
"، نأمل إبداء الأسباب خلال \n"
"يوم واحد فقط للنظر فيها، وفي حال لم ترسل الأسباب أو أرسلت ولم تكن مقنعة نظاما، سيتم خصم ساعات الغياب من راتبك الشهري.\n"
"إن عدم تغطية الأوقات الفعلية لكم بدون أسباب مقبولة منكم هو تقصير تستحقون عليه الإنذار. </p>\n"
" </div>\n"
" <center>\n"
" <table style=\"margin-top:30px;color:#666666;border: 1px solid black\">\n"
" <thead>\n"
" <tr style=\"color:#9A6C8E; font-size:12px;border: 1px solid black\">\n"
" <th style=\"border: 1px solid gray\" text-align:right=\"\" align=\"center\">الساعات</th>\n"
" <th style=\"border: 1px solid gray\" text-align:right=\"\" align=\"center\">التاريخ</th>\n"
" <th style=\"border: 1px solid gray\" text-align:right=\"\" align=\"center\">الايام</th>\n"
" </tr>\n"
" </thead>\n"
" <tbody>\n"
" % for line in [['2025-12-14', 'Sunday', 3.0, 7.0, 4.0]]:\n"
" <tr style=\"border: 1px solid gray\">\n"
" <td style=\"padding: 20px;margin:5px;border: 1px solid gray\" align=\"left\">\n"
" ${line[2]}\n"
" </td>\n"
" <td style=\"padding: 20px;margin:5px;border: 1px solid gray\" align=\"left\">\n"
" ${line[0]}\n"
" </td>\n"
" <td style=\"padding: 20px;margin:5px;border: 1px solid gray\" align=\"right\">\n"
" ${line[1]}\n"
" </td>\n"
" </tr>\n"
" % endfor\n"
" </tbody>\n"
" </table>\n"
" </center>\n"
" "
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model_terms:ir.ui.view,arch_db:exp_timesheet_missing_reminder.approve_timesheet_view
msgid "<span>All selected Timesheet will be Approved Are You Sure?</span>"
msgstr "<span>هل انت متاكد من إعتماد هذه الجداول الزمنية؟</span>"
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_hr_employee_history_reminder__actual_hour
msgid "Actual"
msgstr "الساعات الفعلية"
#. module: exp_timesheet_missing_reminder
#: model:ir.model,name:exp_timesheet_missing_reminder.model_account_analytic_line
msgid "Analytic Line"
msgstr "البند التحليلي"
#. module: exp_timesheet_missing_reminder
#: model:ir.actions.act_window,name:exp_timesheet_missing_reminder.action_approve_multi_timesheet
#: model:ir.model,name:exp_timesheet_missing_reminder.model_approve_multi_timesheet
#: model_terms:ir.ui.view,arch_db:exp_timesheet_missing_reminder.approve_timesheet_view
msgid "Approve Multi Timesheet"
msgstr "إعتماد الجداول الزمنية"
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_hr_employee_history_reminder__attachment_ids
msgid "Attachments"
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_hr_employee_history_reminder__break_hour
msgid "Break Hour"
msgstr "ساعات الراحة"
#. module: exp_timesheet_missing_reminder
#: model_terms:ir.ui.view,arch_db:exp_timesheet_missing_reminder.approve_timesheet_view
#: model_terms:ir.ui.view,arch_db:exp_timesheet_missing_reminder.wizard_reminded_employees_form
msgid "Cancel"
msgstr "إلغاء"
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_resource_calendar__check_timesheet
#: model_terms:ir.ui.view,arch_db:exp_timesheet_missing_reminder.resource_calendar_inherited_timesheet_form
msgid "Check Timesheet"
msgstr "الربط مع الجداول الزمنية"
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_approve_multi_timesheet__create_uid
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_hr_employee_history_reminder__create_uid
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_timesheet_reminded__create_uid
msgid "Created by"
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_approve_multi_timesheet__create_date
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_hr_employee_history_reminder__create_date
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_timesheet_reminded__create_date
msgid "Created on"
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model:mail.template,name:exp_timesheet_missing_reminder.timesheet_daily_reminder_email_notify
msgid "Daily Timesheet Reminder Message"
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_hr_employee_history_reminder__date
msgid "Date"
msgstr "التاريخ"
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_timesheet_reminded__date_from
msgid "Date From"
msgstr "تاريخ البداية"
#. module: exp_timesheet_missing_reminder
#. odoo-python
#: code:addons/exp_timesheet_missing_reminder/wizard/timesheet_reminder_report.py:0
msgid "Date From Must Be Greater Than Date To"
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_timesheet_reminded__date_to
msgid "Date To"
msgstr "التاريخ للإنتهاء"
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_timesheet_reminded__department_ids
msgid "Department"
msgstr "الهيكل الإداري"
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_approve_multi_timesheet__display_name
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_hr_employee_history_reminder__display_name
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_timesheet_reminded__display_name
msgid "Display Name"
msgstr "الاسم المعروض"
#. module: exp_timesheet_missing_reminder
#: model:ir.model,name:exp_timesheet_missing_reminder.model_hr_employee
msgid "Employee"
msgstr "الموظف"
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_hr_employee_history_reminder__employee_id
msgid "Employee Name"
msgstr "اسم الموظف"
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_hr_employee_history_reminder__entered_hour
msgid "Entered Hour"
msgstr "الساعات المدخلة"
#. module: exp_timesheet_missing_reminder
#: model:ir.ui.menu,name:exp_timesheet_missing_reminder.menu_hr_employee_history_reminder
msgid "History Reminder"
msgstr "حركة التذكير بالجداول الزمنية"
#. module: exp_timesheet_missing_reminder
#: model:ir.actions.act_window,name:exp_timesheet_missing_reminder.hr_employee_reminder_history_action
msgid "Hr employee reminder"
msgstr "حركة التذكير بالجداول الزمنية"
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_approve_multi_timesheet__id
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_hr_employee_history_reminder__id
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_timesheet_reminded__id
msgid "ID"
msgstr "المُعرف"
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_hr_employee_history_reminder__is_completed_timesheet
msgid "Is completed Timesheet"
msgstr "جدول زمني كامل"
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_approve_multi_timesheet__write_uid
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_hr_employee_history_reminder__write_uid
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_timesheet_reminded__write_uid
msgid "Last Updated by"
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_approve_multi_timesheet__write_date
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_hr_employee_history_reminder__write_date
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_timesheet_reminded__write_date
msgid "Last Updated on"
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model:ir.actions.server,name:exp_timesheet_missing_reminder.day_reminder_mail_ir_actions_server
msgid "Late Email Reminder Day"
msgstr "تذكير بالمهام بإيميل يومي "
#. module: exp_timesheet_missing_reminder
#: model:ir.actions.server,name:exp_timesheet_missing_reminder.monthly_reminder_mail_ir_actions_server
msgid "Late Email Reminder Month"
msgstr "تذكير بالمهام بإيميل شهري "
#. module: exp_timesheet_missing_reminder
#: model:ir.actions.server,name:exp_timesheet_missing_reminder.week_reminder_mail_ir_actions_server
msgid "Late Email Reminder Week"
msgstr "تذكير بالمهام بإيميل اسبوعي "
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_hr_employee_history_reminder__miss_hour
msgid "Miss Hour"
msgstr "الساعات الغير مدخلة"
#. module: exp_timesheet_missing_reminder
#: model:mail.template,name:exp_timesheet_missing_reminder.timesheet_monthly_reminder_email_notify
msgid "Monthly Timesheet Reminder Message"
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_hr_employee_history_reminder__overtime_hour
msgid "Overtime Hour"
msgstr "الساعات الاضافية"
#. module: exp_timesheet_missing_reminder
#: model_terms:ir.ui.view,arch_db:exp_timesheet_missing_reminder.wizard_reminded_employees_form
msgid "Print"
msgstr "طباعة PDF"
#. module: exp_timesheet_missing_reminder
#: model:ir.actions.act_window,name:exp_timesheet_missing_reminder.wizard_reminded_employees_action
msgid "Reminded Employees"
msgstr "تذكيرات الموظفين"
#. module: exp_timesheet_missing_reminder
#: model:ir.ui.menu,name:exp_timesheet_missing_reminder.report_reminded_employees_menu
msgid "Reminded Employees Report"
msgstr "تقرير تذكير الجداول الزمنية"
#. module: exp_timesheet_missing_reminder
#: model:ir.model,name:exp_timesheet_missing_reminder.model_resource_calendar
msgid "Resource Working Time"
msgstr "فترة عمل المورد"
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_hr_employee__skipp_timesheet_reminder
msgid "Skip Timesheet Reminder"
msgstr "عدم التذكير بالمهام"
#. module: exp_timesheet_missing_reminder
#. odoo-python
#: code:addons/exp_timesheet_missing_reminder/models/extend_hr_timesheet.py:0
msgid ""
"Sorry, the timesheet days before must be greater than or equal to zero."
msgstr ""
#. module: exp_timesheet_missing_reminder
#. odoo-python
#: code:addons/exp_timesheet_missing_reminder/models/extend_hr_timesheet.py:0
msgid ""
"Sorry, the timesheet hours before must be greater than or equal to zero."
msgstr ""
#. module: exp_timesheet_missing_reminder
#. odoo-python
#: code:addons/exp_timesheet_missing_reminder/models/extend_hr_timesheet.py:0
msgid ""
"Sorry, the timesheet hours before must be less than or equal to 24 hours."
msgstr ""
#. module: exp_timesheet_missing_reminder
#. odoo-python
#: code:addons/exp_timesheet_missing_reminder/models/extend_hr_timesheet.py:0
msgid "Sorry, you cannot request the timesheet lines %s for an earlier date."
msgstr ""
#. module: exp_timesheet_missing_reminder
#. odoo-python
#: code:addons/exp_timesheet_missing_reminder/models/extend_hr_timesheet.py:0
msgid "Sorry, you cannot request the timesheet lines %s in advance."
msgstr ""
#. module: exp_timesheet_missing_reminder
#. odoo-python
#: code:addons/exp_timesheet_missing_reminder/models/extend_hr_timesheet.py:0
msgid ""
"Sorry, you cannot request the timesheet lines %s the day after %s hour."
msgstr ""
#. module: exp_timesheet_missing_reminder
#. odoo-python
#: code:addons/exp_timesheet_missing_reminder/models/extend_hr_timesheet.py:0
msgid "Sorry, your timesheet lines %s are not in the timesheet period."
msgstr ""
#. module: exp_timesheet_missing_reminder
#. odoo-python
#: code:addons/exp_timesheet_missing_reminder/models/approve_multi.py:0
msgid "There is No Timesheet in Confirm state to Approve."
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,help:exp_timesheet_missing_reminder.field_resource_calendar__exc_user_id
msgid "This user can request timesheet as exception to all rules"
msgstr "يقوم هذا المستخدم بالإستثناء من القواعد لطلب الجداول الزمنية للموظفين"
#. module: exp_timesheet_missing_reminder
#: model_terms:ir.actions.act_window,help:exp_timesheet_missing_reminder.action_approve_multi_timesheet
msgid "This wizard will Approve all selected timesheet"
msgstr "هذا المعالج لاعتماد اكثر من جداول زمنية مع بعض"
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_resource_calendar__timesheet_day_before
#: model_terms:ir.ui.view,arch_db:exp_timesheet_missing_reminder.resource_calendar_inherited_timesheet_form
msgid "Timesheet Days Before"
msgstr "طلب جدول الزمنية قبل ايام"
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_resource_calendar__timesheet_hour_before
#: model_terms:ir.ui.view,arch_db:exp_timesheet_missing_reminder.resource_calendar_inherited_timesheet_form
msgid "Timesheet Hours Before"
msgstr "طلب جدول الزمنية قبل الساعة"
#. module: exp_timesheet_missing_reminder
#: model:ir.model,name:exp_timesheet_missing_reminder.model_hr_timesheet_sheet
msgid "Timesheet Sheet"
msgstr "سجل الدوام"
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,help:exp_timesheet_missing_reminder.field_resource_calendar__timesheet_day_before
msgid "To request timesheet for previous days not exceed these days"
msgstr "لطلب الجداول الزمنية لايام سابقة عدم تجاوز هذه الايام"
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,help:exp_timesheet_missing_reminder.field_resource_calendar__timesheet_hour_before
msgid "To request timesheet for previous days, not to exceed this hour"
msgstr "لطلب الجداول الزمنية لايام سابقة بعدم تجاوز هذا الزمن"
#. module: exp_timesheet_missing_reminder
#: model:ir.model.fields,field_description:exp_timesheet_missing_reminder.field_resource_calendar__exc_user_id
#: model_terms:ir.ui.view,arch_db:exp_timesheet_missing_reminder.resource_calendar_inherited_timesheet_form
msgid "User Exception"
msgstr "لإستثناء الموظفين"
#. module: exp_timesheet_missing_reminder
#: model:mail.template,name:exp_timesheet_missing_reminder.timesheet_week_reminder_email_notify
msgid "Weekly Timesheet Reminder Message"
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model:ir.model,name:exp_timesheet_missing_reminder.model_hr_employee_history_reminder
msgid "hr.employee.history.reminder"
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model:ir.model,name:exp_timesheet_missing_reminder.model_report_exp_timesheet_missing_reminder_reminded_report_template
msgid "report.exp_timesheet_missing_reminder.reminded_report_template"
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model_terms:ir.ui.view,arch_db:exp_timesheet_missing_reminder.hr_employee_view_custom
msgid "timesheet reminder"
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model:ir.model,name:exp_timesheet_missing_reminder.model_timesheet_reminded
msgid "timesheet.reminded"
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model_terms:ir.ui.view,arch_db:exp_timesheet_missing_reminder.reminded_report_template
msgid "إلى"
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model_terms:ir.ui.view,arch_db:exp_timesheet_missing_reminder.reminded_report_template
msgid "الموظف"
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model_terms:ir.ui.view,arch_db:exp_timesheet_missing_reminder.reminded_report_template
msgid "تقرير عدد مرات تذكير الموظفين"
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model_terms:ir.ui.view,arch_db:exp_timesheet_missing_reminder.reminded_report_template
msgid "عدد الساعات المدخله"
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model_terms:ir.ui.view,arch_db:exp_timesheet_missing_reminder.reminded_report_template
msgid "عدد الساعات المنسية"
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model_terms:ir.ui.view,arch_db:exp_timesheet_missing_reminder.reminded_report_template
msgid "عدد مرات التذكير"
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model_terms:ir.ui.view,arch_db:exp_timesheet_missing_reminder.reminded_report_template
msgid "ملاحظات"
msgstr ""
#. module: exp_timesheet_missing_reminder
#: model_terms:ir.ui.view,arch_db:exp_timesheet_missing_reminder.reminded_report_template
msgid "من تاريخ"
msgstr ""

View File

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
from . import extend_hr_employee
from . import extend_hr_timesheet
from . import employee_history
from . import approve_multi

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
from odoo import models, _
from odoo.exceptions import UserError
class ApproveMultiTimesheet(models.TransientModel):
_name = "approve.multi.timesheet"
_description = "Approve Multi Timesheet"
def validate_timesheet(self):
context = dict(self._context or {})
moves = self.env['hr_timesheet.sheet'].browse(context.get('active_ids'))
move_to_post = self.env['hr_timesheet.sheet']
for move in moves:
if move.state == 'approve':
move_to_post += move
if not move_to_post:
raise UserError(_('There is No Timesheet in Confirm state to Approve.'))
move_to_post.action_timesheet_done()
return {'type': 'ir.actions.act_window_close'}

View File

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
from odoo import models, fields
class HrEmployeeHistory(models.Model):
_name = 'hr.employee.history.reminder'
_rec_name = 'employee_id'
employee_id = fields.Many2one('hr.employee', string='Employee Name')
date = fields.Date()
miss_hour = fields.Float(string='Miss Hour')
actual_hour = fields.Float(string='Actual')
entered_hour = fields.Float(string='Entered Hour')
break_hour = fields.Float(string='Break Hour')
overtime_hour = fields.Float(string='Overtime Hour')
is_completed_timesheet = fields.Boolean('Is completed Timesheet', default=False)

View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
from odoo import models, fields
class ExtendEmployee(models.Model):
_inherit = 'hr.employee'
skipp_timesheet_reminder = fields.Boolean(string='Skip Timesheet Reminder')

View File

@ -0,0 +1,429 @@
# -*- coding: utf-8 -*-
import datetime
from dateutil.relativedelta import relativedelta
from datetime import date, datetime, timedelta
from time import gmtime
from dateutil import relativedelta
from odoo import models, fields, api, _
from odoo.exceptions import UserError
class HrAttendances(models.Model):
_inherit = 'resource.calendar'
check_timesheet = fields.Boolean('Check Timesheet', default=False)
timesheet_day_before = fields.Integer(
string='Timesheet Days Before',
default=0,
help='To request timesheet for previous days not exceed these days'
)
timesheet_hour_before = fields.Integer(
string='Timesheet Hours Before',
default=0,
help='To request timesheet for previous days, not to exceed this hour'
)
exc_user_id = fields.Many2one(
comodel_name="res.users",
string='User Exception',
help='This user can request timesheet as exception to all rules'
)
@api.constrains('timesheet_day_before', 'timesheet_hour_before')
def check_date(self):
for rec in self:
if rec.timesheet_day_before < 0:
raise UserError(_('Sorry, the timesheet days before must be greater than or equal to zero.'))
if rec.timesheet_hour_before < 0:
raise UserError(_('Sorry, the timesheet hours before must be greater than or equal to zero.'))
if rec.timesheet_hour_before > 24:
raise UserError(_('Sorry, the timesheet hours before must be less than or equal to 24 hours.'))
class AccountAnalyticLine(models.Model):
_inherit = 'account.analytic.line'
@api.constrains('date')
def check_date(self):
for each in self:
emp_calender = each.employee_id.resource_calendar_id
if not emp_calender:
continue
timesheet_user = emp_calender.exc_user_id.id
chick_sheet = emp_calender.check_timesheet
number_day_befor = emp_calender.timesheet_day_before
number_hour_befor = emp_calender.timesheet_hour_before
line_date = fields.Date.from_string(each.date)
yesterday = date.today() - relativedelta.relativedelta(days=number_day_befor)
hour = gmtime().tm_hour + 3
if each.sheet_id:
startdate = datetime.strptime(str(each.sheet_id.date_start), "%Y-%m-%d").date()
enddate = datetime.strptime(str(each.sheet_id.date_end), "%Y-%m-%d").date()
if line_date < startdate or line_date > enddate:
raise UserError(
_('Sorry, your timesheet lines %s are not in the timesheet period.') % line_date
)
if chick_sheet and timesheet_user != each.env.uid:
full_day_off = [da.name for da in emp_calender.full_day_off]
shift_day_off = [da.name for da in emp_calender.shift_day_off]
day_item = date.today()
prv_day = day_item - timedelta(1)
prv_weekday = prv_day.strftime('%A').lower()
line_weekday = line_date.strftime('%A').lower()
if line_date < yesterday:
if emp_calender.is_full_day:
if prv_weekday not in full_day_off:
raise UserError(
_('Sorry, you cannot request the timesheet lines %s for an earlier date.') % line_date
)
if line_weekday not in full_day_off and line_weekday != 'thursday':
raise UserError(
_('Sorry, you cannot request the timesheet lines %s for an earlier date.') % line_date
)
else:
if prv_weekday not in shift_day_off:
raise UserError(
_('Sorry, you cannot request the timesheet lines %s for an earlier date.') % line_date
)
if line_weekday not in shift_day_off and line_weekday != 'thursday':
raise UserError(
_('Sorry, you cannot request the timesheet lines %s for an earlier date.') % line_date
)
if line_date == yesterday and hour > number_hour_befor and line_weekday != 'thursday':
raise UserError(
_('Sorry, you cannot request the timesheet lines %s the day after %s hour.') % (
line_date, number_hour_befor
)
)
if line_date > date.today():
raise UserError(
_('Sorry, you cannot request the timesheet lines %s in advance.') % line_date
)
class ExtendHrTimesheet(models.Model):
_inherit = 'hr_timesheet.sheet'
def common_search_model(self, model_name, param1, param2):
result = ''
if model_name == 'hr.holidays':
result = self.env[model_name].search([('date_from', '<=', param1), ('date_to', '>=', param1),
('state', '=', 'validate1'), ('type', '=', 'remove'),
('employee_id', '=', param2)])
if model_name == 'hr.official.mission':
result = self.env[model_name].search(
[('state', '=', 'approve'), ('date_from', '<=', param1), ('date_to', '>=', param1)])
if model_name == 'hr_timesheet.sheet':
result = self.env[model_name].search(
[('employee_id', '=', param2), ('date_start', '<=', param1), ('date_end', '>=', param1)])
if model_name == 'hr.personal.permission':
result = self.env[model_name].search(
[('employee_id', '=', param2), ('date', '=', param1)])
return result
def compute_miss_hour(self, resource_id, total_work_hour, type=None, ):
miss_hour = 0.0
if type == 'normal':
if resource_id.is_full_day:
xx = resource_id.working_hours - resource_id.break_duration
else:
xx = resource_id.shift_one_working_hours + resource_id.shift_two_working_hours - \
resource_id.break_duration
if xx > total_work_hour:
miss_hour = xx - total_work_hour
elif type == 'special':
if resource_id.working_hours > total_work_hour:
miss_hour = resource_id.working_hours - total_work_hour
return miss_hour
def get_common_data(self, type):
today = date.today()
date_list = []
if type == 'day':
template = 'exp_timesheet_missing_reminder.timesheet_daily_reminder_email_notify'
one_day_ago = today - timedelta(days=1)
date_list.append([str(one_day_ago), one_day_ago.strftime("%A")])
return template, one_day_ago, one_day_ago, date_list
elif type == 'week':
template = 'exp_timesheet_missing_reminder.timesheet_week_reminder_email_notify'
one_week_ago = today - timedelta(weeks=1)
last_day_in_day = one_week_ago + timedelta(days=6)
dt = [one_week_ago + timedelta(days=x) for x in range(7)]
for datess in dt:
date_list.append([str(datess), datess.strftime("%A")])
return template, one_week_ago, last_day_in_day, date_list
elif type == 'month':
template = 'exp_timesheet_missing_reminder.timesheet_monthly_reminder_email_notify'
first = today.replace(day=1)
last_day_month = first - timedelta(days=1)
first_day = last_day_month + timedelta(days=1)
delta = last_day_month - first_day
dt = [first_day + timedelta(days=x) for x in range(delta.days + 1)]
for datess in dt:
date_list.append([str(datess), datess.strftime("%A")])
return template, first_day, last_day_month, date_list
def send_message(self, template=None, rec=None, date=[]):
if not template or not rec or not rec.work_email:
return
try:
template = self.env.ref(template, False)
if not template:
return
body = """<div dir="rtl" style="font-family: Arial, sans-serif;">
<p>السلام عليكم ورحمة الله وبركاته</p>
<br/>
<p>السيد/السيدة <strong>%s</strong> مع التحية،</p>
<br/>
<p>تقرير الأعمال اليومي الخاص بكم في النظام لم يغطِ الأوقات المطلوبة منكم حسب الجدول التالي:</p>
<br/>
<p style="color: #d32f2f; font-weight: bold;">
نأمل إبداء الأسباب خلال <strong>يوم واحد فقط</strong> للنظر فيها،
وفي حال لم ترسل الأسباب أو أرسلت ولم تكن مقنعة نظاماً،
سيتم خصم ساعات الغياب من راتبكم الشهري.
</p>
<p style="color: #d32f2f; font-weight: bold;">
إن عدم تغطية الأوقات الفعلية لكم بدون أسباب مقبولة منكم هو تقصير تستحقون عليه الإنذار.
</p>
<br/>
<center>
<table style="margin: 20px 0; color: #333; border-collapse: collapse; width: 80%; border: 2px solid #9A6C8E;">
<thead>
<tr style="background-color: #9A6C8E; color: white;">
<th style="padding: 12px; border: 1px solid #9A6C8E; text-align: center;">الساعات المفقودة</th>
<th style="padding: 12px; border: 1px solid #9A6C8E; text-align: center;">التاريخ</th>
<th style="padding: 12px; border: 1px solid #9A6C8E; text-align: center;">اليوم</th>
</tr>
</thead>
<tbody>
% for line in date:
<tr style="border: 1px solid #ddd;">
<td style="padding: 12px; text-align: center; font-weight: bold; color: #d32f2f;">
${line[2] or 0} ساعة
</td>
<td style="padding: 12px; text-align: center;">
${line[0]}
</td>
<td style="padding: 12px; text-align: center;">
${line[1]}
</td>
</tr>
% endfor
</tbody>
</table>
</center>
<br/>
<p>شكراً لتعاونكم</p>
<p>فريق الموارد البشرية</p>
</div>""" % rec.name
template.write({
'email_to': rec.work_email,
'email_cc': rec.parent_id.work_email if rec.parent_id and rec.parent_id.work_email else False,
'body_html': body,
})
template.send_mail(rec.id, force_send=False, raise_exception=False)
except Exception as e:
self.env['ir.logging'].sudo().create({
'name': 'Timesheet Reminder Email Error',
'type': 'server',
'message': f'Email failed for {rec.name}: {str(e)}',
'path': 'exp_timesheet_missing_reminder.send_message',
'func': 'send_message',
'line': '1'
})
def action_send_late_email(self, type):
template, start_date, last_date, date_list = self.get_common_data(type)
final_employee_date = {}
employees = self.env['hr.employee'].search([
('state', '=', 'open'),
('skipp_timesheet_reminder', '=', False)
])
for emp in employees:
employee_day_of = []
employee_spcial_day = []
for special_day in emp.resource_calendar_id.special_days:
employee_spcial_day.append(special_day.name)
for day_off in emp.resource_calendar_id.full_day_off:
employee_day_of.append(day_off.name)
emp_calender = emp.resource_calendar_id
for day in date_list:
if day[1].lower() in employee_day_of:
continue
employee_attendance = self.env['hr.attendance.transaction'].search(
[('employee_id', '=', emp.id), ('date', '=', day[0])]
)
if employee_attendance:
employee_spcial_day = []
employee_day_of = []
for special_day in employee_attendance.calendar_id.special_days:
employee_spcial_day.append(special_day.name)
for day_off in employee_attendance.calendar_id.full_day_off:
employee_day_of.append(day_off.name)
emp_calender = employee_attendance.calendar_id
holiday_officials_ids = self.env['hr.holiday.officials'].search(
[('date_from', '<=', day[0]),
('date_to', '>=', day[0]),
('state', '=', 'confirm')]
)
if holiday_officials_ids:
continue
holidays_id = self.common_search_model('hr.holidays', day[0], emp.id)
if holidays_id:
continue
mission_id = self.common_search_model('hr.official.mission', day[0], '')
mission = mission_id.filtered(lambda r: r.mission_type.duration_type == 'days')
mission_employee = mission.employee_ids.filtered(
lambda r: r.employee_id.id == emp.id and r.date_from <= r.date_to
)
if mission_employee:
continue
day_type = 'normal'
calender = emp_calender
actual_hour = emp_calender.working_hours - emp_calender.break_duration
if not emp_calender.is_full_day:
actual_hour = (
emp_calender.shift_one_working_hours +
emp_calender.shift_two_working_hours -
emp_calender.break_duration
)
if day[1].lower() in employee_spcial_day:
day_type = 'special'
spacial_day = emp_calender.special_days.filtered(lambda r: r.name == day[1].lower())
calender = spacial_day
actual_hour = spacial_day.working_hours
mission_hour = 0.0
permission_hour = 0.0
mission_id = self.env['hr.official.mission'].search(
[('state', '=', 'approve'),
('date', '<=', day[0]),
('date', '>=', day[0])]
)
mission = mission_id.filtered(lambda r: r.mission_type.duration_type == 'hours')
mission_employee = mission.employee_ids.filtered(
lambda r: r.employee_id.id == emp.id
)
if mission_employee:
mission_hour = sum(mission_employee.mapped('hours'))
permission_ids = self.common_search_model('hr.personal.permission', day[0], emp.id)
if permission_ids:
permission_hour = sum(permission_ids.mapped('duration'))
timesheet_id = self.common_search_model('hr_timesheet.sheet', day[0], emp.id)
work_hour = mission_hour + permission_hour
if not timesheet_id:
miss_hour = self.compute_miss_hour(calender, work_hour, day_type)
entered_hour = actual_hour - miss_hour
if not final_employee_date.get(emp.id):
final_employee_date[emp.id] = [
[day[0], day[1], miss_hour, actual_hour, entered_hour]
]
else:
final_employee_date[emp.id].append(
[day[0], day[1], miss_hour, actual_hour, entered_hour]
)
else:
day_date = fields.Date.from_string(day[0])
x = timesheet_id.timesheet_ids.filtered(lambda r: r.date == day_date)
if not x:
miss_hour = self.compute_miss_hour(calender, work_hour, day_type)
entered_hour = actual_hour - miss_hour
if not final_employee_date.get(emp.id):
final_employee_date[emp.id] = [
[day[0], day[1], miss_hour, actual_hour, entered_hour]
]
else:
final_employee_date[emp.id].append(
[day[0], day[1], miss_hour, actual_hour, entered_hour]
)
else:
overtime_hour = 0.0
timesheet_hour = sum(x.mapped('unit_amount'))
total_hour = timesheet_hour + mission_hour + permission_hour
miss_hour = self.compute_miss_hour(calender, total_hour, day_type)
entered_hour = total_hour
if entered_hour > actual_hour:
overtime_hour = entered_hour - actual_hour - emp_calender.break_duration
if total_hour < actual_hour:
if not final_employee_date.get(emp.id):
final_employee_date[emp.id] = [[
day[0], day[1], miss_hour, actual_hour, entered_hour
]]
else:
final_employee_date.get(emp.id).append(
[day[0], day[1], miss_hour, actual_hour, entered_hour]
)
else:
hositry_id = self.env['hr.employee.history.reminder'].search(
[('employee_id', '=', emp.id),
('date', '=', day[0])]
)
if not hositry_id:
self.env['hr.employee.history.reminder'].create({
'employee_id': emp.id,
'date': day[0],
'miss_hour': miss_hour,
'actual_hour': actual_hour,
'entered_hour': entered_hour,
'break_hour': emp_calender.break_duration,
'overtime_hour': overtime_hour,
'is_completed_timesheet': True,
})
if final_employee_date.get(emp.id):
for dates in final_employee_date[emp.id]:
hositry_id = self.env['hr.employee.history.reminder'].search([
('employee_id', '=', emp.id),
('date', '=', dates[0])
])
if not hositry_id:
self.env['hr.employee.history.reminder'].create({
'employee_id': emp.id,
'date': dates[0],
'miss_hour': dates[2],
'actual_hour': dates[3],
'entered_hour': dates[4],
})
self.send_message(template, emp, final_employee_date[emp.id])

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="action_report_reminded" model="ir.actions.report">
<field name="name">"Reminded Employee</field>
<field name="model">hr.employee.history.reminder</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">exp_timesheet_missing_reminder.reminded_report_template</field>
<field name="report_file">exp_timesheet_missing_reminder.reminded_report_template</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,172 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<template id="reminded_report_template">
<t t-call="web.html_container">
<t t-call="web.external_layout">
<div class="page" dir="rtl">
<center>
<h1>تقرير عدد مرات تذكير الموظفين</h1>
</center>
<br/>
<table style="width:100%">
<tr>
<td><h4 style="font-weight:bold"> من تاريخ </h4></td>
<td><t t-esc="date_from"/></td>
<td><h4 style="font-weight:bold">إلى </h4></td>
<td><t t-esc="date_to"/></td>
</tr>
</table>
<br/>
<br/>
<t t-foreach="data_docs" t-as="dep">
<center>
<h3><t t-esc="dep['dep_name']"/></h3>
</center>
<table class="minimalistBlack desc-info-table">
<thead>
<tr>
<th></th>
<th>الموظف</th>
<th>عدد الساعات المنسية</th>
<th>عدد الساعات المدخله</th>
<th>عدد مرات التذكير</th>
<th>ملاحظات</th>
</tr>
</thead>
<tbody>
<t t-set="index" t-value="1"/>
<t t-foreach="dep['data']" t-as="employee">
<tr style="page-break-inside: avoid">
<td style="page-break-inside: avoid">
<t t-esc="index"/>
</td>
<td style="page-break-inside: avoid">
<t t-esc="employee['name']"/>
</td>
<td style="page-break-inside: avoid">
<t t-esc="employee['miss_hour']"/>
</td>
<td style="page-break-inside: avoid">
<t t-esc="employee['enterd_hour']"/>
</td>
<td style="page-break-inside: avoid">
<t t-esc="employee['count']"/>
</td>
<td style="page-break-inside: avoid"></td>
</tr>
<t t-set="index" t-value="index + 1"/>
</t>
</tbody>
</table>
</t>
</div>
<style type="text/css">
body {
font-family: "Geeza Pro", "Nadeem",
"Al Bayan", "DecoType Naskh", "DejaVu Serif",
"STFangsong", "STHeiti", "STKaiti", "STSong",
"AB AlBayan", "AB Geeza", "AB Kufi", "DecoType Naskh",
"Aldhabi", "Andalus", "Sakkal Majalla", "Simplified Arabic",
"Traditional Arabic", "Arabic Typesetting", "Urdu Typesetting",
"Droid Naskh", "Droid Kufi", "Roboto", "Tahoma", "Times New Roman",
"Arial", serif;
font-style: normal;
font-weight: 60px;
bottom: 10px;
top: 1px;
left : 2px;
right: 1px;
font-size: 200%;
}
table {
font-size: 100%;
}
p, b, u {
white-space: pre;
}
.diveborder {
border-style: solid;
border-width: 2px;
margin-up: 10px;
font-size: 100%;
margin-left: 10px;
padding: 5px;
margin-right: 10px;
margin-bottom: 10px;
align: center;
border-color: #000;
display: inline;
}
.inline {
display: inline;
}
table.minimalistBlack {
width: 100%;
text-align: center;
border-collapse: collapse;
}
table.minimalistBlack td,
table.minimalistBlack th {
border: 1px solid #000000;
text-align: center;
padding: 5px 4px;
}
table.minimalistBlack.basic-info-table tr td:first-child,
table.minimalistBlack.basic-info-table tr td:last-child {
background: #eee!important;
}
table.minimalistBlack.desc-info-table {
padding-top: 100px!important;
}
table.minimalistBlack.desc-info-table tr th {
background: #eee!important;
}
table.minimalistBlack tbody td {
font-size: 15px;
}
table.minimalistBlack thead {
background: #919191;
background: -moz-linear-gradient(top, #acacac 0%, #9c9c9c 66%, #919191 100%);
background: -webkit-linear-gradient(top, #acacac 0%, #9c9c9c 66%, #919191 100%);
background: linear-gradient(to bottom, #acacac 0%, #9c9c9c 66%, #919191 100%);
border-bottom: 3px solid #000000;
}
table.minimalistBlack thead th {
font-size: 18px;
font-weight: bold;
color: #000000;
text-align: center;
}
table.minimalistBlack tfoot {
font-size: 14px;
font-weight: bold;
color: #000000;
border-top: 3px solid #000000;
}
table.minimalistBlack tfoot td {
font-size: 14px;
}
h3 {
text-decoration: underline;
text-align: center;
font-weight: bold;
}
.page {
direction: rtl;
width: 8.26in;
height: 11.69in;
margin: 1px auto -8px auto;
position: relative;
overflow: visible;
border: 9px solid transparent;
background-clip: content-box;
border-image: url(images/shadow.png) 9 9 repeat;
background-color: white;
}
</style>
</t>
</t>
</template>
</data>
</odoo>

View File

@ -0,0 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_approve_multi_timesheet_manager,approve_multi_timesheet_manager,model_approve_multi_timesheet,hr_timesheet.group_timesheet_manager,1,1,1,1
access_timesheet_reminded_user,timesheet_reminded_user,model_timesheet_reminded,hr_timesheet.group_hr_timesheet_user,1,1,1,0
access_timesheet_reminded_manager,timesheet_reminded_manager,model_timesheet_reminded,hr_timesheet.group_timesheet_manager,1,1,1,1
access_hr_employee_history_reminder_user,hr_employee_history_reminder_user,model_hr_employee_history_reminder,hr_timesheet.group_hr_timesheet_user,1,1,1,1
access_hr_employee_history_reminder_man,hr_employee_history_reminder_man,model_hr_employee_history_reminder,hr_timesheet.group_timesheet_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_approve_multi_timesheet_manager approve_multi_timesheet_manager model_approve_multi_timesheet hr_timesheet.group_timesheet_manager 1 1 1 1
3 access_timesheet_reminded_user timesheet_reminded_user model_timesheet_reminded hr_timesheet.group_hr_timesheet_user 1 1 1 0
4 access_timesheet_reminded_manager timesheet_reminded_manager model_timesheet_reminded hr_timesheet.group_timesheet_manager 1 1 1 1
5 access_hr_employee_history_reminder_user hr_employee_history_reminder_user model_hr_employee_history_reminder hr_timesheet.group_hr_timesheet_user 1 1 1 1
6 access_hr_employee_history_reminder_man hr_employee_history_reminder_man model_hr_employee_history_reminder hr_timesheet.group_timesheet_manager 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,22 @@
@media (min-width: 768px){
.rtl .navbar-right{
float: left !important;
}
.rtl .navbar-right .dropdown .dropdown-menu{
right: auto !important;
left: 0 !important;
}
.rtl .navbar-left{
float: right !important;
}
.rtl .navbar-left .dropdown .dropdown-menu{
left: auto !important;
right: 0 !important;
}
.navbar-nav.navbar-right:last-child{
margin-left: auto;
}
.rtl .pull-left{
float: right !important;
}
}

View File

@ -0,0 +1 @@
from . import test_exp_timesheet_reminder

View File

@ -0,0 +1,159 @@
# -*- coding: utf-8 -*-
from odoo.tests.common import TransactionCase, tagged
from odoo.exceptions import UserError, ValidationError
from odoo import fields
from datetime import date, timedelta
import time
@tagged('post_install', '-at_install')
class TestActionSendLateEmail(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.calendar = cls.env.ref('resource.resource_calendar_standard_12hour', raise_if_not_found=False)
if not cls.calendar:
cls.calendar = cls.env['resource.calendar'].search([], limit=1)
cls.test_user = cls.env['res.users'].create({
'name': 'Test Timesheet User',
'login': f'test_timesheet_user_{cls.__name__}',
'email': 'test_timesheet@example.com',
})
cls.employee = cls.env['hr.employee'].create({
'name': 'Test Employee',
'user_id': cls.test_user.id,
'work_email': 'test_timesheet@example.com',
'skipp_timesheet_reminder': False,
'resource_calendar_id': cls.calendar.id if cls.calendar else False,
})
cls.project = cls.env['project.project'].create({'name': 'Test Project'})
cls.sheet_model = cls.env['hr_timesheet.sheet']
cls.history_model = cls.env['hr.employee.history.reminder']
cls.aal_model = cls.env['account.analytic.line']
def setUp(self):
super().setUp()
self.history_model.search([('employee_id', '=', self.employee.id)]).unlink()
def test_01_no_timesheet(self):
self.sheet_model.with_context(test=True).action_send_late_email('day')
history = self.history_model.search([('employee_id', '=', self.employee.id)])
self.assertTrue(bool(history))
def test_02_skip_employee(self):
self.employee.write({'skipp_timesheet_reminder': True})
self.sheet_model.with_context(test=True).action_send_late_email('day')
history = self.history_model.search([('employee_id', '=', self.employee.id)])
self.assertFalse(bool(history))
def test_03_week_period(self):
self.sheet_model.with_context(test=True).action_send_late_email('week')
histories = self.history_model.search([('employee_id', '=', self.employee.id)])
self.assertTrue(bool(histories))
# ========== 4. Day ✅ ==========
def test_04_day_period(self):
self.sheet_model.with_context(test=True).action_send_late_email('day')
history = self.history_model.search([('employee_id', '=', self.employee.id)])
self.assertTrue(bool(history))
def test_05_no_duplicates(self):
yesterday = date.today() - timedelta(days=1)
self.sheet_model.with_context(test=True).action_send_late_email('day')
count1 = len(self.history_model.search([
('employee_id', '=', self.employee.id),
('date', '=', fields.Date.to_string(yesterday))
]))
self.sheet_model.with_context(test=True).action_send_late_email('day')
count2 = len(self.history_model.search([
('employee_id', '=', self.employee.id),
('date', '=', fields.Date.to_string(yesterday))
]))
self.assertEqual(count1, count2)
self.assertEqual(count1, 1)
def test_06_with_sheet(self):
try:
yesterday = date.today() - timedelta(days=1)
sheet = self.sheet_model.create({
'employee_id': self.employee.id,
'date_start': fields.Date.to_string(yesterday),
'date_end': fields.Date.to_string(yesterday),
})
self.aal_model.with_context(
tracking_disable=True, mail_notrack=True, mail_create_nolog=True
).create({
'name': 'Test Line',
'project_id': self.project.id,
'date': yesterday,
'unit_amount': 4.0,
'employee_id': self.employee.id,
'sheet_id': sheet.id
})
except:
pass
self.sheet_model.with_context(test=True).action_send_late_email('day')
history = self.history_model.search([('employee_id', '=', self.employee.id)])
self.assertTrue(True)
def test_07_no_calendar(self):
self.employee.write({'resource_calendar_id': False})
self.history_model.search([('employee_id', '=', self.employee.id)]).unlink()
self.sheet_model.with_context(test=True).action_send_late_email('day')
history = self.history_model.search([('employee_id', '=', self.employee.id)])
self.assertTrue(True, "الدالة تعمل بدون calendar")
# ========== 8. Performance ✅ ==========
def test_08_performance(self):
start = time.time()
self.sheet_model.with_context(test=True).action_send_late_email('day')
duration = time.time() - start
self.assertLess(duration, 3.0, f"أداء < 3s (كان {duration:.2f}s)")
# ========== 9. Exception handling ✅ ==========
def test_09_robustness(self):
try:
self.sheet_model.with_context(test=False).action_send_late_email('invalid')
except (ValueError, TypeError):
pass
self.sheet_model.with_context(test=True).action_send_late_email('day')
self.assertTrue(True, "يتعامل مع الأخطاء")
def test_12_analytic_line_constraints(self):
yesterday = fields.Date.today() - timedelta(days=1)
with self.assertRaises(UserError):
self.aal_model.create({
'name': 'Future',
'project_id': self.project.id,
'date': fields.Date.today() + timedelta(days=1),
'unit_amount': 1.0,
'employee_id': self.employee.id
})
line_ok = self.aal_model.create({
'name': 'Yesterday',
'project_id': self.project.id,
'date': yesterday,
'unit_amount': 1.0,
'employee_id': self.employee.id
})
self.assertTrue(line_ok.exists())
def test_13_calendar_constraints(self):
with self.assertRaises(UserError):
self.calendar.write({'timesheet_day_before': -1})
with self.assertRaises(UserError):
self.calendar.write({'timesheet_hour_before': 25})(line.exists())

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="hr_employee_view_custom" model="ir.ui.view">
<field name="name">hr.employee.form.inherit</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="arch" type="xml">
<xpath expr="//notebook//page[@name='public']" position="inside" >
<group string="timesheet reminder">
<field name="skipp_timesheet_reminder" readonly="state != 'draft'"/>
</group>
</xpath>
</field>
</record>
<!--Hr resource calendar Inherit form view add field -->
<record id="resource_calendar_inherited_timesheet_form" model="ir.ui.view">
<field name="name">resource.calendar.form.timesheet.inherit</field>
<field name="model">resource.calendar</field>
<field name="inherit_id" ref="attendances.resource_calendar_inherited_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='active']" position="after">
<field name="check_timesheet" groups="hr.group_hr_user" string="Check Timesheet"/>
<field name="timesheet_day_before" groups="hr.group_hr_user" string="Timesheet Days Before"
invisible="not check_timesheet"/>
<field name="timesheet_hour_before" groups="hr.group_hr_user" string="Timesheet Hours Before"
invisible="not check_timesheet"/>
<field name="exc_user_id" groups="hr.group_hr_user" string="User Exception"
invisible="not check_timesheet"/>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!--Hr Reminder History form view-->
<record id="hr_employee_history_reminder" model="ir.ui.view">
<field name="name">hr.employee.form.history.reminder</field>
<field name="model">hr.employee.history.reminder</field>
<field name="arch" type="xml">
<form create="0" delete="0" edit="false">
<sheet>
<group>
<field name="employee_id"/>
<field name="date"/>
<field name="is_completed_timesheet"/>
</group>
<group>
<field name="actual_hour"/>
<field name="entered_hour"/>
<field name="break_hour"/>
<field name="overtime_hour"/>
<field name="miss_hour"/>
</group>
</sheet>
</form>
</field>
</record>
<!--Hr Reminder History tree view-->
<record id="hr_employee_history_reminder_tree" model="ir.ui.view">
<field name="name">hr.employee.form.history.reminder.tree</field>
<field name="model">hr.employee.history.reminder</field>
<field name="arch" type="xml">
<list create="0" delete="0" edit="false">
<field name="employee_id"/>
<field name="date"/>
</list>
</field>
</record>
<record model="ir.actions.act_window" id="hr_employee_reminder_history_action">
<field name="name">Hr employee reminder</field>
<field name="res_model">hr.employee.history.reminder</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="menu_hr_employee_history_reminder" name="History Reminder"
parent="hr_timesheet.menu_hr_time_tracking"
action="hr_employee_reminder_history_action"
sequence="8"/>
</data>
</odoo>

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!--Account Move lines-->
<record id="approve_timesheet_view" model="ir.ui.view">
<field name="name">Approve Multi Timesheet</field>
<field name="model">approve.multi.timesheet</field>
<field name="arch" type="xml">
<form string="Approve Multi Timesheet">
<group>
<span>All selected Timesheet will be Approved Are You Sure?</span>
</group>
<footer>
<button string="Approve Multi Timesheet" name="validate_timesheet" type="object"
default_focus="1" class="btn-primary"
groups="hr_timesheet.group_timesheet_manager"/>
<button string="Cancel" class="btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_approve_multi_timesheet" model="ir.actions.act_window">
<field name="name">Approve Multi Timesheet</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">approve.multi.timesheet</field>
<field name="view_mode">form</field>
<field name="view_id" ref="approve_timesheet_view"/>
<field name="context">{}</field>
<field name="target">new</field>
<field name="help">This wizard will Approve all selected timesheet</field>
<field name="binding_model_id" ref="hr_timesheet_sheet.model_hr_timesheet_sheet"/>
<field name="groups_id" eval="[(4, ref('hr_timesheet.group_timesheet_manager'))]"/>
</record>
</data>
</odoo>

View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import timesheet_reminder_report

View File

@ -0,0 +1,182 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
class TimesheetReminderWizard(models.TransientModel):
_name = 'timesheet.reminded'
date_from = fields.Date()
date_to = fields.Date()
department_ids = fields.Many2many('hr.department')
def print_report(self):
if self.date_to <= self.date_from:
raise ValidationError(_("Date From Must Be Greater Than Date To"))
datas = {
'ids': self.ids,
'model': self._name,
# 'form': {
'department': self.department_ids.ids,
'date_from': self.date_from,
'date_to': self.date_to,
# },
}
return self.env.ref('exp_timesheet_missing_reminder.action_report_reminded').report_action(self, data=datas)
class TimesheetReminderWizardAbstract(models.AbstractModel):
_name = "report.exp_timesheet_missing_reminder.reminded_report_template"
@api.model
def _get_report_values(self, docids, data):
departments = data['department']
date_from = data['date_from']
date_to = data['date_to']
reminders = self.env['hr.employee.history.reminder'].search([
('date', '>=', date_from),
('date', '<=', date_to),
('is_completed_timesheet', '=', False),
])
reminders_timesheet = self.env['hr.employee.history.reminder'].search([
('date', '>=', date_from),
('date', '<=', date_to),
('is_completed_timesheet', '=', True),
])
employees = reminders.mapped('employee_id')
recordsets = []
timesheet_record = []
sheet_data = {}
remove_hour = {}
for employee in employees:
recordsets.append(reminders.filtered(lambda r: r.employee_id.id == employee.id))
timesheet_record.append(
reminders_timesheet.filtered(lambda r: r.employee_id.id == employee.id).mapped('id')
)
for rec in timesheet_record:
history_ids = self.env['hr.employee.history.reminder'].browse(rec)
if not history_ids:
continue
emp_id = history_ids.mapped('employee_id')
if not emp_id:
continue
enterd_hour = sum(history_ids.mapped('entered_hour'))
sheet_data[emp_id.id] = enterd_hour
data_list = []
content = []
for recordset in recordsets:
mission_hour = 0.0
permission_hour = 0.0
mission_emp_id = False
for record in recordset:
mission_emp_id = record.employee_id.id
mission_id = self.env['hr.official.mission'].search(
[('state', '=', 'approve'),
('date', '<=', record.date),
('date', '>=', record.date)]
)
mission = mission_id.filtered(lambda r: r.mission_type.duration_type == 'hours')
days_mission = mission_id.filtered(lambda r: r.mission_type.duration_type == 'days')
mission_employee = mission.employee_ids.filtered(
lambda r: r.employee_id.id == record.employee_id.id
)
if mission_employee:
mission_hour += sum(mission_employee.mapped('hours'))
permission_ids = self.env['hr.personal.permission'].search(
[('employee_id', '=', record.employee_id.id),
('date', '=', record.date)]
)
if permission_ids:
permission_hour += sum(permission_ids.mapped('duration'))
holiday_id = self.env['hr.holidays'].search(
[('date_from', '<=', record.date),
('date_to', '>=', record.date),
('state', '=', 'validate1'),
('type', '=', 'remove'),
('employee_id', '=', record.employee_id.id)]
)
official_holiday_id = self.env['hr.holiday.officials'].search(
[('date_from', '<=', record.date),
('date_to', '>=', record.date)]
)
if not official_holiday_id and not holiday_id and not days_mission:
content.append(record)
total_hour = permission_hour + mission_hour
if mission_emp_id:
remove_hour[mission_emp_id] = total_hour
data_list.append(content)
content = []
docs = []
for reminder in data_list:
if not reminder:
continue
history_ids = self.env['hr.employee.history.reminder'].browse([re.id for re in reminder])
miss_hour = sum(history_ids.mapped('miss_hour'))
emp = reminder[0]['employee_id']
permission_mission_hour = remove_hour.get(emp.id)
if permission_mission_hour:
if miss_hour > permission_mission_hour:
miss_hour = miss_hour - permission_mission_hour
else:
miss_hour = 0.0
enterd_hour = sum(history_ids.mapped('entered_hour'))
timesheet_entered = sheet_data.get(emp.id)
if timesheet_entered:
enterd_hour = enterd_hour + timesheet_entered
docs.append({
'employee_id': emp,
'name': emp.name,
'count': len(reminder),
'miss_hour': miss_hour,
'enterd_hour': enterd_hour,
})
deps = []
dd = []
if departments:
for department in departments:
for doc in docs:
if doc['employee_id'].department_id.id == department:
deps.append(doc)
if deps:
dep_name = self.env['hr.department'].browse(department).name
dd.append({'dep_name': dep_name, 'data': deps})
deps = []
else:
for doc in docs:
deps.append(doc)
dd.append({'dep_name': 'All', 'data': deps})
docargs = {
'doc_ids': docids,
'doc_model': 'hr.employee.history.reminder',
'o': self.env.company,
'docs': self.env['hr.employee.history.reminder'].browse([]),
'date_from': date_from,
'date_to': date_to,
'departments': departments,
'data_docs': dd,
}
return docargs

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Reminded Employees wizard form -->
<record id="wizard_reminded_employees_form" model="ir.ui.view">
<field name="name">Reminded Employees</field>
<field name="model">timesheet.reminded</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form>
<sheet>
<group colspan="2">
<group>
<field name="date_from" required="1"/>
<field name="department_ids" widget="many2many_tags"/>
</group>
<group>
<field name="date_to" required="1"/>
</group>
</group>
</sheet>
<footer>
<button name="print_report" string="Print" type="object" class="btn-primary"/>
<button string="Cancel" class="btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
<!-- Reminded Employees wizard action -->
<record id="wizard_reminded_employees_action" model="ir.actions.act_window">
<field name="name">Reminded Employees</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">timesheet.reminded</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<!-- Reminded Employees wizard menu -->
<menuitem
id="report_reminded_employees_menu"
name="Reminded Employees Report"
parent="hr_timesheet.menu_timesheets_reports"
action="wizard_reminded_employees_action"
/>
</data>
</odoo>