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 c10d98af9..2a501974c 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 @@ -125,6 +125,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { self._customSearchState.value = value; return self.update({}, {reload: false}); }).catch(function(error) { + console.error('FIMS Search Error:', error); self._showSearchError(); return Promise.resolve(); }).finally(function() { @@ -177,11 +178,19 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { }); }, + /** + * بناء domain للبحث - مع دعم non-stored fields + * @param {String} value - قيمة البحث + * @returns {Array} domain صحيح دائماً + */ _buildCustomSearchDomain: function(value) { if (!value) return []; var fields = this._getCustomSearchableFields(); - if (!fields || fields.length === 0) return []; + if (!fields || fields.length === 0) { + console.warn('FIMS Search: No searchable fields found'); + return []; + } var conditions = []; var normalized = this._normalizeText(value); @@ -193,47 +202,70 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { fields.forEach(function(field) { var fieldType = field.type; var fieldName = field.name; + var fieldPath = field.searchPath || fieldName; // دعم related fields switch(fieldType) { case 'char': case 'text': case 'html': - case 'many2one': - case 'selection': searchValues.forEach(function(searchVal) { - conditions.push([fieldName, 'ilike', searchVal]); + conditions.push([fieldPath, 'ilike', searchVal]); }); break; + + case 'many2one': + // البحث في اسم الـ relation + searchValues.forEach(function(searchVal) { + conditions.push([fieldPath, 'ilike', searchVal]); + }); + break; + + case 'selection': + searchValues.forEach(function(searchVal) { + conditions.push([fieldPath, 'ilike', searchVal]); + }); + break; + case 'integer': case 'float': case 'monetary': var numValue = parseFloat(value); if (!isNaN(numValue)) { - conditions.push([fieldName, '=', numValue]); + conditions.push([fieldPath, '=', numValue]); } break; + case 'boolean': var lowerValue = value.toLowerCase().trim(); var boolMap = { - 'true': true, 'yes': true, '1': true, - 'false': false, 'no': false, '0': false + 'true': true, 'yes': true, '1': true, 'نعم': true, + 'false': false, 'no': false, '0': false, 'لا': false }; if (lowerValue in boolMap) { - conditions.push([fieldName, '=', boolMap[lowerValue]]); + conditions.push([fieldPath, '=', boolMap[lowerValue]]); } break; + case 'date': case 'datetime': if (value.match(/^\d{4}-\d{2}-\d{2}/)) { - conditions.push([fieldName, 'ilike', value]); + conditions.push([fieldPath, 'ilike', value]); } break; } }); - if (conditions.length === 0) return []; - if (conditions.length === 1) return conditions[0]; + // **الإصلاح الأساسي: التأكد من return domain صحيح دائماً** + if (conditions.length === 0) { + return []; + } + // دائماً نُرجع array حتى لو condition واحد + if (conditions.length === 1) { + return conditions; // ✅ return [condition] وليس condition + } + + // بناء OR domain للـ conditions المتعددة var orDomain = []; for (var i = 1; i < conditions.length; i++) { orDomain.push('|'); @@ -241,7 +273,12 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { return orDomain.concat(conditions); }, + /** + * جلب الحقول القابلة للبحث - مع دعم computed/related fields + * @returns {Array} قائمة الحقول + */ _getCustomSearchableFields: function() { + var self = this; var fields = []; var state = this.model.get(this.handle); @@ -251,53 +288,128 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { var fieldsInfo = state.fieldsInfo; var viewType = state.viewType || 'list'; + // محاولة 1: جلب من fieldsInfo (الحقول الموجودة في الـ view) if (fieldsInfo && fieldsInfo[viewType]) { Object.keys(fieldsInfo[viewType]).forEach(function(fieldName) { var fieldInfo = fieldsInfo[viewType][fieldName]; if (fieldInfo && !fieldInfo.invisible && fieldDefs[fieldName]) { var fieldDef = fieldDefs[fieldName]; - if (fieldDef.store !== false && fieldDef.searchable !== false) { + + // **التعديل الأساسي: إزالة شرط store** + // نقبل جميع الحقول القابلة للبحث سواء stored أو computed + if (self._isFieldSearchable(fieldDef)) { fields.push({ name: fieldName, type: fieldDef.type, - string: fieldDef.string || fieldName + string: fieldDef.string || fieldName, + searchPath: self._getFieldSearchPath(fieldName, fieldDef) }); } } }); } + // محاولة 2: جلب من columns (إذا لم نجد في fieldsInfo) if (fields.length === 0 && this.renderer && this.renderer.columns) { this.renderer.columns.forEach(function(col) { if (!col.invisible && col.attrs && col.attrs.name) { var fieldName = col.attrs.name; var field = fieldDefs[fieldName]; - if (field && field.store !== false) { + if (field && self._isFieldSearchable(field)) { fields.push({ name: fieldName, type: field.type, - string: field.string || fieldName + string: field.string || fieldName, + searchPath: self._getFieldSearchPath(fieldName, field) }); } } }); } + // محاولة 3: حقول افتراضية (fallback) if (fields.length === 0) { - ['name', 'display_name', 'code', 'reference'].forEach(function(fname) { - if (fieldDefs[fname] && fieldDefs[fname].store !== false) { + ['name', 'display_name', 'login', 'code', 'reference', 'email'].forEach(function(fname) { + if (fieldDefs[fname] && self._isFieldSearchable(fieldDefs[fname])) { fields.push({ name: fname, type: fieldDefs[fname].type, - string: fieldDefs[fname].string || fname + string: fieldDefs[fname].string || fname, + searchPath: self._getFieldSearchPath(fname, fieldDefs[fname]) }); } }); } + console.log('FIMS Search: Found', fields.length, 'searchable fields:', + fields.map(f => f.searchPath || f.name).join(', ')); + return fields; }, + /** + * التحقق من إمكانية البحث في الحقل + * @param {Object} fieldDef - تعريف الحقل + * @returns {Boolean} + */ + _isFieldSearchable: function(fieldDef) { + if (!fieldDef) return false; + + // استبعاد الحقول الخاصة + var excludedTypes = ['binary', 'one2many', 'many2many', 'reference']; + if (excludedTypes.indexOf(fieldDef.type) !== -1) { + return false; + } + + // إذا الحقل صرّح أنه غير قابل للبحث + if (fieldDef.searchable === false) { + return false; + } + + // **التعديل الأساسي: قبول computed fields** + // stored fields = بحث مباشر + // computed fields = بحث عبر related path إذا وُجد + // related fields = بحث عبر related path + + // نقبل الحقل إذا: + // 1. stored field + // 2. computed field مع related + // 3. related field + if (fieldDef.store !== false || + fieldDef.related || + fieldDef.compute) { + return true; + } + + return false; + }, + + /** + * الحصول على المسار الصحيح للبحث في الحقل + * @param {String} fieldName - اسم الحقل + * @param {Object} fieldDef - تعريف الحقل + * @returns {String} - المسار للبحث + */ + _getFieldSearchPath: function(fieldName, fieldDef) { + // إذا كان related field، نستخدم الـ related path + if (fieldDef.related && fieldDef.related.length > 0) { + return fieldDef.related.join('.'); + } + + // إذا كان computed مع related في الـ definition + if (fieldDef.store === false && fieldDef.depends) { + // محاولة استخراج أول depend كـ search path + var depends = fieldDef.depends; + if (depends && depends.length > 0) { + // استخدام أول dependency + return depends[0]; + } + } + + // default: استخدام اسم الحقل مباشرة + return fieldName; + }, + _combineCustomDomains: function(searchDomain) { var originalDomain = this._customSearchState.originalDomain || []; @@ -323,7 +435,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { _showSearchError: function() { if (this.renderer && this.renderer.$) { this.renderer.$('.oe_search_count') - .text(_t('Search error occurred')) + .text(_t('حدث خطأ في البحث')) .addClass('text-danger') .show(); } @@ -347,21 +459,17 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { }); ListRenderer.include({ - // **إزالة events من class definition لتجنب التعارضات** - // سيتم ربط events programmatically عند الحاجة فقط - init: function() { this._super.apply(this, arguments); this._searchTimer = null; this._customSearchReady = false; this._lastInputValue = ''; - this._customEventsbound = false; // تجنب double binding + this._customEventsbound = false; }, _renderView: function () { var self = this; return this._super.apply(this, arguments).then(function (result) { - // تأخير قليل للتأكد من DOM readiness setTimeout(function() { try { if (self._shouldAddSearchBox()) { @@ -373,7 +481,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { } catch (error) { console.error('FIMS Search: Error in _renderView:', error); } - }, 50); // تأخير أكبر قليلاً للاستقرار + }, 50); return result; }); }, @@ -411,7 +519,6 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { _shouldAddSearchBox: function() { try { - // **فحوصات الأمان الأساسية** if (!this.arch || this.arch.tag !== 'tree' || !this.$el || @@ -421,30 +528,18 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { return false; } - // **الحل النهائي المؤكد: فحص hasSelectors** - // embedded tree views: hasSelectors = false أو undefined - // standalone tree views: hasSelectors = true - if (this.hasSelectors === false) { - console.log('FIMS Search: Hidden - embedded tree view (hasSelectors = false)'); return false; } - // **فحوصات إضافية للأمان:** - - // فحص DOM structure كـ backup if (this.$el.closest('.o_form_view').length > 0) { - console.log('FIMS Search: Hidden - inside form view'); return false; } - // فحص field widget if (this.$el.closest('.o_field_widget').length > 0) { - console.log('FIMS Search: Hidden - inside field widget'); return false; } - console.log('FIMS Search: Shown - standalone tree view (hasSelectors:', this.hasSelectors, ')'); return true; } catch (error) { @@ -455,9 +550,7 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { _addCustomSearchBox: function() { try { - // **التحقق من DOM validity** if (!this.$el || !this.$el.length) { - console.warn('FIMS Search: Invalid DOM element'); return; } @@ -512,16 +605,14 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { } }, - // **ربط Events programmatically** _bindCustomEvents: function() { if (this._customEventsbound) { - return; // تجنب double binding + return; } try { var self = this; - // bind events to the search container this.$el.on('keyup', '.oe_search_input', function(e) { self._onCustomSearchKeyUp(e); }); @@ -544,7 +635,6 @@ odoo.define('fims_general_search_tree_view.list_search', function (require) { } }, - // **تنظيف Events عند destroy** destroy: function() { if (this._customEventsbound) { this.$el.off('keyup', '.oe_search_input');