summaryrefslogtreecommitdiff
path: root/addons/hr_skills/static
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_skills/static
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/hr_skills/static')
-rw-r--r--addons/hr_skills/static/description/icon.pngbin0 -> 7646 bytes
-rw-r--r--addons/hr_skills/static/description/icon.svg24
-rw-r--r--addons/hr_skills/static/src/css/hr_skills.scss152
-rw-r--r--addons/hr_skills/static/src/js/resume_widget.js240
-rw-r--r--addons/hr_skills/static/src/xml/resume_templates.xml55
-rw-r--r--addons/hr_skills/static/src/xml/skills_templates.xml24
-rw-r--r--addons/hr_skills/static/tests/widget_tests.js328
7 files changed, 823 insertions, 0 deletions
diff --git a/addons/hr_skills/static/description/icon.png b/addons/hr_skills/static/description/icon.png
new file mode 100644
index 00000000..845e53c8
--- /dev/null
+++ b/addons/hr_skills/static/description/icon.png
Binary files differ
diff --git a/addons/hr_skills/static/description/icon.svg b/addons/hr_skills/static/description/icon.svg
new file mode 100644
index 00000000..753554b0
--- /dev/null
+++ b/addons/hr_skills/static/description/icon.svg
@@ -0,0 +1,24 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="70" height="70" viewBox="0 0 70 70">
+ <defs>
+ <path id="icon-a" d="M4,5.35309892e-14 C36.4160122,9.87060235e-15 58.0836068,-3.97961823e-14 65,5.07020818e-14 C69,6.733808e-14 70,1 70,5 C70,43.0488877 70,62.4235458 70,65 C70,69 69,70 65,70 C61,70 9,70 4,70 C1,70 7.10542736e-15,69 7.10542736e-15,65 C7.25721566e-15,62.4676575 3.83358709e-14,41.8005206 3.60818146e-14,5 C-1.13686838e-13,1 1,5.75716207e-14 4,5.35309892e-14 Z"/>
+ <linearGradient id="icon-c" x1="100%" x2="0%" y1="0%" y2="100%">
+ <stop offset="0%" stop-color="#269396"/>
+ <stop offset="100%" stop-color="#218689"/>
+ </linearGradient>
+ <path id="icon-d" d="M35.5,22.75 C39.6529297,22.75 43.0195312,26.1166016 43.0195312,30.2695312 C43.0195312,34.4224609 39.6529297,37.7890625 35.5,37.7890625 C31.3470703,37.7890625 27.9804687,34.4224609 27.9804687,30.2695312 C27.9804687,26.1166016 31.3470703,22.75 35.5,22.75 Z M43.6256055,38.3165755 L40.7623112,37.6007161 C37.2411654,40.1333659 32.9730794,39.5681836 30.2377604,37.6007161 L27.3744661,38.3165755 C25.0790039,38.8904232 23.46875,40.9527799 23.46875,43.3188542 L23.46875,47.671875 C23.46875,49.0957161 24.6230339,50.25 26.046875,50.25 L44.953125,50.25 C46.3769661,50.25 47.53125,49.0957161 47.53125,47.671875 L47.53125,43.3188542 C47.53125,40.9527799 45.9209961,38.8904232 43.6256055,38.3165755 Z M50.3958333,39.6510417 C53.1644531,39.6510417 55.4088542,37.4066406 55.4088542,34.6380208 C55.4088542,31.869401 53.1644531,29.625 50.3958333,29.625 C47.6272135,29.625 45.3828125,31.869401 45.3828125,34.6380208 C45.3828125,37.4066406 47.6272135,39.6510417 50.3958333,39.6510417 Z M20.6041667,39.6510417 C23.3727865,39.6510417 25.6171875,37.4066406 25.6171875,34.6380208 C25.6171875,31.869401 23.3727865,29.625 20.6041667,29.625 C17.8355469,29.625 15.5911458,31.869401 15.5911458,34.6380208 C15.5911458,37.4066406 17.8355469,39.6510417 20.6041667,39.6510417 Z M22.3229167,47.671875 L22.3229167,43.3188542 C22.3229167,42.1335612 22.6518424,41.0125781 23.2326367,40.0533008 C21.0850586,41.1074674 18.6968555,40.6769206 17.0959831,39.5255013 L15.1870964,40.0027409 C13.6568359,40.3852344 12.5833333,41.7602344 12.5833333,43.3375456 L12.5833333,46.2395833 C12.5833333,47.1888346 13.352832,47.9583333 14.3020833,47.9583333 L22.3350195,47.9583333 C22.3273388,47.8630362 22.3233016,47.7674804 22.3229167,47.671875 Z M55.8129036,40.0026693 L53.9040169,39.5254297 C51.9041797,40.9638802 49.5434049,40.902793 47.7604883,40.0423437 C48.3454362,41.004056 48.6770833,42.1289779 48.6770833,43.3188542 L48.6770833,47.671875 C48.6770833,47.7683398 48.6722135,47.8636589 48.6649805,47.9583333 L56.6979167,47.9583333 C57.647168,47.9583333 58.4166667,47.1888346 58.4166667,46.2395833 L58.4166667,43.3375456 C58.4166667,41.7602344 57.3431641,40.3852344 55.8129036,40.0026693 Z"/>
+ <path id="icon-e" d="M35.5,20.75 C39.6529297,20.75 43.0195312,24.1166016 43.0195312,28.2695312 C43.0195312,32.4224609 39.6529297,35.7890625 35.5,35.7890625 C31.3470703,35.7890625 27.9804687,32.4224609 27.9804687,28.2695312 C27.9804687,24.1166016 31.3470703,20.75 35.5,20.75 Z M43.6256055,36.3165755 L40.7623112,35.6007161 C37.2411654,38.1333659 32.9730794,37.5681836 30.2377604,35.6007161 L27.3744661,36.3165755 C25.0790039,36.8904232 23.46875,38.9527799 23.46875,41.3188542 L23.46875,45.671875 C23.46875,47.0957161 24.6230339,48.25 26.046875,48.25 L44.953125,48.25 C46.3769661,48.25 47.53125,47.0957161 47.53125,45.671875 L47.53125,41.3188542 C47.53125,38.9527799 45.9209961,36.8904232 43.6256055,36.3165755 Z M50.3958333,37.6510417 C53.1644531,37.6510417 55.4088542,35.4066406 55.4088542,32.6380208 C55.4088542,29.869401 53.1644531,27.625 50.3958333,27.625 C47.6272135,27.625 45.3828125,29.869401 45.3828125,32.6380208 C45.3828125,35.4066406 47.6272135,37.6510417 50.3958333,37.6510417 Z M20.6041667,37.6510417 C23.3727865,37.6510417 25.6171875,35.4066406 25.6171875,32.6380208 C25.6171875,29.869401 23.3727865,27.625 20.6041667,27.625 C17.8355469,27.625 15.5911458,29.869401 15.5911458,32.6380208 C15.5911458,35.4066406 17.8355469,37.6510417 20.6041667,37.6510417 Z M22.3229167,45.671875 L22.3229167,41.3188542 C22.3229167,40.1335612 22.6518424,39.0125781 23.2326367,38.0533008 C21.0850586,39.1074674 18.6968555,38.6769206 17.0959831,37.5255013 L15.1870964,38.0027409 C13.6568359,38.3852344 12.5833333,39.7602344 12.5833333,41.3375456 L12.5833333,44.2395833 C12.5833333,45.1888346 13.352832,45.9583333 14.3020833,45.9583333 L22.3350195,45.9583333 C22.3273388,45.8630362 22.3233016,45.7674804 22.3229167,45.671875 Z M55.8129036,38.0026693 L53.9040169,37.5254297 C51.9041797,38.9638802 49.5434049,38.902793 47.7604883,38.0423437 C48.3454362,39.004056 48.6770833,40.1289779 48.6770833,41.3188542 L48.6770833,45.671875 C48.6770833,45.7683398 48.6722135,45.8636589 48.6649805,45.9583333 L56.6979167,45.9583333 C57.647168,45.9583333 58.4166667,45.1888346 58.4166667,44.2395833 L58.4166667,41.3375456 C58.4166667,39.7602344 57.3431641,38.3852344 55.8129036,38.0026693 Z"/>
+ </defs>
+ <g fill="none" fill-rule="evenodd">
+ <mask id="icon-b" fill="#fff">
+ <use xlink:href="#icon-a"/>
+ </mask>
+ <g mask="url(#icon-b)">
+ <rect width="70" height="70" fill="url(#icon-c)"/>
+ <path fill="#FFF" fill-opacity=".383" d="M4,1.8 L65,1.8 C67.6666667,1.8 69.3333333,1.13333333 70,-0.2 C70,2.46666667 70,3.46666667 70,2.8 L1.10547097e-14,2.8 C-1.65952376e-14,3.46666667 -2.9161925e-14,2.46666667 -2.66453526e-14,-0.2 C0.666666667,1.13333333 2,1.8 4,1.8 Z" transform="matrix(1 0 0 -1 0 2.8)"/>
+ <path fill="#393939" d="M44,47 L4,47 C2,47 -7.10542736e-15,46.8509317 0,42.826087 L1.81527147e-16,22.6291049 L17.2090667,6.04664397 L19.583071,9.5209307 L30.2767729,0.11143939 L40.9146315,10.270152 L46.6446282,6.41116033 L55.3045682,10.7749724 L52.3812234,16.1277957 L58.2417324,21.9036543 L44,47 Z" opacity=".324" transform="translate(0 23)"/>
+ <path fill="#000" fill-opacity=".383" d="M4,4 L65,4 C67.6666667,4 69.3333333,3 70,1 C70,3.66666667 70,5 70,5 L1.77635684e-15,5 C1.77635684e-15,5 1.77635684e-15,3.66666667 1.77635684e-15,1 C0.666666667,3 2,4 4,4 Z" transform="translate(0 65)"/>
+ <use fill="#000" fill-rule="nonzero" opacity=".3" xlink:href="#icon-d"/>
+ <use fill="#FFF" fill-rule="nonzero" xlink:href="#icon-e"/>
+ </g>
+ </g>
+</svg>
diff --git a/addons/hr_skills/static/src/css/hr_skills.scss b/addons/hr_skills/static/src/css/hr_skills.scss
new file mode 100644
index 00000000..9ff24615
--- /dev/null
+++ b/addons/hr_skills/static/src/css/hr_skills.scss
@@ -0,0 +1,152 @@
+.o_form_sheet {
+ $o-hrs-timeline-entry-padding: .5rem;
+ $o-hrs-timeline-dot-size: .6rem;
+
+ // Overall design
+ // =========================================
+ .o_hr_skills_group {
+ // Overwrite '.o_list_table' default design
+ .o_list_view {
+ .o_list_table {
+ cursor: auto;
+ }
+
+ tbody:first-of-type > .o_resume_group_header:first-child {
+ box-shadow: none;
+ }
+ }
+
+ // Use 'cursor:pointer' where is needed only
+ .o_data_row {
+ cursor: pointer;
+ }
+
+ // Deny user interaction to headers but keep access to buttons
+ .o_group_header, .o_resume_group_header {
+ &, &:hover {
+ background: none !important;
+ cursor: initial;
+ pointer-events: none;
+ }
+ .o_field_x2many_list_row_add, .o_field_x2many_list_row_add:hover {
+ cursor: pointer;
+ pointer-events: initial;
+ }
+ }
+ }
+
+ // Resumé design
+ // =========================================
+ .o_group_resume {
+ .o_data_row td {
+ padding: $o-hrs-timeline-entry-padding;
+
+ &.o_resume_timeline_cell {
+ div {
+ @include size($o-hrs-timeline-dot-size);
+ }
+
+ &:before {
+ @include o-position-absolute(0, $left: ($o-hrs-timeline-dot-size * .5 + $o-hrs-timeline-entry-padding));
+ @include size(1px, 100%);
+ margin-left: -.01rem;
+ background-color: $border-color;
+ content: "";
+ }
+ }
+ }
+
+ .o_resume_line_title, .o_resume_line_desc {
+ white-space: normal;
+ }
+
+ .o_resume_line_title, .o_resume_line_dates {
+ line-height: 1;
+ }
+
+ .o_resume_group_header + .o_data_row .o_resume_timeline_cell:before {
+ top: $o-hrs-timeline-entry-padding;
+ }
+
+ .o_data_row.o_data_row_last {
+ .o_resume_line_desc {
+ margin-bottom: $headings-margin-bottom;
+ }
+
+ .o_resume_timeline_cell:before {
+ height: $o-hrs-timeline-entry-padding;
+ }
+ }
+ }
+
+ // Skills design
+ // =========================================
+ .o_group_skills {
+ .o_resume_empty_helper {
+ display: none;
+ }
+
+ .o_group_header {
+ > .o_group_name {
+ padding: .8rem .5rem 0 0;
+ }
+
+ &:first-child > .o_group_name {
+ padding-top: 0;
+ }
+ }
+
+ .o_skill_cell {
+ padding-left: 0;
+ white-space: normal !important;
+
+ .o_progressbar {
+ display: inline-flex;
+ align-items: center;
+ }
+
+ .o_progress {
+ border: 0;
+ background: $gray-300;
+ height: 5px;
+ }
+
+ .o_progressbar_value {
+ width: auto;
+ font-size: $font-size-sm;
+ font-weight: bold;
+ }
+ }
+ }
+
+ // Editing mode
+ // =========================================
+ .o_form_view.o_form_editable & {
+ .o_group_name {
+ background-color: gray('200');
+ }
+
+ .o_resume_group_header .btn {
+ margin-top: .25rem;
+ margin-right: .4rem;
+ }
+
+ .o_group_skills .o_group_name {
+ padding-top: .4em;
+ padding-bottom: .4rem;
+ }
+
+ .o_group_name, .o_skill_cell {
+ padding-left: .5rem;
+ }
+
+ .o_group_skills .o_group_name > b, .o_hr_skills_group .o_horizontal_separator {
+ color: color-yiq(gray('200'));
+ font-style: italic;
+ }
+
+ .o_list_record_remove > button {
+ @include o-hover-text-color($text-muted, theme-color('danger'));
+ }
+ }
+}
diff --git a/addons/hr_skills/static/src/js/resume_widget.js b/addons/hr_skills/static/src/js/resume_widget.js
new file mode 100644
index 00000000..413942ba
--- /dev/null
+++ b/addons/hr_skills/static/src/js/resume_widget.js
@@ -0,0 +1,240 @@
+odoo.define('web.FieldResume', function (require) {
+"use strict";
+
+var time = require('web.time');
+var FieldOne2Many = require('web.relational_fields').FieldOne2Many;
+var FieldProgressBar = require('web.basic_fields').FieldProgressBar;
+var ListRenderer = require('web.ListRenderer');
+var field_registry = require('web.field_registry');
+
+var core = require('web.core');
+var qweb = core.qweb;
+var _t = core._t;
+
+var AbstractGroupedOne2ManyRenderer = ListRenderer.extend({
+ /**
+ * This abstract renderer is use to render a one2many field in a form view.
+ * The records in the one2many field are displayed grouped by a specific field.
+ *
+ * A concrete renderer can/should set:
+ * - groupBy: field to group records
+ * - dataRowTemplate: template to render a record's data
+ * - groupTitleTemplate (optional): template to render the header row of a group
+ * - addLineButtonTemplate (optional): template to render the 'Add a line' button at the end of each group (edit mode only)
+ **/
+
+ groupBy: '', // Field: records are grouped based on this field
+ groupTitleTemplate: 'hr_default_group_row', // Template used to render the title row of a group
+ dataRowTemplate: '', // Template used to render a record
+ addLineButtonTemplate: 'group_add_item',
+
+ /**
+ * Don't freeze the columns because as the header is empty, the algorithm
+ * won't work.
+ *
+ * @override
+ * @private
+ */
+ _freezeColumnWidths: function () {},
+
+ /**
+ * Renders a empty header
+ *
+ * @override
+ * @private
+ */
+ _renderHeader: function () {
+ return $('<thead/>');
+ },
+
+ /**
+ * Renders a empty footer
+ *
+ * @override
+ * @private
+ */
+ _renderFooter: function () {
+ return $('<tfoot/>');
+ },
+
+ /**
+ * @override
+ * @private
+ */
+ _renderGroupRow: function (display_name) {
+ return qweb.render(this.groupTitleTemplate, {display_name: display_name});
+ },
+
+ /**
+ * This method is meant to be overriten by concrete renderers and
+ * is called each time a row is rendered.
+ * It is a hook to format record's data before it's given to the qweb template.
+ *
+ * @private
+ */
+ _formatData: function (data) {
+ return data;
+ },
+
+ _renderRow: function (record, isLast) {
+ return $(qweb.render(this.dataRowTemplate, {
+ id: record.id,
+ data: this._formatData(record.data),
+ is_last: isLast,
+ }));
+ },
+
+ /**
+ * This method is meant to be overridden by concrete renderers.
+ * Returns a context used for the 'Add a line' button.
+ * It's useful to set default values.
+ * An 'Add a line' button is added after each group of records.
+ * The group passed as parameters allow to set a different context based on the group.
+ * If no records exist, group is undefined.
+ *
+ * @private
+ */
+ _getCreateLineContext: function (group) {
+ return {};
+ },
+
+ _renderTrashIcon: function() {
+ return qweb.render('hr_trash_button');
+ },
+
+ _renderAddItemButton: function (group) {
+ return qweb.render(this.addLineButtonTemplate, {
+ context: JSON.stringify(this._getCreateLineContext(group)),
+ });
+ },
+
+ _renderBody: function () {
+ var self = this;
+
+ var grouped_by = _.groupBy(this.state.data, function (record) {
+ return record.data[self.groupBy].res_id;
+ });
+
+ var groupTitle;
+ var $body = $('<tbody>');
+ for (var key in grouped_by) {
+ var group = grouped_by[key];
+ if (key === 'undefined') {
+ groupTitle = _t("Other");
+ } else {
+ groupTitle = group[0].data[self.groupBy].data.display_name;
+ }
+ var $title_row = $(self._renderGroupRow(groupTitle));
+ $body.append($title_row);
+
+ // Render each rows
+ group.forEach(function (record, index) {
+ var isLast = (index + 1 === group.length);
+ var $row = self._renderRow(record, isLast);
+ if (self.addTrashIcon) $row.append(self._renderTrashIcon());
+ $body.append($row);
+ });
+
+ if (self.addCreateLine) {
+ $title_row.find('.o_group_name').append(self._renderAddItemButton(group));
+ }
+ }
+
+ if ($body.is(':empty') && self.addCreateLine) {
+ $body.append(this._renderAddItemButton());
+ }
+ return $body;
+ },
+
+});
+
+var ResumeLineRenderer = AbstractGroupedOne2ManyRenderer.extend({
+
+ groupBy: 'line_type_id',
+ groupTitleTemplate: 'hr_resume_group_row',
+ dataRowTemplate: 'hr_resume_data_row',
+
+ _formatData: function (data) {
+ var dateFormat = time.getLangDateFormat();
+ var date_start = data.date_start && data.date_start.format(dateFormat) || "";
+ var date_end = data.date_end && data.date_end.format(dateFormat) || _t("Current");
+ return _.extend(data, {
+ date_start: date_start,
+ date_end: date_end,
+ });
+ },
+
+ _getCreateLineContext: function (group) {
+ var ctx = this._super(group);
+ return group ? _.extend({default_line_type_id: group[0].data[this.groupBy] && group[0].data[this.groupBy].data.id || ""}, ctx) : ctx;
+ },
+
+ _render: function () {
+ var self = this;
+ return this._super().then(function () {
+ self.$el.find('table').removeClass('table-striped o_list_table_ungrouped');
+ self.$el.find('table').addClass('o_resume_table table-borderless');
+ });
+ },
+});
+
+
+var SkillsRenderer = AbstractGroupedOne2ManyRenderer.extend({
+
+ groupBy: 'skill_type_id',
+ dataRowTemplate: 'hr_skill_data_row',
+
+ _renderRow: function (record) {
+ var $row = this._super(record);
+ // Add progress bar widget at the end of rows
+ var $td = $('<td/>', {class: 'o_data_cell o_skill_cell'});
+ var progress = new FieldProgressBar(this, 'level_progress', record, {
+ current_value: record.data.level_progress,
+ attrs: this.arch.attrs,
+ });
+ progress.appendTo($td);
+ return $row.append($td);
+ },
+
+ _getCreateLineContext: function (group) {
+ var ctx = this._super(group);
+ return group ? _.extend({ default_skill_type_id: group[0].data[this.groupBy].data.id }, ctx) : ctx;
+ },
+
+ _render: function () {
+ var self = this;
+ return this._super().then(function () {
+ self.$el.find('table').toggleClass('table-striped');
+ });
+ },
+});
+
+
+var FieldResume = FieldOne2Many.extend({
+
+ /**
+ * @override
+ * @private
+ */
+ _getRenderer: function () {
+ return ResumeLineRenderer;
+ },
+});
+
+var FieldSkills = FieldOne2Many.extend({
+
+ /**
+ * @override
+ * @private
+ */
+ _getRenderer: function () {
+ return SkillsRenderer;
+ },
+});
+
+field_registry.add('hr_resume', FieldResume);
+field_registry.add('hr_skills', FieldSkills);
+
+return FieldResume;
+
+});
diff --git a/addons/hr_skills/static/src/xml/resume_templates.xml b/addons/hr_skills/static/src/xml/resume_templates.xml
new file mode 100644
index 00000000..5db3b006
--- /dev/null
+++ b/addons/hr_skills/static/src/xml/resume_templates.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+<t t-name="hr_resume_data_row">
+ <tr class="o_data_row" t-attf-class="o_data_row #{is_last? 'o_data_row_last' : ''}" t-att-data-id="id">
+ <t t-if="data.display_type === 'classic'">
+ <td class="o_resume_timeline_cell position-relative pr-lg-2">
+ <div class="rounded-circle bg-info position-relative"/>
+ </td>
+ <td class="o_data_cell pt-0 w-100">
+ <div class="o_resume_line" t-att-data-id="id">
+ <small class="o_resume_line_dates">
+ <b t-esc="data.date_start"/> - <b t-esc="data.date_end"/>
+ </small>
+ <h4 class="o_resume_line_title mt-2" t-esc="data.name"/>
+ <p t-if="data.description" class="o_resume_line_desc" t-esc="data.description"/>
+ </div>
+ </td>
+ </t>
+ </tr>
+</t>
+
+<t t-name="hr_trash_button">
+ <td class="o_list_record_remove pr-3">
+ <button name="delete" arial-label="Delete row" class="btn btn-secondary">
+ <i class="fa fa-trash"/>
+ </button>
+ </td>
+</t>
+
+<t t-name="hr_resume_group_row">
+ <tr class="o_resume_group_header">
+ <td class="o_group_name" colspan="100%"><span class="o_horizontal_separator my-0" t-esc="display_name"/></td>
+ </tr>
+</t>
+
+<t t-name="group_add_item">
+ <t t-set="empty" t-value="Object.keys(context).length == 2"/>
+
+ <div t-attf-class="o_field_x2many_list_row_add #{empty? 'd-block w-100' : 'd-inline pull-right'}">
+ <div t-if="empty" class="o_resume_empty_helper o_horizontal_separator text-muted my-0">
+ <em>Resumé empty</em>
+ </div>
+ <a href="#"
+ role="button"
+ t-attf-class="btn o-kanban-button-new #{empty? 'btn-primary mt-3' : 'btn-secondary btn-sm'}"
+ t-attf-data-context="{{ context }}">
+ <t t-if="empty">CREATE A NEW ENTRY</t>
+ <t t-else="">ADD</t>
+ </a>
+ </div>
+</t>
+
+
+</templates>
diff --git a/addons/hr_skills/static/src/xml/skills_templates.xml b/addons/hr_skills/static/src/xml/skills_templates.xml
new file mode 100644
index 00000000..e078ca3e
--- /dev/null
+++ b/addons/hr_skills/static/src/xml/skills_templates.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+<t t-name="hr_skill_data_row">
+ <tr class="o_data_row" t-att-data-id="id">
+ <td class="o_data_cell o_skill_cell w-100">
+ <t t-esc="data.skill_id.data.display_name"/>
+ </td>
+ <td class="o_data_cell o_skill_cell pr-3">
+ <t t-esc="data.skill_level_id.data.display_name"/>
+ </td>
+ </tr>
+</t>
+
+<t t-name="hr_default_group_row">
+ <tr class="o_group_header o_group_has_content">
+ <td class="o_group_name border-0 pr-2" colspan="99">
+ <b t-esc="display_name"/>
+ </td>
+ </tr>
+</t>
+
+
+</templates>
diff --git a/addons/hr_skills/static/tests/widget_tests.js b/addons/hr_skills/static/tests/widget_tests.js
new file mode 100644
index 00000000..c8d417ec
--- /dev/null
+++ b/addons/hr_skills/static/tests/widget_tests.js
@@ -0,0 +1,328 @@
+odoo.define('hr_skills.field_one_to_many_group_tests', function (require) {
+ "use strict";
+
+ var FormView = require('web.FormView');
+ var testUtils = require('web.test_utils');
+
+ var createView = testUtils.createView;
+
+ QUnit.module('skills_widgets', {
+ beforeEach: function () {
+ this.data = {
+ partner: {
+ fields: {
+ display_name: { string: "Displayed name", type: "char" },
+ line_ids: { string: "one2many field", type: "one2many", relation: 'line', relation_field: 'trululu' },
+ skill_ids: { string: "one2many field", type: "one2many", relation: 'partner_skill', relation_field: 'partner_id' },
+ },
+ records: [{
+ id: 1,
+ display_name: "first record",
+ line_ids: [37, 38, 39],
+ skill_ids: [75, 76, 77]
+ }],
+ onchanges: {},
+ },
+ partner_skill: {
+ fields: {
+ skill_id: { string: "Name", type: "many2one", relation: 'skill'},
+ skill_type_id: { string: "Type", type: "many2one", relation: 'skill_type' },
+ skill_level_id: { string: "Level", type: "many2one", relation: 'skill_level' },
+ level_progress: { string: "Progress", type: "int" },
+ },
+ records: [{
+ id: 75,
+ skill_id: 444,
+ skill_type_id: 221,
+ skill_level_id: 112,
+ level_progress: 50,
+ }, {
+ id: 76,
+ skill_id: 445,
+ skill_type_id: 222,
+ skill_level_id: 111,
+ level_progress: 50,
+ }, {
+ id: 77,
+ skill_id: 446,
+ skill_type_id: 222,
+ skill_level_id: 111,
+ level_progress: 70,
+ }],
+ },
+ skill: {
+ fields: {
+ name: { string: "Name", type: "char" },
+ },
+ records: [
+ { id: 444, name: 'Python' },
+ { id: 445, name: 'Piano' },
+ { id: 446, name: 'Flute' },
+ ]
+ },
+ skill_level: {
+ fields: {
+ name: { string: "Name", type: "char" },
+ },
+ records: [
+ { id: 111, name: 'L1' },
+ { id: 112, name: 'Intermediate' }
+ ]
+ },
+ skill_type: {
+ fields: {
+ name: { string: "Name", type: "char" },
+ },
+ records: [
+ { id: 221, name: 'Dev' },
+ { id: 222, name: 'Music' },
+ ]
+ },
+ line: {
+ fields: {
+ name: { string: "Name", type: "char" },
+ line_type_id: { string: "Type", relation: 'line_type', type: "many2one" },
+ description: { string: "Description", type: "text" },
+ date_start: { string: "Date start", type: "date" },
+ date_end: { string: "Date end", type: "date" },
+ trululu: { string: "Trululu", type: "many2one", relation: 'partner' },
+ display_type: { string: "display type", type: "selection"},
+ },
+ records: [{
+ id: 37,
+ name: "ULB",
+ line_type_id: 50,
+ date_start: "2017-01-25",
+ date_end: "2019-01-25",
+ description: 'Hello',
+ trululu: 1,
+ display_type: 'classic',
+ }, {
+ id: 38,
+ name: "UCL",
+ line_type_id: 50,
+ date_start: "2013-01-25",
+ date_end: "2014-01-25",
+ description: 'World',
+ trululu: 1,
+ display_type: 'classic',
+ }, {
+ id: 39,
+ name: "KUL",
+ line_type_id: 51,
+ date_start: "2008-01-25",
+ description: 'Hi',
+ trululu: 1,
+ display_type: 'classic',
+ }],
+ onchanges: {},
+ },
+ line_type: {
+ fields: {
+ name: { string: "Name", type: "char" },
+ },
+ records: [{
+ id: 50,
+ name: 'AAA',
+ }, {
+ id: 51,
+ name: 'BBB'
+ }],
+ }
+ };
+ }
+ }, function () {
+ QUnit.test('resumé one2many field group by field render', async function (assert) {
+ assert.expect(16);
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="line_ids" widget="hr_resume">' +
+ '<tree>' +
+ '<field name="name"/>' +
+ '<field name="line_type_id"/>' +
+ '<field name="description"/>' +
+ '<field name="date_start"/>' +
+ '<field name="date_end"/>' +
+ '<field name="display_type"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ });
+ var $headers = form.$('.o_resume_group_header');
+ assert.strictEqual($headers.length, 2, 'There should be 2 headers');
+ assert.strictEqual($headers.find('td:contains(AAA)').length, 1, "it should have line type AAA");
+ assert.strictEqual($headers.find('td:contains(BBB)').length, 1, "it should have line type BBB");
+
+ var dataRows = form.$('.o_data_row');
+ assert.strictEqual(dataRows.length, 3, 'There should be 3 data rows');
+
+ var $row = $(dataRows[0]);
+ assert.strictEqual($row.find('td:contains(01/25/2017)').length, 1, "it should have start date 01/25/2017");
+ assert.strictEqual($row.find('td:contains(01/25/2019)').length, 1, "it should have end date 01/25/2019");
+ assert.strictEqual($row.find('td:contains(ULB)').length, 1, "it should have line name ULB");
+ assert.strictEqual($row.find('td:contains(Hello)').length, 1, "it should have line description Hello");
+
+ $row = $(dataRows[1]);
+ assert.strictEqual($row.find('td:contains(01/25/2013)').length, 1, "it should have start date 01/25/2013");
+ assert.strictEqual($row.find('td:contains(01/25/2014)').length, 1, "it should have end date 01/25/2014");
+ assert.strictEqual($row.find('td:contains(UCL)').length, 1, "it should have line name UCL");
+ assert.strictEqual($row.find('td:contains(World)').length, 1, "it should have line description World");
+
+ $row = $(dataRows[2]);
+ assert.strictEqual($row.find('td:contains(01/25/2008)').length, 1, "it should have start date 01/25/2008");
+ assert.strictEqual($row.find('td:contains(Current)').length, 1, "it should have end date Current");
+ assert.strictEqual($row.find('td:contains(KUL)').length, 1, "it should have line name KUL");
+ assert.strictEqual($row.find('td:contains(Hi)').length, 1, "it should have line description Hi");
+
+ form.destroy();
+ });
+ QUnit.test('resumé one2many field group by field create', async function (assert) {
+ assert.expect(5);
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="line_ids" widget="hr_resume">' +
+ '<tree>' +
+ '<field name="name"/>' +
+ '<field name="line_type_id"/>' +
+ '<field name="description"/>' +
+ '<field name="date_start"/>' +
+ '<field name="date_end"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ archs: {
+ 'line,false,form': '<form>' +
+ '<field name="name"/>' +
+ '<field name="line_type_id"/>' +
+ '<field name="description"/>' +
+ '<field name="date_start"/>' +
+ '<field name="date_end"/>' +
+ '</form>',
+ },
+ res_id: 1,
+ mockRPC: function (route, args) {
+ var result = this._super.apply(this, arguments);
+ if (args.method === 'write') {
+ var new_line_data = args.args[1].line_ids[3][2];
+ assert.strictEqual(new_line_data.date_end, '2030-01-01');
+ assert.strictEqual(new_line_data.date_start, '2025-01-01');
+ assert.strictEqual(new_line_data.line_type_id, 50, "it should have the line type from context");
+ assert.strictEqual(new_line_data.name, 'new line');
+ assert.strictEqual(new_line_data.description, 'new description');
+ }
+ return result;
+ },
+ });
+
+ await testUtils.form.clickEdit(form);
+ await testUtils.dom.click(form.$('.o_field_x2many_list_row_add a')[0]);
+
+ // Fill line form (type should be set from the add button context)
+ await testUtils.fields.editInput($('input[name="name"]'), 'new line');
+ await testUtils.fields.editInput($('textarea[name="description"]'), 'new description');
+ await testUtils.fields.editSelect($('input[name="date_start"]'), '2025-01-01');
+ await testUtils.fields.editSelect($('input[name="date_end"]'), '2030-01-01');
+ await testUtils.modal.clickButton('Save & Close');
+
+ await testUtils.form.clickSave(form);
+ form.destroy();
+ });
+ QUnit.test('resumé one2many field group by field delete', async function (assert) {
+ assert.expect(2);
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="line_ids" widget="hr_resume">' +
+ '<tree>' +
+ '<field name="name"/>' +
+ '<field name="line_type_id"/>' +
+ '<field name="description"/>' +
+ '<field name="date_start"/>' +
+ '<field name="date_end"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ archs: {
+ 'line,false,form': '<form>' +
+ '<field name="name"/>' +
+ '<field name="line_type_id"/>' +
+ '<field name="description"/>' +
+ '<field name="date_start"/>' +
+ '<field name="date_end"/>' +
+ '</form>',
+ },
+ res_id: 1,
+ mockRPC: function (route, args) {
+ var result = this._super.apply(this, arguments);
+ if (args.method === 'write') {
+ var orm_cmd = args.args[1].line_ids[2][0];
+ var id = args.args[1].line_ids[2][1];
+ assert.strictEqual(orm_cmd, 2, "it should delete resume line");
+ assert.strictEqual(id, 37, "it should delete resume line #37");
+ }
+ return result;
+ },
+ });
+
+ await testUtils.form.clickEdit(form);
+ await testUtils.dom.click(form.$('.o_list_record_remove')[0]);
+
+ await testUtils.form.clickSave(form);
+ form.destroy();
+ });
+
+ QUnit.test('skills one2many field group by field render', async function (assert) {
+ assert.expect(13);
+ var form = await createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form string="Partners">' +
+ '<field name="skill_ids" widget="hr_skills">' +
+ '<tree>' +
+ '<field name="skill_id"/>' +
+ '<field name="skill_type_id"/>' +
+ '<field name="skill_level_id"/>' +
+ '<field name="level_progress"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ var $headers = form.$('.o_group_header');
+ assert.strictEqual($headers.length, 2, 'There should be 2 headers');
+ assert.strictEqual($headers.find('td:contains(Dev)').length, 1, "it should have skill type Dev");
+ assert.strictEqual($headers.find('td:contains(Music)').length, 1, "it should have skill type Music");
+
+ var dataRows = form.$('.o_data_row');
+ assert.strictEqual(dataRows.length, 3, 'There should be 3 data rows');
+
+ var $row = $(dataRows[0]);
+ assert.strictEqual($row.find('td:contains(Python)').length, 1, "it should have skill name Python");
+ assert.strictEqual($row.find('td:contains(Intermediate)').length, 1, "it should have skill name Intermediate");
+ assert.strictEqual($row.find('td:contains(50)').length, 1, "it should have skill progress 50");
+
+ $row = $(dataRows[1]);
+ assert.strictEqual($row.find('td:contains(Piano)').length, 1, "it should have skill name Piano");
+ assert.strictEqual($row.find('td:contains(L1)').length, 1, "it should have skill level L1");
+ assert.strictEqual($row.find('td:contains(50)').length, 1, "it should have skill progress 50");
+
+ $row = $(dataRows[2]);
+ assert.strictEqual($row.find('td:contains(Flute)').length, 1, "it should have skill name Flute");
+ assert.strictEqual($row.find('td:contains(L1)').length, 1, "it should have skill level L1");
+ assert.strictEqual($row.find('td:contains(70)').length, 1, "it should have skill progress 70");
+
+ form.destroy();
+ });
+ });
+});