diff --git a/odex25_base/fims_general_search_tree_view/static/src/js/list_search.js b/odex25_base/fims_general_search_tree_view/static/src/js/list_search.js
index 431b91e72..985db20bf 100644
--- a/odex25_base/fims_general_search_tree_view/static/src/js/list_search.js
+++ b/odex25_base/fims_general_search_tree_view/static/src/js/list_search.js
@@ -3,12 +3,17 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
var ListRenderer = require('web.ListRenderer');
var ListController = require('web.ListController');
+ var relational_fields = require('web.relational_fields');
var core = require('web.core');
var _t = core._t;
var concurrency = require('web.concurrency');
var rpc = require('web.rpc');
var session = require('web.session');
+ // Get relational field classes
+ var FieldOne2Many = relational_fields.FieldOne2Many;
+ var FieldMany2Many = relational_fields.FieldMany2Many;
+
ListController.include({
init: function () {
this._super.apply(this, arguments);
@@ -512,8 +517,305 @@ 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 =
+ '
' +
+ '' +
+ '' +
+ '' +
+ countText +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
';
+
+ 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 {
ListController: ListController,
- ListRenderer: ListRenderer
+ ListRenderer: ListRenderer,
+ FieldOne2Many: FieldOne2Many,
+ FieldMany2Many: FieldMany2Many
};
});
\ No newline at end of file