mirror of
https://github.com/bringout/oca-ocb-hr.git
synced 2026-04-25 09:12:01 +02:00
Initial commit: Hr packages
This commit is contained in:
commit
62531cd146
2820 changed files with 1432848 additions and 0 deletions
|
|
@ -0,0 +1,46 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import session from 'web.session'
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { useEnv } from "@odoo/owl";
|
||||
|
||||
/**
|
||||
* Redirect to the sub employee kanban view.
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} event
|
||||
* @returns {Promise} action loaded
|
||||
*
|
||||
*/
|
||||
export function onEmployeeSubRedirect() {
|
||||
const actionService = useService('action');
|
||||
const orm = useService('orm');
|
||||
const rpc = useService('rpc');
|
||||
const env = useEnv();
|
||||
|
||||
return async (event) => {
|
||||
const employeeId = parseInt(event.currentTarget.dataset.employeeId);
|
||||
if (!employeeId) {
|
||||
return {};
|
||||
}
|
||||
const type = event.currentTarget.dataset.type || 'direct';
|
||||
// Get subordonates of an employee through a rpc call.
|
||||
const subordinateIds = await rpc('/hr/get_subordinates', {
|
||||
employee_id: employeeId,
|
||||
subordinates_type: type,
|
||||
context: session.user_context
|
||||
});
|
||||
let action = await orm.call('hr.employee', 'get_formview_action', [employeeId]);
|
||||
action = {...action,
|
||||
name: env._t('Team'),
|
||||
view_mode: 'kanban,list,form',
|
||||
views: [[false, 'kanban'], [false, 'list'], [false, 'form']],
|
||||
domain: [['id', 'in', subordinateIds]],
|
||||
res_id: false,
|
||||
context: {
|
||||
default_parent_id: employeeId,
|
||||
}
|
||||
};
|
||||
actionService.doAction(action);
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import {Field} from '@web/views/fields/field';
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { usePopover } from "@web/core/popover/popover_hook";
|
||||
import { onEmployeeSubRedirect } from './hooks';
|
||||
|
||||
const { Component, onWillStart, onWillUpdateProps, useState } = owl;
|
||||
|
||||
function useUniquePopover() {
|
||||
const popover = usePopover();
|
||||
let remove = null;
|
||||
return Object.assign(Object.create(popover), {
|
||||
add(target, component, props, options) {
|
||||
if (remove) {
|
||||
remove();
|
||||
}
|
||||
remove = popover.add(target, component, props, options);
|
||||
return () => {
|
||||
remove();
|
||||
remove = null;
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
class HrOrgChartPopover extends Component {
|
||||
async setup() {
|
||||
super.setup();
|
||||
|
||||
this.rpc = useService('rpc');
|
||||
this.orm = useService('orm');
|
||||
this.actionService = useService("action");
|
||||
this._onEmployeeSubRedirect = onEmployeeSubRedirect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the employee form view.
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} event
|
||||
* @returns {Promise} action loaded
|
||||
*/
|
||||
async _onEmployeeRedirect(employeeId) {
|
||||
const action = await this.orm.call('hr.employee', 'get_formview_action', [employeeId]);
|
||||
this.actionService.doAction(action);
|
||||
}
|
||||
}
|
||||
HrOrgChartPopover.template = 'hr_org_chart.hr_orgchart_emp_popover';
|
||||
|
||||
export class HrOrgChart extends Field {
|
||||
async setup() {
|
||||
super.setup();
|
||||
|
||||
this.rpc = useService('rpc');
|
||||
this.orm = useService('orm');
|
||||
this.actionService = useService("action");
|
||||
this.popover = useUniquePopover();
|
||||
|
||||
this.jsonStringify = JSON.stringify;
|
||||
|
||||
this.state = useState({'employee_id': null});
|
||||
this.lastParent = null;
|
||||
this.max_level = null;
|
||||
this._onEmployeeSubRedirect = onEmployeeSubRedirect();
|
||||
|
||||
onWillStart(async () => {
|
||||
this.employee = this.props.record.data;
|
||||
// the widget is either dispayed in the context of a hr.employee form or a res.users form
|
||||
this.state.employee_id =
|
||||
this.employee.employee_ids !== undefined
|
||||
? this.employee.employee_ids.resIds[0]
|
||||
: this.employee.id;
|
||||
const parentId =
|
||||
this.employee.parent_id && this.employee.parent_id[0]
|
||||
? this.employee.parent_id[0]
|
||||
: false;
|
||||
const forceReload =
|
||||
this.lastRecord !== this.props.record || this.lastParent != parentId;
|
||||
this.lastParent = parentId;
|
||||
this.lastRecord = this.props.record;
|
||||
await this.fetchEmployeeData(this.state.employee_id, forceReload);
|
||||
});
|
||||
|
||||
onWillUpdateProps(async (nextProps) => {
|
||||
const newParentId =
|
||||
nextProps.record.data.parent_id && nextProps.record.data.parent_id[0]
|
||||
? nextProps.record.data.parent_id[0]
|
||||
: false;
|
||||
const newEmployeeId = nextProps.record.data.id || false;
|
||||
if (this.lastParent !== newParentId || this.state.employee_id !== newEmployeeId) {
|
||||
this.lastParent = newParentId;
|
||||
this.max_level = null; // Reset max_level to default
|
||||
await this.fetchEmployeeData(newEmployeeId, true);
|
||||
}
|
||||
this.state.employee_id = newEmployeeId;
|
||||
});
|
||||
}
|
||||
|
||||
async fetchEmployeeData(employeeId, force = false) {
|
||||
if (!employeeId) {
|
||||
this.managers = [];
|
||||
this.children = [];
|
||||
if (this.view_employee_id) {
|
||||
this.render(true);
|
||||
}
|
||||
this.view_employee_id = null;
|
||||
} else if (employeeId !== this.view_employee_id || force) {
|
||||
this.view_employee_id = employeeId;
|
||||
var orgData = await this.rpc(
|
||||
'/hr/get_org_chart',
|
||||
{
|
||||
employee_id: employeeId,
|
||||
context: {
|
||||
...Component.env.session.user_context,
|
||||
max_level: this.max_level,
|
||||
new_parent_id: this.lastParent,
|
||||
},
|
||||
});
|
||||
if (Object.keys(orgData).length === 0) {
|
||||
orgData = {
|
||||
managers: [],
|
||||
children: [],
|
||||
}
|
||||
}
|
||||
this.managers = orgData.managers;
|
||||
this.children = orgData.children;
|
||||
this.managers_more = orgData.managers_more;
|
||||
this.self = orgData.self;
|
||||
this.render(true);
|
||||
}
|
||||
}
|
||||
|
||||
_onOpenPopover(event, employee) {
|
||||
this.popover.add(
|
||||
event.currentTarget,
|
||||
this.constructor.components.Popover,
|
||||
{employee},
|
||||
{closeOnClickAway: true}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the employee form view.
|
||||
*
|
||||
* @private
|
||||
* @param {MouseEvent} event
|
||||
* @returns {Promise} action loaded
|
||||
*/
|
||||
async _onEmployeeRedirect(employeeId) {
|
||||
const action = await this.orm.call('hr.employee', 'get_formview_action', [employeeId]);
|
||||
this.actionService.doAction(action);
|
||||
}
|
||||
|
||||
async _onEmployeeMoreManager(managerId) {
|
||||
this.max_level = 100; // Set a high level to fetch all managers
|
||||
await this.fetchEmployeeData(this.state.employee_id, true);
|
||||
}
|
||||
}
|
||||
|
||||
HrOrgChart.components = {
|
||||
Popover: HrOrgChartPopover,
|
||||
};
|
||||
|
||||
HrOrgChart.template = 'hr_org_chart.hr_org_chart';
|
||||
|
||||
registry.category("fields").add("hr_org_chart", HrOrgChart);
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
|
||||
#o_employee_right {
|
||||
@include media-breakpoint-up(lg) {
|
||||
border-left: $border-width solid $o-form-separator-color;
|
||||
}
|
||||
|
||||
.o_org_chart_entry {
|
||||
.o_media_object {
|
||||
width: $o-hr-org-chart-entry-pic-small-size;
|
||||
height: $o-hr-org-chart-entry-pic-small-size;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
}
|
||||
|
||||
&.o_org_chart_entry_self .o_media_object {
|
||||
width: $o-hr-org-chart-entry-pic-size;
|
||||
height: $o-hr-org-chart-entry-pic-size;
|
||||
}
|
||||
|
||||
&:not(.o_org_chart_entry_self):hover .o_media_object {
|
||||
box-shadow: 0 0 0 $border-width*2 $info;
|
||||
}
|
||||
}
|
||||
|
||||
.o_org_chart_entry_self_container.o_org_chart_has_managers {
|
||||
margin-left: $o-hr-org-chart-entry-pic-small-size * .25;
|
||||
}
|
||||
|
||||
.o_org_chart_group_down.o_org_chart_has_managers {
|
||||
padding-left: $o-hr-org-chart-entry-pic-size * .6;
|
||||
}
|
||||
|
||||
// ORGANIGRAM LINES
|
||||
.o_org_chart_group_up .o_treeEntry {
|
||||
--treeEntry-padding-h: 0px;
|
||||
--treeEntry--before-top: 50%;
|
||||
--treeEntry--after-display: none;
|
||||
--treeEntry--beforeAfter-left: #{$o-hr-org-chart-entry-pic-small-size * .5};
|
||||
}
|
||||
|
||||
.o_org_chart_entry_self_container .o_treeEntry {
|
||||
--treeEntry-padding-h: #{$o-hr-org-chart-entry-pic-small-size * .5};
|
||||
--treeEntry-padding-v: #{$o-hr-org-chart-entry-pic-size * .25};
|
||||
}
|
||||
|
||||
.o_org_chart_group_down .o_treeEntry {
|
||||
--treeEntry-padding-h: #{$o-hr-org-chart-entry-pic-size};
|
||||
--treeEntry-padding-v: #{$o-hr-org-chart-entry-pic-small-size * .5};
|
||||
}
|
||||
}
|
||||
|
||||
// Right to Left specific style to flip the popover arrow
|
||||
.o_rtl .o_org_chart_popup.popover .arrow {
|
||||
left: 100%;
|
||||
transform: matrix(-1, 0, 0, 1, 0, 0);
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="hr_org_chart.hr_org_chart_employee" owl="1">
|
||||
<t t-set="is_self" t-value="employee.id == view_employee_id"/>
|
||||
|
||||
<section t-if="employee_type == 'self'" t-attf-class="o_org_chart_entry_self_container #{managers.length > 0 ? 'o_org_chart_has_managers' : ''}">
|
||||
<div t-attf-class="o_org_chart_entry o_org_chart_entry_#{employee_type} d-flex position-relative py-2 overflow-visible #{managers.length > 0 ? 'o_treeEntry' : ''}">
|
||||
<t t-call="hr_org_chart.hr_org_chart_employee_content">
|
||||
<t t-set="is_self" t-value="is_self"/>
|
||||
</t>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div t-else="" t-attf-class="o_org_chart_entry o_org_chart_entry_#{employee_type} o_treeEntry d-flex position-relative py-2 overflow-visible">
|
||||
<t t-call="hr_org_chart.hr_org_chart_employee_content">
|
||||
<t t-set="is_self" t-value="is_self"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="hr_org_chart.hr_org_chart_employee_content" owl="1">
|
||||
<div class="o_media_left position-relative">
|
||||
<!-- NOTE: Since by the default on not squared images odoo add white borders,
|
||||
use bg-images to get a clean and centred images -->
|
||||
<a t-if="! is_self"
|
||||
class="o_media_object d-block rounded-circle o_employee_redirect"
|
||||
t-att-style="'background-image:url(\'/web/image/hr.employee.public/' + employee.id + '/avatar_1024/\')'"
|
||||
t-att-alt="employee.name"
|
||||
t-att-data-employee-id="employee.id"
|
||||
t-att-href="employee.link"
|
||||
t-on-click.prevent="() => this._onEmployeeRedirect(employee.id)"/>
|
||||
<div t-if="is_self"
|
||||
class="o_media_object d-block rounded-circle border border-info"
|
||||
t-att-style="'background-image:url(\'/web/image/hr.employee.public/' + employee.id + '/avatar_1024/\')'"/>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-grow-1 align-items-center justify-content-between position-relative px-3">
|
||||
<a t-if="!is_self" t-att-href="employee.link" class="o_employee_redirect d-flex flex-column" t-att-data-employee-id="employee.id" t-on-click.prevent="() => this._onEmployeeRedirect(employee.id)">
|
||||
<b class="o_media_heading m-0 fs-6" t-esc="employee.name"/>
|
||||
<small class="text-muted fw-bold" t-esc="employee.job_title"/>
|
||||
</a>
|
||||
<div t-if="is_self" class="d-flex flex-column">
|
||||
<h5 class="o_media_heading m-0" t-esc="employee.name"/>
|
||||
<small class="text-muted fw-bold" t-esc="employee.job_title"/>
|
||||
</div>
|
||||
<button t-if="employee.indirect_sub_count > 0"
|
||||
class="btn p-0 fs-3"
|
||||
tabindex="0"
|
||||
t-att-data-emp-name="employee.name"
|
||||
t-att-data-emp-id="employee.id"
|
||||
t-att-data-emp-dir-subs="employee.direct_sub_count"
|
||||
t-att-data-emp-ind-subs="employee.indirect_sub_count"
|
||||
data-bs-trigger="focus"
|
||||
data-bs-toggle="popover"
|
||||
t-on-click="(event) => this._onOpenPopover(event, employee)">
|
||||
<a href="#"
|
||||
t-attf-class="badge rounded-pill bg-white border {{employee.indirect_sub_count < 10 ? 'px-2' : 'px-1' }}"
|
||||
t-esc="employee.indirect_sub_count"
|
||||
/>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="hr_org_chart.hr_org_chart" owl="1">
|
||||
<!-- NOTE: Desidered behaviour:
|
||||
The maximun number of people is always 7 (including 'self'). Managers have priority over suburdinates
|
||||
Eg. 1 Manager + 1 self = show just 5 subordinates (if availables)
|
||||
Eg. 0 Manager + 1 self = show 6 subordinates (if available)
|
||||
|
||||
-->
|
||||
<t t-set="emp_count" t-value="0"/>
|
||||
<div t-if='managers.length > 0' class="o_org_chart_group_up position-relative">
|
||||
<div t-if='managers_more' class="o_org_chart_more pe-3" t-attf-class="{{max_level !== null ? 'invisible' : ''}}">
|
||||
<a href="#" t-att-data-employee-id="managers[0].id" class="o_employee_more_managers d-block bg-100 px-3" t-on-click.prevent="() => this._onEmployeeMoreManager(managers[0].id)">
|
||||
<i class="fa fa-angle-double-up" role="img" aria-label="More managers" title="More managers"/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<t t-foreach="managers" t-as="employee" t-key="employee_index">
|
||||
<t t-set="emp_count" t-value="emp_count + 1"/>
|
||||
<t t-call="hr_org_chart.hr_org_chart_employee">
|
||||
<t t-set="employee_type" t-value="'manager'"/>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<t t-if="children.length || managers.length" t-call="hr_org_chart.hr_org_chart_employee">
|
||||
<t t-set="employee_type" t-value="'self'"/>
|
||||
<t t-set="employee" t-value="self"/>
|
||||
</t>
|
||||
|
||||
<t t-if="!children.length && !managers.length">
|
||||
<div class="alert alert-info" role="alert">
|
||||
<p><b>No hierarchy position.</b></p>
|
||||
<p>This employee has no manager or subordinate.</p>
|
||||
<p>In order to get an organigram, set a manager and save the record.</p>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<div t-if="children.length" t-attf-class="o_org_chart_group_down position-relative #{managers.length > 0 ? 'o_org_chart_has_managers' : ''}">
|
||||
<t t-foreach="children" t-as="employee" t-key="employee_index">
|
||||
<t t-set="emp_count" t-value="emp_count + 1"/>
|
||||
<t t-if="emp_count < 20">
|
||||
<t t-call="hr_org_chart.hr_org_chart_employee">
|
||||
<t t-set="employee_type" t-value="'sub'"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-if="(children.length + managers.length) > 19">
|
||||
<div class="o_org_chart_entry o_org_chart_more d-flex overflow-visible">
|
||||
<div class="o_media_left position-relative">
|
||||
<a href="#"
|
||||
t-att-data-employee-id="self.id"
|
||||
t-att-data-employee-name="self.name"
|
||||
class="o_org_chart_show_more o_employee_sub_redirect btn btn-link ps-2"
|
||||
t-on-click.prevent="_onEmployeeSubRedirect">See All</a>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="hr_org_chart.hr_orgchart_emp_popover" owl="1">
|
||||
<div class="popover o_org_chart_popup" role="tooltip">
|
||||
<div class="tooltip-arrow">
|
||||
|
||||
</div>
|
||||
<h3 class="popover-header">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="flex-shrink-0" t-att-style='"background-image:url(\"/web/image/hr.employee.public/" + props.employee.id + "/avatar_1024/\")"'/>
|
||||
<b class="flew-grow-1"><t t-esc="props.employee.name"/></b>
|
||||
<a href="#" class="ms-auto o_employee_redirect" t-att-data-employee-id="props.employee.id" t-on-click.prevent="() => this._onEmployeeRedirect(props.employee.id)"><i class="fa fa-external-link" role="img" aria-label='Redirect' title="Redirect"></i></a>
|
||||
</div>
|
||||
</h3>
|
||||
<div class="popover-body">
|
||||
<table class="table table-sm table-borderless mb-0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="text-end"><b t-esc="props.employee.direct_sub_count"/></td>
|
||||
<td>
|
||||
<a href="#" class="o_employee_sub_redirect" data-type='direct'
|
||||
t-att-data-employee-name="props.employee.name" t-att-data-employee-id="props.employee.id"
|
||||
t-on-click.prevent="_onEmployeeSubRedirect">
|
||||
<b>Direct subordinates</b></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-end">
|
||||
<b t-esc="props.employee.indirect_sub_count - props.employee.direct_sub_count"/>
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" class="o_employee_sub_redirect" data-type='indirect'
|
||||
t-att-data-employee-name="props.employee.name" t-att-data-employee-id="props.employee.id"
|
||||
t-on-click.prevent="_onEmployeeSubRedirect">
|
||||
Indirect subordinates</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-end"><b t-esc="props.employee.indirect_sub_count"/></td>
|
||||
<td>
|
||||
<a href="#" class="o_employee_sub_redirect" data-type='total'
|
||||
t-att-data-employee-name="props.employee.name" t-att-data-employee-id="props.employee.id"
|
||||
t-on-click.prevent="_onEmployeeSubRedirect">
|
||||
Total</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
$o-hr-org-chart-entry-pic-size: 46px;
|
||||
$o-hr-org-chart-entry-pic-small-size: $o-hr-org-chart-entry-pic-size * .8;
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { getFixture } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
|
||||
let target;
|
||||
let serverData;
|
||||
|
||||
QUnit.module("hr_org_chart", {
|
||||
async beforeEach() {
|
||||
target = getFixture();
|
||||
serverData = {
|
||||
models: {
|
||||
hr_employee: {
|
||||
fields: {
|
||||
child_ids: {string: "one2many Subordinates field", type: "one2many", relation: 'hr_employee'},
|
||||
},
|
||||
records: [{
|
||||
id: 1,
|
||||
child_ids: [],
|
||||
}]
|
||||
},
|
||||
},
|
||||
};
|
||||
setupViewRegistries();
|
||||
},
|
||||
}, function () {
|
||||
QUnit.test("hr org chart: empty render", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: 'hr_employee',
|
||||
serverData: serverData,
|
||||
arch:
|
||||
'<form>' +
|
||||
'<field name="child_ids" widget="hr_org_chart"/>' +
|
||||
'</form>',
|
||||
resId: 1,
|
||||
mockRPC: function (route, args) {
|
||||
if (route === '/hr/get_org_chart') {
|
||||
assert.ok('employee_id' in args, "it should have 'employee_id' as argument");
|
||||
return Promise.resolve({
|
||||
children: [],
|
||||
managers: [],
|
||||
managers_more: false,
|
||||
});
|
||||
} else if (route === '/hr/get_redirect_model') {
|
||||
return Promise.resolve('hr.employee');
|
||||
}
|
||||
}
|
||||
});
|
||||
assert.strictEqual($(target.querySelector('[name="child_ids"]')).children().length, 1,
|
||||
"the chart should have 1 child");
|
||||
});
|
||||
QUnit.test("hr org chart: render without data", async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: 'hr_employee',
|
||||
serverData: serverData,
|
||||
arch:
|
||||
'<form>' +
|
||||
'<field name="child_ids" widget="hr_org_chart"/>' +
|
||||
'</form>',
|
||||
resId: 1,
|
||||
mockRPC: function (route, args) {
|
||||
if (route === '/hr/get_org_chart') {
|
||||
assert.ok('employee_id' in args, "it should have 'employee_id' as argument");
|
||||
return Promise.resolve({}); // return no data
|
||||
}
|
||||
}
|
||||
});
|
||||
assert.strictEqual($(target.querySelector('[name="child_ids"]')).children().length, 1,
|
||||
"the chart should have 1 child");
|
||||
});
|
||||
QUnit.test("hr org chart: basic render", async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: 'hr_employee',
|
||||
serverData: serverData,
|
||||
arch:
|
||||
'<form>' +
|
||||
'<sheet>' +
|
||||
'<div id="o_employee_container"><div id="o_employee_main">' +
|
||||
'<div id="o_employee_right">' +
|
||||
'<field name="child_ids" widget="hr_org_chart"/>' +
|
||||
'</div>' +
|
||||
'</div></div>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
resId: 1,
|
||||
mockRPC: function (route, args) {
|
||||
if (route === '/hr/get_org_chart') {
|
||||
assert.ok('employee_id' in args, "it should have 'employee_id' as argument");
|
||||
return Promise.resolve({
|
||||
children: [{
|
||||
direct_sub_count: 0,
|
||||
indirect_sub_count: 0,
|
||||
job_id: 2,
|
||||
job_name: 'Sub-Gooroo',
|
||||
link: 'fake_link',
|
||||
name: 'Michael Hawkins',
|
||||
id: 2,
|
||||
}],
|
||||
managers: [],
|
||||
managers_more: false,
|
||||
self: {
|
||||
direct_sub_count: 1,
|
||||
id: 1,
|
||||
indirect_sub_count: 1,
|
||||
job_id: 1,
|
||||
job_name: 'Gooroo',
|
||||
link: 'fake_link',
|
||||
name: 'Antoine Langlais',
|
||||
}
|
||||
});
|
||||
} else if (route === '/hr/get_redirect_model') {
|
||||
return Promise.resolve('hr.employee');
|
||||
}
|
||||
}
|
||||
});
|
||||
assert.containsOnce(target, '.o_org_chart_entry_sub',
|
||||
"the chart should have 1 subordinate");
|
||||
assert.containsOnce(target, '.o_org_chart_entry_self',
|
||||
"the current employee should only be displayed once in the chart");
|
||||
});
|
||||
QUnit.test("hr org chart: basic manager render", async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: 'hr_employee',
|
||||
serverData: serverData,
|
||||
arch:
|
||||
'<form>' +
|
||||
'<sheet>' +
|
||||
'<div id="o_employee_container"><div id="o_employee_main">' +
|
||||
'<div id="o_employee_right">' +
|
||||
'<field name="child_ids" widget="hr_org_chart"/>' +
|
||||
'</div>' +
|
||||
'</div></div>' +
|
||||
'</sheet>' +
|
||||
'</form>',
|
||||
resId: 1,
|
||||
mockRPC: function (route, args) {
|
||||
if (route === '/hr/get_org_chart') {
|
||||
assert.ok('employee_id' in args, "should have 'employee_id' as argument");
|
||||
return Promise.resolve({
|
||||
children: [{
|
||||
direct_sub_count: 0,
|
||||
indirect_sub_count: 0,
|
||||
job_id: 2,
|
||||
job_name: 'Sub-Gooroo',
|
||||
link: 'fake_link',
|
||||
name: 'Michael Hawkins',
|
||||
id: 2,
|
||||
}],
|
||||
managers: [{
|
||||
direct_sub_count: 1,
|
||||
id: 1,
|
||||
indirect_sub_count: 2,
|
||||
job_id: 1,
|
||||
job_name: 'Chief Gooroo',
|
||||
link: 'fake_link',
|
||||
name: 'Antoine Langlais',
|
||||
}],
|
||||
managers_more: false,
|
||||
self: {
|
||||
direct_sub_count: 1,
|
||||
id: 1,
|
||||
indirect_sub_count: 1,
|
||||
job_id: 3,
|
||||
job_name: 'Gooroo',
|
||||
link: 'fake_link',
|
||||
name: 'John Smith',
|
||||
}
|
||||
});
|
||||
} else if (route === '/hr/get_redirect_model') {
|
||||
return Promise.resolve('hr.employee');
|
||||
}
|
||||
}
|
||||
});
|
||||
assert.containsOnce(target, '.o_org_chart_group_up .o_org_chart_entry_manager', "the chart should have 1 manager");
|
||||
assert.containsOnce(target, '.o_org_chart_group_down .o_org_chart_entry_sub', "the chart should have 1 subordinate");
|
||||
assert.containsOnce(target, '.o_org_chart_entry_self', "the chart should have only once the current employee");
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue