diff --git a/exp_timesheet_missing_reminder/__init__.py b/exp_timesheet_missing_reminder/__init__.py new file mode 100644 index 0000000..408a600 --- /dev/null +++ b/exp_timesheet_missing_reminder/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +from . import models +from . import wizard diff --git a/exp_timesheet_missing_reminder/__manifest__.py b/exp_timesheet_missing_reminder/__manifest__.py new file mode 100644 index 0000000..220db73 --- /dev/null +++ b/exp_timesheet_missing_reminder/__manifest__.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Odex - Communications Management System. +# Copyright (C) 2019 Expert Co. Ltd. (). +# +############################################################################## +{ + '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'], + +} diff --git a/exp_timesheet_missing_reminder/data/corn_timesheet_reminder.xml b/exp_timesheet_missing_reminder/data/corn_timesheet_reminder.xml new file mode 100644 index 0000000..d399db4 --- /dev/null +++ b/exp_timesheet_missing_reminder/data/corn_timesheet_reminder.xml @@ -0,0 +1,37 @@ + + + + + + Late Email Reminder Day + True + 1 + days + + code + model.action_send_late_email('day') + + + + + Late Email Reminder Week + True + 7 + days + + code + model.action_send_late_email('week') + + + + + Late Email Reminder Month + True + 30 + days + + code + model.action_send_late_email('month') + + + diff --git a/exp_timesheet_missing_reminder/data/timesheet_email_template.xml b/exp_timesheet_missing_reminder/data/timesheet_email_template.xml new file mode 100644 index 0000000..0a4db9c --- /dev/null +++ b/exp_timesheet_missing_reminder/data/timesheet_email_template.xml @@ -0,0 +1,91 @@ + + + + + + + Daily Timesheet Reminder Message + + + ]]> + + + +

السادة الفضلاء:}

+

حفظهم الله،،

+

+ السلام عليكم ورحمة الله. + +

+

+ الرجاء تعبئة الايام المفقوده +


+

+


+

+ ${user.company_id.name} +

+ + ]]> +
+
+ + + + Weekly Timesheet Reminder Message + + + ]]> + + + +

السادة الفضلاء:}

+

حفظهم الله،،

+

+ السلام عليكم ورحمة الله. +

+

+ الرجاء تعبئة الايام المفقوده +


+

+


+

+ ${user.company_id.name} +

+ + ]]> +
+
+ + + + + Monthly Timesheet Reminder Message + + + ]]> + + + +

السادة الفضلاء:}

+

حفظهم الله،،

+

+ السلام عليكم ورحمة الله. +

+

+ الرجاء تعبئة الايام المفقوده +


+

+


+

+ ${user.company_id.name} +

+ + ]]> +
+
+
+
diff --git a/exp_timesheet_missing_reminder/i18n/ar_001.po b/exp_timesheet_missing_reminder/i18n/ar_001.po new file mode 100644 index 0000000..31cfaa1 --- /dev/null +++ b/exp_timesheet_missing_reminder/i18n/ar_001.po @@ -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 "" +"
\n" +"

السادة الفضلاء:}

\n" +"

حفظهم الله،،

\n" +"

\n" +" السلام عليكم ورحمة الله.\n" +"

\n" +"

\n" +" الرجاء تعبئة الايام المفقوده\n" +"


\n" +"

\n" +"


\n" +"

\n" +" ${user.company_id.name}\n" +"

\n" +"
\n" +" \n" +" " +msgstr "" + +#. module: exp_timesheet_missing_reminder +#: model:mail.template,body_html:exp_timesheet_missing_reminder.timesheet_week_reminder_email_notify +msgid "" +"
\n" +"

السلام عليكم ورحمة الله

\n" +"
\n" +"

السيد\\ة esam alsrmaah مع التحية

\n" +"
\n" +"

تقرير الأعمال اليومي الخاص بكم في النظام لم يغط الأوقات المطلوبة منكم حسب الجدول التالي:\n" +"\n" +"، نأمل إبداء الأسباب خلال \n" +"يوم واحد فقط للنظر فيها، وفي حال لم ترسل الأسباب أو أرسلت ولم تكن مقنعة نظاما، سيتم خصم ساعات الغياب من راتبك الشهري.\n" +"إن عدم تغطية الأوقات الفعلية لكم بدون أسباب مقبولة منكم هو تقصير تستحقون عليه الإنذار.

\n" +"
\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \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" +" \n" +" \n" +" \n" +" \n" +" \n" +" % endfor\n" +" \n" +"
الساعاتالتاريخالايام
\n" +" ${line[2]}\n" +" \n" +" ${line[0]}\n" +" \n" +" ${line[1]}\n" +"
\n" +"
\n" +" " +msgstr "" + +#. module: exp_timesheet_missing_reminder +#: model:mail.template,body_html:exp_timesheet_missing_reminder.timesheet_daily_reminder_email_notify +msgid "" +"
\n" +"

السلام عليكم ورحمة الله

\n" +"
\n" +"

السيد\\ة esam alsrmaah مع التحية

\n" +"
\n" +"

تقرير الأعمال اليومي الخاص بكم في النظام لم يغط الأوقات المطلوبة منكم حسب الجدول التالي:\n" +"\n" +"، نأمل إبداء الأسباب خلال \n" +"يوم واحد فقط للنظر فيها، وفي حال لم ترسل الأسباب أو أرسلت ولم تكن مقنعة نظاما، سيتم خصم ساعات الغياب من راتبك الشهري.\n" +"إن عدم تغطية الأوقات الفعلية لكم بدون أسباب مقبولة منكم هو تقصير تستحقون عليه الإنذار.

\n" +"
\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" % for line in [['2025-12-14', 'Sunday', 3.0, 7.0, 4.0]]:\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" % endfor\n" +" \n" +"
الساعاتالتاريخالايام
\n" +" ${line[2]}\n" +" \n" +" ${line[0]}\n" +" \n" +" ${line[1]}\n" +"
\n" +"
\n" +" " +msgstr "" + +#. module: exp_timesheet_missing_reminder +#: model_terms:ir.ui.view,arch_db:exp_timesheet_missing_reminder.approve_timesheet_view +msgid "All selected Timesheet will be Approved Are You Sure?" +msgstr "هل انت متاكد من إعتماد هذه الجداول الزمنية؟" + +#. 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 "" \ No newline at end of file diff --git a/exp_timesheet_missing_reminder/models/__init__.py b/exp_timesheet_missing_reminder/models/__init__.py new file mode 100644 index 0000000..6c7fee7 --- /dev/null +++ b/exp_timesheet_missing_reminder/models/__init__.py @@ -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 diff --git a/exp_timesheet_missing_reminder/models/approve_multi.py b/exp_timesheet_missing_reminder/models/approve_multi.py new file mode 100644 index 0000000..f9a1efe --- /dev/null +++ b/exp_timesheet_missing_reminder/models/approve_multi.py @@ -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'} + \ No newline at end of file diff --git a/exp_timesheet_missing_reminder/models/employee_history.py b/exp_timesheet_missing_reminder/models/employee_history.py new file mode 100644 index 0000000..46528b0 --- /dev/null +++ b/exp_timesheet_missing_reminder/models/employee_history.py @@ -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) diff --git a/exp_timesheet_missing_reminder/models/extend_hr_employee.py b/exp_timesheet_missing_reminder/models/extend_hr_employee.py new file mode 100644 index 0000000..31ff113 --- /dev/null +++ b/exp_timesheet_missing_reminder/models/extend_hr_employee.py @@ -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') diff --git a/exp_timesheet_missing_reminder/models/extend_hr_timesheet.py b/exp_timesheet_missing_reminder/models/extend_hr_timesheet.py new file mode 100644 index 0000000..efebb59 --- /dev/null +++ b/exp_timesheet_missing_reminder/models/extend_hr_timesheet.py @@ -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 = """
+

السلام عليكم ورحمة الله وبركاته

+
+

السيد/السيدة %s مع التحية،

+
+

تقرير الأعمال اليومي الخاص بكم في النظام لم يغطِ الأوقات المطلوبة منكم حسب الجدول التالي:

+
+

+ نأمل إبداء الأسباب خلال يوم واحد فقط للنظر فيها، + وفي حال لم ترسل الأسباب أو أرسلت ولم تكن مقنعة نظاماً، + سيتم خصم ساعات الغياب من راتبكم الشهري. +

+

+ إن عدم تغطية الأوقات الفعلية لكم بدون أسباب مقبولة منكم هو تقصير تستحقون عليه الإنذار. +

+
+
+ + + + + + + + + + % for line in date: + + + + + + % endfor + +
الساعات المفقودةالتاريخاليوم
+ ${line[2] or 0} ساعة + + ${line[0]} + + ${line[1]} +
+
+
+

شكراً لتعاونكم

+

فريق الموارد البشرية

+
""" % 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]) + diff --git a/exp_timesheet_missing_reminder/report/report_action.xml b/exp_timesheet_missing_reminder/report/report_action.xml new file mode 100644 index 0000000..afbae24 --- /dev/null +++ b/exp_timesheet_missing_reminder/report/report_action.xml @@ -0,0 +1,12 @@ + + + + + "Reminded Employee + hr.employee.history.reminder + qweb-pdf + exp_timesheet_missing_reminder.reminded_report_template + exp_timesheet_missing_reminder.reminded_report_template + + + \ No newline at end of file diff --git a/exp_timesheet_missing_reminder/report/report_template.xml b/exp_timesheet_missing_reminder/report/report_template.xml new file mode 100644 index 0000000..7dd63cd --- /dev/null +++ b/exp_timesheet_missing_reminder/report/report_template.xml @@ -0,0 +1,172 @@ + + + + + + diff --git a/exp_timesheet_missing_reminder/security/ir.model.access.csv b/exp_timesheet_missing_reminder/security/ir.model.access.csv new file mode 100644 index 0000000..f9ca983 --- /dev/null +++ b/exp_timesheet_missing_reminder/security/ir.model.access.csv @@ -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 diff --git a/exp_timesheet_missing_reminder/static/description/icon.png b/exp_timesheet_missing_reminder/static/description/icon.png new file mode 100644 index 0000000..4141f52 Binary files /dev/null and b/exp_timesheet_missing_reminder/static/description/icon.png differ diff --git a/exp_timesheet_missing_reminder/static/fonts/ae_AlMohanad.ttf b/exp_timesheet_missing_reminder/static/fonts/ae_AlMohanad.ttf new file mode 100644 index 0000000..bdd7360 Binary files /dev/null and b/exp_timesheet_missing_reminder/static/fonts/ae_AlMohanad.ttf differ diff --git a/exp_timesheet_missing_reminder/static/src/css/website_rtl.css b/exp_timesheet_missing_reminder/static/src/css/website_rtl.css new file mode 100644 index 0000000..85347c6 --- /dev/null +++ b/exp_timesheet_missing_reminder/static/src/css/website_rtl.css @@ -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; + } +} diff --git a/exp_timesheet_missing_reminder/tests/__init__.py b/exp_timesheet_missing_reminder/tests/__init__.py new file mode 100644 index 0000000..bb7c244 --- /dev/null +++ b/exp_timesheet_missing_reminder/tests/__init__.py @@ -0,0 +1 @@ +from . import test_exp_timesheet_reminder diff --git a/exp_timesheet_missing_reminder/tests/test_exp_timesheet_reminder.py b/exp_timesheet_missing_reminder/tests/test_exp_timesheet_reminder.py new file mode 100644 index 0000000..74c6d53 --- /dev/null +++ b/exp_timesheet_missing_reminder/tests/test_exp_timesheet_reminder.py @@ -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()) \ No newline at end of file diff --git a/exp_timesheet_missing_reminder/views/extend_hr_employee.xml b/exp_timesheet_missing_reminder/views/extend_hr_employee.xml new file mode 100644 index 0000000..202cafe --- /dev/null +++ b/exp_timesheet_missing_reminder/views/extend_hr_employee.xml @@ -0,0 +1,45 @@ + + + + + + + hr.employee.form.inherit + hr.employee + + + + + + + + + + + + + + + resource.calendar.form.timesheet.inherit + resource.calendar + + + + + + + + + + + + + + + + + + diff --git a/exp_timesheet_missing_reminder/views/hr_employee_reminder_history.xml b/exp_timesheet_missing_reminder/views/hr_employee_reminder_history.xml new file mode 100644 index 0000000..ef47bbb --- /dev/null +++ b/exp_timesheet_missing_reminder/views/hr_employee_reminder_history.xml @@ -0,0 +1,51 @@ + + + + + + hr.employee.form.history.reminder + hr.employee.history.reminder + +
+ + + + + + + + + + + + + + +
+
+
+ + + + hr.employee.form.history.reminder.tree + hr.employee.history.reminder + + + + + + + + + + Hr employee reminder + hr.employee.history.reminder + list,form + + + +
+
diff --git a/exp_timesheet_missing_reminder/views/wizard_approve.xml b/exp_timesheet_missing_reminder/views/wizard_approve.xml new file mode 100644 index 0000000..69eb9a6 --- /dev/null +++ b/exp_timesheet_missing_reminder/views/wizard_approve.xml @@ -0,0 +1,38 @@ + + + + + + + Approve Multi Timesheet + approve.multi.timesheet + +
+ + All selected Timesheet will be Approved Are You Sure? + +
+
+
+
+
+ + + Approve Multi Timesheet + ir.actions.act_window + approve.multi.timesheet + form + + {} + new + This wizard will Approve all selected timesheet + + + + +
+
diff --git a/exp_timesheet_missing_reminder/wizard/__init__.py b/exp_timesheet_missing_reminder/wizard/__init__.py new file mode 100644 index 0000000..27fd0d6 --- /dev/null +++ b/exp_timesheet_missing_reminder/wizard/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import timesheet_reminder_report diff --git a/exp_timesheet_missing_reminder/wizard/timesheet_reminder_report.py b/exp_timesheet_missing_reminder/wizard/timesheet_reminder_report.py new file mode 100644 index 0000000..6033fb8 --- /dev/null +++ b/exp_timesheet_missing_reminder/wizard/timesheet_reminder_report.py @@ -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 diff --git a/exp_timesheet_missing_reminder/wizard/timesheet_reminder_report.xml b/exp_timesheet_missing_reminder/wizard/timesheet_reminder_report.xml new file mode 100644 index 0000000..ed839e8 --- /dev/null +++ b/exp_timesheet_missing_reminder/wizard/timesheet_reminder_report.xml @@ -0,0 +1,46 @@ + + + + + + Reminded Employees + timesheet.reminded + form + +
+ + + + + + + + + + + +
+
+
+
+
+ + + Reminded Employees + ir.actions.act_window + timesheet.reminded + form + new + + + + +
+
\ No newline at end of file