Revert "🚀 [FEATURE] Enhanced Search Support for Many2Many/One2Many Fields in fims_general_search_tree_view"
This commit is contained in:
parent
dbf4d552ab
commit
465c84dad6
|
|
@ -3,17 +3,12 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
|
||||||
|
|
||||||
var ListRenderer = require('web.ListRenderer');
|
var ListRenderer = require('web.ListRenderer');
|
||||||
var ListController = require('web.ListController');
|
var ListController = require('web.ListController');
|
||||||
var relational_fields = require('web.relational_fields');
|
|
||||||
var core = require('web.core');
|
var core = require('web.core');
|
||||||
var _t = core._t;
|
var _t = core._t;
|
||||||
var concurrency = require('web.concurrency');
|
var concurrency = require('web.concurrency');
|
||||||
var rpc = require('web.rpc');
|
var rpc = require('web.rpc');
|
||||||
var session = require('web.session');
|
var session = require('web.session');
|
||||||
|
|
||||||
// Get relational field classes
|
|
||||||
var FieldOne2Many = relational_fields.FieldOne2Many;
|
|
||||||
var FieldMany2Many = relational_fields.FieldMany2Many;
|
|
||||||
|
|
||||||
ListController.include({
|
ListController.include({
|
||||||
init: function () {
|
init: function () {
|
||||||
this._super.apply(this, arguments);
|
this._super.apply(this, arguments);
|
||||||
|
|
@ -387,7 +382,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
|
||||||
|
|
||||||
if (state.isFiltered && state.filteredCount >= 0) {
|
if (state.isFiltered && state.filteredCount >= 0) {
|
||||||
this.$('.oe_search_count')
|
this.$('.oe_search_count')
|
||||||
.text(_t('عدد السجلات: ') + state.filteredCount)
|
.text(_t('Records: ') + state.filteredCount)
|
||||||
.removeClass('text-danger')
|
.removeClass('text-danger')
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
@ -415,7 +410,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var countText = isFiltered ?
|
var countText = isFiltered ?
|
||||||
_t('عدد السجلات: ') + savedCount : '';
|
_t('Records: ') + savedCount : '';
|
||||||
|
|
||||||
var html =
|
var html =
|
||||||
'<div class="oe_search_container d-flex align-items-center">' +
|
'<div class="oe_search_container d-flex align-items-center">' +
|
||||||
|
|
@ -517,305 +512,8 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ============= NEW: Relational Fields Support =============
|
|
||||||
|
|
||||||
// Mixin for common relational field functionality
|
|
||||||
var RelationalFieldMixin = {
|
|
||||||
|
|
||||||
_searchState: null,
|
|
||||||
_searchMutex: null,
|
|
||||||
|
|
||||||
init: function() {
|
|
||||||
this._super.apply(this, arguments);
|
|
||||||
this._initRelationalSearch();
|
|
||||||
},
|
|
||||||
|
|
||||||
_initRelationalSearch: function() {
|
|
||||||
this._searchState = {
|
|
||||||
timer: null,
|
|
||||||
value: '',
|
|
||||||
isFiltered: false,
|
|
||||||
filteredCount: 0,
|
|
||||||
originalRecords: null,
|
|
||||||
allRecords: null,
|
|
||||||
lastSearchValue: ''
|
|
||||||
};
|
|
||||||
this._searchMutex = new concurrency.Mutex();
|
|
||||||
},
|
|
||||||
|
|
||||||
_renderView: function() {
|
|
||||||
var self = this;
|
|
||||||
return this._super.apply(this, arguments).then(function(result) {
|
|
||||||
if (self._shouldAddRelationalSearchBox()) {
|
|
||||||
self._addRelationalSearchBox();
|
|
||||||
self._storeOriginalRecords();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_shouldAddRelationalSearchBox: function() {
|
|
||||||
return this.renderer &&
|
|
||||||
this.renderer.$el &&
|
|
||||||
this.renderer.$el.find('.o_list_view').length > 0 &&
|
|
||||||
!this.renderer.$el.find('.oe_relational_search_container').length &&
|
|
||||||
this.value &&
|
|
||||||
this.value.data &&
|
|
||||||
this.value.data.length > 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
_storeOriginalRecords: function() {
|
|
||||||
if (this.value && this.value.data && !this._searchState.originalRecords) {
|
|
||||||
this._searchState.originalRecords = JSON.parse(JSON.stringify(this.value.data));
|
|
||||||
this._searchState.allRecords = JSON.parse(JSON.stringify(this.value.data));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_addRelationalSearchBox: function() {
|
|
||||||
var self = this;
|
|
||||||
var savedValue = this._searchState.value || '';
|
|
||||||
var savedCount = this._searchState.filteredCount || 0;
|
|
||||||
var isFiltered = this._searchState.isFiltered || false;
|
|
||||||
|
|
||||||
var countText = isFiltered ?
|
|
||||||
_t('عدد السجلات: ') + savedCount : '';
|
|
||||||
|
|
||||||
var html =
|
|
||||||
'<div class="oe_relational_search_container d-flex align-items-center mb-2" style="padding: 8px; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border: 1px solid #dee2e6; border-radius: 6px; gap: 8px;">' +
|
|
||||||
'<input type="text" class="oe_relational_search_input form-control flex-grow-1" ' +
|
|
||||||
'placeholder="' + _.escape(_t('البحث في السجلات المرتبطة...')) + '" ' +
|
|
||||||
'value="' + _.escape(savedValue) + '" ' +
|
|
||||||
'autocomplete="off" style="height: 32px; border-radius: 4px;">' +
|
|
||||||
'<button class="btn btn-secondary oe_relational_clear_search" style="display: ' +
|
|
||||||
(savedValue ? 'inline-block' : 'none') + '; height: 32px; min-width: 60px;">' +
|
|
||||||
'<i class="fa fa-times mr-1"></i>' + _t('مسح') +
|
|
||||||
'</button>' +
|
|
||||||
'<span class="oe_relational_search_count badge badge-success" style="display: ' +
|
|
||||||
(isFiltered ? 'inline-block' : 'none') + '; font-size: 0.9em;">' +
|
|
||||||
countText +
|
|
||||||
'</span>' +
|
|
||||||
'<span class="oe_relational_search_loading" style="display: none; color: #007bff;">' +
|
|
||||||
'<i class="fa fa-spinner fa-spin"></i>' +
|
|
||||||
'</span>' +
|
|
||||||
'</div>';
|
|
||||||
|
|
||||||
var $listContainer = this.renderer.$el.find('.o_list_view').first();
|
|
||||||
$listContainer.prepend($(html));
|
|
||||||
|
|
||||||
// Bind events
|
|
||||||
this.renderer.$el.find('.oe_relational_search_input').on('keyup', function(e) {
|
|
||||||
self._onRelationalSearchKeyUp(e);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.renderer.$el.find('.oe_relational_search_input').on('input', function(e) {
|
|
||||||
self._onRelationalSearchInput(e);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.renderer.$el.find('.oe_relational_clear_search').on('click', function(e) {
|
|
||||||
self._onRelationalClearSearch(e);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.renderer.$el.find('.oe_relational_search_input').on('keydown', function(e) {
|
|
||||||
self._onRelationalSearchKeyDown(e);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_onRelationalSearchInput: function(e) {
|
|
||||||
var currentValue = $(e.currentTarget).val();
|
|
||||||
var hasValue = !!currentValue.trim();
|
|
||||||
|
|
||||||
this.renderer.$el.find('.oe_relational_clear_search').toggle(hasValue);
|
|
||||||
|
|
||||||
if (!hasValue) {
|
|
||||||
this.renderer.$el.find('.oe_relational_search_count').hide();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_onRelationalSearchKeyUp: function(e) {
|
|
||||||
var self = this;
|
|
||||||
var value = $(e.currentTarget).val().trim();
|
|
||||||
var ignoreKeys = [13, 27, 16, 17, 18, 91, 93, 37, 38, 39, 40,
|
|
||||||
33, 34, 35, 36, 9, 20, 144, 145,
|
|
||||||
112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123];
|
|
||||||
|
|
||||||
if (ignoreKeys.indexOf(e.which) !== -1) return;
|
|
||||||
if (value === this._searchState.lastSearchValue) return;
|
|
||||||
|
|
||||||
this._searchState.lastSearchValue = value;
|
|
||||||
|
|
||||||
if (this._searchState.timer) {
|
|
||||||
clearTimeout(this._searchState.timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._searchState.timer = setTimeout(function() {
|
|
||||||
self._executeRelationalSearch(value);
|
|
||||||
}, 300);
|
|
||||||
},
|
|
||||||
|
|
||||||
_onRelationalSearchKeyDown: function(e) {
|
|
||||||
if (e.which === 13) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (this._searchState.timer) {
|
|
||||||
clearTimeout(this._searchState.timer);
|
|
||||||
}
|
|
||||||
var value = $(e.currentTarget).val().trim();
|
|
||||||
this._executeRelationalSearch(value);
|
|
||||||
} else if (e.which === 27) {
|
|
||||||
e.preventDefault();
|
|
||||||
this._onRelationalClearSearch();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_onRelationalClearSearch: function() {
|
|
||||||
this.renderer.$el.find('.oe_relational_search_input').val('').focus();
|
|
||||||
this.renderer.$el.find('.oe_relational_clear_search').hide();
|
|
||||||
this.renderer.$el.find('.oe_relational_search_count').hide();
|
|
||||||
this._searchState.lastSearchValue = '';
|
|
||||||
this._clearRelationalSearch();
|
|
||||||
},
|
|
||||||
|
|
||||||
_executeRelationalSearch: function(value) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
return this._searchMutex.exec(function() {
|
|
||||||
if (!value || value.length === 0) {
|
|
||||||
return self._clearRelationalSearch();
|
|
||||||
}
|
|
||||||
|
|
||||||
return self._applyRelationalSearch(value);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_applyRelationalSearch: function(value) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (!this._searchState.allRecords || this._searchState.allRecords.length === 0) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
var normalizedSearch = this._normalizeText(value.toLowerCase());
|
|
||||||
var searchValues = [value.toLowerCase(), normalizedSearch].filter(function(v, i, arr) {
|
|
||||||
return arr.indexOf(v) === i; // Remove duplicates
|
|
||||||
});
|
|
||||||
|
|
||||||
var filteredRecords = this._searchState.allRecords.filter(function(record) {
|
|
||||||
return self._recordMatchesSearch(record, searchValues);
|
|
||||||
});
|
|
||||||
|
|
||||||
this._searchState.filteredCount = filteredRecords.length;
|
|
||||||
this._searchState.isFiltered = true;
|
|
||||||
this._searchState.value = value;
|
|
||||||
|
|
||||||
// Update the UI
|
|
||||||
this._updateRelationalSearchUI(filteredRecords.length);
|
|
||||||
|
|
||||||
// Update the field's value with filtered data
|
|
||||||
var newValue = _.clone(this.value);
|
|
||||||
newValue.data = filteredRecords;
|
|
||||||
newValue.count = filteredRecords.length;
|
|
||||||
|
|
||||||
// Re-render with filtered data
|
|
||||||
return this._setValue(newValue, {forceChange: true}).then(function() {
|
|
||||||
return Promise.resolve();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_recordMatchesSearch: function(record, searchValues) {
|
|
||||||
var self = this;
|
|
||||||
var recordData = record.data || {};
|
|
||||||
|
|
||||||
// Get searchable fields from the record
|
|
||||||
var searchableValues = [];
|
|
||||||
|
|
||||||
// Add all text-like fields
|
|
||||||
Object.keys(recordData).forEach(function(fieldName) {
|
|
||||||
var value = recordData[fieldName];
|
|
||||||
if (value !== null && value !== undefined) {
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
searchableValues.push(value.toLowerCase());
|
|
||||||
searchableValues.push(self._normalizeText(value.toLowerCase()));
|
|
||||||
} else if (typeof value === 'number') {
|
|
||||||
searchableValues.push(value.toString());
|
|
||||||
} else if (Array.isArray(value) && value.length === 2) {
|
|
||||||
// Many2one field format [id, display_name]
|
|
||||||
if (typeof value[1] === 'string') {
|
|
||||||
searchableValues.push(value[1].toLowerCase());
|
|
||||||
searchableValues.push(self._normalizeText(value[1].toLowerCase()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if any search value matches any record value
|
|
||||||
return searchValues.some(function(searchValue) {
|
|
||||||
return searchableValues.some(function(recordValue) {
|
|
||||||
return recordValue.indexOf(searchValue) !== -1;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_clearRelationalSearch: function() {
|
|
||||||
if (!this._searchState.originalRecords) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
this._searchState.value = '';
|
|
||||||
this._searchState.isFiltered = false;
|
|
||||||
this._searchState.filteredCount = 0;
|
|
||||||
this._searchState.lastSearchValue = '';
|
|
||||||
|
|
||||||
// Restore original records
|
|
||||||
var newValue = _.clone(this.value);
|
|
||||||
newValue.data = JSON.parse(JSON.stringify(this._searchState.originalRecords));
|
|
||||||
newValue.count = this._searchState.originalRecords.length;
|
|
||||||
|
|
||||||
// Hide search UI elements
|
|
||||||
if (this.renderer && this.renderer.$el) {
|
|
||||||
this.renderer.$el.find('.oe_relational_search_count').hide();
|
|
||||||
this.renderer.$el.find('.oe_relational_search_loading').hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-render with original data
|
|
||||||
return this._setValue(newValue, {forceChange: true});
|
|
||||||
},
|
|
||||||
|
|
||||||
_updateRelationalSearchUI: function(count) {
|
|
||||||
if (this.renderer && this.renderer.$el) {
|
|
||||||
this.renderer.$el.find('.oe_relational_search_count')
|
|
||||||
.text(_t('عدد السجلات: ') + count)
|
|
||||||
.removeClass('text-danger')
|
|
||||||
.show();
|
|
||||||
this.renderer.$el.find('.oe_relational_clear_search').show();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_normalizeText: function(text) {
|
|
||||||
if (!text) return '';
|
|
||||||
return text
|
|
||||||
.replace(/[\u064B-\u065F]/g, '') // Remove Arabic diacritics
|
|
||||||
.replace(/[٠-٩]/g, function(d) { // Convert Arabic-Indic digits
|
|
||||||
return String.fromCharCode(d.charCodeAt(0) - 0x0660 + 0x0030);
|
|
||||||
})
|
|
||||||
.replace(/[۰-۹]/g, function(d) { // Convert Extended Arabic-Indic digits
|
|
||||||
return String.fromCharCode(d.charCodeAt(0) - 0x06F0 + 0x0030);
|
|
||||||
})
|
|
||||||
.replace(/[آأإا]/g, 'ا') // Normalize Alef variations
|
|
||||||
.replace(/ة/g, 'ه') // Normalize Taa Marbouta
|
|
||||||
.replace(/[يئى]/g, 'ي') // Normalize Yaa variations
|
|
||||||
.trim();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Apply the mixin to FieldOne2Many
|
|
||||||
FieldOne2Many.include(RelationalFieldMixin);
|
|
||||||
|
|
||||||
// Apply the mixin to FieldMany2Many
|
|
||||||
FieldMany2Many.include(RelationalFieldMixin);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ListController: ListController,
|
ListController: ListController,
|
||||||
ListRenderer: ListRenderer,
|
ListRenderer: ListRenderer
|
||||||
FieldOne2Many: FieldOne2Many,
|
|
||||||
FieldMany2Many: FieldMany2Many
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
Loading…
Reference in New Issue