diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/hr/static/src | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/hr/static/src')
| -rw-r--r-- | addons/hr/static/src/bugfix/bugfix.js | 10 | ||||
| -rw-r--r-- | addons/hr/static/src/bugfix/bugfix.scss | 6 | ||||
| -rw-r--r-- | addons/hr/static/src/bugfix/bugfix.xml | 11 | ||||
| -rw-r--r-- | addons/hr/static/src/bugfix/bugfix_tests.js | 18 | ||||
| -rw-r--r-- | addons/hr/static/src/default_image.png | 0 | ||||
| -rw-r--r-- | addons/hr/static/src/img/default_image.png | bin | 0 -> 2723 bytes | |||
| -rw-r--r-- | addons/hr/static/src/js/chat.js | 74 | ||||
| -rw-r--r-- | addons/hr/static/src/js/language.js | 27 | ||||
| -rw-r--r-- | addons/hr/static/src/js/many2one_avatar_employee.js | 43 | ||||
| -rw-r--r-- | addons/hr/static/src/js/standalone_m2o_avatar_employee.js | 61 | ||||
| -rw-r--r-- | addons/hr/static/src/models/employee/employee.js | 204 | ||||
| -rw-r--r-- | addons/hr/static/src/models/messaging/messaging.js | 36 | ||||
| -rw-r--r-- | addons/hr/static/src/models/partner/partner.js | 63 | ||||
| -rw-r--r-- | addons/hr/static/src/models/user/user.js | 18 | ||||
| -rw-r--r-- | addons/hr/static/src/scss/hr.scss | 22 | ||||
| -rw-r--r-- | addons/hr/static/src/xml/hr_templates.xml | 8 |
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 Binary files differnew file mode 100644 index 00000000..87fdbc5a --- /dev/null +++ b/addons/hr/static/src/img/default_image.png 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> |
