متابعة: تعيين تعارض الإضافة

في السابق، واجهنا مشكلات عند الترقية إلى أحدث إصدار من Discourse، وتبين أن السبب مرتبط بمشكلة في إضافة Assign/البيانات الإنتاجية، وقد ساعدنا @angus في تحديد هذه المشكلة وحلها عن طريق إزالة الإضافة.

نرغب في المستقبل في إعادة تفعيل الإضافة، لكننا نحتاج إلى استبعاد احتمال مواجهة الخطأ الذي يعطل النظام والذي واجهناه سابقًا. فيما يلي تفاصيل مقتطفات السجلات التي تم تسجيلها أثناء وقوع المشكلة.

/var/www/discourse/plugins/discourse-assign/plugin.rb:284:in `block (2 levels) in activate!’

التتبع الخلفي:

> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-mini-profiler-1.0.2/lib/patches/db/pg.rb:69:in `async_exec_params'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-mini-profiler-1.0.2/lib/patches/db/pg.rb:69:in `exec_params'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql_adapter.rb:611:in `block (2 levels) in exec_no_cache'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies/interlock.rb:48:in `block in permit_concurrent_loads'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/concurrency/share_lock.rb:187:in `yield_shares'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies/interlock.rb:47:in `permit_concurrent_loads'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql_adapter.rb:610:in `block in exec_no_cache'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract_adapter.rb:581:in `block (2 levels) in log'
> /usr/local/lib/ruby/2.6.0/monitor.rb:230:in `mon_synchronize'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract_adapter.rb:580:in `block in log'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/notifications/instrumenter.rb:23:in `instrument'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract_adapter.rb:571:in `log'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql_adapter.rb:609:in `exec_no_cache'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql_adapter.rb:596:in `execute_and_clear'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/database_statements.rb:81:in `exec_query'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/database_statements.rb:478:in `select'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/database_statements.rb:70:in `select_all'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/query_cache.rb:104:in `block in select_all'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/query_cache.rb:127:in `block in cache_sql'
> /usr/local/lib/ruby/2.6.0/monitor.rb:230:in `mon_synchronize'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/query_cache.rb:113:in `cache_sql'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/query_cache.rb:104:in `select_all'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/database_statements.rb:77:in `select_one'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/finder_methods.rb:322:in `block in exists?'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation.rb:584:in `skip_query_cache_if_necessary'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/finder_methods.rb:322:in `exists?'
> /var/www/discourse/plugins/discourse-assign/plugin.rb:69:in `block (2 levels) in activate!'
> /var/www/discourse/lib/plugin/instance.rb:188:in `public_send'
> /var/www/discourse/lib/plugin/instance.rb:188:in `block (2 levels) in add_to_class'
> /var/www/discourse/plugins/discourse-assign/plugin.rb:284:in `block (2 levels) in activate!'
> (eval):49:in `_fast_attributes'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/active_model_serializers-0.8.4/lib/active_model/serializer.rb:456:in `attributes'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/active_model_serializers-0.8.4/lib/active_model/serializer.rb:480:in `_serializable_hash'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/active_model_serializers-0.8.4/lib/active_model/serializer.rb:359:in `serializable_hash'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/active_model_serializers-0.8.4/lib/active_model/serializer.rb:347:in `as_json'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/json/encoding.rb:22:in `encode'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/json/encoding.rb:22:in `encode'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/json.rb:41:in `to_json'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/active_model_serializers-0.8.4/lib/active_model/serializer.rb:331:in `to_json'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/multi_json-1.13.1/lib/multi_json/adapters/oj.rb:40:in `dump'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/multi_json-1.13.1/lib/multi_json/adapters/oj.rb:40:in `dump'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/multi_json-1.13.1/lib/multi_json/adapter.rb:25:in `dump'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/multi_json-1.13.1/lib/multi_json.rb:139:in `dump'
> /var/www/discourse/app/controllers/application_controller.rb:535:in `preload_current_user_data'
> /var/www/discourse/app/controllers/application_controller.rb:354:in `preload_json'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/callbacks.rb:426:in `block in make_lambda'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/callbacks.rb:198:in `block (2 levels) in halting'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/callbacks.rb:34:in `block (2 levels) in <module:Callbacks>'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/callbacks.rb:199:in `block in halting'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/callbacks.rb:513:in `block in invoke_before'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/callbacks.rb:513:in `each'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/callbacks.rb:513:in `invoke_before'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/callbacks.rb:131:in `run_callbacks'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/callbacks.rb:41:in `process_action'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/rescue.rb:22:in `process_action'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/instrumentation.rb:34:in `block in process_action'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/notifications.rb:168:in `block in instrument'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/notifications/instrumenter.rb:23:in `instrument'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/notifications.rb:168:in `instrument'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/instrumentation.rb:32:in `process_action'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/params_wrapper.rb:256:in `process_action'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/railties/controller_runtime.rb:24:in `process_action'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/base.rb:134:in `process'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/rendering.rb:32:in `process'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-mini-profiler-1.0.2/lib/mini_profiler/profiling_methods.rb:78:in `block in profile_method'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal.rb:191:in `dispatch'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal.rb:252:in `dispatch'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/route_set.rb:52:in `dispatch'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/route_set.rb:34:in `serve'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/router.rb:52:in `block in serve'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/router.rb:35:in `each'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/router.rb:35:in `serve'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/route_set.rb:840:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-protection-2.0.5/lib/rack/protection/frame_options.rb:31:in `call'
> /var/www/discourse/lib/middleware/omniauth_bypass_middleware.rb:68:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-2.0.7/lib/rack/tempfile_reaper.rb:15:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-2.0.7/lib/rack/conditional_get.rb:25:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-2.0.7/lib/rack/head.rb:12:in `call'
> /var/www/discourse/lib/content_security_policy/middleware.rb:12:in `call'
> /var/www/discourse/lib/middleware/anonymous_cache.rb:220:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-2.0.7/lib/rack/session/abstract/id.rb:232:in `context'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-2.0.7/lib/rack/session/abstract/id.rb:226:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/cookies.rb:670:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/callbacks.rb:28:in `block in call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/callbacks.rb:98:in `run_callbacks'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/callbacks.rb:26:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/debug_exceptions.rb:61:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/logster-2.3.0/lib/logster/middleware/reporter.rb:30:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/railties-5.2.3/lib/rails/rack/logger.rb:38:in `call_app'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/railties-5.2.3/lib/rails/rack/logger.rb:28:in `call'
> /var/www/discourse/config/initializers/100-quiet_logger.rb:18:in `call'
> /var/www/discourse/config/initializers/100-silence_logger.rb:31:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/request_id.rb:27:in `call'
> /var/www/discourse/lib/middleware/enforce_hostname.rb:17:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-2.0.7/lib/rack/method_override.rb:22:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/executor.rb:14:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-2.0.7/lib/rack/sendfile.rb:111:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-mini-profiler-1.0.2/lib/mini_profiler/profiler.rb:171:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/message_bus-2.2.2/lib/message_bus/rack/middleware.rb:57:in `call'
> /var/www/discourse/lib/middleware/request_tracker.rb:169:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/railties-5.2.3/lib/rails/engine.rb:524:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/railties-5.2.3/lib/rails/railtie.rb:190:in `public_send'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/railties-5.2.3/lib/rails/railtie.rb:190:in `method_missing'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-2.0.7/lib/rack/urlmap.rb:68:in `block in call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-2.0.7/lib/rack/urlmap.rb:53:in `each'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-2.0.7/lib/rack/urlmap.rb:53:in `call'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/unicorn-5.5.1/lib/unicorn/http_server.rb:605:in `process_client'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/unicorn-5.5.1/lib/unicorn/http_server.rb:700:in `worker_loop'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/unicorn-5.5.1/lib/unicorn/http_server.rb:548:in `spawn_missing_workers'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/unicorn-5.5.1/lib/unicorn/http_server.rb:144:in `start'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/unicorn-5.5.1/bin/unicorn:128:in `<top (required)>'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/bin/unicorn:23:in `load'
> /var/www/discourse/vendor/bundle/ruby/2.6.0/bin/unicorn:23:in `<main>'

معلومات:

> ActiveRecord::StatementInvalid (PG::InvalidTextRepresentation: ERROR: invalid input syntax for integer: «admins»
> LINE 1: …p_users"."user_id" = 15040 AND (groups.id in ('3','admins','…)
> ^
> : SELECT 1 AS one FROM "groups" INNER JOIN "group_users" ON "groups"."id" = "group_users"."group_id" WHERE "group_users"."user_id" = 15040 AND (groups.id in ('3','admins','RDSE')) LIMIT 1)
> /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/rack-mini-profiler-1.0.2/lib/patches/db/pg.rb:69:in `async_exec_params'

يرجى إخبارنا إذا كان هناك أي شيء آخر تحتاجه لمساعدتنا في المضي قدمًا. شكرًا لك.

عذرًا، لم أفهم؟ تعارض مع ماذا؟

هل أنت متأكد من أن “group id 3 admins” صحيح؟

@Mittineague لقد أكدت هذا الصباح أن معرف المجموعة للمسؤولين هو بالفعل معرف المجموعة الافتراضي.

كانت معلومات السجل التي أدرجتها خلال فترة توقفنا، وقد قمنا مؤقتًا بإزالة إضافة Assign، مما حل المشكلة فورًا وسمح لنا باستخدام discourse بنجاح مرة أخرى.

تُظهر إعداد الموقع assign_allowed_on_groups حالة غير صحيحة. لقد قمنا بالانتقال من استخدام أسماء المجموعات إلى استخدام معرفات المجموعات.

(groups.id in (‘3’, ‘admins’, '…

يجب استبدال admins بمعرف المجموعة لحل المشكلة.

كيف يمكنني تعديل ذلك؟

من مجلد Discourse الخاص بك، يجب عليك:

  • الدخول إلى الحاوية بتشغيل ./launcher enter app
  • فتح وحدة تحكم Rails بتشغيل rails c

بمجرد الدخول إلى وحدة التحكم، يمكنك تشغيل هذا السكربت لإصلاح المشكلة:

allowed_groups = SiteSetting.assign_allowed_on_groups.split('|')
allowed_groups.map! do |ag| 
  ag.to_i.to_s == ag ? ag : Group.select(:id).find_by(name: ag).id
end

SiteSetting.assign_allowed_on_groups = allowed_groups.join('|')

بعد تنفيذ ما تم اقتراحه أعلاه، لم أتمكن من تشغيل السكربت بنجاح. فيما يلي مخرجات المحاولة. أي توجيه سيكون موضع تقدير كبير :pray:

app:/var/www/discourse# rails c

[1] pry(main)> allowed_groups = SiteSetting.assign_allowed_on_groups.split('|')

NoMethodError: undefined method `assign_allowed_on_groups' for #<Class:0x0000563ec4670260>

from /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/dynamic_matchers.rb:22:in `method_missing'

[2] pry(main)> allowed_groups.map! do |ag|

[2] pry(main)* ag.to_i.to_s == ag ? ag : Group.select(:id).find_by(name: ag).id

[2] pry(main)* end

NoMethodError: undefined method `map!' for nil:NilClass

from (pry):2:in `__pry__'

[3] pry(main)> SiteSetting.assign_allowed_on_groups = allowed_groups.join('|')

NoMethodError: undefined method `join' for nil:NilClass

from (pry):5:in `__pry__'

يبدو لي أنك تستخدم إصدارًا قديمًا من إضافة Discourse assign … هل أنت على وضع tests-passed مع أحدث إصدار من Discourse assign؟

دعني أفحص هذا @sam وسأخبرك بما أجده :+1:

بما أن مكون الإضافة (Assign Plugin) قد أزيل حاليًا، @سام، هل تعتقد أن إعادة تثبيت المكون مع أحدث إصدار قد يكون أفضل مسار للعمل؟

@sam @angus قمت بإعادة تثبيت إضافة Assign مرة أخرى الليلة واجهت فشلاً كاملاً مرة أخرى بعد تسجيل الدخول. لقد التقطت سجلات الفشل لكنني لا أرغب في نشرها علناً، مع استعدادي لمشاركتها عبر الرسائل الخاصة.


أي اقتراحات حول كيفية المضي قدماً؟

هل أنت على الفرع المستقر؟ هل هناك إضافات أخرى قمت بتثبيتها قد تسبب تعارضًا؟

أعتقد ذلك، حيث قمت بسحب من Master.

فقط ما يلي، ولا شيء يلفت انتباهي بشكل خاص.

ستحتاج إلى بذل بعض الجهد لتكون في فرع (الأقل تفضيلاً) المستقر. هل ترى شيئًا مثل

version: stable

في ملف app.yml؟ إذا كنت على stable، فقد تكون الأمور أكثر تعقيدًا.

يمكنك تجربة ./discourse-doctor. و/أو التأكد من تنفيذ git pull قبل إعادة البناء.

57%20PM

هذا ما أراه في ملف App.yml الخاص بي

هل هناك أي أفكار إضافية هنا لمساعدتنا على المضي قدمًا مع إضافة Assign؟ @angus؟

مرحبًا @Jim.Morrison،

هل يمكنك تشغيل هذا داخل وحدة التحكم وإظهار المخرجات:

DB.query_single(<<~SQL SELECT site_settings.value FROM site_settings WHERE site_settings.name = 'assign_allowed_on_groups' SQL ).first.to_s.split('|')

أعتقد أن سكريبت لم ينجح لأن الإضافة كانت معطلة.

لقد أرسلت لك التفاصيل عبر الرسائل المباشرة من وحدة التحكم. شكرًا لك يا @Roman!

بعد التبادل مع @Roman بشأن هذا الأمر، تمكنا أخيرًا من حله!

بعد تشغيل الاستعلام الذي كتبه Roman للقضاء على تضارب البيانات، وإضافة مكون إضافي للتعيين ثم إعادة بناء التطبيق، واجهت النجاح!

كان Roman أصلًا كبيرًا طوال هذه التجربة، ولا يمكنني مدحه بما يكفي لهذا الأمر! شكرًا جزيلاً على تعاونك معي في هذا الأمر @Roman!