[REF] hr: update to the new module system
This commit is contained in:
@@ -1,14 +1,18 @@
|
||||
openerp.hr = function(openerp) {
|
||||
"use strict";
|
||||
odoo.define('hr.hr', ['web_kanban.common'], function(require) {
|
||||
"use strict";
|
||||
|
||||
openerp.web_kanban.KanbanRecord.include({
|
||||
on_card_clicked: function() {
|
||||
if (this.view.dataset.model === 'hr.department') {
|
||||
this.$('.oe_kanban_department .oe_kanban_content a').first().click();
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
var common = require('web_kanban.common');
|
||||
|
||||
common.KanbanRecord.include({
|
||||
on_card_clicked: function() {
|
||||
if (this.view.dataset.model === 'hr.department') {
|
||||
this.$('.oe_kanban_department .oe_kanban_content a').first().click();
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
@@ -1,78 +1,83 @@
|
||||
openerp.hr = function(session) {
|
||||
var _t = session.web._t;
|
||||
var QWeb = session.web.qweb;
|
||||
odoo.define('hr.suggestions', ['mail.mail', 'web.core', 'web.data', 'web.Widget'], function (require) {
|
||||
"use strict";
|
||||
|
||||
var suggestions = session.suggestions;
|
||||
var removed_suggested_employee = session.suggestions.removed_suggested_employee = [];
|
||||
var mail = require('mail.mail');
|
||||
var core = require('web.core');
|
||||
var data = require('web.data');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
suggestions.Employees = session.web.Widget.extend({
|
||||
events: {
|
||||
'click .oe_suggestion_remove.oe_suggestion_employee': 'stop_employee_suggestion',
|
||||
'click .oe_suggestion_remove_item.oe_suggestion_employee': 'remove_employee_suggestion',
|
||||
'click .oe_suggestion_follow': 'follow_employee',
|
||||
},
|
||||
var QWeb = core.qweb;
|
||||
|
||||
init: function () {
|
||||
this._super(this, arguments);
|
||||
this.hr_employee = new session.web.DataSetSearch(this, 'hr.employee');
|
||||
this.res_users = new session.web.DataSetSearch(this, 'res.users');
|
||||
this.employees = [];
|
||||
},
|
||||
var removed_suggested_employee = [];
|
||||
|
||||
start: function () {
|
||||
this._super.apply(this, arguments);
|
||||
return this.fetch_suggested_employee();
|
||||
},
|
||||
var Employees = Widget.extend({
|
||||
events: {
|
||||
'click .oe_suggestion_remove.oe_suggestion_employee': 'stop_employee_suggestion',
|
||||
'click .oe_suggestion_remove_item.oe_suggestion_employee': 'remove_employee_suggestion',
|
||||
'click .oe_suggestion_follow': 'follow_employee',
|
||||
},
|
||||
|
||||
fetch_suggested_employee: function () {
|
||||
var self = this;
|
||||
var employee = self.hr_employee.call('get_suggested_thread', {'removed_suggested_threads': removed_suggested_employee}).then(function (res) {
|
||||
_(res).each(function (result) {
|
||||
result['image']=self.session.url('/web/binary/image', {model: 'hr.employee', field: 'image_small', id: result.id});
|
||||
});
|
||||
self.employees = res;
|
||||
init: function () {
|
||||
this._super(this, arguments);
|
||||
this.hr_employee = new data.DataSetSearch(this, 'hr.employee');
|
||||
this.res_users = new data.DataSetSearch(this, 'res.users');
|
||||
this.employees = [];
|
||||
},
|
||||
|
||||
start: function () {
|
||||
this._super.apply(this, arguments);
|
||||
return this.fetch_suggested_employee();
|
||||
},
|
||||
|
||||
fetch_suggested_employee: function () {
|
||||
var self = this;
|
||||
var employee = self.hr_employee.call('get_suggested_thread', {'removed_suggested_threads': removed_suggested_employee}).then(function (res) {
|
||||
_(res).each(function (result) {
|
||||
result['image']=self.session.url('/web/binary/image', {model: 'hr.employee', field: 'image_small', id: result.id});
|
||||
});
|
||||
return $.when(employee).done(this.proxy('display_suggested_employees'));
|
||||
},
|
||||
self.employees = res;
|
||||
});
|
||||
return $.when(employee).done(this.proxy('display_suggested_employees'));
|
||||
},
|
||||
|
||||
display_suggested_employees: function () {
|
||||
var suggested_employees = this.$('.oe_sidebar_suggestion.oe_suggestion_employee');
|
||||
if (suggested_employees) {
|
||||
suggested_employees.remove();
|
||||
}
|
||||
if (this.employees.length === 0) {
|
||||
return this.$el.empty();
|
||||
}
|
||||
return this.$el.empty().html(QWeb.render('hr.suggestions.employees', {'widget': this}));
|
||||
},
|
||||
|
||||
follow_employee: function (event) {
|
||||
var self = this;
|
||||
var employee_id = parseInt($(event.currentTarget).attr('id'), 10);
|
||||
return this.hr_employee.call('message_subscribe_users', [[employee_id], [this.session.uid], undefined]).then(function(res) {
|
||||
self.fetch_suggested_employee();
|
||||
});
|
||||
},
|
||||
|
||||
remove_employee_suggestion: function (event) {
|
||||
removed_suggested_employee.push($(event.currentTarget).attr('id'));
|
||||
return this.fetch_suggested_employee();
|
||||
},
|
||||
|
||||
stop_employee_suggestion: function (event) {
|
||||
var self = this;
|
||||
return this.res_users.call('stop_showing_employees_suggestions', [this.session.uid]).then(function(res) {
|
||||
self.$(".oe_sidebar_suggestion.oe_suggestion_employee").hide();
|
||||
});
|
||||
display_suggested_employees: function () {
|
||||
var suggested_employees = this.$('.oe_sidebar_suggestion.oe_suggestion_employee');
|
||||
if (suggested_employees) {
|
||||
suggested_employees.remove();
|
||||
}
|
||||
});
|
||||
if (this.employees.length === 0) {
|
||||
return this.$el.empty();
|
||||
}
|
||||
return this.$el.empty().html(QWeb.render('hr.suggestions.employees', {'widget': this}));
|
||||
},
|
||||
|
||||
session.mail.WallSidebar.include({
|
||||
start: function () {
|
||||
this._super.apply(this, arguments);
|
||||
var sug_employees = new suggestions.Employees(this);
|
||||
return sug_employees.appendTo(this.$('.oe_suggestions_employees'));
|
||||
},
|
||||
});
|
||||
follow_employee: function (event) {
|
||||
var self = this;
|
||||
var employee_id = parseInt($(event.currentTarget).attr('id'), 10);
|
||||
return this.hr_employee.call('message_subscribe_users', [[employee_id], [this.session.uid], undefined]).then(function(res) {
|
||||
self.fetch_suggested_employee();
|
||||
});
|
||||
},
|
||||
|
||||
};
|
||||
remove_employee_suggestion: function (event) {
|
||||
removed_suggested_employee.push($(event.currentTarget).attr('id'));
|
||||
return this.fetch_suggested_employee();
|
||||
},
|
||||
|
||||
stop_employee_suggestion: function (event) {
|
||||
var self = this;
|
||||
return this.res_users.call('stop_showing_employees_suggestions', [this.session.uid]).then(function(res) {
|
||||
self.$(".oe_sidebar_suggestion.oe_suggestion_employee").hide();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
mail.WallSidebar.include({
|
||||
start: function () {
|
||||
this._super.apply(this, arguments);
|
||||
var sug_employees = new Employees(this);
|
||||
return sug_employees.appendTo(this.$('.oe_suggestions_employees'));
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,79 +1,86 @@
|
||||
odoo.define('hr_attendance.hr_attendance', ['web.core', 'web.data', 'web.formats', 'web.Model', 'web.SystrayMenu', 'web.time', 'web.Widget'], function(require) {
|
||||
"use strict";
|
||||
|
||||
openerp.hr_attendance = function (instance) {
|
||||
|
||||
var QWeb = instance.web.qweb;
|
||||
var _t = instance.web._t;
|
||||
var _lt = instance.web._lt;
|
||||
var core = require('web.core');
|
||||
var data = require('web.data');
|
||||
var formats = require('web.formats');
|
||||
var Model = require('web.Model');
|
||||
var SystrayMenu = require('web.SystrayMenu');
|
||||
var time = require('web.time');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
instance.hr_attendance.AttendanceSlider = instance.web.Widget.extend({
|
||||
template: 'AttendanceSlider',
|
||||
init: function (parent) {
|
||||
this._super(parent);
|
||||
this.set({"signed_in": false});
|
||||
},
|
||||
start: function() {
|
||||
var self = this;
|
||||
var tmp = function() {
|
||||
var $sign_in_out_icon = this.$('#oe_attendance_sign_in_out_icon');
|
||||
$sign_in_out_icon.toggleClass("fa-sign-in", ! this.get("signed_in"));
|
||||
$sign_in_out_icon.toggleClass("fa-sign-out", this.get("signed_in"));
|
||||
};
|
||||
this.on("change:signed_in", this, tmp);
|
||||
_.bind(tmp, this)();
|
||||
this.$(".oe_attendance_sign_in_out").click(function(ev) {
|
||||
ev.preventDefault();
|
||||
self.do_update_attendance();
|
||||
});
|
||||
this.$el.tooltip({
|
||||
title: function() {
|
||||
var last_text = instance.web.format_value(self.last_sign, {type: "datetime"});
|
||||
var current_text = instance.web.format_value(new Date(), {type: "datetime"});
|
||||
var duration = self.last_sign ? $.timeago(self.last_sign) : "none";
|
||||
if (self.get("signed_in")) {
|
||||
return _.str.sprintf(_t("Last sign in: %s,<br />%s.<br />Click to sign out."), last_text, duration);
|
||||
} else {
|
||||
return _.str.sprintf(_t("Click to Sign In at %s."), current_text);
|
||||
}
|
||||
},
|
||||
});
|
||||
return this.check_attendance();
|
||||
},
|
||||
do_update_attendance: function () {
|
||||
var self = this;
|
||||
var hr_employee = new instance.web.DataSet(self, 'hr.employee');
|
||||
hr_employee.call('attendance_action_change', [
|
||||
[self.employee.id]
|
||||
]).done(function (result) {
|
||||
self.last_sign = new Date();
|
||||
self.set({"signed_in": ! self.get("signed_in")});
|
||||
});
|
||||
},
|
||||
check_attendance: function () {
|
||||
var self = this;
|
||||
self.employee = false;
|
||||
this.$el.hide();
|
||||
var employee = new instance.web.DataSetSearch(self, 'hr.employee', self.session.user_context, [
|
||||
['user_id', '=', self.session.uid]
|
||||
]);
|
||||
return employee.read_slice(['id', 'name', 'state', 'last_sign', 'attendance_access']).then(function (res) {
|
||||
if (_.isEmpty(res) )
|
||||
return;
|
||||
if (res[0].attendance_access === false){
|
||||
return;
|
||||
var _t = core._t;
|
||||
|
||||
var AttendanceSlider = Widget.extend({
|
||||
template: 'AttendanceSlider',
|
||||
init: function (parent) {
|
||||
this._super(parent);
|
||||
this.set({"signed_in": false});
|
||||
},
|
||||
start: function() {
|
||||
var self = this;
|
||||
var tmp = function() {
|
||||
var $sign_in_out_icon = this.$('#oe_attendance_sign_in_out_icon');
|
||||
$sign_in_out_icon.toggleClass("fa-sign-in", ! this.get("signed_in"));
|
||||
$sign_in_out_icon.toggleClass("fa-sign-out", this.get("signed_in"));
|
||||
};
|
||||
this.on("change:signed_in", this, tmp);
|
||||
_.bind(tmp, this)();
|
||||
this.$(".oe_attendance_sign_in_out").click(function(ev) {
|
||||
ev.preventDefault();
|
||||
self.do_update_attendance();
|
||||
});
|
||||
this.$el.tooltip({
|
||||
title: function() {
|
||||
var last_text = formats.format_value(self.last_sign, {type: "datetime"});
|
||||
var current_text = formats.format_value(new Date(), {type: "datetime"});
|
||||
var duration = self.last_sign ? $.timeago(self.last_sign) : "none";
|
||||
if (self.get("signed_in")) {
|
||||
return _.str.sprintf(_t("Last sign in: %s,<br />%s.<br />Click to sign out."), last_text, duration);
|
||||
} else {
|
||||
return _.str.sprintf(_t("Click to Sign In at %s."), current_text);
|
||||
}
|
||||
self.$el.show();
|
||||
self.employee = res[0];
|
||||
self.last_sign = instance.web.str_to_datetime(self.employee.last_sign);
|
||||
self.set({"signed_in": self.employee.state !== "absent"});
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
return this.check_attendance();
|
||||
},
|
||||
do_update_attendance: function () {
|
||||
var self = this;
|
||||
var hr_employee = new data.DataSet(self, 'hr.employee');
|
||||
hr_employee.call('attendance_action_change', [
|
||||
[self.employee.id]
|
||||
]).done(function (result) {
|
||||
self.last_sign = new Date();
|
||||
self.set({"signed_in": ! self.get("signed_in")});
|
||||
});
|
||||
},
|
||||
check_attendance: function () {
|
||||
var self = this;
|
||||
self.employee = false;
|
||||
this.$el.hide();
|
||||
var employee = new data.DataSetSearch(self, 'hr.employee', self.session.user_context, [
|
||||
['user_id', '=', self.session.uid]
|
||||
]);
|
||||
return employee.read_slice(['id', 'name', 'state', 'last_sign', 'attendance_access']).then(function (res) {
|
||||
if (_.isEmpty(res) )
|
||||
return;
|
||||
if (res[0].attendance_access === false){
|
||||
return;
|
||||
}
|
||||
self.$el.show();
|
||||
self.employee = res[0];
|
||||
self.last_sign = time.str_to_datetime(self.employee.last_sign);
|
||||
self.set({"signed_in": self.employee.state !== "absent"});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Put the AttendanceSlider widget in the systray menu if the user is an employee
|
||||
var Users = new instance.web.Model('res.users');
|
||||
Users.call('has_group', ['base.group_user']).done(function(is_employee) {
|
||||
if (is_employee) {
|
||||
instance.web.SystrayItems.push(instance.hr_attendance.AttendanceSlider);
|
||||
}
|
||||
})
|
||||
};
|
||||
// Put the AttendanceSlider widget in the systray menu if the user is an employee
|
||||
var Users = new Model('res.users');
|
||||
Users.call('has_group', ['base.group_user']).done(function(is_employee) {
|
||||
if (is_employee) {
|
||||
SystrayMenu.Items.push(AttendanceSlider);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
openerp.hr_gamification = function(instance) {
|
||||
instance.web_kanban.KanbanRecord.include({
|
||||
on_card_clicked: function() {
|
||||
if (this.view.dataset.model === 'gamification.badge.user') {
|
||||
var action = {
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: 'gamification.badge',
|
||||
view_mode: 'form',
|
||||
view_type: 'form,kanban,tree',
|
||||
views: [[false, 'form']],
|
||||
res_id: this.record.badge_id.raw_value[0]
|
||||
};
|
||||
this.do_action(action);
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
odoo.define('hr_gamification.hr_gamification', ['web_kanban.common'], function(require) {
|
||||
"use strict";
|
||||
|
||||
var common = require('web_kanban.common');
|
||||
|
||||
common.KanbanRecord.include({
|
||||
on_card_clicked: function() {
|
||||
if (this.view.dataset.model === 'gamification.badge.user') {
|
||||
var action = {
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: 'gamification.badge',
|
||||
view_mode: 'form',
|
||||
view_type: 'form,kanban,tree',
|
||||
views: [[false, 'form']],
|
||||
res_id: this.record.badge_id.raw_value[0]
|
||||
};
|
||||
this.do_action(action);
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
openerp.hr_recruitment = function (openerp) {
|
||||
"use strict";
|
||||
odoo.define('hr_recruitment.hr_recruitment', ['web_kanban.common'], function(require) {
|
||||
"use strict";
|
||||
|
||||
openerp.web_kanban.KanbanRecord.include({
|
||||
on_card_clicked: function() {
|
||||
if (this.view.dataset.model === 'hr.job') {
|
||||
this.$('.oe_applications a').first().click();
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
var common = require('web_kanban.common');
|
||||
|
||||
};
|
||||
common.KanbanRecord.include({
|
||||
on_card_clicked: function() {
|
||||
if (this.view.dataset.model === 'hr.job') {
|
||||
this.$('.oe_applications a').first().click();
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,357 +1,368 @@
|
||||
odoo.define('hr_timesheet_sheet.sheet', ['web.core', 'web.data', 'web.form_common', 'web.formats', 'web.Model', 'web.time', 'web.utils'], function (require) {
|
||||
"use strict";
|
||||
|
||||
openerp.hr_timesheet_sheet = function(instance) {
|
||||
var QWeb = instance.web.qweb;
|
||||
var _t = instance.web._t;
|
||||
var core = require('web.core');
|
||||
var data = require('web.data');
|
||||
var form_common = require('web.form_common');
|
||||
var formats = require('web.formats');
|
||||
var Model = require('web.Model');
|
||||
var time = require('web.time');
|
||||
var utils = require('web.utils');
|
||||
|
||||
instance.hr_timesheet_sheet.WeeklyTimesheet = instance.web.form.FormWidget.extend(instance.web.form.ReinitializeWidgetMixin, {
|
||||
events: {
|
||||
"click .oe_timesheet_weekly_account a": "go_to",
|
||||
},
|
||||
ignore_fields: function() {
|
||||
return ['line_id'];
|
||||
},
|
||||
init: function() {
|
||||
this._super.apply(this, arguments);
|
||||
var self = this;
|
||||
this.set({
|
||||
sheets: [],
|
||||
date_to: false,
|
||||
date_from: false,
|
||||
});
|
||||
this.updating = false;
|
||||
this.defs = [];
|
||||
this.field_manager.on("field_changed:timesheet_ids", this, this.query_sheets);
|
||||
this.field_manager.on("field_changed:date_from", this, function() {
|
||||
this.set({"date_from": instance.web.str_to_date(this.field_manager.get_field_value("date_from"))});
|
||||
});
|
||||
this.field_manager.on("field_changed:date_to", this, function() {
|
||||
this.set({"date_to": instance.web.str_to_date(this.field_manager.get_field_value("date_to"))});
|
||||
});
|
||||
this.field_manager.on("field_changed:user_id", this, function() {
|
||||
this.set({"user_id": this.field_manager.get_field_value("user_id")});
|
||||
});
|
||||
this.on("change:sheets", this, this.update_sheets);
|
||||
this.res_o2m_drop = new instance.web.DropMisordered();
|
||||
this.render_drop = new instance.web.DropMisordered();
|
||||
this.description_line = _t("/");
|
||||
// Original save function is overwritten in order to wait all running deferreds to be done before actually applying the save.
|
||||
this.view.original_save = _.bind(this.view.save, this.view);
|
||||
this.view.save = function(prepend_on_create){
|
||||
self.prepend_on_create = prepend_on_create;
|
||||
return $.when.apply($, self.defs).then(function(){
|
||||
return self.view.original_save(self.prepend_on_create);
|
||||
});
|
||||
};
|
||||
},
|
||||
go_to: function(event) {
|
||||
var id = JSON.parse($(event.target).data("id"));
|
||||
this.do_action({
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: "account.analytic.account",
|
||||
res_id: id,
|
||||
views: [[false, 'form']],
|
||||
target: 'current'
|
||||
});
|
||||
},
|
||||
query_sheets: function() {
|
||||
var self = this;
|
||||
if (self.updating)
|
||||
return;
|
||||
var commands = this.field_manager.get_field_value("timesheet_ids");
|
||||
this.res_o2m_drop.add(new instance.web.Model(this.view.model).call("resolve_2many_commands", ["timesheet_ids", commands, [],
|
||||
new instance.web.CompoundContext()]))
|
||||
.done(function(result) {
|
||||
self.querying = true;
|
||||
self.set({sheets: result});
|
||||
self.querying = false;
|
||||
});
|
||||
},
|
||||
update_sheets: function() {
|
||||
var self = this;
|
||||
if (self.querying)
|
||||
return;
|
||||
self.updating = true;
|
||||
self.field_manager.set_values({timesheet_ids: self.get("sheets")}).done(function() {
|
||||
self.updating = false;
|
||||
});
|
||||
},
|
||||
initialize_field: function() {
|
||||
instance.web.form.ReinitializeWidgetMixin.initialize_field.call(this);
|
||||
var self = this;
|
||||
self.on("change:sheets", self, self.initialize_content);
|
||||
self.on("change:date_to", self, self.initialize_content);
|
||||
self.on("change:date_from", self, self.initialize_content);
|
||||
self.on("change:user_id", self, self.initialize_content);
|
||||
},
|
||||
initialize_content: function() {
|
||||
var self = this;
|
||||
if (self.setting)
|
||||
return;
|
||||
// don't render anything until we have date_to and date_from
|
||||
if (!self.get("date_to") || !self.get("date_from"))
|
||||
return;
|
||||
this.destroy_content();
|
||||
var QWeb = core.qweb;
|
||||
var _t = core._t;
|
||||
|
||||
// it's important to use those vars to avoid race conditions
|
||||
var dates;
|
||||
var accounts;
|
||||
var account_names;
|
||||
var default_get;
|
||||
return this.render_drop.add(new instance.web.Model("hr.analytic.timesheet").call("default_get", [
|
||||
['account_id','general_account_id', 'journal_id','date','name','user_id','product_id','product_uom_id','to_invoice','amount','unit_amount'],
|
||||
new instance.web.CompoundContext({'user_id': self.get('user_id')})]).then(function(result) {
|
||||
default_get = result;
|
||||
// calculating dates
|
||||
dates = [];
|
||||
var start = self.get("date_from");
|
||||
var end = self.get("date_to");
|
||||
while (start <= end) {
|
||||
dates.push(start);
|
||||
var m_start = moment(start).add(1,'days');
|
||||
start = m_start.toDate();
|
||||
}
|
||||
// group by account
|
||||
accounts = _(self.get("sheets")).chain()
|
||||
.map(function(el) {
|
||||
// much simpler to use only the id in all cases
|
||||
if (typeof(el.account_id) === "object")
|
||||
el.account_id = el.account_id[0];
|
||||
return el;
|
||||
})
|
||||
.groupBy("account_id").value();
|
||||
|
||||
var account_ids = _.map(_.keys(accounts), function(el) { return el === "false" ? false : Number(el) });
|
||||
|
||||
return new instance.web.Model("hr.analytic.timesheet").call("multi_on_change_account_id", [[], account_ids,
|
||||
new instance.web.CompoundContext({'user_id': self.get('user_id')})]).then(function(accounts_defaults) {
|
||||
accounts = _(accounts).chain().map(function(lines, account_id) {
|
||||
account_defaults = _.extend({}, default_get, (accounts_defaults[account_id] || {}).value || {});
|
||||
// group by days
|
||||
account_id = account_id === "false" ? false : Number(account_id);
|
||||
var index = _.groupBy(lines, "date");
|
||||
var days = _.map(dates, function(date) {
|
||||
var day = {day: date, lines: index[instance.web.date_to_str(date)] || []};
|
||||
// add line where we will insert/remove hours
|
||||
var to_add = _.find(day.lines, function(line) { return line.name === self.description_line });
|
||||
if (to_add) {
|
||||
day.lines = _.without(day.lines, to_add);
|
||||
day.lines.unshift(to_add);
|
||||
} else {
|
||||
day.lines.unshift(_.extend(_.clone(account_defaults), {
|
||||
name: self.description_line,
|
||||
unit_amount: 0,
|
||||
date: instance.web.date_to_str(date),
|
||||
account_id: account_id,
|
||||
}));
|
||||
}
|
||||
return day;
|
||||
});
|
||||
return {account: account_id, days: days, account_defaults: account_defaults};
|
||||
}).value();
|
||||
|
||||
// we need the name_get of the analytic accounts
|
||||
return new instance.web.Model("account.analytic.account").call("name_get", [_.pluck(accounts, "account"),
|
||||
new instance.web.CompoundContext()]).then(function(result) {
|
||||
account_names = {};
|
||||
_.each(result, function(el) {
|
||||
account_names[el[0]] = el[1];
|
||||
});
|
||||
accounts = _.sortBy(accounts, function(el) {
|
||||
return account_names[el.account];
|
||||
});
|
||||
});;
|
||||
});
|
||||
})).then(function(result) {
|
||||
// we put all the gathered data in self, then we render
|
||||
self.dates = dates;
|
||||
self.accounts = accounts;
|
||||
self.account_names = account_names;
|
||||
self.default_get = default_get;
|
||||
//real rendering
|
||||
self.display_data();
|
||||
var WeeklyTimesheet = form_common.FormWidget.extend(form_common.ReinitializeWidgetMixin, {
|
||||
events: {
|
||||
"click .oe_timesheet_weekly_account a": "go_to",
|
||||
},
|
||||
ignore_fields: function() {
|
||||
return ['line_id'];
|
||||
},
|
||||
init: function() {
|
||||
this._super.apply(this, arguments);
|
||||
var self = this;
|
||||
this.set({
|
||||
sheets: [],
|
||||
date_to: false,
|
||||
date_from: false,
|
||||
});
|
||||
this.updating = false;
|
||||
this.defs = [];
|
||||
this.field_manager.on("field_changed:timesheet_ids", this, this.query_sheets);
|
||||
this.field_manager.on("field_changed:date_from", this, function() {
|
||||
this.set({"date_from": time.str_to_date(this.field_manager.get_field_value("date_from"))});
|
||||
});
|
||||
this.field_manager.on("field_changed:date_to", this, function() {
|
||||
this.set({"date_to": time.str_to_date(this.field_manager.get_field_value("date_to"))});
|
||||
});
|
||||
this.field_manager.on("field_changed:user_id", this, function() {
|
||||
this.set({"user_id": this.field_manager.get_field_value("user_id")});
|
||||
});
|
||||
this.on("change:sheets", this, this.update_sheets);
|
||||
this.res_o2m_drop = new utils.DropMisordered();
|
||||
this.render_drop = new utils.DropMisordered();
|
||||
this.description_line = _t("/");
|
||||
// Original save function is overwritten in order to wait all running deferreds to be done before actually applying the save.
|
||||
this.view.original_save = _.bind(this.view.save, this.view);
|
||||
this.view.save = function(prepend_on_create){
|
||||
self.prepend_on_create = prepend_on_create;
|
||||
return $.when.apply($, self.defs).then(function(){
|
||||
return self.view.original_save(self.prepend_on_create);
|
||||
});
|
||||
},
|
||||
destroy_content: function() {
|
||||
if (this.dfm) {
|
||||
this.dfm.destroy();
|
||||
this.dfm = undefined;
|
||||
};
|
||||
},
|
||||
go_to: function(event) {
|
||||
var id = JSON.parse($(event.target).data("id"));
|
||||
this.do_action({
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: "account.analytic.account",
|
||||
res_id: id,
|
||||
views: [[false, 'form']],
|
||||
target: 'current'
|
||||
});
|
||||
},
|
||||
query_sheets: function() {
|
||||
var self = this;
|
||||
if (self.updating)
|
||||
return;
|
||||
var commands = this.field_manager.get_field_value("timesheet_ids");
|
||||
this.res_o2m_drop.add(new Model(this.view.model).call("resolve_2many_commands", ["timesheet_ids", commands, [],
|
||||
new data.CompoundContext()]))
|
||||
.done(function(result) {
|
||||
self.querying = true;
|
||||
self.set({sheets: result});
|
||||
self.querying = false;
|
||||
});
|
||||
},
|
||||
update_sheets: function() {
|
||||
var self = this;
|
||||
if (self.querying)
|
||||
return;
|
||||
self.updating = true;
|
||||
self.field_manager.set_values({timesheet_ids: self.get("sheets")}).done(function() {
|
||||
self.updating = false;
|
||||
});
|
||||
},
|
||||
initialize_field: function() {
|
||||
form_common.ReinitializeWidgetMixin.initialize_field.call(this);
|
||||
var self = this;
|
||||
self.on("change:sheets", self, self.initialize_content);
|
||||
self.on("change:date_to", self, self.initialize_content);
|
||||
self.on("change:date_from", self, self.initialize_content);
|
||||
self.on("change:user_id", self, self.initialize_content);
|
||||
},
|
||||
initialize_content: function() {
|
||||
var self = this;
|
||||
if (self.setting)
|
||||
return;
|
||||
// don't render anything until we have date_to and date_from
|
||||
if (!self.get("date_to") || !self.get("date_from"))
|
||||
return;
|
||||
this.destroy_content();
|
||||
|
||||
// it's important to use those vars to avoid race conditions
|
||||
var dates;
|
||||
var accounts;
|
||||
var account_names;
|
||||
var default_get;
|
||||
return this.render_drop.add(new Model("hr.analytic.timesheet").call("default_get", [
|
||||
['account_id','general_account_id', 'journal_id','date','name','user_id','product_id','product_uom_id','to_invoice','amount','unit_amount'],
|
||||
new data.CompoundContext({'user_id': self.get('user_id')})]).then(function(result) {
|
||||
default_get = result;
|
||||
// calculating dates
|
||||
dates = [];
|
||||
var start = self.get("date_from");
|
||||
var end = self.get("date_to");
|
||||
while (start <= end) {
|
||||
dates.push(start);
|
||||
var m_start = moment(start).add(1,'days');
|
||||
start = m_start.toDate();
|
||||
}
|
||||
},
|
||||
is_valid_value:function(value){
|
||||
var split_value = value.split(":");
|
||||
var valid_value = true;
|
||||
if (split_value.length > 2)
|
||||
return false;
|
||||
_.detect(split_value,function(num){
|
||||
if(isNaN(num)){
|
||||
valid_value = false;
|
||||
// group by account
|
||||
accounts = _(self.get("sheets")).chain()
|
||||
.map(function(el) {
|
||||
// much simpler to use only the id in all cases
|
||||
if (typeof(el.account_id) === "object")
|
||||
el.account_id = el.account_id[0];
|
||||
return el;
|
||||
})
|
||||
.groupBy("account_id").value();
|
||||
|
||||
var account_ids = _.map(_.keys(accounts), function(el) { return el === "false" ? false : Number(el); });
|
||||
|
||||
return new Model("hr.analytic.timesheet").call("multi_on_change_account_id", [[], account_ids,
|
||||
new data.CompoundContext({'user_id': self.get('user_id')})]).then(function(accounts_defaults) {
|
||||
accounts = _(accounts).chain().map(function(lines, account_id) {
|
||||
var account_defaults = _.extend({}, default_get, (accounts_defaults[account_id] || {}).value || {});
|
||||
// group by days
|
||||
account_id = account_id === "false" ? false : Number(account_id);
|
||||
var index = _.groupBy(lines, "date");
|
||||
var days = _.map(dates, function(date) {
|
||||
var day = {day: date, lines: index[time.date_to_str(date)] || []};
|
||||
// add line where we will insert/remove hours
|
||||
var to_add = _.find(day.lines, function(line) { return line.name === self.description_line; });
|
||||
if (to_add) {
|
||||
day.lines = _.without(day.lines, to_add);
|
||||
day.lines.unshift(to_add);
|
||||
} else {
|
||||
day.lines.unshift(_.extend(_.clone(account_defaults), {
|
||||
name: self.description_line,
|
||||
unit_amount: 0,
|
||||
date: time.date_to_str(date),
|
||||
account_id: account_id,
|
||||
}));
|
||||
}
|
||||
return day;
|
||||
});
|
||||
return {account: account_id, days: days, account_defaults: account_defaults};
|
||||
}).value();
|
||||
|
||||
// we need the name_get of the analytic accounts
|
||||
return new Model("account.analytic.account").call("name_get", [_.pluck(accounts, "account"),
|
||||
new data.CompoundContext()]).then(function(result) {
|
||||
account_names = {};
|
||||
_.each(result, function(el) {
|
||||
account_names[el[0]] = el[1];
|
||||
});
|
||||
accounts = _.sortBy(accounts, function(el) {
|
||||
return account_names[el.account];
|
||||
});
|
||||
});
|
||||
});
|
||||
})).then(function(result) {
|
||||
// we put all the gathered data in self, then we render
|
||||
self.dates = dates;
|
||||
self.accounts = accounts;
|
||||
self.account_names = account_names;
|
||||
self.default_get = default_get;
|
||||
//real rendering
|
||||
self.display_data();
|
||||
});
|
||||
},
|
||||
destroy_content: function() {
|
||||
if (this.dfm) {
|
||||
this.dfm.destroy();
|
||||
this.dfm = undefined;
|
||||
}
|
||||
},
|
||||
is_valid_value:function(value){
|
||||
var split_value = value.split(":");
|
||||
var valid_value = true;
|
||||
if (split_value.length > 2)
|
||||
return false;
|
||||
_.detect(split_value,function(num){
|
||||
if(isNaN(num)){
|
||||
valid_value = false;
|
||||
}
|
||||
});
|
||||
return valid_value;
|
||||
},
|
||||
display_data: function() {
|
||||
var self = this;
|
||||
self.$el.html(QWeb.render("hr_timesheet_sheet.WeeklyTimesheet", {widget: self}));
|
||||
_.each(self.accounts, function(account) {
|
||||
_.each(_.range(account.days.length), function(day_count) {
|
||||
if (!self.get('effective_readonly')) {
|
||||
self.get_box(account, day_count).val(self.sum_box(account, day_count, true)).change(function() {
|
||||
var num = $(this).val();
|
||||
if (self.is_valid_value(num)){
|
||||
num = (num === 0)?0:Number(self.parse_client(num));
|
||||
}
|
||||
if (isNaN(num)) {
|
||||
$(this).val(self.sum_box(account, day_count, true));
|
||||
} else {
|
||||
account.days[day_count].lines[0].unit_amount += num - self.sum_box(account, day_count);
|
||||
var product = (account.days[day_count].lines[0].product_id instanceof Array) ? account.days[day_count].lines[0].product_id[0] : account.days[day_count].lines[0].product_id;
|
||||
var journal = (account.days[day_count].lines[0].journal_id instanceof Array) ? account.days[day_count].lines[0].journal_id[0] : account.days[day_count].lines[0].journal_id;
|
||||
self.defs.push(new Model("hr.analytic.timesheet").call("on_change_unit_amount", [[], product, account.days[day_count].lines[0].unit_amount, false, false, journal]).then(function(res) {
|
||||
account.days[day_count].lines[0]['amount'] = res.value.amount || 0;
|
||||
self.display_totals();
|
||||
self.sync();
|
||||
}));
|
||||
if(!isNaN($(this).val())){
|
||||
$(this).val(self.sum_box(account, day_count, true));
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self.get_box(account, day_count).html(self.sum_box(account, day_count, true));
|
||||
}
|
||||
});
|
||||
return valid_value;
|
||||
},
|
||||
display_data: function() {
|
||||
var self = this;
|
||||
self.$el.html(QWeb.render("hr_timesheet_sheet.WeeklyTimesheet", {widget: self}));
|
||||
_.each(self.accounts, function(account) {
|
||||
_.each(_.range(account.days.length), function(day_count) {
|
||||
if (!self.get('effective_readonly')) {
|
||||
self.get_box(account, day_count).val(self.sum_box(account, day_count, true)).change(function() {
|
||||
var num = $(this).val();
|
||||
if (self.is_valid_value(num)){
|
||||
num = (num == 0)?0:Number(self.parse_client(num));
|
||||
}
|
||||
if (isNaN(num)) {
|
||||
$(this).val(self.sum_box(account, day_count, true));
|
||||
} else {
|
||||
account.days[day_count].lines[0].unit_amount += num - self.sum_box(account, day_count);
|
||||
var product = (account.days[day_count].lines[0].product_id instanceof Array) ? account.days[day_count].lines[0].product_id[0] : account.days[day_count].lines[0].product_id
|
||||
var journal = (account.days[day_count].lines[0].journal_id instanceof Array) ? account.days[day_count].lines[0].journal_id[0] : account.days[day_count].lines[0].journal_id
|
||||
self.defs.push(new instance.web.Model("hr.analytic.timesheet").call("on_change_unit_amount", [[], product, account.days[day_count].lines[0].unit_amount, false, false, journal]).then(function(res) {
|
||||
account.days[day_count].lines[0]['amount'] = res.value.amount || 0;
|
||||
self.display_totals();
|
||||
self.sync();
|
||||
}));
|
||||
if(!isNaN($(this).val())){
|
||||
$(this).val(self.sum_box(account, day_count, true));
|
||||
}
|
||||
});
|
||||
self.display_totals();
|
||||
self.$(".oe_timesheet_weekly_adding button").click(_.bind(this.init_add_account, this));
|
||||
},
|
||||
init_add_account: function() {
|
||||
var self = this;
|
||||
if (self.dfm)
|
||||
return;
|
||||
self.$(".oe_timesheet_weekly_add_row").show();
|
||||
self.dfm = new form_common.DefaultFieldManager(self);
|
||||
self.dfm.extend_field_desc({
|
||||
account: {
|
||||
relation: "account.analytic.account",
|
||||
},
|
||||
});
|
||||
var FieldMany2One = core.form_widget_registry.get('many2one');
|
||||
self.account_m2o = new FieldMany2One(self.dfm, {
|
||||
attrs: {
|
||||
name: "account",
|
||||
type: "many2one",
|
||||
domain: [
|
||||
['type','in',['normal', 'contract']],
|
||||
['state', '<>', 'close'],
|
||||
['invoice_on_timesheets','=',1],
|
||||
['id', 'not in', _.pluck(self.accounts, "account")],
|
||||
],
|
||||
context: {
|
||||
default_invoice_on_timesheets: 1,
|
||||
default_type: "contract",
|
||||
},
|
||||
modifiers: '{"required": true}',
|
||||
},
|
||||
});
|
||||
self.account_m2o.prependTo(self.$(".oe_timesheet_weekly_add_row td"));
|
||||
self.$(".oe_timesheet_weekly_add_row button").click(function() {
|
||||
var id = self.account_m2o.get_value();
|
||||
if (id === false) {
|
||||
self.dfm.set({display_invalid_fields: true});
|
||||
return;
|
||||
}
|
||||
var ops = self.generate_o2m_value();
|
||||
new Model("hr.analytic.timesheet").call("multi_on_change_account_id", [[], [id],
|
||||
new data.CompoundContext({'user_id': self.get('user_id')})]).then(function(res) {
|
||||
res = res[id];
|
||||
var def = _.extend({}, self.default_get, res.value, {
|
||||
name: self.description_line,
|
||||
unit_amount: 0,
|
||||
date: time.date_to_str(self.dates[0]),
|
||||
account_id: id,
|
||||
});
|
||||
ops.push(def);
|
||||
self.set({"sheets": ops});
|
||||
});
|
||||
});
|
||||
},
|
||||
get_box: function(account, day_count) {
|
||||
return this.$('[data-account="' + account.account + '"][data-day-count="' + day_count + '"]');
|
||||
},
|
||||
get_total: function(account) {
|
||||
return this.$('[data-account-total="' + account.account + '"]');
|
||||
},
|
||||
get_day_total: function(day_count) {
|
||||
return this.$('[data-day-total="' + day_count + '"]');
|
||||
},
|
||||
get_super_total: function() {
|
||||
return this.$('.oe_timesheet_weekly_supertotal');
|
||||
},
|
||||
sum_box: function(account, day_count, show_value_in_hour) {
|
||||
var line_total = 0;
|
||||
_.each(account.days[day_count].lines, function(line) {
|
||||
line_total += line.unit_amount;
|
||||
});
|
||||
return (show_value_in_hour && line_total !== 0)?this.format_client(line_total):line_total;
|
||||
},
|
||||
display_totals: function() {
|
||||
var self = this;
|
||||
var day_tots = _.map(_.range(self.dates.length), function() { return 0; });
|
||||
var super_tot = 0;
|
||||
_.each(self.accounts, function(account) {
|
||||
var acc_tot = 0;
|
||||
_.each(_.range(self.dates.length), function(day_count) {
|
||||
var sum = self.sum_box(account, day_count);
|
||||
acc_tot += sum;
|
||||
day_tots[day_count] += sum;
|
||||
super_tot += sum;
|
||||
});
|
||||
self.get_total(account).html(self.format_client(acc_tot));
|
||||
});
|
||||
_.each(_.range(self.dates.length), function(day_count) {
|
||||
self.get_day_total(day_count).html(self.format_client(day_tots[day_count]));
|
||||
});
|
||||
self.get_super_total().html(self.format_client(super_tot));
|
||||
},
|
||||
sync: function() {
|
||||
var self = this;
|
||||
self.setting = true;
|
||||
self.set({sheets: this.generate_o2m_value()});
|
||||
self.setting = false;
|
||||
},
|
||||
//converts hour value to float
|
||||
parse_client: function(value) {
|
||||
return formats.parse_value(value, { type:"float_time" });
|
||||
},
|
||||
//converts float value to hour
|
||||
format_client:function(value){
|
||||
return formats.format_value(value, { type:"float_time" });
|
||||
},
|
||||
generate_o2m_value: function() {
|
||||
var self = this;
|
||||
var ops = [];
|
||||
var ignored_fields = self.ignore_fields();
|
||||
_.each(self.accounts, function(account) {
|
||||
_.each(account.days, function(day) {
|
||||
_.each(day.lines, function(line) {
|
||||
if (line.unit_amount !== 0) {
|
||||
var tmp = _.clone(line);
|
||||
tmp.id = undefined;
|
||||
_.each(line, function(v, k) {
|
||||
if (v instanceof Array) {
|
||||
tmp[k] = v[0];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self.get_box(account, day_count).html(self.sum_box(account, day_count, true));
|
||||
// we remove line_id as the reference to the _inherits field will no longer exists
|
||||
tmp = _.omit(tmp, ignored_fields);
|
||||
ops.push(tmp);
|
||||
}
|
||||
});
|
||||
});
|
||||
self.display_totals();
|
||||
self.$(".oe_timesheet_weekly_adding button").click(_.bind(this.init_add_account, this));
|
||||
},
|
||||
init_add_account: function() {
|
||||
var self = this;
|
||||
if (self.dfm)
|
||||
return;
|
||||
self.$(".oe_timesheet_weekly_add_row").show();
|
||||
self.dfm = new instance.web.form.DefaultFieldManager(self);
|
||||
self.dfm.extend_field_desc({
|
||||
account: {
|
||||
relation: "account.analytic.account",
|
||||
},
|
||||
});
|
||||
self.account_m2o = new instance.web.form.FieldMany2One(self.dfm, {
|
||||
attrs: {
|
||||
name: "account",
|
||||
type: "many2one",
|
||||
domain: [
|
||||
['type','in',['normal', 'contract']],
|
||||
['state', '<>', 'close'],
|
||||
['invoice_on_timesheets','=',1],
|
||||
['id', 'not in', _.pluck(self.accounts, "account")],
|
||||
],
|
||||
context: {
|
||||
default_invoice_on_timesheets: 1,
|
||||
default_type: "contract",
|
||||
},
|
||||
modifiers: '{"required": true}',
|
||||
},
|
||||
});
|
||||
self.account_m2o.prependTo(self.$(".oe_timesheet_weekly_add_row td"));
|
||||
self.$(".oe_timesheet_weekly_add_row button").click(function() {
|
||||
var id = self.account_m2o.get_value();
|
||||
if (id === false) {
|
||||
self.dfm.set({display_invalid_fields: true});
|
||||
return;
|
||||
}
|
||||
var ops = self.generate_o2m_value();
|
||||
new instance.web.Model("hr.analytic.timesheet").call("multi_on_change_account_id", [[], [id],
|
||||
new instance.web.CompoundContext({'user_id': self.get('user_id')})]).then(function(res) {
|
||||
res = res[id];
|
||||
var def = _.extend({}, self.default_get, res.value, {
|
||||
name: self.description_line,
|
||||
unit_amount: 0,
|
||||
date: instance.web.date_to_str(self.dates[0]),
|
||||
account_id: id,
|
||||
});
|
||||
ops.push(def);
|
||||
self.set({"sheets": ops});
|
||||
});
|
||||
});
|
||||
},
|
||||
get_box: function(account, day_count) {
|
||||
return this.$('[data-account="' + account.account + '"][data-day-count="' + day_count + '"]');
|
||||
},
|
||||
get_total: function(account) {
|
||||
return this.$('[data-account-total="' + account.account + '"]');
|
||||
},
|
||||
get_day_total: function(day_count) {
|
||||
return this.$('[data-day-total="' + day_count + '"]');
|
||||
},
|
||||
get_super_total: function() {
|
||||
return this.$('.oe_timesheet_weekly_supertotal');
|
||||
},
|
||||
sum_box: function(account, day_count, show_value_in_hour) {
|
||||
var line_total = 0;
|
||||
_.each(account.days[day_count].lines, function(line) {
|
||||
line_total += line.unit_amount;
|
||||
});
|
||||
return (show_value_in_hour && line_total != 0)?this.format_client(line_total):line_total;
|
||||
},
|
||||
display_totals: function() {
|
||||
var self = this;
|
||||
var day_tots = _.map(_.range(self.dates.length), function() { return 0 });
|
||||
var super_tot = 0;
|
||||
_.each(self.accounts, function(account) {
|
||||
var acc_tot = 0;
|
||||
_.each(_.range(self.dates.length), function(day_count) {
|
||||
var sum = self.sum_box(account, day_count);
|
||||
acc_tot += sum;
|
||||
day_tots[day_count] += sum;
|
||||
super_tot += sum;
|
||||
});
|
||||
self.get_total(account).html(self.format_client(acc_tot));
|
||||
});
|
||||
_.each(_.range(self.dates.length), function(day_count) {
|
||||
self.get_day_total(day_count).html(self.format_client(day_tots[day_count]));
|
||||
});
|
||||
self.get_super_total().html(self.format_client(super_tot));
|
||||
},
|
||||
sync: function() {
|
||||
var self = this;
|
||||
self.setting = true;
|
||||
self.set({sheets: this.generate_o2m_value()});
|
||||
self.setting = false;
|
||||
},
|
||||
//converts hour value to float
|
||||
parse_client: function(value) {
|
||||
return instance.web.parse_value(value, { type:"float_time" });
|
||||
},
|
||||
//converts float value to hour
|
||||
format_client:function(value){
|
||||
return instance.web.format_value(value, { type:"float_time" });
|
||||
},
|
||||
generate_o2m_value: function() {
|
||||
var self = this;
|
||||
var ops = [];
|
||||
var ignored_fields = self.ignore_fields();
|
||||
_.each(self.accounts, function(account) {
|
||||
_.each(account.days, function(day) {
|
||||
_.each(day.lines, function(line) {
|
||||
if (line.unit_amount !== 0) {
|
||||
var tmp = _.clone(line);
|
||||
tmp.id = undefined;
|
||||
_.each(line, function(v, k) {
|
||||
if (v instanceof Array) {
|
||||
tmp[k] = v[0];
|
||||
}
|
||||
});
|
||||
// we remove line_id as the reference to the _inherits field will no longer exists
|
||||
tmp = _.omit(tmp, ignored_fields);
|
||||
ops.push(tmp);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
return ops;
|
||||
},
|
||||
});
|
||||
});
|
||||
return ops;
|
||||
},
|
||||
});
|
||||
|
||||
instance.web.form.custom_widgets.add('weekly_timesheet', 'instance.hr_timesheet_sheet.WeeklyTimesheet');
|
||||
core.form_custom_registry.add('weekly_timesheet', WeeklyTimesheet);
|
||||
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user