ADD hr_attendance_excel_report module

This commit is contained in:
younes 2025-09-03 14:37:44 +01:00
parent d86b96d0a6
commit 2f43fdf27e
20 changed files with 3769 additions and 0 deletions

View File

@ -0,0 +1,217 @@
# HR Attendance Excel Report
## Overview
This Odoo module provides comprehensive Excel attendance reports with customizable status codes, colors, and advanced filtering options. It integrates seamlessly with the existing HR attendance system to generate professional reports that match the format shown in the provided sample.
## Features
### 📊 **Report Generation**
- **Excel Output**: Professional Excel reports with cell formatting and colors
- **Configurable Layouts**: Choose which columns to include/exclude
- **Date Range Selection**: Flexible date filtering with quick month selections
- **Employee Filtering**: Filter by departments, branches, or specific employees
- **Summary Columns**: Automatic calculation of attendance totals, leaves, and absences
### 🎨 **Customizable Status Codes**
- **Flexible Status System**: Configure unlimited status codes for different attendance situations
- **Color Coding**: Assign background and text colors to each status
- **Condition Mapping**: Link status codes to leave types, mission types, or custom conditions
- **Multi-language Support**: Arabic and English descriptions for each status
### 📋 **Default Status Codes**
| Code | Arabic | English | Color | Condition |
|------|--------|---------|-------|-----------|
| **PH** | عطلة رسمية | Public Holiday | Gray | `public_holiday = True` |
| **AB** | غياب | Absent | Light Red | `is_absent = True` |
| **AL** | إجازة سنوية | Annual Leave | Light Blue | Leave type match |
| **SL** | إجازة مرضية | Sick Leave | Light Orange | Sick leave types |
| **ML** | إجازة زواج | Marriage Leave | Light Purple | Marriage leave |
| **BL** | إجازة وفاة | Bereavement Leave | Light Gray | Bereavement leave |
| **OD** | دوام ميداني | On Duty (Outside) | Light Yellow | Official mission |
| **TR** | تدريب | Training | Light Green | Training missions |
| **UL** | إجازة بدون راتب | Unpaid Leave | Light Salmon | Unpaid leave types |
| **ML/PL** | إجازة أمومة/أبوة | Maternity/Paternity | Light Beige | Maternity/Paternity leave |
### 🔧 **Advanced Configuration**
- **Multiple Configurations**: Create different report templates for different needs
- **Custom Conditions**: Write Python expressions for complex status matching
- **Column Customization**: Show/hide employee information columns
- **Working Hours Format**: Display as time ranges (8:00-3:30) or decimal hours (7.5)
## Installation
### Prerequisites
- Odoo 14.0+
- Python xlsxwriter library: `pip install xlsxwriter`
### Steps
1. Copy the module to your Odoo addons directory
2. Update the app list: Settings → Apps → Update Apps List
3. Search for "HR Attendance Excel Report"
4. Click Install
## Configuration
### 1. **Report Configuration**
Navigate to: `HR → Attendance Reports → Configuration → Report Configurations`
- Create a new configuration
- Set default colors for normal attendance
- Choose which employee information columns to include
- Configure working hours display format
### 2. **Status Configuration**
Navigate to: `HR → Attendance Reports → Configuration → Status Configurations`
For each status code:
- Set the code (e.g., "PH", "AB", "AL")
- Add Arabic and English descriptions
- Choose background and text colors
- Configure matching conditions:
- **Leave Types**: Link to specific hr.holidays.status records
- **Mission Types**: Link to hr.official.mission.type records
- **Custom Conditions**: Write Python expressions
### 3. **Custom Condition Examples**
```python
# Late arrival (more than 30 minutes)
transaction.lateness > 0.5
# Early exit
transaction.early_exit > 0
# Absent without approved leave
transaction.is_absent and not transaction.leave_id
# Working on weekend
transaction.date.weekday() >= 5 and transaction.sign_in > 0
# Overtime (more than 8 hours)
transaction.office_hours > 8.0
```
## Usage
### Generating Reports
1. Navigate to: `HR → Attendance Reports → Reports → Excel Attendance Report`
2. Select date range (quick buttons available for current/previous month)
3. Choose report configuration
4. Apply filters (employees, departments, branches)
5. Configure options:
- Group by Department
- Include Summary Columns
- Include Status Legend
6. Click "Generate Excel Report"
### Report Structure
The generated Excel file contains:
#### **Main Columns:**
- Employee Number (optional)
- Employee Name
- National ID (optional)
- Department (optional)
- Job Position (optional)
- Branch/Location (optional)
#### **Daily Columns:**
- One column per day in the selected date range
- Color-coded cells based on attendance status
- Time ranges for normal attendance (e.g., "8:00 - 3:30")
- Status codes for special situations (e.g., "PH", "AL", "AB")
#### **Summary Columns (optional):**
- Attendance Days
- Leave Days
- Emergency Leave Days
- Absence Days
- Actual Working Hours
- Additional Hours
- Late Hours
- Early Exit Hours
#### **Legend (optional):**
- Color-coded explanation of all status codes
- Arabic and English descriptions
## Technical Details
### Database Integration
The module integrates with these existing Odoo models:
- `hr.attendance.transaction` - Main attendance data
- `hr.employee` - Employee information
- `hr.holidays` - Leave requests
- `hr.holidays.status` - Leave types
- `hr.official.mission` - Official missions
- `hr.personal.permission` - Personal permissions
- `hr.department` - Departments and branches
### File Structure
```
hr_attendance_excel_report/
├── __manifest__.py
├── models/
│ ├── attendance_report_config.py
│ └── attendance_status_config.py
├── wizard/
│ └── attendance_report_wizard.py
├── views/
│ ├── attendance_report_config_views.xml
│ ├── attendance_status_config_views.xml
│ ├── attendance_report_wizard_views.xml
│ └── menu_views.xml
├── data/
│ └── default_status_config.xml
├── security/
│ ├── ir.model.access.csv
│ └── security.xml
└── static/
├── src/css/report_wizard.css
└── src/js/color_picker_widget.js
```
### Security
- **HR User**: Can generate reports
- **HR Manager**: Can configure report settings and status codes
- **Multi-company**: Automatic filtering by company
## Troubleshooting
### Common Issues
**1. xlsxwriter not found**
```bash
pip install xlsxwriter
# Restart Odoo server
```
**2. No employees found**
- Check employee filters (department, branch, employee selection)
- Ensure employees are active
- Verify company assignment
**3. Empty cells in report**
- Check if attendance transactions exist for the date range
- Verify status configuration conditions
- Review employee working calendar
**4. Status codes not appearing**
- Ensure status configurations are active
- Check condition type and mapping
- Verify sequence order (lower numbers have priority)
### Performance Considerations
- Limit date ranges to 3 months maximum
- Use employee/department filters for large organizations
- The module is optimized for up to 500 employees per report
## Support
For issues, bugs, or feature requests, please contact the development team or create an issue in the project repository.
## License
LGPL-3
---
**Note**: This module requires the base `hr`, `hr_holidays`, and attendance-related modules to be installed and properly configured.

View File

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

View File

@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
{
'name': 'HR Attendance Excel Report with Smart Filtering',
'version': '14.0.4.0.0',
'category': 'Human Resources',
'summary': 'Dynamic Excel attendance reports with configurable status mappings and enhanced flexibility',
'description': """
HR Attendance Excel Report - Enhanced with Smart Filtering
=========================================================
This module provides comprehensive Excel reports for employee attendance with advanced smart filtering capabilities:
🔥 KEY FEATURES:
===============
- **Dynamic Status Mapping**: Link attendance statuses with leave types, permission types, and mission types
- **Smart Filtering**: Transaction-based filtering with intelligent shift detection
- **Advanced Excel Reports**: RTL support with 11 comprehensive summary columns
- **Interactive UI**: Simplified wizard with clickable statistics icons
- **Multi-language Support**: Complete Arabic and English translations
🎯 CORE FUNCTIONALITY:
=====================
- Uses hr.attendance.transaction for accurate data filtering
- Dynamic calendar analysis for shift-based employee selection
- Configurable status codes with custom colors and conditions
- Real-time statistics and preview functions
- Enhanced validation and error handling
📊 EXCEL FEATURES:
=================
- 11 summary columns (attendance, leaves, absences, permissions, etc.)
- Color-coded cells based on attendance status
- Official working hours display with proper RTL formatting
- Smart time display (sign-out first, then sign-in for Arabic)
- Company and shift information in headers
🔧 TECHNICAL:
============
- No hard-coded patterns - fully dynamic configuration
- Priority system for status determination
- Clean architecture with proper error handling
- Integration with HR modules (holidays, permissions, missions)
Supported status types: Leave, Permission, Mission, Absent, Holiday, Attendance, Late/Early Exit
""",
'author': 'AI Assistant - Enhanced',
'website': '',
'depends': [
'hr_base_reports',
'hr_holidays_public',
],
'external_dependencies': {
'python': ['xlsxwriter'],
},
'data': [
'security/security.xml',
'security/ir.model.access.csv',
'data/default_status_config.xml',
'views/attendance_report_config_views.xml',
'wizard/attendance_report_wizard_views.xml',
'views/menu_views.xml',
],
'assets': {
'web.assets_backend': [
'hr_attendance_excel_report/static/src/css/report_wizard.css',
'hr_attendance_excel_report/static/src/js/color_picker_widget.js',
],
},
'installable': True,
'auto_install': False,
'application': False,
'license': 'LGPL-3',
}

View File

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- FIXED: Default Attendance Report Configuration - حذف الحقول المحذوفة -->
<record id="default_attendance_config" model="hr.attendance.report.config">
<field name="name">الإعداد الافتراضي</field>
<!-- REMOVED: description field - deleted from model -->
<field name="active">True</field>
<!-- REMOVED: default_attendance_bg_color - deleted from model -->
<!-- REMOVED: default_attendance_text_color - deleted from model -->
<field name="include_employee_number">True</field>
<field name="include_national_id">True</field>
<field name="include_department">True</field>
<field name="include_job_position">True</field>
<field name="include_branch">True</field>
<field name="include_summary_columns">True</field>
<!-- REMOVED: working_hours_format - deleted from model (fixed to 'time_format' in code) -->
</record>
<!-- CORE STATUS CONFIGURATIONS - الحالات الأساسية بدون ربط محدد -->
<!-- 1. Absent - أولوية عالية -->
<record id="status_absent" model="hr.attendance.status.config">
<field name="config_id" ref="default_attendance_config"/>
<field name="sequence">10</field>
<field name="code">AB</field>
<field name="name_ar">غياب</field>
<field name="name_en">Absent</field>
<field name="bg_color">#FFB6C1</field>
<field name="text_color">#000000</field>
<field name="condition_type">absent</field>
<field name="active">True</field>
<field name="notes">حالة الغياب - تطبق على جميع حالات الغياب</field>
</record>
<!-- 2. Normal Attendance - حضور عادي -->
<record id="status_normal_attendance" model="hr.attendance.status.config">
<field name="config_id" ref="default_attendance_config"/>
<field name="sequence">20</field>
<field name="code">AT</field>
<field name="name_ar">حضور</field>
<field name="name_en">Attendance</field>
<field name="bg_color">#D9F2D0</field>
<field name="text_color">#000000</field>
<field name="condition_type">attendance</field>
<field name="active">True</field>
<field name="notes">الحضور العادي - تطبق على جميع حالات الحضور</field>
</record>
<!-- 3. General Leave - إجازة عامة (بدون ربط محدد) -->
<record id="status_general_leave" model="hr.attendance.status.config">
<field name="config_id" ref="default_attendance_config"/>
<field name="sequence">30</field>
<field name="code">LV</field>
<field name="name_ar">إجازة</field>
<field name="name_en">Leave</field>
<field name="bg_color">#87CEEB</field>
<field name="text_color">#000000</field>
<field name="condition_type">leave</field>
<field name="active">True</field>
<field name="notes">إجازة عامة - تطبق على جميع أنواع الإجازات غير المخصصة</field>
<!-- لا ربط بأنواع محددة - سيطبق على جميع الإجازات -->
</record>
<!-- 4. General Permission - استئذان عام -->
<record id="status_general_permission" model="hr.attendance.status.config">
<field name="config_id" ref="default_attendance_config"/>
<field name="sequence">40</field>
<field name="code">PR</field>
<field name="name_ar">استئذان</field>
<field name="name_en">Permission</field>
<field name="bg_color">#E6E6FA</field>
<field name="text_color">#000000</field>
<field name="condition_type">permission</field>
<field name="active">True</field>
<field name="notes">استئذان عام - يطبق على جميع أنواع الاستئذانات غير المخصصة</field>
</record>
<!-- 5. General Mission - مهمة عامة -->
<record id="status_general_mission" model="hr.attendance.status.config">
<field name="config_id" ref="default_attendance_config"/>
<field name="sequence">50</field>
<field name="code">MS</field>
<field name="name_ar">مهمة</field>
<field name="name_en">Mission</field>
<field name="bg_color">#F0E68C</field>
<field name="text_color">#000000</field>
<field name="condition_type">official</field>
<field name="active">True</field>
<field name="notes">مهمة عامة - تطبق على جميع أنواع المهام غير المخصصة</field>
</record>
<!-- 6. Public Holiday - عطلة رسمية (fallback) -->
<record id="status_public_holiday" model="hr.attendance.status.config">
<field name="config_id" ref="default_attendance_config"/>
<field name="sequence">999</field>
<field name="code">PH</field>
<field name="name_ar">عطلة رسمية</field>
<field name="name_en">Public Holiday</field>
<field name="bg_color">#CCCCCC</field>
<field name="text_color">#000000</field>
<field name="condition_type">holiday</field>
<field name="active">True</field>
<field name="notes">عطلة رسمية - حالة احتياطية</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,382 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * hr_attendance_excel_report
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-24 12:00+0000\n"
"PO-Revision-Date: 2025-08-24 12:00+0000\n"
"Last-Translator: AI Assistant\n"
"Language-Team: Arabic\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ar\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
"X-Generator: Odoo\n"
#. module: hr_attendance_excel_report
#: model:ir.actions.act_window,name:hr_attendance_excel_report.action_attendance_report_wizard
#: model:ir.actions.act_window,name:hr_attendance_excel_report.action_attendance_report_wizard_menu
msgid "Generate Attendance Report"
msgstr "إنشاء تقرير الحضور"
#. module: hr_attendance_excel_report
#: model:ir.ui.menu,name:hr_attendance_excel_report.menu_hr_attendance_excel_report
msgid "Excel Attendance Report"
msgstr "تقرير الحضور Excel"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Attendance Report"
msgstr "تقرير الحضور"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Employee Number"
msgstr "الرقم الوظيفي"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Employee Name"
msgstr "اسم الموظف"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "National ID"
msgstr "رقم الهوية الوطنية"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Department"
msgstr "القسم"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Job Position"
msgstr "المنصب الوظيفي"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Official Working Hours"
msgstr "ساعات العمل الرسمية"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Attendance Days"
msgstr "أيام الحضور"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Total Leave Days"
msgstr "إجمالي أيام الإجازات"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Absent Days"
msgstr "أيام الغياب"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Attendance %"
msgstr "معدل الحضور %"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Permission Count"
msgstr "عدد الأذونات"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Permission Hours"
msgstr "ساعات الأذونات"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Public Holiday Days"
msgstr "أيام العطل الرسمية"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Actual Office Hours"
msgstr "ساعات العمل الفعلية"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Additional Hours"
msgstr "الساعات الإضافية"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Total Lateness"
msgstr "إجمالي التأخير"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Total Early Exit"
msgstr "إجمالي الخروج المبكر"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Company:"
msgstr "الشركة:"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Shift Type:"
msgstr "نوع الشيفت:"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Period: from %s to %s"
msgstr "الفترة: من %s إلى %s"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Generated: %s"
msgstr "تاريخ الإنشاء: %s"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Status Legend:"
msgstr "دليل رموز الحالات:"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "xlsxwriter library is not installed"
msgstr "مكتبة xlsxwriter غير مثبتة"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "No employees with %s in the selected period"
msgstr "لا يوجد موظفون بـ %s في الفترة المحددة"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "From date must be before to date"
msgstr "يجب أن يكون تاريخ البداية قبل تاريخ النهاية"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Report period cannot exceed one year"
msgstr "لا يمكن أن تزيد فترة التقرير عن سنة واحدة"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Please select employees"
msgstr "يرجى اختيار الموظفين"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Please select departments"
msgstr "يرجى اختيار الأقسام"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Please select branches"
msgstr "يرجى اختيار الفروع"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Preview error: %s"
msgstr "خطأ في المعاينة: %s"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Transaction display error: %s"
msgstr "خطأ في عرض المعاملات: %s"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "No Department"
msgstr "بدون قسم"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_report_wizard__company_id
msgid "Company"
msgstr "الشركة"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_report_wizard__config_id
msgid "Report Configuration"
msgstr "إعدادات التقرير"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_report_wizard__date_from
msgid "From Date"
msgstr "من تاريخ"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_report_wizard__date_to
msgid "To Date"
msgstr "إلى تاريخ"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_report_wizard__group_by_department
msgid "Group by Department"
msgstr "تجميع حسب القسم"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_report_wizard__include_summary_columns
msgid "Include Summary Columns"
msgstr "تضمين أعمدة الملخص"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_report_wizard__include_legend
msgid "Include Status Legend"
msgstr "تضمين دليل الرموز"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_report_wizard__include_official_hours
msgid "Include Official Working Hours"
msgstr "تضمين ساعات العمل الرسمية"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_report_config__name
msgid "Configuration Name"
msgstr "اسم الإعداد"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_report_config__description
msgid "Description"
msgstr "الوصف"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_report_config__active
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_status_config__active
msgid "Active"
msgstr "نشط"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_report_config__include_employee_number
msgid "Include Employee Number"
msgstr "تضمين الرقم الوظيفي"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_report_config__include_national_id
msgid "Include National ID"
msgstr "تضمين رقم الهوية"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_report_config__include_department
msgid "Include Department"
msgstr "تضمين القسم"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_report_config__include_job_position
msgid "Include Job Position"
msgstr "تضمين المنصب الوظيفي"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_status_config__name_ar
msgid "Arabic Name"
msgstr "الاسم العربي"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_status_config__name_en
msgid "English Name"
msgstr "الاسم الإنجليزي"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_status_config__code
msgid "Status Code"
msgstr "رمز الحالة"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_status_config__bg_color
msgid "Background Color"
msgstr "لون الخلفية"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_status_config__text_color
msgid "Text Color"
msgstr "لون النص"
#. module: hr_attendance_excel_report
#: model:ir.model.fields,field_description:hr_attendance_excel_report.field_hr_attendance_status_config__sequence
msgid "Sequence"
msgstr "التسلسل"
#. module: hr_attendance_excel_report
#: model:ir.model,name:hr_attendance_excel_report.model_hr_attendance_report_wizard
msgid "Attendance Report Wizard"
msgstr "معالج تقرير الحضور"
#. module: hr_attendance_excel_report
#: model:ir.model,name:hr_attendance_excel_report.model_hr_attendance_report_config
msgid "HR Attendance Report Configuration"
msgstr "إعدادات تقرير الحضور"
#. module: hr_attendance_excel_report
#: model:ir.model,name:hr_attendance_excel_report.model_hr_attendance_status_config
msgid "HR Attendance Status Configuration"
msgstr "إعدادات حالة الحضور"
#. module: hr_attendance_excel_report
#: model:ir.ui.menu,name:hr_attendance_excel_report.menu_hr_attendance_reports_config
msgid "Attendance Report Configurations"
msgstr "إعدادات تقرير الحضور"
#. module: hr_attendance_excel_report
#: model:ir.ui.menu,name:hr_attendance_excel_report.menu_hr_attendance_report_config
msgid "Report Configurations"
msgstr "إعدادات التقارير"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Employee Preview - %s"
msgstr "معاينة الموظفين - %s"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Attendance Transactions - %s"
msgstr "معاملات الحضور - %s"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Attendance Report - %s"
msgstr "تقرير الحضور - %s"
#. module: hr_attendance_excel_report
#: code:addons/hr_attendance_excel_report/wizard/attendance_report_wizard.py:0
#, python-format
msgid "Company: %s | Shift Type: %s"
msgstr "الشركة: %s | نوع الشيفت: %s"

View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
from . import attendance_report_config
from . import attendance_status_config
from . import wizard_helpers
from . import hr_employee_wizard_fields

View File

@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
class AttendanceReportConfig(models.Model):
_name = 'hr.attendance.report.config'
_description = 'Attendance Report Configuration'
_order = 'name'
name = fields.Char(string='Configuration Name', required=True)
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company, required=True)
active = fields.Boolean(string='Active', default=True)
# Status configurations
status_line_ids = fields.One2many('hr.attendance.status.config', 'config_id', string='Status Configurations')
# Column settings
include_employee_number = fields.Boolean(string='Include Employee Number', default=True)
include_national_id = fields.Boolean(string='Include National ID', default=True)
include_department = fields.Boolean(string='Include Department', default=True)
include_job_position = fields.Boolean(string='Include Job Position', default=True)
include_branch = fields.Boolean(string='Include Branch', default=True)
include_summary_columns = fields.Boolean(string='Include Summary Columns', default=True)
# Report options - moved from wizard
group_by_department = fields.Boolean(string='Group by Department', default=False)
include_legend = fields.Boolean(string='Include Status Legend', default=True)
include_official_hours = fields.Boolean(string='Include Official Working Hours', default=True)
@api.model
def create_default_config(self):
default_config = self.create({'name': 'Standard Configuration'})
# Create default status configurations
default_statuses = [
{'code': 'PH', 'name_ar': 'عطلة رسمية', 'name_en': 'Public Holiday',
'bg_color': '#CCCCCC', 'text_color': '#000000', 'condition_type': 'holiday', 'sequence': 10},
{'code': 'AB', 'name_ar': 'غياب', 'name_en': 'Absent',
'bg_color': '#FFB6C1', 'text_color': '#000000', 'condition_type': 'absent', 'sequence': 20},
{'code': 'AL', 'name_ar': 'إجازة سنوية', 'name_en': 'Annual Leave',
'bg_color': '#87CEEB', 'text_color': '#000000', 'condition_type': 'leave', 'sequence': 30},
{'code': 'SL', 'name_ar': 'إجازة مرضية', 'name_en': 'Sick Leave',
'bg_color': '#FFE4B5', 'text_color': '#000000', 'condition_type': 'leave', 'sequence': 40},
{'code': 'ML', 'name_ar': 'إجازة زواج', 'name_en': 'Marriage Leave',
'bg_color': '#DDA0DD', 'text_color': '#000000', 'condition_type': 'leave', 'sequence': 50},
{'code': 'BL', 'name_ar': 'إجازة وفاة', 'name_en': 'Bereavement Leave',
'bg_color': '#D3D3D3', 'text_color': '#000000', 'condition_type': 'leave', 'sequence': 60},
{'code': 'OD', 'name_ar': 'دوام ميداني / خارج المكتب', 'name_en': 'On Duty (Outside Work)',
'bg_color': '#F0E68C', 'text_color': '#000000', 'condition_type': 'official', 'sequence': 70},
{'code': 'TR', 'name_ar': 'تدريب أو ورشة عمل', 'name_en': 'Training',
'bg_color': '#98FB98', 'text_color': '#000000', 'condition_type': 'official', 'sequence': 80},
{'code': 'UL', 'name_ar': 'إجازة بدون راتب', 'name_en': 'Unpaid Leave',
'bg_color': '#FFA07A', 'text_color': '#000000', 'condition_type': 'leave', 'sequence': 90},
{'code': 'ML/PL', 'name_ar': 'إجازة أمومة / أبوة', 'name_en': 'Maternity / Paternity Leave',
'bg_color': '#F5DEB3', 'text_color': '#000000', 'condition_type': 'leave', 'sequence': 100},
]
for status_data in default_statuses:
status_data['config_id'] = default_config.id
self.env['hr.attendance.status.config'].create(status_data)
return default_config
def name_get(self):
result = []
for record in self:
result.append((record.id, record.name))
return result

View File

@ -0,0 +1,294 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
import logging
_logger = logging.getLogger(__name__)
try:
from odoo.tools.safe_eval import safe_eval
except ImportError:
def safe_eval(expr, context):
import ast
allowed_names = {
'__builtins__': {}, 'len': len, 'str': str, 'int': int, 'float': float,
'bool': bool, 'abs': abs, 'min': min, 'max': max,
}
allowed_names.update(context)
return eval(expr, {"__builtins__": {}}, allowed_names)
class AttendanceStatusConfig(models.Model):
_name = 'hr.attendance.status.config'
_description = 'Attendance Status Configuration'
_order = 'sequence, code'
# Core fields
config_id = fields.Many2one(
'hr.attendance.report.config', string='Report Configuration',
required=True, ondelete='cascade')
sequence = fields.Integer(string='Sequence', default=10)
code = fields.Char(string='Status Code', required=True, size=10)
name_ar = fields.Char(string='Arabic Name', required=True)
name_en = fields.Char(string='English Name', required=True)
# Display colors
bg_color = fields.Char(string='Background Color', default='#FFFFFF')
text_color = fields.Char(string='Text Color', default='#000000')
# Condition settings
condition_type = fields.Selection([
('leave', 'إجازة / Leave'),
('permission', 'استئذان / Permission'),
('official', 'مهمة / Mission'),
('absent', 'غياب / Absent'),
('holiday', 'عطلة رسمية / Public Holiday'),
('attendance', 'حضور / Attendance'),
('late', 'تأخير/انصراف مبكر / Late/Early Exit'),
], string='نوع الشرط', required=True, default='leave')
# Custom condition (Python expression)
custom_condition = fields.Text(string='Custom Condition')
active = fields.Boolean(string='Active', default=True)
notes = fields.Text(string='Notes')
# Dynamic linking fields
holiday_status_ids = fields.Many2many(
'hr.holidays.status', 'status_config_holiday_rel', 'status_id', 'holiday_id',
string='أنواع الإجازات المرتبطة', domain="[('active', '=', True)]")
permission_type_ids = fields.Many2many(
'hr.personal.permission.type', 'status_config_permission_rel',
'status_id', 'permission_type_id', string='أنواع الاستئذانات المرتبطة')
mission_type_ids = fields.Many2many(
'hr.official.mission.type', 'status_config_mission_rel',
'status_id', 'mission_type_id', string='أنواع المهام المرتبطة')
# Field visibility controls
show_holiday_fields = fields.Boolean(string='Show Holiday Fields', compute='_compute_field_visibility')
show_permission_fields = fields.Boolean(string='Show Permission Fields', compute='_compute_field_visibility')
show_mission_fields = fields.Boolean(string='Show Mission Fields', compute='_compute_field_visibility')
@api.depends('condition_type')
def _compute_field_visibility(self):
for record in self:
record.show_holiday_fields = (record.condition_type == 'leave')
record.show_permission_fields = (record.condition_type == 'permission')
record.show_mission_fields = (record.condition_type == 'official')
# Validation
@api.constrains('bg_color', 'text_color')
def _check_color_format(self):
import re
color_pattern = re.compile(r'^#[0-9A-Fa-f]{6}$')
for record in self:
if record.bg_color and not color_pattern.match(record.bg_color):
raise ValidationError(_('Background color must be in hex format (#RRGGBB)'))
if record.text_color and not color_pattern.match(record.text_color):
raise ValidationError(_('Text color must be in hex format (#RRGGBB)'))
@api.constrains('code')
def _check_unique_code(self):
for record in self:
if record.config_id:
domain = [('config_id', '=', record.config_id.id), ('code', '=', record.code), ('id', '!=', record.id)]
if self.search_count(domain) > 0:
raise ValidationError(_('Status code "%s" must be unique within the same configuration.') % record.code)
@api.constrains('custom_condition')
def _check_custom_condition(self):
for record in self:
if record.custom_condition:
try:
compile(record.custom_condition, '<string>', 'eval')
except SyntaxError as e:
raise ValidationError(_('Invalid Python expression in custom condition: %s') % str(e))
# Core functionality - Dynamic linking system without hard-coded patterns
def check_condition(self, transaction, shift_type=None, wizard=None):
self.ensure_one()
if not self.active:
return False
try:
# Basic conditions
if self.condition_type == 'holiday':
return bool(getattr(transaction, 'public_holiday', False))
elif self.condition_type == 'absent':
return bool(getattr(transaction, 'is_absent', False))
elif self.condition_type == 'attendance':
sign_in = getattr(transaction, 'sign_in', 0)
sign_out = getattr(transaction, 'sign_out', 0)
return sign_in > 0 and sign_out > 0
# Dynamic conditions
elif self.condition_type == 'leave':
return self._check_dynamic_leave_condition(transaction)
elif self.condition_type == 'permission':
return self._check_dynamic_permission_condition(transaction)
elif self.condition_type == 'official':
return self._check_dynamic_mission_condition(transaction)
elif self.condition_type == 'late':
lateness = getattr(transaction, 'lateness', 0) or 0
early_exit = getattr(transaction, 'early_exit', 0) or 0
return lateness > 0 or early_exit > 0
elif self.custom_condition:
return self._evaluate_custom_condition(transaction, shift_type, wizard)
except Exception as e:
_logger.warning(f"Error in check_condition for status {self.code}: {str(e)}")
return False
return False
def _check_dynamic_leave_condition(self, transaction):
leave_id = getattr(transaction, 'leave_id', False)
if not leave_id:
return False
holiday_status = getattr(leave_id, 'holiday_status_id', False)
if not holiday_status:
return False
if not self.holiday_status_ids:
return True
return holiday_status.id in self.holiday_status_ids.ids
def _check_dynamic_permission_condition(self, transaction):
permission_id = getattr(transaction, 'personal_permission_id', False)
if not permission_id:
return False
permission_type = getattr(permission_id, 'permission_type_id', False)
if not permission_type:
return False
if not self.permission_type_ids:
return True
return permission_type.id in self.permission_type_ids.ids
def _check_dynamic_mission_condition(self, transaction):
official_id = getattr(transaction, 'official_id', False)
is_official = getattr(transaction, 'is_official', False)
if not (official_id or is_official):
return False
if not self.mission_type_ids:
return True
if official_id:
mission_type = getattr(official_id, 'mission_type_id', False)
if mission_type:
return mission_type.id in self.mission_type_ids.ids
return False
def _evaluate_custom_condition(self, transaction, shift_type, wizard):
try:
eval_context = {
'transaction': transaction,
'employee': getattr(transaction, 'employee_id', self.env['hr.employee']),
'date': getattr(transaction, 'date', False),
'shift_type': shift_type,
'wizard': wizard,
'has_attendance': lambda: (getattr(transaction, 'sign_in', 0) > 0 and getattr(transaction, 'sign_out', 0) > 0),
'has_leave': lambda: bool(getattr(transaction, 'leave_id', False)),
'has_permission': lambda: bool(getattr(transaction, 'personal_permission_id', False)),
'is_holiday': lambda: bool(getattr(transaction, 'public_holiday', False)),
'get_leave_type': lambda: (getattr(transaction, 'holiday_name', None) and getattr(transaction.holiday_name, 'name', '') or ''),
}
return bool(safe_eval(self.custom_condition, eval_context))
except Exception as e:
_logger.warning(f"Error evaluating custom condition for {self.code}: {str(e)}")
return False
# Display value generation
def get_display_value(self, transaction):
self.ensure_one()
if (self.condition_type == 'attendance' and
getattr(transaction, 'sign_in', 0) > 0 and
getattr(transaction, 'sign_out', 0) > 0):
return self._get_attendance_display_value(transaction)
elif (self.condition_type == 'permission' and
getattr(transaction, 'personal_permission_id', False)):
return self._get_permission_display_value(transaction)
return self.code
def _get_attendance_display_value(self, transaction):
return self._format_time_range(transaction)
def _get_permission_display_value(self, transaction):
has_attendance = (getattr(transaction, 'sign_in', 0) > 0 and
getattr(transaction, 'sign_out', 0) > 0)
permission_hours = getattr(transaction, 'total_permission_hours', 0) or 0
if has_attendance:
time_str = self._format_time_range(transaction)
if permission_hours > 0:
return f"{time_str}{self.code}{permission_hours:.1f}"
else:
return f"{time_str}{self.code}"
else:
if permission_hours > 0:
return f"{self.code}{permission_hours:.1f}"
else:
return self.code
def _format_time_range(self, transaction):
sign_in = getattr(transaction, 'sign_in', 0) or 0
sign_out = getattr(transaction, 'sign_out', 0) or 0
if sign_in <= 0 or sign_out <= 0:
return ""
in_h, in_m = int(sign_in), int((sign_in % 1) * 60)
out_h, out_m = int(sign_out), int((sign_out % 1) * 60)
# RTL format: sign-out first, then sign-in
return f"{out_h:02d}:{out_m:02d}-{in_h:02d}:{in_m:02d}"
# Utility functions
def name_get(self):
result = []
for record in self:
name = f"{record.code} - {record.name_ar}"
if record.condition_type:
name += f" ({dict(record._fields['condition_type'].selection)[record.condition_type]})"
result.append((record.id, name))
return result
@api.model
def get_default_status_configs(self):
return [
{'sequence': 10, 'code': 'غ', 'name_ar': 'غياب', 'name_en': 'Absent',
'condition_type': 'absent', 'bg_color': '#FFB6C1', 'text_color': '#000000'},
{'sequence': 20, 'code': 'إس', 'name_ar': 'إجازة سنوية', 'name_en': 'Annual Leave',
'condition_type': 'leave', 'bg_color': '#87CEEB', 'text_color': '#000000'},
{'sequence': 30, 'code': 'إم', 'name_ar': 'إجازة مرضية', 'name_en': 'Sick Leave',
'condition_type': 'leave', 'bg_color': '#FFE4B5', 'text_color': '#000000'},
{'sequence': 40, 'code': 'مه', 'name_ar': 'مهمة رسمية', 'name_en': 'Official Mission',
'condition_type': 'official', 'bg_color': '#F0E68C', 'text_color': '#000000'},
{'sequence': 50, 'code': 'ع', 'name_ar': 'استئذان', 'name_en': 'Permission',
'condition_type': 'permission', 'bg_color': '#E6E6FA', 'text_color': '#000000'},
{'sequence': 60, 'code': 'ح', 'name_ar': 'حضور عادي', 'name_en': 'Normal Attendance',
'condition_type': 'attendance', 'bg_color': '#D9F2D0', 'text_color': '#000000'},
{'sequence': 999, 'code': 'عر', 'name_ar': 'عطلة رسمية', 'name_en': 'Public Holiday',
'condition_type': 'holiday', 'bg_color': '#CCCCCC', 'text_color': '#000000'},
]

View File

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models
class HrEmployeeReportFields(models.Model):
_inherit = 'hr.employee'
# Computed fields for wizard display
wizard_employee_number = fields.Char(string='Employee Number', compute='_compute_wizard_display_fields', store=False)
wizard_national_id = fields.Char(string='National ID', compute='_compute_wizard_display_fields', store=False)
@api.depends('barcode', 'identification_id')
def _compute_wizard_display_fields(self):
for employee in self:
employee_number = ''
national_id = ''
# Get employee number from transaction first
transaction = self.env['hr.attendance.transaction'].search([('employee_id', '=', employee.id)], limit=1)
if transaction and hasattr(transaction, 'employee_number') and transaction.employee_number:
employee_number = transaction.employee_number
else:
# Fallback to employee fields
if employee.barcode:
employee_number = employee.barcode
elif hasattr(employee, 'registration_number') and employee.registration_number:
employee_number = employee.registration_number
# Get national ID
if employee.identification_id:
national_id = employee.identification_id
elif hasattr(employee, 'identity_number') and employee.identity_number:
national_id = employee.identity_number
elif hasattr(employee, 'ssnid') and employee.ssnid:
national_id = employee.ssnid
employee.wizard_employee_number = employee_number
employee.wizard_national_id = national_id

View File

@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models
class AttendanceReportWizardEmployee(models.TransientModel):
_name = 'hr.attendance.report.wizard.employee'
_description = 'Attendance Report Wizard - Employee Selection'
_order = 'employee_name'
wizard_id = fields.Many2one('hr.attendance.report.wizard', string='Wizard', required=True, ondelete='cascade')
employee_id = fields.Many2one('hr.employee', string='Employee', required=True, domain="[('active', '=', True)]")
# Display fields
employee_name = fields.Char(string='Employee Name', related='employee_id.name', store=True, readonly=True)
employee_number = fields.Char(string='Employee Number', compute='_compute_employee_details', store=True)
national_id = fields.Char(string='National ID', compute='_compute_employee_details', store=True)
department_name = fields.Char(string='Department', related='employee_id.department_id.name', store=True, readonly=True)
job_title = fields.Char(string='Job Position', related='employee_id.job_id.name', store=True, readonly=True)
@api.depends('employee_id')
def _compute_employee_details(self):
for record in self:
if record.employee_id:
employee_number = ''
national_id = ''
# Get employee number from transaction first
transaction = self.env['hr.attendance.transaction'].search([('employee_id', '=', record.employee_id.id)], limit=1)
if transaction and transaction.employee_number:
employee_number = transaction.employee_number
else:
# Fallback to employee fields
if hasattr(record.employee_id, 'barcode') and record.employee_id.barcode:
employee_number = record.employee_id.barcode
elif hasattr(record.employee_id, 'employee_id') and record.employee_id.employee_id:
employee_number = record.employee_id.employee_id
elif hasattr(record.employee_id, 'registration_number') and record.employee_id.registration_number:
employee_number = record.employee_id.registration_number
# Get national ID
if hasattr(record.employee_id, 'identification_id') and record.employee_id.identification_id:
national_id = record.employee_id.identification_id
elif hasattr(record.employee_id, 'identity_number') and record.employee_id.identity_number:
national_id = record.employee_id.identity_number
elif hasattr(record.employee_id, 'ssnid') and record.employee_id.ssnid:
national_id = record.employee_id.ssnid
record.employee_number = employee_number
record.national_id = national_id
else:
record.employee_number = ''
record.national_id = ''
@api.model
def create(self, vals):
if 'wizard_id' in vals and 'employee_id' in vals:
existing = self.search([('wizard_id', '=', vals['wizard_id']), ('employee_id', '=', vals['employee_id'])])
if existing:
return existing
return super().create(vals)
class AttendanceReportWizardDepartment(models.TransientModel):
_name = 'hr.attendance.report.wizard.department'
_description = 'Attendance Report Wizard - Department Selection'
_order = 'department_name'
wizard_id = fields.Many2one('hr.attendance.report.wizard', string='Wizard', required=True, ondelete='cascade')
department_id = fields.Many2one('hr.department', string='Department', required=True)
# Display fields
department_name = fields.Char(string='Department Name', related='department_id.name', store=True, readonly=True)
manager_name = fields.Char(string='Manager', related='department_id.manager_id.name', store=True, readonly=True)
employee_count = fields.Integer(string='Employees Count', compute='_compute_employee_count', store=True)
@api.depends('department_id')
def _compute_employee_count(self):
for record in self:
if record.department_id:
count = self.env['hr.employee'].search_count([
('department_id', '=', record.department_id.id),
('active', '=', True)
])
record.employee_count = count
else:
record.employee_count = 0
@api.model
def create(self, vals):
if 'wizard_id' in vals and 'department_id' in vals:
existing = self.search([('wizard_id', '=', vals['wizard_id']), ('department_id', '=', vals['department_id'])])
if existing:
return existing
return super().create(vals)
class AttendanceReportWizardBranch(models.TransientModel):
_name = 'hr.attendance.report.wizard.branch'
_description = 'Attendance Report Wizard - Branch Selection'
_order = 'branch_name'
wizard_id = fields.Many2one('hr.attendance.report.wizard', string='Wizard', required=True, ondelete='cascade')
branch_id = fields.Many2one('hr.department', string='Branch', required=True, domain="[('is_branch', '=', True)]")
# Display fields
branch_name = fields.Char(string='Branch Name', related='branch_id.name', store=True, readonly=True)
manager_name = fields.Char(string='Branch Manager', related='branch_id.manager_id.name', store=True, readonly=True)
employee_count = fields.Integer(string='Employees Count', compute='_compute_employee_count', store=True)
@api.depends('branch_id')
def _compute_employee_count(self):
for record in self:
if record.branch_id:
# Count employees with transactions in this branch
employee_ids = self.env['hr.attendance.transaction'].search([
('is_branch', '=', record.branch_id.id)
]).mapped('employee_id.id')
# Remove duplicates and count active employees
unique_employee_ids = list(set(employee_ids))
count = self.env['hr.employee'].search_count([
('id', 'in', unique_employee_ids),
('active', '=', True)
])
record.employee_count = count
else:
record.employee_count = 0
@api.model
def create(self, vals):
if 'wizard_id' in vals and 'branch_id' in vals:
existing = self.search([('wizard_id', '=', vals['wizard_id']), ('branch_id', '=', vals['branch_id'])])
if existing:
return existing
return super().create(vals)

View File

@ -0,0 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hr_attendance_report_config_user,hr.attendance.report.config.user,model_hr_attendance_report_config,hr.group_hr_user,1,0,0,0
access_hr_attendance_report_config_manager,hr.attendance.report.config.manager,model_hr_attendance_report_config,hr.group_hr_manager,1,1,1,1
access_hr_attendance_status_config_user,hr.attendance.status.config.user,model_hr_attendance_status_config,hr.group_hr_user,1,0,0,0
access_hr_attendance_status_config_manager,hr.attendance.status.config.manager,model_hr_attendance_status_config,hr.group_hr_manager,1,1,1,1
access_hr_attendance_report_wizard_user,hr.attendance.report.wizard.user,model_hr_attendance_report_wizard,hr.group_hr_user,1,1,1,1
access_hr_attendance_report_wizard_manager,hr.attendance.report.wizard.manager,model_hr_attendance_report_wizard,hr.group_hr_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_hr_attendance_report_config_user hr.attendance.report.config.user model_hr_attendance_report_config hr.group_hr_user 1 0 0 0
3 access_hr_attendance_report_config_manager hr.attendance.report.config.manager model_hr_attendance_report_config hr.group_hr_manager 1 1 1 1
4 access_hr_attendance_status_config_user hr.attendance.status.config.user model_hr_attendance_status_config hr.group_hr_user 1 0 0 0
5 access_hr_attendance_status_config_manager hr.attendance.status.config.manager model_hr_attendance_status_config hr.group_hr_manager 1 1 1 1
6 access_hr_attendance_report_wizard_user hr.attendance.report.wizard.user model_hr_attendance_report_wizard hr.group_hr_user 1 1 1 1
7 access_hr_attendance_report_wizard_manager hr.attendance.report.wizard.manager model_hr_attendance_report_wizard hr.group_hr_manager 1 1 1 1

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<!-- Security Groups -->
<record id="group_attendance_report_user" model="res.groups">
<field name="name">Attendance Report User</field>
<field name="category_id" ref="base.module_category_human_resources"/>
<field name="comment">Can view and generate attendance reports</field>
<field name="implied_ids" eval="[(4, ref('hr.group_hr_user'))]"/>
</record>
<record id="group_attendance_report_manager" model="res.groups">
<field name="name">Attendance Report Manager</field>
<field name="category_id" ref="base.module_category_human_resources"/>
<field name="comment">Can configure attendance report settings</field>
<field name="implied_ids" eval="[(4, ref('group_attendance_report_user')), (4, ref('hr.group_hr_manager'))]"/>
</record>
<!-- Record Rules -->
<record id="hr_attendance_report_config_rule" model="ir.rule">
<field name="name">Attendance Report Config: multi-company</field>
<field name="model_id" ref="model_hr_attendance_report_config"/>
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
<record id="hr_attendance_status_config_rule" model="ir.rule">
<field name="name">Attendance Status Config: multi-company</field>
<field name="model_id" ref="model_hr_attendance_status_config"/>
<field name="domain_force">[('config_id.company_id', 'in', company_ids)]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
</data>
</odoo>

View File

@ -0,0 +1,394 @@
/* HR Attendance Excel Report Styles */
.attendance_status_preview {
display: inline-block;
padding: 8px 16px;
border: 2px solid #ddd;
border-radius: 4px;
text-align: center;
font-weight: bold;
font-family: 'Courier New', monospace;
background: var(--bg-color, #ffffff);
color: var(--text-color, #000000);
min-width: 80px;
min-height: 40px;
line-height: 24px;
}
.legend-preview {
max-height: 300px;
overflow-y: auto;
border: 1px solid #e9ecef;
border-radius: 4px;
padding: 10px;
background-color: #f8f9fa;
}
.legend-preview .badge {
font-size: 12px;
font-weight: bold;
min-width: 40px;
padding: 4px 8px;
font-family: 'Courier New', monospace;
}
/* Color picker enhancements */
.o_field_widget[name="bg_color"] input,
.o_field_widget[name="text_color"] input {
border-radius: 4px;
border: 2px solid #ddd;
padding: 4px 8px;
}
/* Wizard form improvements */
.o_form_view .oe_title h1 {
color: #2c3e50;
margin-bottom: 20px;
font-size: 1.75rem;
}
.o_form_view .oe_title h1 i {
margin-right: 10px;
color: #1f7244;
}
/* Enhanced Group styling - Vertical Layout */
.o_group[col="1"] {
display: flex;
flex-direction: column;
gap: 15px;
}
.o_group[col="1"] > .o_group {
margin-bottom: 15px;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 15px;
background-color: #f8f9fa;
}
.o_group .o_form_label {
font-weight: 600;
color: #495057;
margin-bottom: 5px;
}
.o_horizontal_separator {
margin: 20px 0 15px 0;
border-top: 2px solid #dee2e6;
color: #6c757d;
font-weight: 600;
font-size: 1.1rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Date button improvements */
.oe_inline .btn {
margin-right: 8px;
margin-top: 8px;
font-size: 0.875rem;
padding: 8px 16px;
border-radius: 6px;
font-weight: 500;
border: 2px solid;
transition: all 0.3s ease;
}
.btn-outline-primary {
border-color: #007bff;
color: #007bff;
background-color: transparent;
}
.btn-outline-primary:hover {
background-color: #007bff;
border-color: #007bff;
color: white;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0,123,255,0.3);
}
.btn-outline-secondary {
border-color: #6c757d;
color: #6c757d;
background-color: transparent;
}
.btn-outline-secondary:hover {
background-color: #6c757d;
border-color: #6c757d;
color: white;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(108,117,125,0.3);
}
/* Notebook and Page styling */
.nav-tabs {
border-bottom: 2px solid #dee2e6;
margin-bottom: 20px;
}
.nav-tabs .nav-link {
padding: 12px 20px;
font-weight: 500;
color: #6c757d;
border: none;
border-bottom: 3px solid transparent;
transition: all 0.3s ease;
}
.nav-tabs .nav-link:hover {
border-bottom-color: #007bff;
color: #007bff;
}
.nav-tabs .nav-link.active {
background-color: transparent;
border-bottom-color: #28a745;
color: #28a745;
font-weight: 600;
}
.tab-content {
padding: 20px 0;
}
/* Many2many tags styling - Full width in notebook pages */
.tab-pane .o_field_many2manytags {
width: 100%;
}
.o_field_many2manytags .badge {
font-size: 0.875rem;
margin: 3px;
padding: 8px 12px;
border-radius: 15px;
border: none;
background-color: #007bff;
color: white;
font-weight: 500;
}
.o_field_many2manytags .o_field_widget {
min-height: 50px;
border: 2px solid #ced4da;
border-radius: 8px;
padding: 10px;
background-color: white;
transition: border-color 0.3s ease;
}
.o_field_many2manytags .o_field_widget:focus-within {
border-color: #007bff;
box-shadow: 0 0 0 0.2rem rgba(0,123,255,0.25);
}
/* Alert box improvements for pages */
.tab-pane .alert-info {
border: 1px solid #b8daff;
background-color: #d1ecf1;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
margin-top: 15px;
padding: 15px;
}
.tab-pane .alert-info i {
color: #17a2b8;
margin-right: 8px;
}
/* Main alert box for report features */
.alert-success {
border: 1px solid #c3e6cb;
background-color: #d4edda;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.alert-success h5 {
color: #155724;
margin-bottom: 15px;
font-weight: 600;
}
.alert-success h5 i {
color: #28a745;
margin-right: 8px;
}
.alert-success strong {
color: #155724;
}
.alert-success small {
color: #6c757d;
line-height: 1.4;
}
/* Table preview styling */
.table-bordered th,
.table-bordered td {
border: 1px solid #dee2e6;
padding: 8px;
text-align: center;
vertical-align: middle;
}
.table-primary th {
background-color: #b6d7ff;
font-weight: bold;
}
/* Status configuration form styling */
.o_form_view .attendance_status_preview {
margin: 10px 0;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
/* Configuration tree view improvements */
.o_list_view .o_field_widget[name="bg_color"],
.o_list_view .o_field_widget[name="text_color"] {
width: 60px;
}
/* Footer button improvements */
.modal-footer .btn-primary {
background-color: #1f7244;
border-color: #1f7244;
font-weight: 600;
padding: 12px 24px;
border-radius: 6px;
box-shadow: 0 2px 4px rgba(31,114,68,0.3);
transition: all 0.3s ease;
}
.modal-footer .btn-primary:hover {
background-color: #155a33;
border-color: #155a33;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(31,114,68,0.4);
}
.modal-footer .btn-secondary {
font-weight: 500;
padding: 12px 24px;
border-radius: 6px;
transition: all 0.3s ease;
}
/* Improved field spacing for vertical layout */
.o_group[col="1"] .o_field_widget {
margin-bottom: 10px;
width: 100%;
}
.o_group[col="1"] .o_form_label {
margin-bottom: 5px;
display: block;
}
/* Better group headers */
.o_group > tbody > tr > td.o_td_label {
font-weight: 600;
color: #2c3e50;
background-color: #f1f3f4;
padding: 8px 12px;
border-radius: 4px 4px 0 0;
border-bottom: 2px solid #dee2e6;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.legend-preview {
max-height: 200px;
}
.table-responsive {
font-size: 0.875rem;
}
.attendance_status_preview {
min-width: 60px;
padding: 6px 12px;
}
.o_form_view .oe_title h1 {
font-size: 1.5rem;
}
.alert-success .row .col-md-4 {
margin-bottom: 15px;
}
.oe_inline .btn {
margin-bottom: 8px;
font-size: 0.8rem;
padding: 6px 12px;
}
.nav-tabs .nav-link {
padding: 8px 12px;
font-size: 0.875rem;
}
}
/* Color validation feedback */
.o_field_invalid input {
border-color: #dc3545;
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
}
/* Loading state for report generation */
.o_form_button_box .btn[disabled] {
opacity: 0.6;
cursor: not-allowed;
}
/* Excel preview table */
.excel-preview-table {
font-family: 'Segoe UI', Arial, sans-serif;
font-size: 11px;
border-collapse: collapse;
}
.excel-preview-table th,
.excel-preview-table td {
border: 1px solid #c0c0c0;
padding: 2px 4px;
min-width: 60px;
text-align: center;
}
.excel-preview-table th {
background-color: #e7e6e6;
font-weight: bold;
}
/* Placeholder text styling */
.o_field_widget input::placeholder,
.o_field_widget textarea::placeholder {
color: #6c757d;
font-style: italic;
}
/* Improved focus states */
.o_field_widget input:focus,
.o_field_widget select:focus {
border-color: #007bff;
box-shadow: 0 0 0 0.2rem rgba(0,123,255,0.25);
outline: none;
}
/* Custom styling for notebook pages content */
.tab-pane .o_group {
border: none;
background: none;
padding: 0;
}
/* Better spacing in pages */
.tab-pane > .o_group {
margin-bottom: 0;
}

View File

@ -0,0 +1,216 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { Component, useState, onMounted } from "@odoo/owl";
/**
* Color Picker Widget for Attendance Report Status Configuration
*/
class ColorPickerWidget extends Component {
setup() {
this.state = useState({
color: this.props.value || '#FFFFFF'
});
onMounted(() => {
this.updatePreview();
});
}
/**
* Update color preview in status configuration
*/
updatePreview() {
const previewElement = document.querySelector('.attendance_status_preview');
if (previewElement && this.props.name) {
if (this.props.name === 'bg_color') {
previewElement.style.backgroundColor = this.state.color;
} else if (this.props.name === 'text_color') {
previewElement.style.color = this.state.color;
}
}
}
/**
* Handle color change
*/
onColorChange(event) {
const newColor = event.target.value;
this.state.color = newColor;
this.updatePreview();
// Trigger change event for Odoo
if (this.props.update) {
this.props.update(newColor);
}
}
/**
* Validate hex color format
*/
validateColor(color) {
const hexPattern = /^#[0-9A-Fa-f]{6}$/;
return hexPattern.test(color);
}
}
ColorPickerWidget.template = "hr_attendance_excel_report.ColorPickerWidget";
// Register the widget
registry.category("fields").add("attendance_color_picker", ColorPickerWidget);
/**
* Quick Date Selection Functions
*/
class AttendanceReportWizard {
/**
* Set date range to current month
*/
static setCurrentMonth() {
const today = new Date();
const firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
const lastDay = new Date(today.getFullYear(), today.getMonth() + 1, 0);
this.updateDateFields(firstDay, lastDay);
}
/**
* Set date range to previous month
*/
static setPreviousMonth() {
const today = new Date();
const firstDay = new Date(today.getFullYear(), today.getMonth() - 1, 1);
const lastDay = new Date(today.getFullYear(), today.getMonth(), 0);
this.updateDateFields(firstDay, lastDay);
}
/**
* Update date fields in the form
*/
static updateDateFields(fromDate, toDate) {
const dateFromField = document.querySelector('input[name="date_from"]');
const dateToField = document.querySelector('input[name="date_to"]');
if (dateFromField) {
dateFromField.value = fromDate.toISOString().split('T')[0];
dateFromField.dispatchEvent(new Event('change'));
}
if (dateToField) {
dateToField.value = toDate.toISOString().split('T')[0];
dateToField.dispatchEvent(new Event('change'));
}
}
/**
* Initialize report wizard enhancements
*/
static init() {
// Add event listeners for quick date buttons
document.addEventListener('DOMContentLoaded', function() {
// Current month button
const currentMonthBtn = document.querySelector('[name="action_set_current_month"]');
if (currentMonthBtn) {
currentMonthBtn.addEventListener('click', function(e) {
e.preventDefault();
AttendanceReportWizard.setCurrentMonth();
});
}
// Previous month button
const previousMonthBtn = document.querySelector('[name="action_set_previous_month"]');
if (previousMonthBtn) {
previousMonthBtn.addEventListener('click', function(e) {
e.preventDefault();
AttendanceReportWizard.setPreviousMonth();
});
}
// Add loading state to generate button
const generateBtn = document.querySelector('[name="action_generate_excel_report"]');
if (generateBtn) {
generateBtn.addEventListener('click', function() {
this.disabled = true;
this.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Generating...';
// Re-enable after 10 seconds (safety fallback)
setTimeout(() => {
this.disabled = false;
this.innerHTML = '<i class="fa fa-download"></i> Generate Excel Report';
}, 10000);
});
}
});
}
}
/**
* Status Configuration Enhancements
*/
class StatusConfigEnhancements {
/**
* Update preview when colors change
*/
static updateStatusPreview(bgColor, textColor, code) {
const preview = document.querySelector('.attendance_status_preview');
if (preview) {
preview.style.backgroundColor = bgColor || '#FFFFFF';
preview.style.color = textColor || '#000000';
if (code) {
preview.textContent = code;
}
}
}
/**
* Initialize status configuration enhancements
*/
static init() {
document.addEventListener('DOMContentLoaded', function() {
// Monitor color field changes
const bgColorField = document.querySelector('input[name="bg_color"]');
const textColorField = document.querySelector('input[name="text_color"]');
const codeField = document.querySelector('input[name="code"]');
if (bgColorField) {
bgColorField.addEventListener('change', function() {
StatusConfigEnhancements.updateStatusPreview(
this.value,
textColorField?.value,
codeField?.value
);
});
}
if (textColorField) {
textColorField.addEventListener('change', function() {
StatusConfigEnhancements.updateStatusPreview(
bgColorField?.value,
this.value,
codeField?.value
);
});
}
if (codeField) {
codeField.addEventListener('change', function() {
StatusConfigEnhancements.updateStatusPreview(
bgColorField?.value,
textColorField?.value,
this.value
);
});
}
});
}
}
// Initialize enhancements
AttendanceReportWizard.init();
StatusConfigEnhancements.init();
// Export for potential external use
export { AttendanceReportWizard, StatusConfigEnhancements, ColorPickerWidget };

View File

@ -0,0 +1,225 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Attendance Report Configuration Tree View -->>
<record id="view_attendance_report_config_tree" model="ir.ui.view">
<field name="name">hr.attendance.report.config.tree</field>
<field name="model">hr.attendance.report.config</field>
<field name="arch" type="xml">
<tree string="إعدادات تقارير الحضور">
<field name="name" string="اسم الإعداد"/>
<field name="company_id" groups="base.group_multi_company" string="الشركة"/>
<field name="active" string="نشط"/>
<field name="include_summary_columns" string="تضمين أعمدة الملخص"/>
<field name="group_by_department" string="تجميع حسب القسم"/>
</tree>
</field>
</record>
<!-- UPDATED: Attendance Report Configuration Form View - إضافة الحقول الجديدة -->
<record id="view_attendance_report_config_form" model="ir.ui.view">
<field name="name">hr.attendance.report.config.form</field>
<field name="model">hr.attendance.report.config</field>
<field name="arch" type="xml">
<form string="إعدادات تقرير الحضور">
<sheet>
<div class="oe_button_box" name="button_box">
<button name="toggle_active" type="object"
class="oe_stat_button" icon="fa-check"
attrs="{'invisible': [('active', '=', True)]}">
<div class="o_stat_info">
<span class="o_stat_text">تفعيل</span>
</div>
</button>
<button name="toggle_active" type="object"
class="oe_stat_button" icon="fa-times"
attrs="{'invisible': [('active', '=', False)]}">
<div class="o_stat_info">
<span class="o_stat_text">إلغاء تفعيل</span>
</div>
</button>
</div>
<widget name="web_ribbon" title="مؤرشف"
bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
<h1><field name="name" placeholder="اسم الإعداد" string="اسم الإعداد"/></h1>
</div>
<!-- UPDATED: تحسين تخطيط الإعدادات مع إضافة الحقول الجديدة -->
<group col="2">
<group string="الإعدادات الأساسية">
<field name="company_id" groups="base.group_multi_company" string="الشركة"/>
<field name="active" invisible="1"/>
<field name="group_by_department" string="تجميع حسب القسم"/>
<field name="include_summary_columns" string="تضمين أعمدة الملخص"/>
<field name="include_legend" string="تضمين وصف الرموز"/>
<field name="include_official_hours" string="تضمين أوقات الدوام الرسمية"/>
</group>
<group string="أعمدة معلومات الموظفين">
<field name="include_employee_number" string="تضمين رقم الموظف"/>
<field name="include_national_id" string="تضمين رقم الهوية"/>
<field name="include_department" string="تضمين القسم"/>
<field name="include_job_position" string="تضمين المنصب الوظيفي"/>
<field name="include_branch" string="تضمين الفرع"/>
</group>
</group>
<!-- إعادة ترتيب قسم حالات الحضور مع تحسين التخطيط -->
<separator string="إعدادات حالات الحضور" style="margin-top: 20px;"/>
<!-- نقل صندوق المعلومات إلى مكان أفضل -->
<div class="alert alert-info" role="alert" style="margin: 15px 0;">
<h4><i class="fa fa-info-circle"/> معلومات حول إعدادات الحالات</h4>
<p>هنا يمكنك إعداد رموز الحالة التي ستظهر في تقارير الحضور. كل رمز له لون وشروط محددة.</p>
<ul style="margin-bottom: 0;">
<li><strong>الرمز:</strong> الرمز المختصر الذي يظهر في Excel (مثل: PH، AB، AL)</li>
<li><strong>الأسماء:</strong> الاسم العربي والإنجليزي للحالة</li>
<li><strong>الألوان:</strong> لون الخلفية ولون النص في Excel</li>
<li><strong>نوع الشرط:</strong> متى يتم تطبيق هذه الحالة</li>
<li><strong>الربط الديناميكي:</strong> ربط الحالات مع أنواع محددة من الإجازات/الاستئذانات/المهام</li>
</ul>
</div>
<!-- حقل الليست مع تحسين التخطيط -->
<field name="status_line_ids" nolabel="1">
<tree editable="bottom" string="إعدادات حالات الحضور">
<!-- الحقول الأساسية -->
<field name="sequence" widget="handle"/>
<field name="active" string="نشط"/>
<field name="code" string="الرمز"/>
<field name="name_ar" string="الاسم العربي"/>
<field name="name_en" string="الاسم الإنجليزي"/>
<field name="condition_type" string="نوع الشرط"/>
<!-- الحقول الديناميكية - تظهر حسب نوع الشرط -->
<field name="show_holiday_fields" invisible="1"/>
<field name="show_permission_fields" invisible="1"/>
<field name="show_mission_fields" invisible="1"/>
<!-- ربط أنواع الإجازات - يظهر فقط عند اختيار Leave -->
<field name="holiday_status_ids" widget="many2many_tags"
string="أنواع الإجازات المرتبطة"
attrs="{'invisible': [('show_holiday_fields', '=', False)]}"
options="{'color_field': 'name', 'no_create': True, 'no_create_edit': True}"
placeholder="اختر أنواع الإجازات أو اتركه فارغاً للجميع"/>
<!-- ربط أنواع الاستئذانات - يظهر فقط عند اختيار Permission -->
<field name="permission_type_ids" widget="many2many_tags"
string="أنواع الاستئذانات المرتبطة"
attrs="{'invisible': [('show_permission_fields', '=', False)]}"
options="{'color_field': 'name', 'no_create': True, 'no_create_edit': True}"
placeholder="اختر أنواع الاستئذانات أو اتركه فارغاً للجميع"/>
<!-- ربط أنواع المهام - يظهر فقط عند اختيار Mission -->
<field name="mission_type_ids" widget="many2many_tags"
string="أنواع المهام المرتبطة"
attrs="{'invisible': [('show_mission_fields', '=', False)]}"
options="{'color_field': 'name', 'no_create': True, 'no_create_edit': True}"
placeholder="اختر أنواع المهام أو اتركه فارغاً للجميع"/>
<!-- الألوان -->
<field name="bg_color" widget="color" string="لون الخلفية"/>
<field name="text_color" widget="color" string="لون النص"/>
</tree>
<!-- SIMPLIFIED FORM VIEW - Basic info only -->
<form string="تفاصيل حالة الحضور">
<sheet>
<div class="oe_title">
<h1>
<field name="code" placeholder="الرمز..." readonly="1"/>
<span> - </span>
<field name="name_ar" placeholder="الاسم العربي..." readonly="1"/>
</h1>
</div>
<group>
<group string="المعلومات الأساسية">
<field name="sequence" readonly="1"/>
<field name="name_en" readonly="1"/>
<field name="condition_type" readonly="1"/>
<field name="active"/>
</group>
<group string="الألوان">
<field name="bg_color" widget="color" readonly="1"/>
<field name="text_color" widget="color" readonly="1"/>
</group>
</group>
<!-- عرض الربط الديناميكي للقراءة فقط -->
<group string="الربط الحالي" attrs="{'invisible': ['&amp;', '&amp;', ('show_holiday_fields', '=', False), ('show_permission_fields', '=', False), ('show_mission_fields', '=', False)]}">
<field name="show_holiday_fields" invisible="1"/>
<field name="show_permission_fields" invisible="1"/>
<field name="show_mission_fields" invisible="1"/>
<field name="holiday_status_ids" widget="many2many_tags" readonly="1"
attrs="{'invisible': [('show_holiday_fields', '=', False)]}"
string="أنواع الإجازات المرتبطة"/>
<field name="permission_type_ids" widget="many2many_tags" readonly="1"
attrs="{'invisible': [('show_permission_fields', '=', False)]}"
string="أنواع الاستئذانات المرتبطة"/>
<field name="mission_type_ids" widget="many2many_tags" readonly="1"
attrs="{'invisible': [('show_mission_fields', '=', False)]}"
string="أنواع المهام المرتبطة"/>
</group>
<group string="ملاحظات" attrs="{'invisible': [('notes', '=', False)]}">
<field name="notes" readonly="1"/>
</group>
<div class="alert alert-info" role="alert">
<h4><i class="fa fa-info-circle"/> للتعديل</h4>
<p>لتعديل هذه الحالة، استخدم التحرير المباشر في جدول الحالات أعلاه.</p>
</div>
</sheet>
</form>
</field>
</sheet>
</form>
</field>
</record>
<!-- Attendance Report Configuration Search View -->
<record id="view_attendance_report_config_search" model="ir.ui.view">
<field name="name">hr.attendance.report.config.search</field>
<field name="model">hr.attendance.report.config</field>
<field name="arch" type="xml">
<search string="البحث في إعدادات تقارير الحضور">
<field name="name" string="الاسم"/>
<field name="company_id" groups="base.group_multi_company" string="الشركة"/>
<filter string="نشط" name="active" domain="[('active', '=', True)]"/>
<filter string="مؤرشف" name="inactive" domain="[('active', '=', False)]"/>
<separator/>
<filter string="مع أعمدة الملخص" name="with_summary" domain="[('include_summary_columns', '=', True)]"/>
<filter string="تجميع حسب القسم" name="group_by_dept" domain="[('group_by_department', '=', True)]"/>
<group expand="0" string="تجميع حسب">
<filter string="الشركة" name="group_company" domain="[]" context="{'group_by': 'company_id'}" groups="base.group_multi_company"/>
</group>
</search>
</field>
</record>
<!-- Attendance Report Configuration Action -->
<record id="action_attendance_report_config" model="ir.actions.act_window">
<field name="name">إعدادات تقارير الحضور</field>
<field name="res_model">hr.attendance.report.config</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_active': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
إنشئ أول إعداد لتقرير الحضور!
</p>
<p>
اضبط كيفية إنشاء تقارير الحضور، بما في ذلك رموز الحالة والألوان وإعدادات الأعمدة.
</p>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,199 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Attendance Status Configuration Tree View -->
<record id="view_attendance_status_config_tree" model="ir.ui.view">
<field name="name">hr.attendance.status.config.tree</field>
<field name="model">hr.attendance.status.config</field>
<field name="arch" type="xml">
<tree string="Attendance Status Configurations" editable="bottom">
<field name="sequence" widget="handle"/>
<field name="active"/>
<field name="code"/>
<field name="name_ar"/>
<field name="name_en"/>
<field name="condition_type"/>
<field name="bg_color" widget="color"/>
<field name="text_color" widget="color"/>
<field name="config_id" invisible="1"/>
</tree>
</field>
</record>
<!-- Attendance Status Configuration Form View -->
<record id="view_attendance_status_config_form" model="ir.ui.view">
<field name="name">hr.attendance.status.config.form</field>
<field name="model">hr.attendance.status.config</field>
<field name="arch" type="xml">
<form string="Attendance Status Configuration">
<sheet>
<div class="oe_button_box" name="button_box">
<button name="toggle_active" type="object"
class="oe_stat_button" icon="fa-check"
attrs="{'invisible': [('active', '=', True)]}">
<div class="o_stat_info">
<span class="o_stat_text">Activate</span>
</div>
</button>
<button name="toggle_active" type="object"
class="oe_stat_button" icon="fa-times"
attrs="{'invisible': [('active', '=', False)]}">
<div class="o_stat_info">
<span class="o_stat_text">Deactivate</span>
</div>
</button>
</div>
<widget name="web_ribbon" title="Archived"
bg_color="bg-danger" attrs="{'invisible': [('active', '=', True)]}"/>
<div class="oe_title">
<label for="code" class="oe_edit_only"/>
<h1>
<field name="code" placeholder="Status Code (e.g., PH, AB, AL)"/>
</h1>
</div>
<group>
<group>
<field name="config_id"/>
<field name="active" invisible="1"/>
<field name="sequence"/>
<field name="condition_type"/>
</group>
<group>
<field name="bg_color" widget="color"/>
<field name="text_color" widget="color"/>
</group>
</group>
<group string="Names">
<group>
<field name="name_ar"/>
<field name="name_en"/>
</group>
</group>
<notebook>
<page string="Condition Settings" name="conditions">
<group>
<group string="Custom Condition" attrs="{'invisible': [('condition_type', '!=', 'custom')]}">
<field name="custom_condition" nolabel="1"
placeholder="Python expression (e.g., transaction.is_absent and transaction.lateness > 0)"/>
<div class="text-muted">
<p><strong>Available variables:</strong></p>
<ul>
<li><code>transaction</code>: hr.attendance.transaction record</li>
<li><code>employee</code>: hr.employee record</li>
<li><code>date</code>: date of the transaction</li>
</ul>
<p><strong>Examples:</strong></p>
<ul>
<li><code>transaction.lateness > 0.5</code> - Late more than 30 minutes</li>
<li><code>transaction.early_exit > 0</code> - Early exit</li>
<li><code>transaction.is_absent and not transaction.leave_id</code> - Absent without leave</li>
</ul>
</div>
</group>
<group string="Pre-defined Conditions" attrs="{'invisible': [('condition_type', '=', 'custom')]}">
<div class="text-muted">
<p><strong>Selected Condition Type:</strong> <field name="condition_type" readonly="1" nolabel="1"/></p>
<ul>
<li><strong>Absent:</strong> Matches when employee is marked as absent</li>
<li><strong>Leave:</strong> Matches when employee has any leave request</li>
<li><strong>Official Duty:</strong> Matches when employee has official mission</li>
<li><strong>Public Holiday:</strong> Matches when day is marked as public holiday</li>
<li><strong>Normal Attendance:</strong> Matches when employee has normal sign in/out</li>
<li><strong>Personal Permission:</strong> Matches when employee has personal permission</li>
<li><strong>Late/Early Exit:</strong> Matches when employee is late or exits early</li>
</ul>
</div>
</group>
</group>
</page>
<page string="Preview" name="preview">
<div class="row">
<div class="col-md-6">
<h4>Excel Cell Preview</h4>
<div class="attendance_status_preview"
style="width: 100px; height: 40px; border: 1px solid #ccc; display: flex; align-items: center; justify-content: center; font-weight: bold;">
<field name="code" readonly="1" style="background: transparent; border: none; text-align: center;"/>
</div>
</div>
<div class="col-md-6">
<h4>Status Information</h4>
<table class="table table-sm">
<tr>
<th>Arabic Name:</th>
<td><field name="name_ar" readonly="1"/></td>
</tr>
<tr>
<th>English Name:</th>
<td><field name="name_en" readonly="1"/></td>
</tr>
<tr>
<th>Condition Type:</th>
<td><field name="condition_type" readonly="1"/></td>
</tr>
</table>
</div>
</div>
</page>
<page string="Notes" name="notes">
<field name="notes" nolabel="1" placeholder="Additional notes about this status configuration..."/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- Attendance Status Configuration Search View -->
<record id="view_attendance_status_config_search" model="ir.ui.view">
<field name="name">hr.attendance.status.config.search</field>
<field name="model">hr.attendance.status.config</field>
<field name="arch" type="xml">
<search string="Search Attendance Status Configurations">
<field name="code"/>
<field name="name_ar"/>
<field name="name_en"/>
<field name="config_id"/>
<filter string="Active" name="active" domain="[('active', '=', True)]"/>
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
<separator/>
<filter string="Leave Types" name="leave_types" domain="[('condition_type', '=', 'leave')]"/>
<filter string="Official Duties" name="official_duties" domain="[('condition_type', '=', 'official')]"/>
<filter string="Absences" name="absences" domain="[('condition_type', '=', 'absent')]"/>
<filter string="Holidays" name="holidays" domain="[('condition_type', '=', 'holiday')]"/>
<separator/>
<group expand="0" string="Group By">
<filter string="Configuration" name="group_config" domain="[]" context="{'group_by': 'config_id'}"/>
<filter string="Condition Type" name="group_condition" domain="[]" context="{'group_by': 'condition_type'}"/>
</group>
</search>
</field>
</record>
<!-- Attendance Status Configuration Action -->
<record id="action_attendance_status_config" model="ir.actions.act_window">
<field name="name">Attendance Status Configurations</field>
<field name="res_model">hr.attendance.status.config</field>
<field name="view_mode">tree,form</field>
<field name="context">{'search_default_active': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create your first attendance status configuration!
</p>
<p>
Configure status codes that will appear in your attendance reports,
including their colors and matching conditions.
</p>
</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Generate Report Menu Item under Attendance Reports -->
<menuitem
id="menu_hr_attendance_excel_report"
name="تقرير الحضور الشهري"
parent="attendances.parent_attendance_report_employee"
action="action_attendance_report_wizard_menu"
sequence="50"
groups="hr.group_hr_user"/>
<!-- Configuration Submenu under main Attendances menu -->
<menuitem
id="menu_hr_attendance_reports_config"
name="Monthly Attendance Report Configuration"
parent="hr_attendance.menu_hr_attendance_root"
sequence="51"
groups="hr.group_hr_manager"/>
<!-- Report Configurations Menu Item - Single unified menu -->
<menuitem
id="menu_hr_attendance_report_config"
name="إعدادات تقرير الحضور الشهري"
parent="menu_hr_attendance_reports_config"
action="action_attendance_report_config"
sequence="10"
groups="hr.group_hr_manager"/>
</data>
</odoo>

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,256 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Tree view for Employee Selection -->
<record id="view_wizard_employee_tree" model="ir.ui.view">
<field name="name">wizard.employee.tree</field>
<field name="model">hr.employee</field>
<field name="arch" type="xml">
<tree string="اختيار الموظفين" multi_edit="0">
<field name="company_id" invisible="1"/>
<field name="name" string="إسم الموظف" readonly="True"/>
<field name="wizard_employee_number" string="الرقم الوظيفي" optional="show" readonly="True"/>
<field name="wizard_national_id" string="رقم الهوية" optional="show" readonly="True"/>
<field name="department_id" string="الإدارة" optional="show" readonly="True"/>
<field name="job_id" string="المسمى الوظيفي" optional="show" readonly="True"/>
<field name="company_id" string="الشركة" optional="hide" groups="base.group_multi_company" readonly="True"/>
</tree>
</field>
</record>
<!-- Tree view for Department Selection -->
<record id="view_wizard_department_tree" model="ir.ui.view">
<field name="name">wizard.department.tree</field>
<field name="model">hr.department</field>
<field name="arch" type="xml">
<tree string="اختيار الأقسام" multi_edit="0">
<field name="company_id" invisible="1"/>
<field name="name" string="اسم القسم" readonly="True"/>
<field name="parent_id" string="القسم الرئيسي" optional="show" readonly="True"/>
<field name="manager_id" string="مدير القسم" optional="show" readonly="True"/>
<field name="total_employee" string="عدد الموظفين" optional="show" readonly="True"/>
<field name="company_id" string="الشركة" optional="hide" groups="base.group_multi_company" readonly="True"/>
</tree>
</field>
</record>
<!-- Tree view for Branch Selection -->
<record id="view_wizard_branch_tree" model="ir.ui.view">
<field name="name">wizard.branch.tree</field>
<field name="model">hr.department</field>
<field name="arch" type="xml">
<tree string="اختيار الفروع" multi_edit="0">
<field name="company_id" invisible="1"/>
<field name="name" string="اسم الفرع" readonly="True"/>
<field name="parent_id" string="الفرع الرئيسي" optional="show" readonly="True"/>
<field name="manager_id" string="مدير الفرع" optional="show" readonly="True"/>
<field name="total_employee" string="عدد الموظفين" optional="show" readonly="True"/>
<field name="company_id" string="الشركة" optional="hide" groups="base.group_multi_company" readonly="True"/>
</tree>
</field>
</record>
<!-- UPDATED: Attendance Report Wizard Form View - إعادة ترتيب وحذف الأقسام المطلوبة -->
<record id="view_attendance_report_wizard_form" model="ir.ui.view">
<field name="name">hr.attendance.report.wizard.form</field>
<field name="model">hr.attendance.report.wizard</field>
<field name="arch" type="xml">
<form string="Generate Attendance Report">
<sheet>
<div class="oe_title">
<h1>
<i class="fa fa-file-excel-o" style="color: #1f7244; margin-right: 10px;"/>
إنشاء تقرير الحضور Excel
</h1>
</div>
<!-- Report Configuration Section -->
<group string="إعدادات التقرير">
<field name="company_id" groups="base.group_multi_company"/>
<field name="config_id" required="1"
domain="[('company_id', '=', company_id)]"
context="{'default_company_id': company_id}"/>
</group>
<!-- Date Range Section -->
<group string="فترة التقرير">
<field name="date_from" required="1" string="من تاريخ"/>
<field name="date_to" required="1" string="إلى تاريخ"/>
</group>
<!-- REPOSITIONED: نوع الشيفت ونوع المرشح معاً -->
<group col="2">
<group string="نوع الشيفت المطلوب">
<field name="shift_type" widget="radio" required="1"
options="{'vertical': true}" nolabel="1"/>
</group>
<group string="نوع المرشح">
<field name="filter_type" string="" widget="radio"
options="{'vertical': true}" nolabel="1"/>
</group>
</group>
<!-- Enhanced Smart Statistics Section with Clickable Icons -->
<div class="row mb-3" attrs="{'invisible': [('shift_type', '=', False)]}">
<div class="col-12">
<div class="alert" style="padding: 15px; margin-bottom: 15px; border-left: 4px solid #17a2b8; background-color: #e7f3ff;">
<h5><i class="fa fa-chart-bar text-info"/> الإحصائيات الذكية للشيفت المحدد</h5>
<div class="row text-center">
<div class="col-3">
<div class="d-flex flex-column align-items-center">
<button type="object" name="action_preview_employees" class="btn btn-link p-0" style="border: none; background: none;">
<i class="fa fa-users text-primary" style="font-size: 24px; margin-bottom: 5px; cursor: pointer;"/>
</button>
<span class="badge badge-primary" style="font-size: 16px; padding: 8px 12px;">
<field name="available_employees_count"/> موظف
</span>
<small class="text-muted mt-1">متوفر للشيفت (انقر للمعاينة)</small>
</div>
</div>
<div class="col-3">
<div class="d-flex flex-column align-items-center">
<i class="fa fa-building text-success" style="font-size: 24px; margin-bottom: 5px;"/>
<span class="badge badge-success" style="font-size: 16px; padding: 8px 12px;">
<field name="available_departments_count"/> قسم
</span>
<small class="text-muted mt-1">يضم موظفين بالشيفت</small>
</div>
</div>
<div class="col-3">
<div class="d-flex flex-column align-items-center">
<i class="fa fa-map-marker text-warning" style="font-size: 24px; margin-bottom: 5px;"/>
<span class="badge badge-warning" style="font-size: 16px; padding: 8px 12px;">
<field name="available_branches_count"/> فرع
</span>
<small class="text-muted mt-1">يضم موظفين بالشيفت</small>
</div>
</div>
<div class="col-3">
<div class="d-flex flex-column align-items-center">
<button type="object" name="action_show_transactions" class="btn btn-link p-0" style="border: none; background: none;">
<i class="fa fa-database text-info" style="font-size: 24px; margin-bottom: 5px; cursor: pointer;"/>
</button>
<span class="badge badge-info" style="font-size: 16px; padding: 8px 12px;">
<field name="valid_transactions_count"/>/<field name="total_transactions_count"/>
</span>
<small class="text-muted mt-1">سجلات صالحة/إجمالي (انقر للعرض)</small>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Simplified Status Messages -->
<div attrs="{'invisible': [('filter_warning_message', '=', False)]}">
<div class="alert alert-info" style="margin-bottom: 15px;">
<i class="fa fa-info-circle"/> <strong>حالة التقرير:</strong><br/>
<field name="filter_warning_message" nolabel="1" readonly="1"/>
</div>
</div>
<!-- REMOVED: قسم إحصائيات المرشحات السفلية تم حذفه بالكامل -->
<!-- REMOVED: قسم خيارات التقرير تم حذفه بالكامل -->
<separator string="المرشحات"/>
<!-- Conditional Filters based on filter_type -->
<div attrs="{'invisible': [('filter_type', '!=', 'employees')]}">
<separator string="اختيار الموظفين"/>
<div class="alert alert-info mb-3" role="alert">
<i class="fa fa-users"/> اختر الموظفين المطلوب تضمينهم في التقرير
</div>
<field name="employee_ids" nolabel="1"
context="{'tree_view_ref': 'hr_attendance_excel_report.view_wizard_employee_tree'}"
attrs="{'required': [('filter_type', '=', 'employees')]}">
<tree string="الموظفون المختارون" editable="false" create="false" delete="true">
<field name="company_id" invisible="1"/>
<field name="name" string="إسم الموظف" readonly="True"/>
<field name="wizard_employee_number" string="الرقم الوظيفي" optional="show" readonly="True"/>
<field name="wizard_national_id" string="رقم الهوية" optional="show" readonly="True"/>
<field name="department_id" string="الإدارة" optional="show" readonly="True"/>
<field name="job_id" string="المسمى الوظيفي" optional="show" readonly="True"/>
</tree>
</field>
</div>
<div attrs="{'invisible': [('filter_type', '!=', 'departments')]}">
<separator string="اختيار الأقسام"/>
<div class="alert alert-info mb-3" role="alert">
<i class="fa fa-building"/> اختر الأقسام المطلوب تضمينها في التقرير
</div>
<field name="department_ids" nolabel="1"
context="{'tree_view_ref': 'hr_attendance_excel_report.view_wizard_department_tree'}"
attrs="{'required': [('filter_type', '=', 'departments')]}">
<tree string="الأقسام المختارة" editable="false" create="false" delete="true">
<field name="company_id" invisible="1"/>
<field name="name" string="اسم القسم" readonly="True"/>
<field name="parent_id" string="القسم الرئيسي" optional="show" readonly="True"/>
<field name="manager_id" string="مدير القسم" optional="show" readonly="True"/>
<field name="total_employee" string="عدد الموظفين" optional="show" readonly="True"/>
</tree>
</field>
</div>
<div attrs="{'invisible': [('filter_type', '!=', 'branches')]}">
<separator string="اختيار الفروع"/>
<div class="alert alert-info mb-3" role="alert">
<i class="fa fa-map-marker"/> اختر الفروع المطلوب تضمينها في التقرير
</div>
<field name="branch_ids" nolabel="1"
domain="[('is_branch', '=', True)]"
context="{'tree_view_ref': 'hr_attendance_excel_report.view_wizard_branch_tree'}"
attrs="{'required': [('filter_type', '=', 'branches')]}">
<tree string="الفروع المختارة" editable="false" create="false" delete="true">
<field name="company_id" invisible="1"/>
<field name="name" string="اسم الفرع" readonly="True"/>
<field name="parent_id" string="الفرع الرئيسي" optional="show" readonly="True"/>
<field name="manager_id" string="مدير الفرع" optional="show" readonly="True"/>
<field name="total_employee" string="عدد الموظفين" optional="show" readonly="True"/>
</tree>
</field>
</div>
<div attrs="{'invisible': [('filter_type', '!=', 'all')]}">
<separator string="جميع الموظفين"/>
<div class="alert alert-success mb-3" role="alert">
<i class="fa fa-check-circle"/> سيتم تضمين جميع الموظفين المتوفرين للشيفت المحدد في التقرير
</div>
</div>
</sheet>
<footer>
<button name="action_generate_excel_report" type="object"
string="إنشاء تقرير Excel" class="btn-primary"
icon="fa-download"/>
<button string="إلغاء" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
<!-- Attendance Report Wizard Action -->
<record id="action_attendance_report_wizard" model="ir.actions.act_window">
<field name="name">إنشاء تقرير الحضور</field>
<field name="res_model">hr.attendance.report.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="context">{}</field>
</record>
<!-- Add wizard action to HR menu -->
<record id="action_attendance_report_wizard_menu" model="ir.actions.act_window">
<field name="name">تقرير الحضور Excel</field>
<field name="res_model">hr.attendance.report.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="context">{}</field>
</record>
</data>
</odoo>