[IMP] hr_holidays: allow quick approval on dashboard

Purpose: HR can view employee leave records from the calendar but can
not approve, validate or refuse it from there.
In case they want to make action, they have to go through
menu < time off < approvals < time off < find the employee
< find the requested time off < open it < approve.

In this commit the action buttons are added on the dashboard.

task - 3175495

closes odoo/odoo#116342

Signed-off-by: Kevin Baptiste <kba@odoo.com>
This commit is contained in:
sofiagvaladze
2023-05-03 12:39:22 +02:00
parent 73aae73f1b
commit 2332624ca4
5 changed files with 48 additions and 10 deletions
+4 -2
View File
@@ -1207,10 +1207,12 @@ class HolidaysRequest(models.Model):
self.activity_update()
return True
def action_approve(self):
def action_approve(self, check_state=True):
# if validation_type == 'both': this method is the first approval approval
# if validation_type != 'both': this method calls action_validate() below
if any(holiday.state != 'confirm' for holiday in self):
# Do not check the state in case we are redirected from the dashboard
if check_state and any(holiday.state != 'confirm' for holiday in self):
raise UserError(_('Time off request must be confirmed ("To Approve") in order to approve it.'))
current_employee = self.env.user.employee_id
@@ -104,6 +104,7 @@ export class TimeOffCalendarController extends CalendarController {
onRecordSaved: onDialogClosed,
onRecordDeleted: (record) => this.deleteRecord(record),
onLeaveCancelled: onDialogClosed,
size: 'md',
},
{ onClose: () => resolve() }
);
@@ -3,6 +3,7 @@
import { FormViewDialog } from "@web/views/view_dialogs/form_view_dialog";
import { registry } from '@web/core/registry';
import { useService } from "@web/core/utils/hooks";
import { formView } from '@web/views/form/form_view';
import { FormController } from '@web/views/form/form_controller';
@@ -13,23 +14,43 @@ export class TimeOffDialogFormController extends FormController {
setup() {
super.setup();
this.leaveCancelWizard = useLeaveCancelWizard();
this.orm = useService("orm");
}
get record() {
return this.model.root.data;
}
async onClick(action) {
const args = (action === 'action_approve') ? [this.record.id, false] : [this.record.id];
await this.orm.call("hr.leave", action, args);
this.props.onLeaveUpdated();
}
get canApprove() {
return !this.model.root.isNew && (this.record.can_approve && ['confirm', 'refuse',].includes(this.record.state));
}
get canValidate() {
return !this.model.root.isNew && (this.record.can_approve && this.record.state === 'validate1');
}
get canRefuse() {
return !this.model.root.isNew && (this.record.can_approve && this.record.state && ['confirm', 'validate1', 'validate'].includes(this.record.state));
}
deleteRecord() {
const record = this.model.root.data
this.props.onRecordDeleted(record)
this.props.onRecordDeleted(this.record)
this.props.onCancelLeave();
if (record.can_cancel) {
this.leaveCancelWizard(record.id, () => {
if (this.record.can_cancel) {
this.leaveCancelWizard(this.record.id, () => {
this.props.onLeaveCancelled();
});
}
}
get canDelete() {
const record = this.model.root.data;
return !this.model.root.isNew && (record.can_cancel || record.state && !['validate', 'refuse'].includes(record.state));
return !this.model.root.isNew && (this.record.can_cancel || this.record.state && !['validate', 'refuse'].includes(this.record.state));
}
}
@@ -38,6 +59,7 @@ TimeOffDialogFormController.props = {
onCancelLeave: Function,
onRecordDeleted: Function,
onLeaveCancelled: Function,
onLeaveUpdated: Function,
}
registry.category('views').add('timeoff_dialog_form', {
@@ -56,6 +78,10 @@ export class TimeOffFormViewDialog extends FormViewDialog {
onCancelLeave: () => {
this.props.close();
},
onLeaveUpdated: () => {
this.props.onRecordSaved();
this.props.close();
},
onRecordDeleted: (record) => {
this.props.onRecordDeleted(record)
},
@@ -9,6 +9,12 @@
<xpath expr="//button[contains(@class, 'o_form_button_cancel')]" position="after">
<button class="btn btn-secondary" t-if="canDelete" t-on-click="deleteRecord" data-hotkey="x">Delete</button>
</xpath>
<xpath expr="//div[contains(@class, 'o_cp_buttons')]" position="after">
<div class="flex-grow-1 d-flex flex-row-reverse">
<button class="btn btn-secondary" t-if="canRefuse" t-on-click="() => this.onClick('action_refuse')" data-hotkey="z">Refuse</button>
<button class="btn btn-secondary" t-if="canApprove" t-on-click="() => this.onClick('action_approve')" data-hotkey="g">Approve</button>
<button class="btn btn-secondary" t-if="canValidate" t-on-click="() => this.onClick('action_validate')" data-hotkey="v">Validate</button>
<div/>
</xpath>
</t>
</templates>
@@ -353,6 +353,9 @@
<xpath expr="//group[@name='col_left']" position="attributes">
<attribute name="colspan">5</attribute>
</xpath>
<xpath expr="//div[hasclass('o_hr_leave_column')]" position="attributes">
<attribute name="class" remove="col_left col-md-6" separator=" "/>
</xpath>
</field>
</record>