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 6d620a886..589cb5795 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
@@ -5,6 +5,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
var core = require('web.core');
var _t = core._t;
var concurrency = require('web.concurrency');
+ var rpc = require('web.rpc');
ListRenderer.include({
events: _.extend({}, ListRenderer.prototype.events, {
@@ -25,16 +26,17 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
},
/**
- * @override
+ * @override - FIXED: Single _renderView method with complete logic
*/
_renderView: function () {
var self = this;
- // Store current search value to restore after render
+ // Store current search state to restore after render
var currentSearchValue = this._search ? this._search.value : '';
var wasFiltered = this._search && this._search.isFiltered;
+ var originalFilteredCount = this._search ? this._search.filteredCount : 0;
- return this._super.apply(this, arguments).then(function () {
+ return this._super.apply(this, arguments).then(function (result) {
// Add search box if this is a tree view
if (self.arch && self.arch.tag === 'tree' &&
self.$el && self.$el.hasClass('o_list_view')) {
@@ -44,22 +46,19 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
self._addSearchBox();
}
- // Restore search UI state
- self._restoreSearchUI();
-
- // If there was a search active, restore the search state
- // but don't re-trigger the search to avoid infinite loops
+ // Restore search UI state without re-triggering search
if (currentSearchValue && wasFiltered) {
console.log('Restoring search state for:', currentSearchValue);
- self._search.value = currentSearchValue;
- self._search.isFiltered = true;
- // Show UI elements
- self.$('.oe_clear_search').show();
- if (self._search.filteredCount) {
- self.$('.oe_search_count').text(_t('Found: ') + self._search.filteredCount + _t(' records')).show();
- }
+ self._restoreSearchUIState(currentSearchValue, originalFilteredCount);
+ }
+
+ // Update search count after Odoo's native rendering if we're in search mode
+ if (self._search && self._search.isFiltered && self._search.value) {
+ self._updateSearchCount();
}
}
+
+ return result;
});
},
@@ -72,9 +71,9 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
}
var html = '
' +
- '' +
+ '' +
'' +
- '' +
+ '' +
'' +
'
';
this.$el.prepend($(html));
@@ -98,24 +97,29 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
},
/**
- * Restore search UI after render
+ * Restore search UI state after render - SIMPLIFIED
*/
- _restoreSearchUI: function() {
- if (!this._search) return;
-
+ _restoreSearchUIState: function(searchValue, filteredCount) {
var $input = this.$('.oe_search_input');
var $clearBtn = this.$('.oe_clear_search');
var $count = this.$('.oe_search_count');
// Restore input value
- if ($input.length && this._search.value) {
- $input.val(this._search.value);
+ if ($input.length && searchValue) {
+ $input.val(searchValue);
$clearBtn.show();
- }
-
- // Restore count if filtered
- if (this._search.isFiltered && this._search.filteredCount) {
- $count.text(_t('Found: ') + this._search.filteredCount + _t(' records')).show();
+
+ // Restore search state
+ this._search.value = searchValue;
+ this._search.isFiltered = true;
+
+ // Show count if available
+ if (filteredCount) {
+ this._search.filteredCount = filteredCount;
+ $count.text(_t('Found: ') + filteredCount + _t(' records')).show();
+ } else {
+ $count.text(_t('Searching...')).show();
+ }
}
},
@@ -127,7 +131,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
},
/**
- * Handle search input keyup - FIXED LOGIC
+ * Handle search input keyup - ENHANCED LOGIC
*/
_onSearchKeyUp: function(e) {
var self = this;
@@ -138,15 +142,14 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
clearTimeout(this._search.timer);
}
- // Update UI
+ // Update UI immediately
this.$('.oe_clear_search').toggle(!!value);
// Store value
this._search.value = value;
- // CRITICAL FIX: Always trigger search, even for empty values
+ // ENHANCED: Always trigger search with appropriate delay
this._search.timer = setTimeout(function() {
- // This will handle both search and clear cases properly
self._performSearch(value);
}, 500);
},
@@ -159,7 +162,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
},
/**
- * REDESIGNED: Perform search using proper Odoo mechanisms - THE CORRECT WAY
+ * ENHANCED: Perform search using proper Odoo mechanisms
*/
_performSearch: function(value) {
var self = this;
@@ -170,17 +173,17 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
return Promise.resolve();
}
- console.log('=== PERFORM SEARCH ===');
+ console.log('=== ENHANCED PERFORM SEARCH ===');
console.log('Search value:', value);
console.log('Value length:', value ? value.length : 0);
return this._searchMutex.exec(function() {
self._searchInProgress = true;
- // FIXED: Handle empty search by calling clear search
+ // Handle empty search by clearing filters
if (!value || value.length === 0) {
- console.log('Empty search - clearing filters using Odoo method');
- return self._clearSearchOdooWay().finally(function() {
+ console.log('Empty search - clearing all filters');
+ return self._clearSearchInternal().finally(function() {
self._searchInProgress = false;
});
}
@@ -192,7 +195,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
return Promise.resolve();
}
- console.log('Starting search using Odoo trigger_up method');
+ console.log('Starting enhanced search for model:', self.state.model);
// Store original domain only once
if (!self._search.originalDomain && !self._search.isFiltered) {
@@ -202,22 +205,22 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
// Show loading
self._showLoading(true);
- // THE CORRECT ODOO WAY: Use trigger_up with do_search
- return self._searchUsingOdooMethod(value).finally(function() {
+ // ENHANCED: Use proper Odoo search with count
+ return self._searchWithCount(value).finally(function() {
self._showLoading(false);
self._searchInProgress = false;
- console.log('=== Search completed ===');
+ console.log('=== Enhanced search completed ===');
});
});
},
/**
- * THE CORRECT ODOO METHOD: Use trigger_up to search
+ * ENHANCED: Search with accurate count using RPC
*/
- _searchUsingOdooMethod: function(value) {
+ _searchWithCount: function(value) {
var self = this;
- console.log('=== USING ODOO SEARCH METHOD ===');
+ console.log('=== ENHANCED SEARCH WITH COUNT ===');
// Build search domain
var searchDomain = this._buildSearchDomain(value);
@@ -227,44 +230,60 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
}
// Combine with original domain
- var finalDomain = [];
- if (this._search.originalDomain && this._search.originalDomain.length > 0) {
- if (searchDomain.length > 0) {
- finalDomain = ['&'].concat(this._search.originalDomain).concat(searchDomain);
- } else {
- finalDomain = this._search.originalDomain;
- }
- } else {
- finalDomain = searchDomain;
- }
+ var finalDomain = this._combineDomains(searchDomain);
console.log('Final search domain:', finalDomain);
- // THE CORRECT ODOO WAY: trigger_up with do_search
- this.trigger_up('do_search', {
- domain: finalDomain,
- context: this.state.context || {},
- groupBy: this.state.groupedBy || []
+ // STEP 1: Get accurate count using RPC
+ return rpc.query({
+ model: this.state.model,
+ method: 'search_count',
+ args: [finalDomain],
+ context: this.state.context || {}
+ }).then(function(count) {
+ console.log('Accurate search count from server:', count);
+
+ // Store count
+ self._search.filteredCount = count;
+
+ // Update UI with accurate count
+ self.$('.oe_search_count').text(_t('Found: ') + count + _t(' records')).show();
+
+ // STEP 2: Apply search using trigger_up
+ self.trigger_up('do_search', {
+ domain: finalDomain,
+ context: self.state.context || {},
+ groupBy: self.state.groupedBy || []
+ });
+
+ // Update search state
+ self._search.isFiltered = true;
+
+ return Promise.resolve();
+ }).catch(function(error) {
+ console.error('Error getting search count:', error);
+
+ // Fallback: still apply search without count
+ self.trigger_up('do_search', {
+ domain: finalDomain,
+ context: self.state.context || {},
+ groupBy: self.state.groupedBy || []
+ });
+
+ self._search.isFiltered = true;
+ self.$('.oe_search_count').text(_t('Search applied')).show();
+
+ return Promise.resolve();
});
-
- // Update search state
- this._search.isFiltered = true;
-
- // We can't get the count immediately since trigger_up is async
- // The count will be updated when the view is re-rendered
- // For now, show a generic "searching..." message
- this.$('.oe_search_count').text(_t('Searching...')).show();
-
- return Promise.resolve();
},
/**
- * THE CORRECT ODOO METHOD: Clear search using trigger_up
+ * ENHANCED: Clear search using proper Odoo method
*/
- _clearSearchOdooWay: function() {
+ _clearSearchInternal: function() {
var self = this;
- console.log('=== CLEARING SEARCH USING ODOO METHOD ===');
+ console.log('=== ENHANCED CLEAR SEARCH ===');
// Clear UI immediately
this.$('.oe_search_input').val('');
@@ -276,7 +295,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
this._search.isFiltered = false;
this._search.filteredCount = 0;
- // THE CORRECT ODOO WAY: trigger_up with original domain
+ // Use original domain to clear search
var originalDomain = this._search.originalDomain || [];
this.trigger_up('do_search', {
@@ -288,15 +307,15 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
// Clear stored original domain
this._search.originalDomain = null;
- console.log('Search cleared using Odoo method');
+ console.log('Search cleared using enhanced method');
return Promise.resolve();
},
/**
- * Clear search public method
+ * Public clear search method
*/
_clearSearch: function() {
- return this._clearSearchOdooWay();
+ return this._clearSearchInternal();
},
/**
@@ -304,62 +323,100 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
*/
_storeOriginalDomain: function() {
if (this.state && this.state.domain && !this._search.originalDomain) {
- this._search.originalDomain = this.state.domain.slice();
+ // Deep copy the domain to avoid reference issues
+ this._search.originalDomain = JSON.parse(JSON.stringify(this.state.domain));
console.log('Stored original domain:', this._search.originalDomain);
}
},
/**
- * Override _renderView to update search count after Odoo's native rendering
+ * ENHANCED: Combine search domain with original domain
*/
- _renderView: function () {
- var self = this;
- var promise = this._super.apply(this, arguments);
+ _combineDomains: function(searchDomain) {
+ var finalDomain = [];
- // After rendering is complete, update the search count if we're in search mode
- return promise.then(function() {
- if (self._search && self._search.isFiltered && self._search.value) {
- // Update count based on actual rendered rows
- var visibleRows = self.$('.o_data_row:visible').length;
- self._search.filteredCount = visibleRows;
- self.$('.oe_search_count').text(_t('Found: ') + visibleRows + _t(' records')).show();
-
- console.log('Updated search count after Odoo rendering:', visibleRows);
+ if (this._search.originalDomain && this._search.originalDomain.length > 0) {
+ if (searchDomain.length > 0) {
+ // Combine: originalDomain AND searchDomain
+ finalDomain = ['&'];
+ finalDomain = finalDomain.concat(this._search.originalDomain);
+ finalDomain = finalDomain.concat(searchDomain);
+ } else {
+ finalDomain = this._search.originalDomain.slice();
}
- return promise;
- });
+ } else {
+ finalDomain = searchDomain.slice();
+ }
+
+ return finalDomain;
},
/**
- * Build search domain
+ * ENHANCED: Update search count after render
+ */
+ _updateSearchCount: function() {
+ // If we have the accurate count from RPC, use it
+ if (this._search.filteredCount) {
+ this.$('.oe_search_count').text(_t('Found: ') + this._search.filteredCount + _t(' records')).show();
+ } else {
+ // Fallback: count visible records (just for immediate feedback)
+ var visibleCount = this.state && this.state.data ? this.state.data.length : 0;
+ if (visibleCount > 0) {
+ this.$('.oe_search_count').text(_t('Showing: ') + visibleCount + _t(' records')).show();
+ }
+ }
+ },
+
+ /**
+ * ENHANCED: Build search domain with better logic
*/
_buildSearchDomain: function(value) {
var fields = this._getSearchableFields();
if (fields.length === 0) {
+ console.warn('No searchable fields found');
return [];
}
+ console.log('Searching in fields:', fields.map(f => f.name + ' (' + f.type + ')'));
+
var conditions = [];
var normalized = this._normalizeArabic(value);
+ // Build conditions for each field type
fields.forEach(function(field) {
- if (['char', 'text', 'html'].includes(field.type)) {
- conditions.push([field.name, 'ilike', value]);
- if (normalized !== value) {
- conditions.push([field.name, 'ilike', normalized]);
- }
- } else if (['integer', 'float', 'monetary'].includes(field.type)) {
- var num = parseFloat(value);
- if (!isNaN(num)) {
- conditions.push([field.name, '=', num]);
- }
- } else if (field.type === 'selection') {
- conditions.push([field.name, 'ilike', value]);
- } else if (field.type === 'many2one') {
- conditions.push([field.name + '.name', 'ilike', value]);
- if (normalized !== value) {
- conditions.push([field.name + '.name', 'ilike', normalized]);
+ try {
+ if (['char', 'text', 'html'].includes(field.type)) {
+ // Text fields: use ilike for partial matching
+ conditions.push([field.name, 'ilike', value]);
+ if (normalized !== value) {
+ conditions.push([field.name, 'ilike', normalized]);
+ }
+ } else if (['integer', 'float', 'monetary'].includes(field.type)) {
+ // Numeric fields: exact match if value is numeric
+ var num = parseFloat(value);
+ if (!isNaN(num)) {
+ conditions.push([field.name, '=', num]);
+ }
+ } else if (field.type === 'selection') {
+ // Selection fields: search in selection values
+ conditions.push([field.name, 'ilike', value]);
+ } else if (field.type === 'many2one') {
+ // Many2one fields: search in related record's name
+ conditions.push([field.name + '.name', 'ilike', value]);
+ if (normalized !== value) {
+ conditions.push([field.name + '.name', 'ilike', normalized]);
+ }
+ } else if (field.type === 'boolean') {
+ // Boolean fields: match true/false/yes/no
+ var lowerValue = value.toLowerCase();
+ if (['true', 'yes', 'نعم', '1'].includes(lowerValue)) {
+ conditions.push([field.name, '=', true]);
+ } else if (['false', 'no', 'لا', '0'].includes(lowerValue)) {
+ conditions.push([field.name, '=', false]);
+ }
}
+ } catch (error) {
+ console.warn('Error processing field', field.name, ':', error);
}
});
@@ -372,7 +429,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
return conditions;
}
- // Add OR operators
+ // Add OR operators: |, |, |, condition1, condition2, condition3, condition4
var domain = [];
for (var i = 1; i < conditions.length; i++) {
domain.push('|');
@@ -381,39 +438,47 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
},
/**
- * Get searchable fields
+ * ENHANCED: Get searchable fields with better logic
*/
_getSearchableFields: function() {
var fields = [];
var fieldDefs = this.state.fields || {};
- // Get from visible columns
- if (this.columns) {
+ console.log('Available fields:', Object.keys(fieldDefs));
+ console.log('Available columns:', this.columns ? this.columns.length : 0);
+
+ // PRIORITY 1: Get from visible columns
+ if (this.columns && this.columns.length > 0) {
this.columns.forEach(function(col) {
if (!col.invisible && col.attrs && col.attrs.name) {
- var field = fieldDefs[col.attrs.name];
- if (field && field.store !== false) {
+ var fieldName = col.attrs.name;
+ var field = fieldDefs[fieldName];
+ if (field && field.store !== false && field.searchable !== false) {
fields.push({
- name: col.attrs.name,
- type: field.type
+ name: fieldName,
+ type: field.type,
+ string: field.string || fieldName
});
}
}
});
}
- // Add common searchable fields if none found
+ // PRIORITY 2: Add common searchable fields if none found from columns
if (fields.length === 0) {
- ['name', 'display_name', 'code', 'reference', 'description'].forEach(function(fname) {
- if (fieldDefs[fname] && fieldDefs[fname].store !== false) {
+ var commonFields = ['name', 'display_name', 'code', 'reference', 'description', 'note'];
+ commonFields.forEach(function(fname) {
+ if (fieldDefs[fname] && fieldDefs[fname].store !== false && fieldDefs[fname].searchable !== false) {
fields.push({
name: fname,
- type: fieldDefs[fname].type
+ type: fieldDefs[fname].type,
+ string: fieldDefs[fname].string || fname
});
}
});
}
+ console.log('Final searchable fields:', fields);
return fields;
},
@@ -422,25 +487,35 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
*/
_showLoading: function(show) {
this.$('.oe_search_loading').toggle(show);
+ if (show) {
+ this.$('.oe_search_count').hide();
+ }
},
/**
- * Normalize Arabic text for better searching
+ * ENHANCED: Normalize Arabic text for better searching
*/
_normalizeArabic: function(text) {
if (!text) return '';
return text
- .replace(/[\u064B-\u065F]/g, '') // Remove diacritics
+ // Remove diacritics (تشكيل)
+ .replace(/[\u064B-\u065F]/g, '')
+ // Convert Arabic-Indic digits to European digits
.replace(/[\u0660-\u0669]/g, function(d) {
return String.fromCharCode(d.charCodeAt(0) - 0x0660 + 0x0030);
})
+ // Convert Persian digits to European digits
.replace(/[\u06F0-\u06F9]/g, function(d) {
return String.fromCharCode(d.charCodeAt(0) - 0x06F0 + 0x0030);
})
+ // Normalize Alef variations
.replace(/[\u0622\u0623\u0625\u0627]/g, 'ا')
+ // Normalize Teh Marbuta
.replace(/[\u0629]/g, 'ه')
+ // Normalize Yeh variations
.replace(/[\u064A\u0626\u0649]/g, 'ي')
+ // Normalize Waw variations
.replace(/[\u0624\u0648]/g, 'و');
},
@@ -451,6 +526,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) {
if (this._search && this._search.timer) {
clearTimeout(this._search.timer);
}
+ this._searchInProgress = false;
return this._super.apply(this, arguments);
}
});