ADD hr_attendance_excel_report module
This commit is contained in:
parent
d86b96d0a6
commit
2f43fdf27e
|
|
@ -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.
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
from . import wizard
|
||||||
|
|
@ -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',
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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'},
|
||||||
|
]
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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 };
|
||||||
|
|
@ -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': ['&', '&', ('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>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import attendance_report_wizard
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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>
|
||||||
Loading…
Reference in New Issue