[IMP] Calendar performance

This commit is contained in:
QS5ELkMu
2018-12-18 03:13:12 +01:00
parent a55c5b4ec2
commit d49419a29b
5 changed files with 130 additions and 159 deletions

View File

@@ -176,6 +176,7 @@ var PMSCalendarController = AbstractController.extend({
_reload_active_calendar: function() {
var self = this;
var filterDates = this.renderer.get_view_filter_dates();
var active_calendar = this._multi_calendar.get_active_calendar();
// Clip dates
var dfrom = filterDates[0].clone(),
dto = filterDates[1].clone();
@@ -203,11 +204,11 @@ var PMSCalendarController = AbstractController.extend({
self._multi_calendar._reserv_tooltips = _.extend(this._multi_calendar._reserv_tooltips, results['tooltips']);
self._multi_calendar.merge_days_tooltips(results['events']);
self._multi_calendar.merge_pricelist(results['pricelist']);
self._multi_calendar.merge_restrictions(results['restrictions']);
self._multi_calendar.merge_reservations(reservs);
self._multi_calendar.merge_pricelist(results['pricelist'], active_calendar);
self._multi_calendar.merge_restrictions(results['restrictions'], active_calendar);
self._multi_calendar.merge_reservations(reservs, active_calendar);
self._multi_calendar._assign_extra_info(this._multi_calendar.get_active_calendar());
self._multi_calendar._assign_extra_info(active_calendar);
}.bind(this)).then(function(){
self.renderer._last_dates = filterDates;
});
@@ -216,15 +217,12 @@ var PMSCalendarController = AbstractController.extend({
_assign_view_events: function() {
var self = this;
var $dateTimePickerBegin = this.renderer.$el.find('#pms-menu #date_begin');
var $dateTimePickerEnd = this.renderer.$el.find('#pms-menu #date_end');
var $dateEndDays = this.renderer.$el.find('#pms-menu #date_end_days');
$dateTimePickerBegin.on("dp.change", function (e) {
$dateTimePickerEnd.data("DateTimePicker").minDate(e.date.clone().add(3,'d'));
$dateTimePickerEnd.data("DateTimePicker").maxDate(e.date.clone().add(2,'M'));
$dateTimePickerBegin.data("DateTimePicker").hide();
self._on_change_filter_date(true);
});
$dateTimePickerEnd.on("dp.change", function (e) {
$dateTimePickerEnd.data("DateTimePicker").hide();
$dateEndDays.on("change", function (e) {
self._on_change_filter_date(false);
});
@@ -550,13 +548,8 @@ var PMSCalendarController = AbstractController.extend({
this._multi_calendar.on_calendar('hcalOnDateChanged', function(ev){
var $dateTimePickerBegin = this.renderer.$el.find('#pms-menu #date_begin');
var $dateTimePickerEnd = this.renderer.$el.find('#pms-menu #date_end');
$dateTimePickerBegin.data("ignore_onchange", true);
$dateTimePickerEnd.data("DateTimePicker").minDate(false);
$dateTimePickerEnd.data("DateTimePicker").maxDate(false);
$dateTimePickerBegin.data("DateTimePicker").date(ev.detail.date_begin.local().add(1, 'd'));
$dateTimePickerEnd.data("ignore_onchange", true);
$dateTimePickerEnd.data("DateTimePicker").date(ev.detail.date_end.local());
this._reload_active_calendar();
}.bind(this));
},
@@ -591,7 +584,7 @@ var PMSCalendarController = AbstractController.extend({
if (['xs', 'md'].indexOf(self._find_bootstrap_environment()) >= 0) {
self._view_options['days'] = 7;
} else {
self._view_options['days'] = (self._view_options['days'] !== 'month')?parseInt(self._view_options['days']):date_begin.daysInMonth();
self._view_options['days'] = 30;
}
self.renderer.load_hcalendar_options(self._view_options);
});
@@ -747,12 +740,12 @@ var PMSCalendarController = AbstractController.extend({
_on_change_filter_date: function(isStartDate) {
isStartDate = isStartDate || false;
var $dateTimePickerBegin = this.renderer.$el.find('#pms-menu #date_begin');
var $dateTimePickerEnd = this.renderer.$el.find('#pms-menu #date_end');
var $dateEndDays = this.renderer.$el.find('#pms-menu #date_end_days');
// FIXME: Hackish onchange ignore (Used when change dates from code)
if ($dateTimePickerBegin.data("ignore_onchange") || $dateTimePickerEnd.data("ignore_onchange")) {
if ($dateTimePickerBegin.data("ignore_onchange") || $dateEndDays.data("ignore_onchange")) {
$dateTimePickerBegin.data("ignore_onchange", false);
$dateTimePickerEnd.data("ignore_onchange", false)
$dateEndDays.data("ignore_onchange", false)
return true;
}
@@ -760,15 +753,10 @@ var PMSCalendarController = AbstractController.extend({
var active_calendar = this._multi_calendar.get_active_calendar();
if (active_calendar && date_begin) {
if (isStartDate) {
var ndate_end = date_begin.clone().add(this._view_options['days'], 'd');
$dateTimePickerEnd.data("ignore_onchange", true);
$dateTimePickerEnd.data("DateTimePicker").date(ndate_end.local());
}
var date_end = $dateTimePickerEnd.data("DateTimePicker").date().set({'hour': 23, 'minute': 59, 'second': 59}).clone().utc();
var days = $dateEndDays.val();
var date_end = date_begin.clone().add(days, 'd');
if (!date_begin.isSame(this.renderer._last_dates[0].clone().utc(), 'd') || !date_end.isSame(this.renderer._last_dates[1].clone().utc(), 'd')) {
active_calendar.setStartDate(date_begin, active_calendar.getDateDiffDays(date_begin, date_end), false, function(){
active_calendar.setStartDate(date_begin, days, false, function(){
this._reload_active_calendar();
}.bind(this));
}
@@ -780,16 +768,18 @@ var PMSCalendarController = AbstractController.extend({
/* Dates */
var $dateTimePickerBegin = this.renderer.$el.find('#pms-menu #date_begin');
var $dateTimePickerEnd = this.renderer.$el.find('#pms-menu #date_end');
var $dateEndDays = this.renderer.$el.find('#pms-menu #date_end_days');
var start_date = active_calendar.getOptions('startDate');
var end_date = start_date.clone().add(active_calendar.getOptions('days'), 'd');
start_date = start_date.clone().add(1, 'd');
$dateTimePickerBegin.data("ignore_onchange", true);
$dateTimePickerBegin.data("DateTimePicker").date(start_date.local());
$dateTimePickerEnd.data("ignore_onchange", true);
$dateTimePickerEnd.data("DateTimePicker").date(end_date.local());
$dateEndDays.data("ignore_onchange", true);
console.log("---- LLL");
console.log(active_calendar.getOptions('days'));
$dateEndDays.val(active_calendar.getOptions('days'));
$dateEndDays.trigger('change');
/* Overbooking Led */
var $led = this.renderer.$el.find('#pms-menu #btn_action_overbooking button .led');

View File

@@ -53,10 +53,12 @@ var HotelCalendarView = AbstractRenderer.extend({
/** CUSTOM METHODS **/
get_view_filter_dates: function () {
var $dateTimePickerBegin = this.$el.find('#pms-menu #date_begin');
var $dateTimePickerEnd = this.$el.find('#pms-menu #date_end');
var $dateEndDays = this.$el.find('#pms-menu #date_end_days');
var date_begin = $dateTimePickerBegin.data("DateTimePicker").date().set({'hour': 0, 'minute': 0, 'second': 0}).clone().utc();
var date_end = $dateTimePickerEnd.data("DateTimePicker").date().set({'hour': 23, 'minute': 59, 'second': 59}).clone().utc();
var date_end = date_begin.clone().add($dateEndDays.val(), 'd').set({'hour': 23, 'minute': 59, 'second': 59}).clone().utc();
console.log("---- YEAH");
console.log(date_end.format("DD-MM-YYYY"));
return [date_begin, date_end];
},
@@ -64,13 +66,13 @@ var HotelCalendarView = AbstractRenderer.extend({
// View Options
this._view_options = options;
var date_begin = moment().startOf('day');
var date_end = date_begin.clone().add(this._view_options['days'], 'd').endOf('day');
var $dateTimePickerBegin = this.$el.find('#pms-menu #date_begin');
var $dateTimePickerEnd = this.$el.find('#pms-menu #date_end');
var $dateEndDays = this.$el.find('#pms-menu #date_end_days');
//$dateTimePickerBegin.data("ignore_onchange", true);
$dateTimePickerBegin.data("DateTimePicker").date(date_begin);
//$dateTimePickerEnd.data("ignore_onchange", true);
$dateTimePickerEnd.data("DateTimePicker").date(date_end);
$dateEndDays.val(30);
$dateEndDays.trigger('change');
this._last_dates = this.get_view_filter_dates();
},
@@ -122,16 +124,26 @@ var HotelCalendarView = AbstractRenderer.extend({
format : HotelConstants.L10N_DATE_MOMENT_FORMAT,
};
var $dateTimePickerBegin = this.$el.find('#pms-menu #date_begin');
var $dateTimePickerEnd = this.$el.find('#pms-menu #date_end');
var $dateEndDays = this.$el.find('#pms-menu #date_end_days');
$dateTimePickerBegin.datetimepicker(DTPickerOptions);
$dateTimePickerEnd.datetimepicker($.extend({}, DTPickerOptions, { 'useCurrent': false }));
// $dateEndDays.select2({
// data: [
// {id:7, text: '1 Week'},
// {id:12, text: '2 Weeks'},
// {id:21, text: '3 Weeks'},
// {id:30, text: '1 Month'},
// {id:60, text: '2 Months'},
// {id:90, text: '3 Months'},
// ],
// allowClear: true
// });
var date_begin = moment().startOf('day');
var days = date_begin.daysInMonth();
var date_end = date_begin.clone().add(days, 'd').endOf('day');
$dateTimePickerBegin.data("ignore_onchange", true);
$dateTimePickerBegin.data("DateTimePicker").date(date_begin);
$dateTimePickerEnd.data("DateTimePicker").date(date_end);
$dateEndDays.val(30);
$dateEndDays.trigger('change');
this._last_dates = this.get_view_filter_dates();
/* TOUCH EVENTS */
@@ -154,9 +166,6 @@ var HotelCalendarView = AbstractRenderer.extend({
date_begin = $dateTimePickerBegin.data("DateTimePicker").date().set({'hour': 0, 'minute': 0, 'second': 0}).clone().subtract(days, 'd');
}
if (date_begin) {
var date_end = date_begin.clone().add(self._view_options['days'], 'd').endOf('day');
$dateTimePickerEnd.data("ignore_onchange", true);
$dateTimePickerEnd.data("DateTimePicker").date(date_end);
$dateTimePickerBegin.data("DateTimePicker").date(date_begin);
}
}
@@ -195,9 +204,7 @@ var HotelCalendarView = AbstractRenderer.extend({
resultsHotelRoomType.forEach(function(item, index){
$list.append(`<option value="${item.id}">${item.name}</option>`);
});
$list.select2({
theme: "classic"
});
$list.select2();
$list.on('change', function(ev){
this.trigger_up('onApplyFilters');
}.bind(this));

View File

@@ -150,7 +150,7 @@ odoo.define('hotel_calendar.MultiCalendar', function(require) {
this._base = element;
},
merge_pricelist: function(pricelist) {
merge_pricelist: function(pricelist, calendar) {
var keys = _.keys(pricelist);
for (var k of keys) {
var pr = pricelist[k];
@@ -176,12 +176,16 @@ odoo.define('hotel_calendar.MultiCalendar', function(require) {
}
}
for (var calendar of this._calendars) {
if (!calendar) {
for (var calendar of this._calendars) {
calendar.setPricelist(this._dataset['pricelist']);
}
} else {
calendar.setPricelist(this._dataset['pricelist']);
}
},
merge_restrictions: function(restrictions) {
merge_restrictions: function(restrictions, calendar) {
var room_type_ids = Object.keys(restrictions);
for (var vid of room_type_ids) {
if (vid in this._dataset['restrictions']) {
@@ -192,12 +196,16 @@ odoo.define('hotel_calendar.MultiCalendar', function(require) {
}
}
for (var calendar of this._calendars) {
if (!calendar) {
for (var calendar of this._calendars) {
calendar.setRestrictions(this._dataset['restrictions']);
}
} else {
calendar.setRestrictions(this._dataset['restrictions']);
}
},
merge_reservations: function(reservations) {
merge_reservations: function(reservations, calendar) {
for (var r of reservations) {
var rindex = _.findKey(this._dataset['reservations'], {'id': r.id});
if (rindex) {
@@ -207,7 +215,11 @@ odoo.define('hotel_calendar.MultiCalendar', function(require) {
}
}
for (var calendar of this._calendars) {
if (!calendar) {
for (var calendar of this._calendars) {
calendar.addReservations(reservations);
}
} else {
calendar.addReservations(reservations);
}
},
@@ -352,7 +364,7 @@ odoo.define('hotel_calendar.MultiCalendar', function(require) {
});
if (data.length > 0) {
$elm.addClass('hcal-event-day');
$elm.prepend("<i class='fa fa-bell' style='margin-right: 0.1em'></i>");
$elm.append("<i class='fa fa-bell' style='margin-right: 0.1em'></i>");
$elm.on("mouseenter", function(data){
var $this = $(this);
if (data.length > 0) {

View File

@@ -41,9 +41,10 @@ function HotelCalendar(/*String*/querySelector, /*Dictionary*/options, /*List*/p
};
/** Options **/
var now_utc = moment(new Date()).utc();
this.options = _.extend({
startDate: moment(new Date()).utc(),
days: moment(new Date()).local().daysInMonth(),
startDate: now_utc,
days: now_utc.daysInMonth(),
rooms: [],
allowInvalidActions: false,
assistedMovement: false,
@@ -122,7 +123,6 @@ HotelCalendar.prototype = {
if (typeof days !== 'undefined') {
this.options.days = days;
}
this._endDate = this.options.startDate.clone().add(this.options.days, 'd');
/*this.e.dispatchEvent(new CustomEvent(
@@ -259,17 +259,17 @@ HotelCalendar.prototype = {
this.addReservations(reservations);
},
addReservations: function(/*List*/reservations, /*Bool*/forced) {
addReservations: function(/*List*/reservations) {
reservations = reservations || [];
if (reservations.length > 0 && !(reservations[0] instanceof HReservation)) {
console.warn("[HotelCalendar][addReservations] Invalid Reservation definition!");
} else {
// Merge
var uzr = [];
var addedReservations = [];
for (var r of reservations) {
var rindex = _.findKey(this._reservations, {'id': r.id});
if (!this.options.showOverbookings && r.overbooking) {
var rindex = !forced?_.findKey(this._reservations, {'id': r.id}):false;
if (rindex) {
this.removeReservation(this._reservations[rindex]);
}
@@ -279,28 +279,33 @@ HotelCalendar.prototype = {
r = r.clone(); // HOT-FIX: Multi-Calendar Support
r.room = this.getRoom(r.room_id, r.overbooking, r.id);
// need create a overbooking row?
if (!r.room && r.overbooking) {
r.room = this.createOBRoom(this.getRoom(r.room_id), r.id);
this.createOBRoomRow(r.room);
}
if (!r.room) {
console.warn(`Can't found a room for the reservation '${r[0]}'!`);
continue;
if (r.overbooking) {
r.room = this.createOBRoom(this.getRoom(r.room_id), r.id);
this.createOBRoomRow(r.room);
} else {
console.warn(`Can't found the room '${r.room_id}' for the reservation '${r.id}' (${r.title})!`);
continue;
}
}
var rindex = !forced?_.findKey(this._reservations, {'id': r.id}):false;
if (rindex) {
r._html = this._reservations[rindex]._html;
if (this._reservations[rindex].overbooking && !r.overbooking) {
if (this.getReservationsByRoom(this._reservations[rindex].room).length === 1) {
this.removeOBRoomRow(this._reservations[rindex]);
var reserv = this._reservations[rindex];
r._html = reserv._html;
if (reserv.overbooking && !r.overbooking) {
if (this.getReservationsByRoom(reserv.room).length === 1) {
this.removeOBRoomRow(reserv);
}
}
this._reservations[rindex] = r;
this._cleanUnusedZones(r);
if (!r.unusedZone) {
this._cleanUnusedZones(r);
}
} else {
this._reservations.push(r);
}
addedReservations.push(r);
}
// Create & Render New Reservations
@@ -311,42 +316,38 @@ HotelCalendar.prototype = {
// Create Map
this._updateReservationsMap();
var toAssignEvents = [];
reservs = reservs.concat(unusedZones);
for (var r of reservs) {
r._active = this._in_domain(r, this._domains[HotelCalendar.DOMAIN.RESERVATIONS]);
this._calcReservationCellLimits(r);
if (r._html) {
r._html.innerText = r.title;
} else {
if (r._limits.isValid()) {
r._html = document.createElement('div');
r._html.dataset.hcalReservationObjId = r.id;
r._html.classList.add('hcal-reservation');
r._html.classList.add('noselect');
r._html.innerText = r.title;
this.edivr.appendChild(r._html);
} else if (r._limits.isValid()) {
r._html = document.createElement('div');
r._html.dataset.hcalReservationObjId = r.id;
r._html.classList.add('hcal-reservation');
r._html.classList.add('noselect');
r._html.innerText = r.title;
this.edivr.appendChild(r._html);
if (r.unusedZone) {
r._html.classList.add('hcal-unused-zone');
} else {
this._assignReservationsEvents([r._html]);
}
if (r.unusedZone) {
r._html.classList.add('hcal-unused-zone');
} else {
toAssignEvents.push(r._html);
}
}
this._updateReservation(r);
}
}.bind(this), this._reservations);
this._updateReservationsMap();
this._assignReservationsEvents(toAssignEvents);
}.bind(this), addedReservations);
_.defer(function(){ this._updateReservationOccupation(); }.bind(this));
}
},
removeReservation: function(/*Int/HReservationObject*/reservation) {
var reserv = reservation;
if (!(reserv instanceof HReservation)) {
reserv = this.getReservation(reservation);
}
removeReservation: function(/*HReservationObject*/reserv) {
if (reserv) {
// Remove all related content...
var elms = [reserv._html, this.e.querySelector(`.hcal-warn-ob-indicator[data-hcal-reservation-obj-id='${reserv.id}']`)];
@@ -362,12 +363,14 @@ HotelCalendar.prototype = {
}
}
// Remove Unused Zones
this._cleanUnusedZones(reserv);
if (!reserv.unusedZone) {
this._cleanUnusedZones(reserv);
}
this._reservations = _.reject(this._reservations, {id: reserv.id});
this._updateReservationsMap();
} else {
console.warn(`[HotelCalendar][removeReservation] Can't remove '${reservation.id}' reservation!`);
console.warn(`[HotelCalendar][removeReservation] Can't remove '${reserv.id}' reservation!`);
}
},
@@ -414,8 +417,12 @@ HotelCalendar.prototype = {
_calcReservationCellLimits: function(/*HReservationObject*/reservation, /*Int?*/nbed, /*Bool?*/notCheck) {
var limits = new HLimit();
if (!reservation.startDate || !reservation.endDate) {
return limits;
if (!reservation.startDate || !reservation.endDate ||
(!reservation.startDate.isBetween(this.options.startDate, this._endDate, 'day', '()') &&
!reservation.endDate.isBetween(this.options.startDate, this._endDate, 'day', '()') &&
!reservation.startDate.isBefore(this.options.startDate, 'day', '()') &&
!reservation.endDate.isAfter(this._endDate, 'day', '()'))) {
return limits;
}
var notFound;
@@ -1842,11 +1849,6 @@ HotelCalendar.prototype = {
nreserv.overbooking = refToRoom.overbooking;
nreserv.room = refFromRoom;
}
// if (this.options.divideRoomsByCapacity) {
// var allReservs = toReservations.concat(fromReservations);
// for (var nreserv of allReservs) { this._updateUnusedZones(nreserv); }
// }
} else {
console.warn("[HotelCalendar][swapReservations] Invalid Swap Operation!");
return false;
@@ -1905,7 +1907,7 @@ HotelCalendar.prototype = {
},
getLinkedReservations: function(/*HReservationObject*/reservationObj) {
return _.reject(this._reservations, function(item){ return item === reservationObj || item.linkedId !== reservationObj.id; });
return _.reject(this._reservations, function(item){ return item.linkedId !== reservationObj.id; });
},
_updateReservation: function(/*HReservationObject*/reservationObj, /*Bool?*/noRefresh) {
@@ -2071,8 +2073,13 @@ HotelCalendar.prototype = {
},
_cleanUnusedZones: function(/*HReservationObject*/reserv) {
var reservs = _.filter(this._reservations, function(item){ return item.unusedZone && item.linkedId === reserv.id; });
for (var creserv of reservs) { this.removeReservation(creserv); }
var unusedReservs = this.getLinkedReservations(reserv);
for (var unusedZone of unusedReservs) {
if (unusedZone._html && unusedZone._html.parentNode) {
unusedZone._html.parentNode.removeChild(unusedZone._html);
}
}
this._reservations = _.reject(this._reservations, {unusedZone: true, linkedId: reserv.id});
},
_createUnusedZones: function(/*Array*/reservs) {
@@ -2106,14 +2113,6 @@ HotelCalendar.prototype = {
return nreservs;
},
_updateUnusedZones: function(/*HReservationObject*/reserv) {
if (!reserv.unusedZone) {
// TODO: Improve this... don't remove stuff that recreate before!!!
this._cleanUnusedZones(reserv);
this.addReservations(this._createUnusedZones([reserv]), true);
}
},
_updateReservationOccupation: function() {
if (!this.options.showAvailability) {
return;
@@ -2704,34 +2703,6 @@ HotelCalendar.prototype = {
}.bind(this));
},
onClickSelectorDate: function(/*EventObject*/ev, /*HTMLObject*/elm) {
var $this = this;
function setSelectorDate(elm) {
var new_date = moment(elm.value, HotelCalendar.DATE_FORMAT_SHORT_);
var span = document.createElement('span');
span.addEventListener('click', function(ev){ $this.onClickSelectorDate(ev, elm); }, false);
if (new_date.isValid()) {
$this.setStartDate(new_date);
} else {
$this.setStartDate(moment(new Date()).utc());
}
}
var str_date = this.options.startDate.format(HotelCalendar.DATE_FORMAT_SHORT_);
var input = document.createElement('input');
input.setAttribute('id', 'start_date_selector');
input.setAttribute('type', 'text');
input.setAttribute('value', str_date);
input.addEventListener('keypress', function(ev){
if (ev.keyCode == 13) { // Press Enter
setSelectorDate(this);
}
}, false);
input.addEventListener('blur', function(ev){ setSelectorDate(this); }, false);
elm.parentNode.insertBefore(input, elm);
elm.parentNode.removeChild(elm);
input.focus();
},
//==== COLOR FUNCTIONS (RANGE: 0.0|1.0)
_intToRgb: function(/*Int*/RGBint) {
return [(RGBint >> 16) & 255, (RGBint >> 8) & 255, RGBint & 255];

View File

@@ -4,18 +4,17 @@
<t t-name="hotel_calendar.HotelCalendarView">
<div class="col-xs-12 col-md-12 nopadding o_hotel_calendar_view">
<div class="col-lg-2 hidden-xs hidden-sm" id="pms-menu">
<div class="col-xs-12 col-md-12 nopadding menu-date-box">
<div class="input-group date" id="date_begin">
<input type="text" class="o_datepicker_input form-control" name="date_begin" required="required"/>
<span class="input-group-addon">
<span class="fa fa-calendar"></span>
</span>
<div class="row nopadding menu-date-box">
<div class="form-group nopadding col-xs-7 col-md-7">
<div class="input-group date" id="date_begin">
<input type="text" class="o_datepicker_input form-control" name="date_begin" required="required"/>
<span class="input-group-addon">
<span class="fa fa-calendar"></span>
</span>
</div>
</div>
<div class="input-group date" id="date_end">
<input type="text" class="o_datepicker_input form-control" name="date_end" required="required"/>
<span class="input-group-addon">
<span class="fa fa-calendar"></span>
</span>
<div class="form-group nopadding col-xs-5 col-md-5">
<input id="date_end_days" required="required" />
</div>
</div>
<div class="col-xs-12 col-md-12 nopadding menu-button-box">
@@ -136,15 +135,7 @@
</div>
</div>
<div class="col-xs-12 col-sm-12 col-md-10 nopadding" id="pms-calendar">
<div id="hcal_widget" class="col-xs-12 col-md-12 nopadding">
<div id="hcal_load">
<span>
<i class="fa fa-refresh fa-spin fa-5x fa-fw"></i><br/>
<h1>Loading Calendar...</h1>
</span>
</div>
</div>
<div style="clear:both;" />
<div id="hcal_widget" class="col-xs-12 col-md-12 nopadding" />
</div>
</div>
</t>