Add TimeDelta field
And remove float_time widget. issue4026 review10891002
This commit is contained in:
1 parent
1e83cfc41c
commit
a8aa4eaa92
6 files changed
+278
-48
No files matched your search
+147
-2
@@ -181,10 +181,155 @@
|
||||
return date;
|
||||
};
|
||||
|
||||
Sao.common.text_to_float_time = function(text, conversion, digit) {
|
||||
// TODO
|
||||
Sao.common.timedelta = {};
|
||||
Sao.common.timedelta.DEFAULT_CONVERTER = {
|
||||
's': 1
|
||||
};
|
||||
Sao.common.timedelta.DEFAULT_CONVERTER.m =
|
||||
Sao.common.timedelta.DEFAULT_CONVERTER.s * 60;
|
||||
Sao.common.timedelta.DEFAULT_CONVERTER.h =
|
||||
Sao.common.timedelta.DEFAULT_CONVERTER.m * 60;
|
||||
Sao.common.timedelta.DEFAULT_CONVERTER.d =
|
||||
Sao.common.timedelta.DEFAULT_CONVERTER.h * 24;
|
||||
Sao.common.timedelta.DEFAULT_CONVERTER.w =
|
||||
Sao.common.timedelta.DEFAULT_CONVERTER.d * 7;
|
||||
Sao.common.timedelta.DEFAULT_CONVERTER.M =
|
||||
Sao.common.timedelta.DEFAULT_CONVERTER.w * 4;
|
||||
Sao.common.timedelta.DEFAULT_CONVERTER.Y =
|
||||
Sao.common.timedelta.DEFAULT_CONVERTER.d * 365;
|
||||
Sao.common.timedelta._get_separator = function() {
|
||||
// TODO translate
|
||||
return {
|
||||
Y: 'Y',
|
||||
M: 'M',
|
||||
w: 'w',
|
||||
d: 'd',
|
||||
h: 'h',
|
||||
m: 'm',
|
||||
s: 's'
|
||||
};
|
||||
};
|
||||
Sao.common.timedelta.format = function(value, converter) {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
if (!converter) {
|
||||
converter = Sao.common.timedelta.DEFAULT_CONVERTER;
|
||||
}
|
||||
var text = [];
|
||||
value = value.asSeconds();
|
||||
var sign = '';
|
||||
if (value < 0) {
|
||||
sign = '-';
|
||||
}
|
||||
value = Math.abs(value);
|
||||
converter = Object.keys(converter).map(function(key) {
|
||||
return [key, converter[key]];
|
||||
});
|
||||
converter.sort(function(first, second) {
|
||||
return second[1] - first[1];
|
||||
});
|
||||
var values = [];
|
||||
var k, v;
|
||||
for (var i = 0; i < converter.length; i++) {
|
||||
k = converter[i][0];
|
||||
v = converter[i][1];
|
||||
var part = Math.floor(value / v);
|
||||
value -= part * v;
|
||||
values.push(part);
|
||||
}
|
||||
for (i = 0; i < converter.length - 3; i++) {
|
||||
k = converter[i][0];
|
||||
v = values[i];
|
||||
if (v) {
|
||||
text.push(v + Sao.common.timedelta._get_separator()[k]);
|
||||
}
|
||||
}
|
||||
if (jQuery(values.slice(-3)).is(function(i, v) { return v; }) ||
|
||||
jQuery.isEmptyObject(text)) {
|
||||
var time = values.slice(-3, -1);
|
||||
time = ('00' + time[0]).slice(-2) + ':' + ('00' + time[1]).slice(-2);
|
||||
if (values.slice(-1)[0] || value) {
|
||||
time += ':' + ('00' + values.slice(-1)[0]).slice(-2);
|
||||
}
|
||||
text.push(time);
|
||||
}
|
||||
text = sign + text.reduce(function(p, c) {
|
||||
if (p) {
|
||||
return p + ' ' + c;
|
||||
} else {
|
||||
return c;
|
||||
}
|
||||
});
|
||||
if (value) {
|
||||
if (!jQuery(values.slice(-3)).is(function(i, v) { return v; })) {
|
||||
// Add space if no time
|
||||
text += ' ';
|
||||
}
|
||||
text += ('' + value.toFixed(6)).slice(1);
|
||||
}
|
||||
return text;
|
||||
};
|
||||
Sao.common.timedelta.parse = function(text, converter) {
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
if (!converter) {
|
||||
converter = Sao.common.timedelta.DEFAULT_CONVERTER;
|
||||
}
|
||||
var separators = Sao.common.timedelta._get_separator();
|
||||
var separator;
|
||||
for (var k in separators) {
|
||||
separator = separators[k];
|
||||
text = text.replace(separator, separator + ' ');
|
||||
}
|
||||
|
||||
var seconds = 0;
|
||||
var sec;
|
||||
var parts = text.split(' ');
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var part = parts[i];
|
||||
if (part.contains(':')) {
|
||||
var subparts = part.split(':');
|
||||
var subconverter = [
|
||||
converter.h, converter.m, converter.s];
|
||||
for (var j = 0;
|
||||
j < Math.min(subparts.length, subconverter.length);
|
||||
j ++) {
|
||||
var t = subparts[j];
|
||||
var v = subconverter[j];
|
||||
sec = Math.abs(Number(t)) * v;
|
||||
if (!isNaN(sec)) {
|
||||
seconds += sec;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var found = false;
|
||||
for (var key in separators) {
|
||||
separator =separators[key];
|
||||
if (part.endsWith(separator)) {
|
||||
part = part.slice(0, -separator.length);
|
||||
sec = Math.abs(parseInt(part, 10)) * converter[key];
|
||||
if (!isNaN(sec)) {
|
||||
seconds += sec;
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
sec = Math.abs(Number(part));
|
||||
if (!isNaN(sec)) {
|
||||
seconds += sec;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (text.contains('-')) {
|
||||
seconds *= -1;
|
||||
}
|
||||
return Sao.TimeDelta(null, seconds);
|
||||
};
|
||||
|
||||
Sao.common.ModelAccess = Sao.class_(Object, {
|
||||
init: function() {
|
||||
|
||||
@@ -983,6 +983,8 @@
|
||||
return Sao.field.Date;
|
||||
case 'time':
|
||||
return Sao.field.Time;
|
||||
case 'timedelta':
|
||||
return Sao.field.TimeDelta;
|
||||
case 'float':
|
||||
return Sao.field.Float;
|
||||
case 'numeric':
|
||||
@@ -1297,6 +1299,26 @@
|
||||
}
|
||||
});
|
||||
|
||||
Sao.field.TimeDelta = Sao.class_(Sao.field.Field, {
|
||||
_default: null,
|
||||
converter: function(record) {
|
||||
// TODO allow local context converter
|
||||
return record.model.session.context[this.description.converter];
|
||||
},
|
||||
set_client: function(record, value, force_change) {
|
||||
if (typeof(value) == 'string') {
|
||||
value = Sao.common.timedelta.parse(value, this.converter(record));
|
||||
}
|
||||
Sao.field.TimeDelta._super.set_client.call(
|
||||
this, record, value, force_change);
|
||||
},
|
||||
get_client: function(record) {
|
||||
var value = Sao.field.TimeDelta._super.get_client.call(
|
||||
this, record);
|
||||
return Sao.common.timedelta.format(value, this.converter(record));
|
||||
}
|
||||
});
|
||||
|
||||
Sao.field.Float = Sao.class_(Sao.field.Field, {
|
||||
_default: null,
|
||||
default_digits: [16, 2],
|
||||
|
||||
@@ -123,6 +123,9 @@
|
||||
value = Sao.Time(value.hour, value.minute,
|
||||
value.second, value.microsecond / 1000);
|
||||
break;
|
||||
case 'timedelta':
|
||||
value = Sao.TimeDelta(null, value.seconds);
|
||||
break;
|
||||
case 'buffer':
|
||||
// javascript's atob does not understand linefeed
|
||||
// characters
|
||||
@@ -195,6 +198,11 @@
|
||||
'second': value.second(),
|
||||
'microsecond': value.millisecond() * 1000
|
||||
};
|
||||
} else if (value.isTimeDelta) {
|
||||
value = {
|
||||
'__class__': 'timedelta',
|
||||
'seconds': value.asSeconds()
|
||||
};
|
||||
} else if (value instanceof Sao.Decimal) {
|
||||
value = {
|
||||
'__class__': 'Decimal',
|
||||
|
||||
+14
@@ -120,6 +120,20 @@ var Sao = {};
|
||||
return time;
|
||||
};
|
||||
|
||||
Sao.TimeDelta = function(days, seconds,
|
||||
milliseconds, minutes, hours, weeks) {
|
||||
var timedelta = moment.duration({
|
||||
days: days,
|
||||
seconds: seconds,
|
||||
milliseconds: milliseconds,
|
||||
minutes: minutes,
|
||||
hours: hours,
|
||||
weeks: weeks
|
||||
});
|
||||
timedelta.isTimeDelta = true;
|
||||
return timedelta;
|
||||
};
|
||||
|
||||
Sao.config = {};
|
||||
Sao.config.limit = 1000;
|
||||
Sao.config.display_size = 20;
|
||||
|
||||
+48
-46
@@ -57,6 +57,8 @@
|
||||
return Sao.View.Tree.DateColumn;
|
||||
case 'time':
|
||||
return Sao.View.Tree.TimeColumn;
|
||||
case 'timedelta':
|
||||
return Sao.View.Tree.TimeDeltaColumn;
|
||||
case 'one2many':
|
||||
return Sao.View.Tree.One2ManyColumn;
|
||||
case 'many2many':
|
||||
@@ -68,8 +70,6 @@
|
||||
case 'float':
|
||||
case 'numeric':
|
||||
return Sao.View.Tree.FloatColumn;
|
||||
case 'float_time':
|
||||
return Sao.View.Tree.FloatTimeColumn;
|
||||
case 'integer':
|
||||
case 'biginteger':
|
||||
return Sao.View.Tree.IntegerColumn;
|
||||
@@ -1048,6 +1048,10 @@
|
||||
}
|
||||
});
|
||||
|
||||
Sao.View.Tree.TimeDeltaColumn = Sao.class_(Sao.View.Tree.CharColumn, {
|
||||
class_: 'column-timedelta'
|
||||
});
|
||||
|
||||
Sao.View.Tree.One2ManyColumn = Sao.class_(Sao.View.Tree.CharColumn, {
|
||||
class_: 'column-one2many',
|
||||
update_text: function(cell, record) {
|
||||
@@ -1059,19 +1063,6 @@
|
||||
class_: 'column-many2many'
|
||||
});
|
||||
|
||||
Sao.View.Tree.FloatTimeColumn = Sao.class_(Sao.View.Tree.CharColumn, {
|
||||
class_: 'column-float_time',
|
||||
init: function(model, attributes) {
|
||||
Sao.View.Tree.FloatTimeColumn._super.init.call(this, model,
|
||||
attributes);
|
||||
this.conv = null; // TODO
|
||||
},
|
||||
update_text: function(cell, record) {
|
||||
cell.text(Sao.common.text_to_float_time(
|
||||
this.field.get_client(record), this.conv));
|
||||
}
|
||||
});
|
||||
|
||||
Sao.View.Tree.ImageColumn = Sao.class_(Sao.View.Tree.CharColumn, {
|
||||
class_: 'column-image',
|
||||
get_cell: function() {
|
||||
@@ -1756,6 +1747,8 @@
|
||||
return Sao.View.Form.DateTime;
|
||||
case 'time':
|
||||
return Sao.View.Form.Time;
|
||||
case 'timedelta':
|
||||
return Sao.View.Form.TimeDelta;
|
||||
case 'integer':
|
||||
case 'biginteger':
|
||||
return Sao.View.Form.Integer;
|
||||
@@ -1764,8 +1757,6 @@
|
||||
return Sao.View.Form.Float;
|
||||
case 'selection':
|
||||
return Sao.View.Form.Selection;
|
||||
case 'float_time':
|
||||
return Sao.View.Form.FloatTime;
|
||||
case 'boolean':
|
||||
return Sao.View.Form.Boolean;
|
||||
case 'text':
|
||||
@@ -2012,6 +2003,37 @@
|
||||
}
|
||||
});
|
||||
|
||||
Sao.View.Form.TimeDelta = Sao.class_(Sao.View.Form.Widget, {
|
||||
class_: 'form-timedelta',
|
||||
init: function(field_name, model, attributes) {
|
||||
Sao.View.Form.TimeDelta._super.init.call(this, field_name, model,
|
||||
attributes);
|
||||
this.el = jQuery('<div/>', {
|
||||
'class': this.class_
|
||||
});
|
||||
this.input = jQuery('<input/>', {
|
||||
'type': 'text',
|
||||
'class': 'form-control input-sm'
|
||||
}).appendTo(this.el);
|
||||
this.el.change(this.focus_out.bind(this));
|
||||
},
|
||||
display: function(record, field) {
|
||||
Sao.View.Form.TimeDelta._super.display.call(this, record, field);
|
||||
if (record) {
|
||||
var value = record.field_get_client(this.field_name);
|
||||
this.input.val(value || '');
|
||||
} else {
|
||||
this.input.val('');
|
||||
}
|
||||
},
|
||||
set_value: function(record, field) {
|
||||
field.set_client(record, this.input.val());
|
||||
},
|
||||
set_readonly: function(readonly) {
|
||||
this.input.prop('disabled', readonly);
|
||||
}
|
||||
});
|
||||
|
||||
Sao.View.Form.Integer = Sao.class_(Sao.View.Form.Char, {
|
||||
class_: 'form-integer',
|
||||
init: function(field_name, model, attributes) {
|
||||
@@ -2130,24 +2152,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
Sao.View.Form.FloatTime = Sao.class_(Sao.View.Form.Char, {
|
||||
class_: 'form-float-time',
|
||||
init: function(field_name, model, attributes) {
|
||||
Sao.View.Form.FloatTime._super.init.call(this, field_name, model,
|
||||
attributes);
|
||||
this.conv = null; // TODO
|
||||
},
|
||||
display: function(record, field) {
|
||||
Sao.View.Form.FloatTime._super.display.call(this, record, field);
|
||||
if (record) {
|
||||
var value = record.field_get_client(this.field_name);
|
||||
this.input.val(Sao.common.text_to_float_time(value, this.conv));
|
||||
} else {
|
||||
this.input.val('');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Sao.View.Form.Boolean = Sao.class_(Sao.View.Form.Widget, {
|
||||
class_: 'form-boolean',
|
||||
init: function(field_name, model, attributes) {
|
||||
@@ -3368,8 +3372,6 @@
|
||||
return Sao.View.EditableTree.Float;
|
||||
case 'selection':
|
||||
return Sao.View.EditableTree.Selection;
|
||||
case 'float_time':
|
||||
return Sao.View.EditableTree.FloatTime;
|
||||
case 'boolean':
|
||||
return Sao.View.EditableTree.Boolean;
|
||||
case 'many2one':
|
||||
@@ -3420,6 +3422,15 @@
|
||||
}
|
||||
});
|
||||
|
||||
Sao.View.EditableTree.TimeDelta = Sao.class_(Sao.View.Form.TimeDelta, {
|
||||
class_: 'editabletree-timedelta',
|
||||
init: function(field_name, model, attributes) {
|
||||
Sao.View.EditableTree.TimeDelta._super.init.call(this, field_name,
|
||||
model, attributes);
|
||||
Sao.View.EditableTree.editable_mixin(this);
|
||||
}
|
||||
});
|
||||
|
||||
Sao.View.EditableTree.Integer = Sao.class_(Sao.View.Form.Integer, {
|
||||
class_: 'editabletree-integer',
|
||||
init: function(field_name, model, attributes) {
|
||||
@@ -3447,15 +3458,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
Sao.View.EditableTree.FloatTime = Sao.class_(Sao.View.Form.FloatTime, {
|
||||
class_: 'editabletree-float_time',
|
||||
init: function(field_name, model, attributes) {
|
||||
Sao.View.EditableTree.FloatTime._super.init.call(this, field_name,
|
||||
model, attributes);
|
||||
Sao.View.EditableTree.editable_mixin(this);
|
||||
}
|
||||
});
|
||||
|
||||
Sao.View.EditableTree.Boolean = Sao.class_(Sao.View.Form.Boolean, {
|
||||
class_: 'editabletree-boolean',
|
||||
init: function(field_name, model, attributes) {
|
||||
|
||||
@@ -840,6 +840,45 @@
|
||||
});
|
||||
});
|
||||
|
||||
var timedelta_tests = [
|
||||
[null, ''],
|
||||
[Sao.TimeDelta(3, 0, 0, 30, 5), '3d 05:30'],
|
||||
[Sao.TimeDelta(0, 0, 0, 0, 0, 48), '12M'],
|
||||
[Sao.TimeDelta(0, 0, 0, 0, 0, 50), '12M 2w'],
|
||||
[Sao.TimeDelta(365), '1Y'],
|
||||
[Sao.TimeDelta(0, 10, 0, 5, 2), '02:05:10'],
|
||||
[Sao.TimeDelta(0, 0, 42, 15), '00:15:00.042000'],
|
||||
[Sao.TimeDelta(1, 0, 42), '1d .042000'],
|
||||
[Sao.TimeDelta(0, -1), '-00:00:01'],
|
||||
[Sao.TimeDelta(-1, 0, 0, -30, -5), '-1d 05:30']
|
||||
];
|
||||
|
||||
QUnit.test('timedelta.format', function() {
|
||||
timedelta_tests.forEach(function(test) {
|
||||
var timedelta = test[0];
|
||||
var text = test[1];
|
||||
QUnit.equal(Sao.common.timedelta.format(timedelta), text,
|
||||
'timedelta.format(' + JSON.stringify(timedelta) + ')');
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('timedelta.parse', function() {
|
||||
function asSeconds(timedelta) {
|
||||
if (timedelta) {
|
||||
return timedelta.asSeconds();
|
||||
} else {
|
||||
return timedelta;
|
||||
}
|
||||
}
|
||||
timedelta_tests.forEach(function(test) {
|
||||
var timedelta = test[0];
|
||||
var text = test[1];
|
||||
QUnit.equal(asSeconds(Sao.common.timedelta.parse(text)),
|
||||
asSeconds(timedelta),
|
||||
'timedelta.format(' + JSON.stringify(timedelta) + ')');
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('DomainParser.group', function() {
|
||||
var parser = new Sao.common.DomainParser({
|
||||
'name': {
|
||||
|
||||
Reference in new issue
Block a user