summaryrefslogtreecommitdiff
path: root/addons/hr/static/src
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/hr/static/src
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/hr/static/src')
-rw-r--r--addons/hr/static/src/bugfix/bugfix.js10
-rw-r--r--addons/hr/static/src/bugfix/bugfix.scss6
-rw-r--r--addons/hr/static/src/bugfix/bugfix.xml11
-rw-r--r--addons/hr/static/src/bugfix/bugfix_tests.js18
-rw-r--r--addons/hr/static/src/default_image.png0
-rw-r--r--addons/hr/static/src/img/default_image.pngbin0 -> 2723 bytes
-rw-r--r--addons/hr/static/src/js/chat.js74
-rw-r--r--addons/hr/static/src/js/language.js27
-rw-r--r--addons/hr/static/src/js/many2one_avatar_employee.js43
-rw-r--r--addons/hr/static/src/js/standalone_m2o_avatar_employee.js61
-rw-r--r--addons/hr/static/src/models/employee/employee.js204
-rw-r--r--addons/hr/static/src/models/messaging/messaging.js36
-rw-r--r--addons/hr/static/src/models/partner/partner.js63
-rw-r--r--addons/hr/static/src/models/user/user.js18
-rw-r--r--addons/hr/static/src/scss/hr.scss22
-rw-r--r--addons/hr/static/src/xml/hr_templates.xml8
16 files changed, 601 insertions, 0 deletions
diff --git a/addons/hr/static/src/bugfix/bugfix.js b/addons/hr/static/src/bugfix/bugfix.js
new file mode 100644
index 00000000..0351046f
--- /dev/null
+++ b/addons/hr/static/src/bugfix/bugfix.js
@@ -0,0 +1,10 @@
+/**
+ * This file allows introducing new JS modules without contaminating other files.
+ * This is useful when bug fixing requires adding such JS modules in stable
+ * versions of Odoo. Any module that is defined in this file should be isolated
+ * in its own file in master.
+ */
+odoo.define('hr/static/src/bugfix/bugfix.js', function (require) {
+'use strict';
+
+});
diff --git a/addons/hr/static/src/bugfix/bugfix.scss b/addons/hr/static/src/bugfix/bugfix.scss
new file mode 100644
index 00000000..c4272e52
--- /dev/null
+++ b/addons/hr/static/src/bugfix/bugfix.scss
@@ -0,0 +1,6 @@
+/**
+* This file allows introducing new styles without contaminating other files.
+* This is useful when bug fixing requires adding new components for instance in
+* stable versions of Odoo. Any style that is defined in this file should be isolated
+* in its own file in master.
+*/
diff --git a/addons/hr/static/src/bugfix/bugfix.xml b/addons/hr/static/src/bugfix/bugfix.xml
new file mode 100644
index 00000000..c17906f7
--- /dev/null
+++ b/addons/hr/static/src/bugfix/bugfix.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates xml:space="preserve">
+
+<!--
+ This file allows introducing new static templates without contaminating other files.
+ This is useful when bug fixing requires adding new components for instance in stable
+ versions of Odoo. Any template that is defined in this file should be isolated
+ in its own file in master.
+-->
+
+</templates>
diff --git a/addons/hr/static/src/bugfix/bugfix_tests.js b/addons/hr/static/src/bugfix/bugfix_tests.js
new file mode 100644
index 00000000..8b23d372
--- /dev/null
+++ b/addons/hr/static/src/bugfix/bugfix_tests.js
@@ -0,0 +1,18 @@
+odoo.define('hr/static/src/bugfix/bugfix_tests.js', function (require) {
+'use strict';
+
+/**
+ * This file allows introducing new QUnit test modules without contaminating
+ * other test files. This is useful when bug fixing requires adding new
+ * components for instance in stable versions of Odoo. Any test that is defined
+ * in this file should be isolated in its own file in master.
+ */
+QUnit.module('hr', {}, function () {
+QUnit.module('bugfix', {}, function () {
+QUnit.module('bugfix_tests.js', {
+
+});
+});
+});
+
+});
diff --git a/addons/hr/static/src/default_image.png b/addons/hr/static/src/default_image.png
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/addons/hr/static/src/default_image.png
diff --git a/addons/hr/static/src/img/default_image.png b/addons/hr/static/src/img/default_image.png
new file mode 100644
index 00000000..87fdbc5a
--- /dev/null
+++ b/addons/hr/static/src/img/default_image.png
Binary files differ
diff --git a/addons/hr/static/src/js/chat.js b/addons/hr/static/src/js/chat.js
new file mode 100644
index 00000000..9bae9d57
--- /dev/null
+++ b/addons/hr/static/src/js/chat.js
@@ -0,0 +1,74 @@
+odoo.define('hr.employee_chat', function (require) {
+'use strict';
+ var viewRegistry = require('web.view_registry');
+
+ var FormController = require('web.FormController');
+ var FormView = require('web.FormView');
+ var FormRenderer = require('web.FormRenderer');
+
+ var KanbanController = require('web.KanbanController');
+ var KanbanView = require('web.KanbanView');
+ var KanbanRenderer = require('web.KanbanRenderer');
+ var KanbanRecord = require('web.KanbanRecord');
+
+ const { Component } = owl;
+
+ // CHAT MIXIN
+ var ChatMixin = {
+ /**
+ * @override
+ */
+ _render: function () {
+ var self = this;
+ return this._super.apply(this, arguments).then(function () {
+ var $chat_button = self.$el.find('.o_employee_chat_btn');
+ $chat_button.off('click').on('click', self._onOpenChat.bind(self));
+ });
+ },
+
+ destroy: function () {
+ if (this.$el) {
+ this.$el.find('.o_employee_chat_btn').off('click');
+ }
+ return this._super();
+ },
+
+ _onOpenChat: function (ev) {
+ ev.preventDefault();
+ ev.stopImmediatePropagation();
+ const env = Component.env;
+ env.messaging.openChat({ employeeId: this.state.data.id });
+ return true;
+ },
+ };
+
+ // USAGE OF CHAT MIXIN IN FORM VIEWS
+ var EmployeeFormRenderer = FormRenderer.extend(ChatMixin);
+
+ var EmployeeFormView = FormView.extend({
+ config: _.extend({}, FormView.prototype.config, {
+ Controller: FormController,
+ Renderer: EmployeeFormRenderer
+ }),
+ });
+
+ viewRegistry.add('hr_employee_form', EmployeeFormView);
+
+ // USAGE OF CHAT MIXIN IN KANBAN VIEWS
+ var EmployeeKanbanRecord = KanbanRecord.extend(ChatMixin);
+
+ var EmployeeKanbanRenderer = KanbanRenderer.extend({
+ config: Object.assign({}, KanbanRenderer.prototype.config, {
+ KanbanRecord: EmployeeKanbanRecord,
+ }),
+ });
+
+ var EmployeeKanbanView = KanbanView.extend({
+ config: _.extend({}, KanbanView.prototype.config, {
+ Controller: KanbanController,
+ Renderer: EmployeeKanbanRenderer
+ }),
+ });
+
+ viewRegistry.add('hr_employee_kanban', EmployeeKanbanView);
+});
diff --git a/addons/hr/static/src/js/language.js b/addons/hr/static/src/js/language.js
new file mode 100644
index 00000000..35140c7f
--- /dev/null
+++ b/addons/hr/static/src/js/language.js
@@ -0,0 +1,27 @@
+odoo.define('hr.employee_language', function (require) {
+'use strict';
+
+var FormController = require('web.FormController');
+var FormView = require('web.FormView');
+var viewRegistry = require('web.view_registry');
+
+var EmployeeFormController = FormController.extend({
+ saveRecord: function () {
+ var self = this;
+ return this._super.apply(this, arguments).then(function () {
+ if (arguments[0].indexOf('lang') >= 0) {
+ self.do_action('reload_context');
+ }
+ });
+ },
+});
+
+var EmployeeProfileFormView = FormView.extend({
+ config: _.extend({}, FormView.prototype.config, {
+ Controller: EmployeeFormController,
+ }),
+});
+
+viewRegistry.add('hr_employee_profile_form', EmployeeProfileFormView);
+return EmployeeProfileFormView;
+});
diff --git a/addons/hr/static/src/js/many2one_avatar_employee.js b/addons/hr/static/src/js/many2one_avatar_employee.js
new file mode 100644
index 00000000..0d5a245a
--- /dev/null
+++ b/addons/hr/static/src/js/many2one_avatar_employee.js
@@ -0,0 +1,43 @@
+odoo.define('hr.Many2OneAvatarEmployee', function (require) {
+ "use strict";
+
+ // This module defines a variant of the Many2OneAvatarUser field widget,
+ // to support many2one fields pointing to 'hr.employee'. It also defines the
+ // kanban version of this widget.
+ //
+ // Usage:
+ // <field name="employee_id" widget="many2one_avatar_employee"/>
+
+ const fieldRegistry = require('web.field_registry');
+ const { Many2OneAvatarUser, KanbanMany2OneAvatarUser } = require('mail.Many2OneAvatarUser');
+
+ const { Component } = owl;
+
+ const Many2OneAvatarEmployeeMixin = {
+ supportedModels: ['hr.employee', 'hr.employee.public'],
+
+ //----------------------------------------------------------------------
+ // Private
+ //----------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ async _onAvatarClicked(ev) {
+ ev.stopPropagation(); // in list view, prevent from opening the record
+ const env = Component.env;
+ await env.messaging.openChat({ employeeId: this.value.res_id });
+ }
+ };
+
+ const Many2OneAvatarEmployee = Many2OneAvatarUser.extend(Many2OneAvatarEmployeeMixin);
+ const KanbanMany2OneAvatarEmployee = KanbanMany2OneAvatarUser.extend(Many2OneAvatarEmployeeMixin);
+
+ fieldRegistry.add('many2one_avatar_employee', Many2OneAvatarEmployee);
+ fieldRegistry.add('kanban.many2one_avatar_employee', KanbanMany2OneAvatarEmployee);
+
+ return {
+ Many2OneAvatarEmployee,
+ KanbanMany2OneAvatarEmployee,
+ };
+});
diff --git a/addons/hr/static/src/js/standalone_m2o_avatar_employee.js b/addons/hr/static/src/js/standalone_m2o_avatar_employee.js
new file mode 100644
index 00000000..64da43ec
--- /dev/null
+++ b/addons/hr/static/src/js/standalone_m2o_avatar_employee.js
@@ -0,0 +1,61 @@
+odoo.define('hr.StandaloneM2OAvatarEmployee', function (require) {
+ 'use strict';
+
+ const StandaloneFieldManagerMixin = require('web.StandaloneFieldManagerMixin');
+ const Widget = require('web.Widget');
+
+ const { Many2OneAvatarEmployee } = require('hr.Many2OneAvatarEmployee');
+
+ const StandaloneM2OAvatarEmployee = Widget.extend(StandaloneFieldManagerMixin, {
+ className: 'o_standalone_avatar_employee',
+
+ /**
+ * @override
+ */
+ init(parent, value) {
+ this._super(...arguments);
+ StandaloneFieldManagerMixin.init.call(this);
+ this.value = value;
+ },
+ /**
+ * @override
+ */
+ willStart() {
+ return Promise.all([this._super(...arguments), this._makeAvatarWidget()]);
+ },
+ /**
+ * @override
+ */
+ start() {
+ this.avatarWidget.$el.appendTo(this.$el);
+ return this._super(...arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Create a record, and initialize and start the avatar widget.
+ *
+ * @private
+ * @returns {Promise}
+ */
+ async _makeAvatarWidget() {
+ const modelName = 'hr.employee';
+ const fieldName = 'employee_id';
+ const recordId = await this.model.makeRecord(modelName, [{
+ name: fieldName,
+ relation: modelName,
+ type: 'many2one',
+ value: this.value,
+ }]);
+ const state = this.model.get(recordId);
+ this.avatarWidget = new Many2OneAvatarEmployee(this, fieldName, state);
+ this._registerWidget(recordId, fieldName, this.avatarWidget);
+ return this.avatarWidget.appendTo(document.createDocumentFragment());
+ },
+ });
+
+ return StandaloneM2OAvatarEmployee;
+});
diff --git a/addons/hr/static/src/models/employee/employee.js b/addons/hr/static/src/models/employee/employee.js
new file mode 100644
index 00000000..6d3c12cb
--- /dev/null
+++ b/addons/hr/static/src/models/employee/employee.js
@@ -0,0 +1,204 @@
+odoo.define('hr/static/src/models/employee/employee.js', function (require) {
+'use strict';
+
+const { registerNewModel } = require('mail/static/src/model/model_core.js');
+const { attr, one2one } = require('mail/static/src/model/model_field.js');
+
+function factory(dependencies) {
+
+ class Employee extends dependencies['mail.model'] {
+
+ //----------------------------------------------------------------------
+ // Public
+ //----------------------------------------------------------------------
+
+ /**
+ * @static
+ * @param {Object} data
+ * @returns {Object}
+ */
+ static convertData(data) {
+ const data2 = {};
+ if ('id' in data) {
+ data2.id = data.id;
+ }
+ if ('user_id' in data) {
+ data2.hasCheckedUser = true;
+ if (!data.user_id) {
+ data2.user = [['unlink']];
+ } else {
+ const partnerNameGet = data['user_partner_id'];
+ const partnerData = {
+ display_name: partnerNameGet[1],
+ id: partnerNameGet[0],
+ };
+ const userNameGet = data['user_id'];
+ const userData = {
+ id: userNameGet[0],
+ partner: [['insert', partnerData]],
+ display_name: userNameGet[1],
+ };
+ data2.user = [['insert', userData]];
+ }
+ }
+ return data2;
+ }
+
+ /**
+ * Performs the `read` RPC on the `hr.employee.public`.
+ *
+ * @static
+ * @param {Object} param0
+ * @param {Object} param0.context
+ * @param {string[]} param0.fields
+ * @param {integer[]} param0.ids
+ */
+ static async performRpcRead({ context, fields, ids }) {
+ const employeesData = await this.env.services.rpc({
+ model: 'hr.employee.public',
+ method: 'read',
+ args: [ids],
+ kwargs: {
+ context,
+ fields,
+ },
+ });
+ this.env.models['hr.employee'].insert(employeesData.map(employeeData =>
+ this.env.models['hr.employee'].convertData(employeeData)
+ ));
+ }
+
+ /**
+ * Performs the `search_read` RPC on `hr.employee.public`.
+ *
+ * @static
+ * @param {Object} param0
+ * @param {Object} param0.context
+ * @param {Array[]} param0.domain
+ * @param {string[]} param0.fields
+ */
+ static async performRpcSearchRead({ context, domain, fields }) {
+ const employeesData = await this.env.services.rpc({
+ model: 'hr.employee.public',
+ method: 'search_read',
+ kwargs: {
+ context,
+ domain,
+ fields,
+ },
+ });
+ this.env.models['hr.employee'].insert(employeesData.map(employeeData =>
+ this.env.models['hr.employee'].convertData(employeeData)
+ ));
+ }
+
+ /**
+ * Checks whether this employee has a related user and partner and links
+ * them if applicable.
+ */
+ async checkIsUser() {
+ return this.env.models['hr.employee'].performRpcRead({
+ ids: [this.id],
+ fields: ['user_id', 'user_partner_id'],
+ context: { active_test: false },
+ });
+ }
+
+ /**
+ * Gets the chat between the user of this employee and the current user.
+ *
+ * If a chat is not appropriate, a notification is displayed instead.
+ *
+ * @returns {mail.thread|undefined}
+ */
+ async getChat() {
+ if (!this.user && !this.hasCheckedUser) {
+ await this.async(() => this.checkIsUser());
+ }
+ // prevent chatting with non-users
+ if (!this.user) {
+ this.env.services['notification'].notify({
+ message: this.env._t("You can only chat with employees that have a dedicated user."),
+ type: 'info',
+ });
+ return;
+ }
+ return this.user.getChat();
+ }
+
+ /**
+ * Opens a chat between the user of this employee and the current user
+ * and returns it.
+ *
+ * If a chat is not appropriate, a notification is displayed instead.
+ *
+ * @param {Object} [options] forwarded to @see `mail.thread:open()`
+ * @returns {mail.thread|undefined}
+ */
+ async openChat(options) {
+ const chat = await this.async(() => this.getChat());
+ if (!chat) {
+ return;
+ }
+ await this.async(() => chat.open(options));
+ return chat;
+ }
+
+ /**
+ * Opens the most appropriate view that is a profile for this employee.
+ */
+ async openProfile() {
+ return this.env.messaging.openDocument({
+ id: this.id,
+ model: 'hr.employee.public',
+ });
+ }
+
+ //----------------------------------------------------------------------
+ // Private
+ //----------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ static _createRecordLocalId(data) {
+ return `${this.modelName}_${data.id}`;
+ }
+
+ }
+
+ Employee.fields = {
+ /**
+ * Whether an attempt was already made to fetch the user corresponding
+ * to this employee. This prevents doing the same RPC multiple times.
+ */
+ hasCheckedUser: attr({
+ default: false,
+ }),
+ /**
+ * Unique identifier for this employee.
+ */
+ id: attr(),
+ /**
+ * Partner related to this employee.
+ */
+ partner: one2one('mail.partner', {
+ inverse: 'employee',
+ related: 'user.partner',
+ }),
+ /**
+ * User related to this employee.
+ */
+ user: one2one('mail.user', {
+ inverse: 'employee',
+ }),
+ };
+
+ Employee.modelName = 'hr.employee';
+
+ return Employee;
+}
+
+registerNewModel('hr.employee', factory);
+
+});
diff --git a/addons/hr/static/src/models/messaging/messaging.js b/addons/hr/static/src/models/messaging/messaging.js
new file mode 100644
index 00000000..435761a9
--- /dev/null
+++ b/addons/hr/static/src/models/messaging/messaging.js
@@ -0,0 +1,36 @@
+odoo.define('hr/static/src/models/messaging/messaging.js', function (require) {
+'use strict';
+
+const {
+ registerInstancePatchModel,
+} = require('mail/static/src/model/model_core.js');
+
+registerInstancePatchModel('mail.messaging', 'hr/static/src/models/messaging/messaging.js', {
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ * @param {integer} [param0.employeeId]
+ */
+ async getChat({ employeeId }) {
+ if (employeeId) {
+ const employee = this.env.models['hr.employee'].insert({ id: employeeId });
+ return employee.getChat();
+ }
+ return this._super(...arguments);
+ },
+ /**
+ * @override
+ */
+ async openProfile({ id, model }) {
+ if (model === 'hr.employee' || model === 'hr.employee.public') {
+ const employee = this.env.models['hr.employee'].insert({ id });
+ return employee.openProfile();
+ }
+ return this._super(...arguments);
+ },
+});
+
+});
diff --git a/addons/hr/static/src/models/partner/partner.js b/addons/hr/static/src/models/partner/partner.js
new file mode 100644
index 00000000..cbbcb987
--- /dev/null
+++ b/addons/hr/static/src/models/partner/partner.js
@@ -0,0 +1,63 @@
+odoo.define('hr/static/src/models/partner/partner.js', function (require) {
+'use strict';
+
+const {
+ registerInstancePatchModel,
+ registerFieldPatchModel,
+} = require('mail/static/src/model/model_core.js');
+const { attr, one2one } = require('mail/static/src/model/model_field.js');
+
+registerInstancePatchModel('mail.partner', 'hr/static/src/models/partner/partner.js', {
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * Checks whether this partner has a related employee and links them if
+ * applicable.
+ */
+ async checkIsEmployee() {
+ await this.async(() => this.env.models['hr.employee'].performRpcSearchRead({
+ context: { active_test: false },
+ domain: [['user_partner_id', '=', this.id]],
+ fields: ['user_id', 'user_partner_id'],
+ }));
+ this.update({ hasCheckedEmployee: true });
+ },
+ /**
+ * When a partner is an employee, its employee profile contains more useful
+ * information to know who he is than its partner profile.
+ *
+ * @override
+ */
+ async openProfile() {
+ // limitation of patch, `this._super` becomes unavailable after `await`
+ const _super = this._super.bind(this, ...arguments);
+ if (!this.employee && !this.hasCheckedEmployee) {
+ await this.async(() => this.checkIsEmployee());
+ }
+ if (this.employee) {
+ return this.employee.openProfile();
+ }
+ return _super();
+ },
+});
+
+registerFieldPatchModel('mail.partner', 'hr/static/src/models/partner/partner.js', {
+ /**
+ * Employee related to this partner. It is computed through
+ * the inverse relation and should be considered read-only.
+ */
+ employee: one2one('hr.employee', {
+ inverse: 'partner',
+ }),
+ /**
+ * Whether an attempt was already made to fetch the employee corresponding
+ * to this partner. This prevents doing the same RPC multiple times.
+ */
+ hasCheckedEmployee: attr({
+ default: false,
+ }),
+});
+
+});
diff --git a/addons/hr/static/src/models/user/user.js b/addons/hr/static/src/models/user/user.js
new file mode 100644
index 00000000..a0482d50
--- /dev/null
+++ b/addons/hr/static/src/models/user/user.js
@@ -0,0 +1,18 @@
+odoo.define('hr/static/src/models/user/user.js', function (require) {
+'use strict';
+
+const {
+ registerFieldPatchModel,
+} = require('mail/static/src/model/model_core.js');
+const { one2one } = require('mail/static/src/model/model_field.js');
+
+registerFieldPatchModel('mail.user', 'hr/static/src/models/user/user.js', {
+ /**
+ * Employee related to this user.
+ */
+ employee: one2one('hr.employee', {
+ inverse: 'user',
+ }),
+});
+
+});
diff --git a/addons/hr/static/src/scss/hr.scss b/addons/hr/static/src/scss/hr.scss
new file mode 100644
index 00000000..af5f76ee
--- /dev/null
+++ b/addons/hr/static/src/scss/hr.scss
@@ -0,0 +1,22 @@
+.o_web_client .o_hr_employee_kanban {
+
+ .o_follow_btn.o_following {
+ .o_unfollow {
+ display: none;
+ }
+
+ &:hover {
+ .o_following {
+ display: none;
+ }
+ .o_unfollow {
+ display: inline;
+ }
+ }
+ }
+
+ .o_employee_summary_icons > span {
+ white-space: nowrap;
+ }
+
+}
diff --git a/addons/hr/static/src/xml/hr_templates.xml b/addons/hr/static/src/xml/hr_templates.xml
new file mode 100644
index 00000000..c805e592
--- /dev/null
+++ b/addons/hr/static/src/xml/hr_templates.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<template xml:space="preserve">
+ <t t-extend="UserMenu.Actions">
+ <t t-jquery="a[data-menu='settings']" t-operation='inner'>
+ My Profile
+ </t>
+ </t>
+</template>