diff --git a/addons_extensions/flutter_odoo/models/__init__.py b/addons_extensions/flutter_odoo/models/__init__.py
index d25661f9c..a70ef9e7a 100644
--- a/addons_extensions/flutter_odoo/models/__init__.py
+++ b/addons_extensions/flutter_odoo/models/__init__.py
@@ -1 +1,2 @@
-from . import hr_employee
\ No newline at end of file
+from . import hr_employee
+from . import hr_leave
\ No newline at end of file
diff --git a/addons_extensions/flutter_odoo/models/hr_leave.py b/addons_extensions/flutter_odoo/models/hr_leave.py
new file mode 100644
index 000000000..518a49b1a
--- /dev/null
+++ b/addons_extensions/flutter_odoo/models/hr_leave.py
@@ -0,0 +1,107 @@
+from odoo import api, models, _, fields
+from odoo.exceptions import ValidationError
+from odoo.tools.misc import format_date
+from datetime import datetime
+
+class HRLeave(models.Model):
+ _inherit = "hr.leave"
+
+ def flutter_check_overlap_constrain(self,employee_id, to_date, from_date):
+ if self.env.context.get('leave_skip_date_check', False):
+ return
+ date_from = datetime.fromisoformat(from_date).replace(hour=0, minute=0, second=0)
+ date_to = datetime.fromisoformat(to_date).replace(hour=23, minute=59, second=59)
+ employee_id = int(employee_id)
+ all_leaves = self.search([
+ ('date_from', '<', date_to),
+ ('date_to', '>', date_from),
+ ('employee_id', 'in', [employee_id]),
+ ('state', 'not in', ['cancel', 'refuse']),
+ ])
+ domain = [
+ ('employee_id', '=', employee_id),
+ ('date_from', '<', date_to),
+ ('date_to', '>', date_from),
+ ('state', 'not in', ['cancel', 'refuse']),
+ ]
+ conflicting_holidays = all_leaves.filtered_domain(domain)
+
+ if conflicting_holidays:
+ conflicting_holidays_list = []
+ # Do not display the name of the employee if the conflicting holidays have an employee_id.user_id equivalent to the user id
+ holidays_only_have_uid = bool(employee_id)
+ holiday_states = dict(conflicting_holidays.fields_get(allfields=['state'])['state']['selection'])
+ for conflicting_holiday in conflicting_holidays:
+ conflicting_holiday_data = {}
+ conflicting_holiday_data['employee_name'] = conflicting_holiday.employee_id.name
+ conflicting_holiday_data['date_from'] = format_date(self.env,
+ min(conflicting_holiday.mapped('date_from')))
+ conflicting_holiday_data['date_to'] = format_date(self.env,
+ min(conflicting_holiday.mapped('date_to')))
+ conflicting_holiday_data['state'] = holiday_states[conflicting_holiday.state]
+ if conflicting_holiday.employee_id.user_id.id != self.env.uid:
+ holidays_only_have_uid = False
+ if conflicting_holiday_data not in conflicting_holidays_list:
+ conflicting_holidays_list.append(conflicting_holiday_data)
+ if not conflicting_holidays_list:
+ return
+ conflicting_holidays_strings = []
+ if holidays_only_have_uid:
+ for conflicting_holiday_data in conflicting_holidays_list:
+ conflicting_holidays_string = _('from %(date_from)s to %(date_to)s - %(state)s',
+ date_from=conflicting_holiday_data['date_from'],
+ date_to=conflicting_holiday_data['date_to'],
+ state=conflicting_holiday_data['state'])
+ conflicting_holidays_strings.append(conflicting_holidays_string)
+ error = """\
+You've already booked time off which overlaps with this period:
+%s
+Attempting to double-book your time off won't magically make your vacation 2x better!
+""".join(conflicting_holidays_strings)
+ return error
+ for conflicting_holiday_data in conflicting_holidays_list:
+ conflicting_holidays_string = "\n" + _(
+ '%(employee_name)s - from %(date_from)s to %(date_to)s - %(state)s',
+ employee_name=conflicting_holiday_data['employee_name'],
+ date_from=conflicting_holiday_data['date_from'],
+ date_to=conflicting_holiday_data['date_to'],
+ state=conflicting_holiday_data['state'])
+ conflicting_holidays_strings.append(conflicting_holidays_string)
+ error = "An employee already booked time off which overlaps with this period:%s","".join(conflicting_holidays_strings)
+ return error
+
+ @api.model
+ def calculate_leave_duration(self, date_from, date_to, employee_id):
+ """
+ Calculate the number of days and hours for the given date range and employee.
+ """
+ employee = self.env['hr.employee'].browse(employee_id)
+ if not employee:
+ return {'error': 'Employee not found'}
+
+ from_date = datetime.fromisoformat(date_from).replace(hour=0, minute=0, second=0)
+ to_date = datetime.fromisoformat(date_to).replace(hour=23, minute=59, second=59)
+
+ # Define a fake leave record to use _get_durations
+ leave_values = {
+ 'employee_id': employee.id,
+ 'date_from': fields.Datetime.from_string(from_date),
+ 'date_to': fields.Datetime.from_string(to_date),
+ 'holiday_status_id': self.env['hr.leave.type'].search([], limit=1).id,
+ # Replace with appropriate leave type ID
+ 'resource_calendar_id': employee.resource_calendar_id.id,
+ }
+ leave = self.new(leave_values)
+ durations = leave._get_durations()
+ leave_id = list(durations.keys())[0]
+ days, hours = durations[leave_id]
+
+ return {
+ 'days': float(days),
+ 'hours': float(hours),
+ }
+
+ @api.model
+ def submit_leave_flutter_odoo(self,leave_request_data):
+ print(leave_request_data)
+ pass
\ No newline at end of file
diff --git a/addons_extensions/hr_attendance_extended/views/hr_attendance.xml b/addons_extensions/hr_attendance_extended/views/hr_attendance.xml
index b250affe0..0b95086cc 100644
--- a/addons_extensions/hr_attendance_extended/views/hr_attendance.xml
+++ b/addons_extensions/hr_attendance_extended/views/hr_attendance.xml
@@ -35,7 +35,7 @@
-
+
@@ -57,7 +57,7 @@
-
+
@@ -77,11 +77,42 @@
-
-
+
+
+
+
+ hr_attendance_management_view_filter_inherit
+ hr.attendance
+
+
+
+
+
+
+
+
+
+
+
+
+ hr_attendance_view_filter_inherit
+ hr.attendance
+
+
+
+
+
+
+
+
+
+
diff --git a/addons_extensions/hr_employee_appraisal/models/apprisal_period_master.py b/addons_extensions/hr_employee_appraisal/models/apprisal_period_master.py
index c943080fb..80f6648c1 100644
--- a/addons_extensions/hr_employee_appraisal/models/apprisal_period_master.py
+++ b/addons_extensions/hr_employee_appraisal/models/apprisal_period_master.py
@@ -18,7 +18,8 @@ class AppraisalPeriod(models.Model):
@api.onchange('date_to')
def onchange_activation_date(self):
for rec in self:
- rec.activation_date = rec.date_to + timedelta(days=1)
+ if rec.date_to:
+ rec.activation_date = rec.date_to + timedelta(days=1)
_defaults = {
'color_name': 'red',
diff --git a/addons_extensions/hr_employee_extended/__manifest__.py b/addons_extensions/hr_employee_extended/__manifest__.py
index dbb1c272b..aa543e864 100644
--- a/addons_extensions/hr_employee_extended/__manifest__.py
+++ b/addons_extensions/hr_employee_extended/__manifest__.py
@@ -18,10 +18,11 @@
'version': '0.1',
# any module necessary for this one to work correctly
- 'depends': ['base','hr'],
+ 'depends': ['base','hr','mail'],
# always loaded
'data': [
+ 'security/security.xml',
'security/ir.model.access.csv',
'views/hr_employee.xml',
'wizards/work_location_wizard.xml'
diff --git a/addons_extensions/hr_employee_extended/models/hr_employee.py b/addons_extensions/hr_employee_extended/models/hr_employee.py
index bcf7fa571..0476fae2e 100644
--- a/addons_extensions/hr_employee_extended/models/hr_employee.py
+++ b/addons_extensions/hr_employee_extended/models/hr_employee.py
@@ -18,6 +18,9 @@ class HrEmployeeBase(models.AbstractModel):
total_exp = fields.Char(string='Total Experience', compute='_compute_total_experience', store=True)
+ emp_type = fields.Many2one('hr.contract.type', "Employee Type", tracking=True)
+
+
@api.constrains('identification_id')
def _check_identification_id(self):
for record in self:
@@ -79,4 +82,5 @@ class HrEmployeeBase(models.AbstractModel):
# If there's no DOJ, total experience is just the previous experience
total_years = record.previous_exp // 12
total_months = record.previous_exp % 12
- record.total_exp = f"{total_years} years {total_months} months 0 days"
\ No newline at end of file
+ record.total_exp = f"{total_years} years {total_months} months 0 days"
+
diff --git a/addons_extensions/hr_employee_extended/security/security.xml b/addons_extensions/hr_employee_extended/security/security.xml
new file mode 100644
index 000000000..92b9ddb70
--- /dev/null
+++ b/addons_extensions/hr_employee_extended/security/security.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ Internal User Category
+ 17
+
+
+ External User
+
+
+
+
+ Internal User
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/addons_extensions/hr_employee_extended/views/hr_employee.xml b/addons_extensions/hr_employee_extended/views/hr_employee.xml
index f907b70e7..08ca2adec 100644
--- a/addons_extensions/hr_employee_extended/views/hr_employee.xml
+++ b/addons_extensions/hr_employee_extended/views/hr_employee.xml
@@ -6,8 +6,12 @@
hr.employee
+
+ 1
+
+
@@ -60,5 +64,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/addons_extensions/hr_timeoff_extended/models/hr_timeoff.py b/addons_extensions/hr_timeoff_extended/models/hr_timeoff.py
index 4d5150d27..b8b484d22 100644
--- a/addons_extensions/hr_timeoff_extended/models/hr_timeoff.py
+++ b/addons_extensions/hr_timeoff_extended/models/hr_timeoff.py
@@ -17,6 +17,8 @@ class hrLeaveAccrualLevel(models.Model):
('monthly', 'Monthly'),
('yearly', 'Yearly'),
], default='daily', required=True, string="Frequency")
+ emp_type = fields.Many2one('hr.contract.type', "Employee Type", tracking=True)
+
max_start_count = fields.Integer(
"Start after",
@@ -98,7 +100,11 @@ class hrTimeoffAllocation(models.Model):
# Calculate the current frequency
run_allocation = self._handel_weekly_frequency(level)
if run_allocation:
- qualified_employees = employees.filtered(lambda emp: self._is_experience_in_range(emp, level))
+ if level.emp_type:
+ level_filtered_employees = employees.filtered(lambda emp: emp.emp_type == level.emp_type)
+ else:
+ level_filtered_employees = employees
+ qualified_employees = level_filtered_employees.filtered(lambda emp: self._emp_filter_by_level(emp, level))
# After filtering, we create the leave allocation for each employee
for emp in qualified_employees:
@@ -163,7 +169,7 @@ class hrTimeoffAllocation(models.Model):
return fields.date.today() >= start_date
return False
- def _is_experience_in_range(self, employee, level):
+ def _emp_filter_by_level(self, employee, level):
"""
Helper method to check if the employee's total experience (including previous experience) is within the
range defined by the accrual's start and max start counts and types.
@@ -254,3 +260,28 @@ class HRLeave(models.Model):
for rec in self:
if rec.employee_id.leave_manager_id.id != self.env.user.id:
raise ValidationError(_("Only Employees Time Off Approver can approve this "))
+
+ @api.ondelete(at_uninstall=False)
+ def _unlink_if_correct_states(self):
+ error_message = _('You cannot delete a time off which is in %s state')
+ state_description_values = {elem[0]: elem[1] for elem in self._fields['state']._description_selection(self.env)}
+ now = fields.Datetime.now().date()
+
+ if not self.env.user.has_group('hr_holidays.group_hr_holidays_user'):
+ for hol in self:
+ if hol.state not in ['draft', 'cancel']:
+ raise UserError(error_message % state_description_values.get(self[:1].state))
+ if hol.date_from.date() < now:
+ raise UserError(_('You cannot delete a time off which is in the past'))
+ else:
+ for holiday in self.filtered(lambda holiday: holiday.state not in ['cancel', 'draft']):
+ raise UserError(error_message % (state_description_values.get(holiday.state),))
+
+
+class HRLeaveType(models.Model):
+ _inherit='hr.leave.type'
+
+ request_unit_type = fields.Selection([
+ ('day', 'Day'),
+ ('half_day', 'Half Day')], default='day', string='Take Time Off in', required=True)
+ request_unit = fields.Selection(related="request_unit_type",store=True)
\ No newline at end of file
diff --git a/addons_extensions/hr_timeoff_extended/views/hr_timeoff.xml b/addons_extensions/hr_timeoff_extended/views/hr_timeoff.xml
index a1d9bae06..1455598c6 100644
--- a/addons_extensions/hr_timeoff_extended/views/hr_timeoff.xml
+++ b/addons_extensions/hr_timeoff_extended/views/hr_timeoff.xml
@@ -7,6 +7,12 @@
hr.leave.accrual.plan
+
+ 1
+
+
+ 1
+
@@ -16,6 +22,9 @@
after employee joining date
+
+ 1
+
@@ -212,6 +221,15 @@
Experience is required
+
+
+
+
+
+
+
+
+
@@ -229,5 +247,20 @@
+
+
+ hr_holidays.edit_holiday_status_form.inherit
+ hr.leave.type
+
+
+
+ 1
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/third_party_addons/muk_web_appsbar/static/src/webclient/appsbar/appsbar.scss b/third_party_addons/muk_web_appsbar/static/src/webclient/appsbar/appsbar.scss
index 7f4135a24..c726ff246 100644
--- a/third_party_addons/muk_web_appsbar/static/src/webclient/appsbar/appsbar.scss
+++ b/third_party_addons/muk_web_appsbar/static/src/webclient/appsbar/appsbar.scss
@@ -1,89 +1,85 @@
.mk_apps_sidebar_panel {
@include mk-disable-scrollbar();
background-color: $mk-appbar-background;
- width: var(--mk-sidebar-width, 0);
+ width: var(--mk-sidebar-collapsed-width, 40px); /* Collapsed width: Icons only */
overflow-y: auto;
+ transition: width 300ms ease; /* Smooth transition for width */
+
+ &:hover {
+ width: var(--mk-sidebar-expanded-width, 150px); /* Expanded width: Fits names */
+ }
+
.mk_apps_sidebar {
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
- white-space: nowrap;
- .mk_apps_sidebar_menu {
- padding: 0;
- > li > a {
- cursor: pointer;
- font-size: 13px;
- font-weight: 300;
- overflow: hidden;
- padding: 8px 11px;
- text-decoration: none;
- color: $mk-appbar-color;
- text-overflow: ellipsis;
- .mk_apps_sidebar_icon {
- width: 22px;
- height: 22px;
- margin-right: 5px;
- }
- }
- > li.active > a {
+ white-space: nowrap;
+
+ .mk_apps_sidebar_menu {
+ padding: 0;
+
+ > li > a {
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 300;
+ overflow: hidden;
+ padding: 8px 11px;
+ text-decoration: none;
+ color: $mk-appbar-color;
+ text-overflow: ellipsis;
+
+ .mk_apps_sidebar_icon {
+ width: 22px;
+ height: 22px;
+ margin-right: 5px;
+ }
+
+ .mk_apps_sidebar_name {
+ opacity: 0;
+ display: inline-block; /* Name stays inline with the icon */
+ margin-left: 5px; /* Spacing between icon and name */
+ transition: opacity 300ms ease;
+ white-space: nowrap;
+ }
+ }
+ > li.active > a {
background: $mk-appbar-active;
- }
- > li:hover > a {
+ }
+ > li:hover > a {
background: $mk-appbar-active;
- }
- }
- }
+ }
+
+ > li > a .mk_apps_sidebar_name {
+ opacity: 1; /* All names visible on hover of the entire sidebar */
+ }
+ }
+ }
}
.mk_sidebar_type_large {
- --mk-sidebar-width: #{$mk-sidebar-large-width};
+ --mk-sidebar-collapsed-width: 40px;
+ --mk-sidebar-expanded-width: 150px; /* Adjust this to match the names' width */
}
.mk_sidebar_type_small {
- --mk-sidebar-width: #{$mk-sidebar-small-width};
- .mk_apps_sidebar_name {
- display: none;
- }
- .mk_apps_sidebar_icon {
- margin-right: 0 !important;
- }
- .mk_apps_sidebar_logo {
- display: none;
- }
+ --mk-sidebar-collapsed-width: 50px;
+ --mk-sidebar-expanded-width: 150px; /* Adjust if needed */
+
+ .mk_apps_sidebar_name {
+ display: none;
+ }
+
+ .mk_apps_sidebar_icon {
+ margin-right: 0 !important;
+ }
+
+ .mk_apps_sidebar_logo {
+ display: none;
+ }
}
.mk_sidebar_type_invisible {
- --mk-sidebar-width: 0;
+ --mk-sidebar-collapsed-width: 0;
}
-.editor_has_snippets_hide_backend_navbar,
-.o_home_menu_background,
-.o_fullscreen {
- --mk-sidebar-width: 0;
-}
-
-.editor_has_snippets_hide_backend_navbar .mk_apps_sidebar_panel {
- transition: width 300ms;
-}
-
-@include media-breakpoint-only(md) {
- .mk_sidebar_type_large {
- --mk-sidebar-width: #{$mk-sidebar-small-width};
- .mk_apps_sidebar_name {
- display: none;
- }
- .mk_apps_sidebar_icon {
- margin-right: 0 !important;
- }
- .mk_apps_sidebar_logo {
- display: none;
- }
- }
-}
-
-@include media-breakpoint-down(md) {
- .mk_sidebar_type_large, .mk_sidebar_type_small {
- --mk-sidebar-width: 0;
- }
-}
diff --git a/third_party_addons/muk_web_appsbar/static/src/webclient/appsbar/appsbar.xml b/third_party_addons/muk_web_appsbar/static/src/webclient/appsbar/appsbar.xml
index 99c3428d2..4dd247344 100644
--- a/third_party_addons/muk_web_appsbar/static/src/webclient/appsbar/appsbar.xml
+++ b/third_party_addons/muk_web_appsbar/static/src/webclient/appsbar/appsbar.xml
@@ -8,23 +8,23 @@