Merge branch 'develop'
This commit is contained in:
administrator 2025-01-21 14:54:26 +05:30
commit 4b255b9e4e
12 changed files with 326 additions and 87 deletions

View File

@ -1 +1,2 @@
from . import hr_employee from . import hr_employee
from . import hr_leave

View File

@ -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

View File

@ -35,7 +35,7 @@
<field name="max_check_out"/> <field name="max_check_out"/>
<field name="out_time" widget="float_time"/> <field name="out_time" widget="float_time"/>
<field name="worked_hours" widget="float_time"/> <field name="worked_hours" widget="float_time"/>
<!-- <field name="extra_hours"/>--> <!-- <field name="extra_hours"/>-->
<field name="status"/> <field name="status"/>
</list> </list>
</field> </field>
@ -57,7 +57,7 @@
<field name="max_check_out"/> <field name="max_check_out"/>
<field name="out_time"/> <field name="out_time"/>
<field name="worked_hours"/> <field name="worked_hours"/>
<!-- <field name="extra_hours"/>--> <!-- <field name="extra_hours"/>-->
<field name="status"/> <field name="status"/>
<field name="attendance_id"/> <field name="attendance_id"/>
</list> </list>
@ -77,11 +77,42 @@
</field> </field>
</record> </record>
<menuitem id="menu_attendance_attendance" name="Management" parent="hr_attendance.menu_hr_attendance_root" <menuitem id="menu_attendance_attendance" name="Management" parent="hr_attendance.menu_hr_attendance_root"
sequence="6" groups="hr_attendance.group_hr_attendance_officer"/> sequence="6" groups="hr_attendance.group_hr_attendance_officer"/>
<!-- Menu for Attendance --> <!-- Menu for Attendance -->
<menuitem id="hr_attendance.menu_hr_attendance_view_attendances_management" name="Attendance" parent="hr_attendance_extended.menu_attendance_attendance" sequence="6" groups="hr_attendance.group_hr_attendance_officer" action="action_attendance_attendance"/> <menuitem id="hr_attendance.menu_hr_attendance_view_attendances_management" name="Attendance"
parent="hr_attendance_extended.menu_attendance_attendance" sequence="6"
groups="hr_attendance.group_hr_attendance_officer" action="action_attendance_attendance"/>
<record id="hr_attendance_management_view_filter_inherit" model="ir.ui.view">
<field name="name">hr_attendance_management_view_filter_inherit</field>
<field name="model">hr.attendance</field>
<field name="inherit_id" ref="hr_attendance.hr_attendance_management_view_filter"/>
<field name="arch" type="xml">
<xpath expr="//search" position="inside">
<searchpanel>
<field name="department_id" icon="fa-users" enable_counters="1"/>
</searchpanel>
</xpath>
</field>
</record>
<record id="hr_attendance_view_filter_inherit" model="ir.ui.view">
<field name="name">hr_attendance_view_filter_inherit</field>
<field name="model">hr.attendance</field>
<field name="inherit_id" ref="hr_attendance.hr_attendance_view_filter"/>
<field name="arch" type="xml">
<xpath expr="//search" position="inside">
<searchpanel>
<field name="department_id" icon="fa-users" enable_counters="1"/>
</searchpanel>
</xpath>
</field>
</record>
</data> </data>
</odoo> </odoo>

View File

@ -18,7 +18,8 @@ class AppraisalPeriod(models.Model):
@api.onchange('date_to') @api.onchange('date_to')
def onchange_activation_date(self): def onchange_activation_date(self):
for rec in 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 = { _defaults = {
'color_name': 'red', 'color_name': 'red',

View File

@ -18,10 +18,11 @@
'version': '0.1', 'version': '0.1',
# any module necessary for this one to work correctly # any module necessary for this one to work correctly
'depends': ['base','hr'], 'depends': ['base','hr','mail'],
# always loaded # always loaded
'data': [ 'data': [
'security/security.xml',
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'views/hr_employee.xml', 'views/hr_employee.xml',
'wizards/work_location_wizard.xml' 'wizards/work_location_wizard.xml'

View File

@ -18,6 +18,9 @@ class HrEmployeeBase(models.AbstractModel):
total_exp = fields.Char(string='Total Experience', compute='_compute_total_experience', store=True) 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') @api.constrains('identification_id')
def _check_identification_id(self): def _check_identification_id(self):
for record in 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 # If there's no DOJ, total experience is just the previous experience
total_years = record.previous_exp // 12 total_years = record.previous_exp // 12
total_months = record.previous_exp % 12 total_months = record.previous_exp % 12
record.total_exp = f"{total_years} years {total_months} months 0 days" record.total_exp = f"{total_years} years {total_months} months 0 days"

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record id="module_internal_user_category" model="ir.module.category">
<field name="name">Internal User Category</field>
<field name="sequence">17</field>
</record>
<record id="group_external_user" model="res.groups">
<field name="name">External User</field>
<field name="category_id" ref="hr_employee_extended.module_internal_user_category"/>
</record>
<record id="group_internal_user" model="res.groups">
<field name="name">Internal User</field>
<field name="implied_ids" eval="[(4, ref('group_external_user'))]"/>
<field name="category_id" ref="hr_employee_extended.module_internal_user_category"/>
</record>
</data>
</odoo>

View File

@ -6,8 +6,12 @@
<field name="model">hr.employee</field> <field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/> <field name="inherit_id" ref="hr.view_employee_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//field[@name='employee_type']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='work_email']" position="before"> <xpath expr="//field[@name='work_email']" position="before">
<field name="employee_id"/> <field name="employee_id"/>
<field name="emp_type"/>
</xpath> </xpath>
<xpath expr="//field[@name='work_location_id']" position="after"> <xpath expr="//field[@name='work_location_id']" position="after">
<field name="doj"/> <field name="doj"/>
@ -60,5 +64,13 @@
</field> </field>
</record> </record>
<record id="mail.menu_root_discuss" model="ir.ui.menu">
<field name="groups_id" eval="[(3,ref('base.group_user')),(4, ref('hr_employee_extended.group_internal_user'))]"/>
</record>
<record id="hr.menu_hr_root" model="ir.ui.menu">
<field name="groups_id" eval="[(3,ref('hr.group_hr_manager')),(3,ref('hr.group_hr_user')),(3,ref('base.group_user')),(3,ref('hr_employee_extended.group_external_user')),(4, ref('hr_employee_extended.group_internal_user'))]"/>
</record>
</data> </data>
</odoo> </odoo>

View File

@ -17,6 +17,8 @@ class hrLeaveAccrualLevel(models.Model):
('monthly', 'Monthly'), ('monthly', 'Monthly'),
('yearly', 'Yearly'), ('yearly', 'Yearly'),
], default='daily', required=True, string="Frequency") ], default='daily', required=True, string="Frequency")
emp_type = fields.Many2one('hr.contract.type', "Employee Type", tracking=True)
max_start_count = fields.Integer( max_start_count = fields.Integer(
"Start after", "Start after",
@ -98,7 +100,11 @@ class hrTimeoffAllocation(models.Model):
# Calculate the current frequency # Calculate the current frequency
run_allocation = self._handel_weekly_frequency(level) run_allocation = self._handel_weekly_frequency(level)
if run_allocation: 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 # After filtering, we create the leave allocation for each employee
for emp in qualified_employees: for emp in qualified_employees:
@ -163,7 +169,7 @@ class hrTimeoffAllocation(models.Model):
return fields.date.today() >= start_date return fields.date.today() >= start_date
return False 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 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. 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: for rec in self:
if rec.employee_id.leave_manager_id.id != self.env.user.id: if rec.employee_id.leave_manager_id.id != self.env.user.id:
raise ValidationError(_("Only Employees Time Off Approver can approve this ")) 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)

View File

@ -7,6 +7,12 @@
<field name="model">hr.leave.accrual.plan</field> <field name="model">hr.leave.accrual.plan</field>
<field name="inherit_id" ref="hr_holidays.hr_accrual_plan_view_form"/> <field name="inherit_id" ref="hr_holidays.hr_accrual_plan_view_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//field[@name='accrued_gain_time']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='transition_mode']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//div[@name='carryover']" position="after"> <xpath expr="//div[@name='carryover']" position="after">
<field name="time_off_type_id" required="1"/> <field name="time_off_type_id" required="1"/>
<div> <div>
@ -16,6 +22,9 @@
<strong>after employee joining date</strong> <strong>after employee joining date</strong>
</div> </div>
</xpath> </xpath>
<xpath expr="//field[@name='carryover_date']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='level_ids']/kanban" position="replace"> <xpath expr="//field[@name='level_ids']/kanban" position="replace">
<kanban default_order="sequence"> <kanban default_order="sequence">
<field name="sequence"/> <field name="sequence"/>
@ -212,6 +221,15 @@
Experience is required Experience is required
</div> </div>
</group> </group>
<group name="emp_types">
<div class="o_td_label">
<label for="emp_type" string="Employee Type"/>
</div>
<div>
<field name="emp_type" style="width: 4" required="max_start_count > 0 or max_start_type != False"/>
</div>
</group>
</xpath> </xpath>
</field> </field>
@ -229,5 +247,20 @@
</record> </record>
<record model="ir.ui.view" id="hr_holidays_edit_holiday_status_form_inherit">
<field name="name">hr_holidays.edit_holiday_status_form.inherit</field>
<field name="model">hr.leave.type</field>
<field name="inherit_id" ref="hr_holidays.edit_holiday_status_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='request_unit']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='request_unit']" position="after">
<field name="request_unit_type"/>
</xpath>
</field>
</record>
</data> </data>
</odoo> </odoo>

View File

@ -1,89 +1,85 @@
.mk_apps_sidebar_panel { .mk_apps_sidebar_panel {
@include mk-disable-scrollbar(); @include mk-disable-scrollbar();
background-color: $mk-appbar-background; 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; 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 { .mk_apps_sidebar {
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
white-space: nowrap; white-space: nowrap;
.mk_apps_sidebar_menu {
padding: 0; .mk_apps_sidebar_menu {
> li > a { padding: 0;
cursor: pointer;
font-size: 13px; > li > a {
font-weight: 300; cursor: pointer;
overflow: hidden; font-size: 13px;
padding: 8px 11px; font-weight: 300;
text-decoration: none; overflow: hidden;
color: $mk-appbar-color; padding: 8px 11px;
text-overflow: ellipsis; text-decoration: none;
.mk_apps_sidebar_icon { color: $mk-appbar-color;
width: 22px; text-overflow: ellipsis;
height: 22px;
margin-right: 5px; .mk_apps_sidebar_icon {
} width: 22px;
} height: 22px;
> li.active > a { 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; background: $mk-appbar-active;
} }
> li:hover > a { > li:hover > a {
background: $mk-appbar-active; 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_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_type_small {
--mk-sidebar-width: #{$mk-sidebar-small-width}; --mk-sidebar-collapsed-width: 50px;
.mk_apps_sidebar_name { --mk-sidebar-expanded-width: 150px; /* Adjust if needed */
display: none;
} .mk_apps_sidebar_name {
.mk_apps_sidebar_icon { display: none;
margin-right: 0 !important; }
}
.mk_apps_sidebar_logo { .mk_apps_sidebar_icon {
display: none; margin-right: 0 !important;
} }
.mk_apps_sidebar_logo {
display: none;
}
} }
.mk_sidebar_type_invisible { .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;
}
}

View File

@ -8,23 +8,23 @@
<ul class="mk_apps_sidebar_menu"> <ul class="mk_apps_sidebar_menu">
<t t-foreach="this.appMenuService.getAppsMenuItems()" t-as="app" t-key="app.id"> <t t-foreach="this.appMenuService.getAppsMenuItems()" t-as="app" t-key="app.id">
<li t-attf-class="nav-item {{ app.id === this.appMenuService.getCurrentApp()?.id ? 'active' : '' }}"> <li t-attf-class="nav-item {{ app.id === this.appMenuService.getCurrentApp()?.id ? 'active' : '' }}">
<a <a
t-att-href="app.href" t-att-href="app.href"
t-att-data-menu-id="app.id" t-att-data-menu-id="app.id"
t-att-data-menu-xmlid="app.xmlid" t-att-data-menu-xmlid="app.xmlid"
t-att-data-action-id="app.actionID" t-att-data-action-id="app.actionID"
t-on-click.prevent="() => app.action()" t-on-click.prevent="() => app.action()"
class="nav-link" class="nav-link"
role="menuitem" role="menuitem"
> >
<img <img
t-if="app.webIconData" t-if="app.webIconData"
class="mk_apps_sidebar_icon" class="mk_apps_sidebar_icon"
t-att-src="app.webIconData" t-att-src="app.webIconData"
/> />
<img <img
t-else="" t-else=""
class="mk_apps_sidebar_icon" class="mk_apps_sidebar_icon"
src="/base/static/description/icon.png" src="/base/static/description/icon.png"
/> />
<span class="mk_apps_sidebar_name"> <span class="mk_apps_sidebar_name">