\n"
-" ملاحظة: هذا البريد الإلكتروني التلقائي مرسل بواسطة محاسبة أودو لتذكيرك قبل انتهاء صلاحية إذن مزامنة البنك.\n"
-"
\n"
-"
\n"
-"
\n"
-"
\n"
-"
\n"
-"
\n"
-" "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__access_token
-msgid "Access Token"
-msgstr "رمز الوصول "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_account__account_data
-msgid "Account Data"
-msgstr "بيانات الحساب "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_account__name
-msgid "Account Name"
-msgstr "اسم الحساب"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_account__name
-msgid "Account Name as provided by third party provider"
-msgstr "اسم الحساب حسب مزود الطرف الثالث "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__account_number
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_account__account_number
-msgid "Account Number"
-msgstr "رقم الحساب"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_selection__account_online_account_ids
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__online_account_id
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_journal__account_online_account_id
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__account_online_account_ids
-msgid "Account Online Account"
-msgstr "حساب عبر الإنترنت "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_selection__account_online_link_id
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line__online_link_id
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_journal__account_online_link_id
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_account__account_online_link_id
-msgid "Account Online Link"
-msgstr "ربط الحساب عبر الإنترنت "
-
-#. module: odex30_account_online_sync
-#: model:ir.actions.server,name:odex30_account_online_sync.online_sync_cron_waiting_synchronization_ir_actions_server
-msgid "Account: Journal online Waiting Synchronization"
-msgstr "الحساب: دفتر اليومية أونلاين بانتظار المزامنة "
-
-#. module: odex30_account_online_sync
-#: model:ir.actions.server,name:odex30_account_online_sync.online_sync_cron_ir_actions_server
-msgid "Account: Journal online sync"
-msgstr "الحساب: مزامنة دفتر اليومية عبر الإنترنت "
-
-#. module: odex30_account_online_sync
-#: model:ir.actions.server,name:odex30_account_online_sync.online_sync_unused_connection_cron_ir_actions_server
-msgid "Account: Journal online sync cleanup unused connections"
-msgstr "الحساب: مسح الاتصالات غير المستخدمة عند مزامنة دفتر اليومية أونلاين "
-
-#. module: odex30_account_online_sync
-#: model:ir.actions.server,name:odex30_account_online_sync.online_sync_mail_cron_ir_actions_server
-msgid "Account: Journal online sync reminder"
-msgstr "الحساب: تذكير مزامنة دفتر اليومية أونلاين "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__message_needaction
-msgid "Action Needed"
-msgstr "إجراء مطلوب"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__activity_ids
-msgid "Activities"
-msgstr "الأنشطة"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__activity_exception_decoration
-msgid "Activity Exception Decoration"
-msgstr "زخرفة استثناء النشاط"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__activity_state
-msgid "Activity State"
-msgstr "حالة النشاط"
-
-#. module: odex30_account_online_sync
-#: model:ir.model,name:odex30_account_online_sync.model_mail_activity_type
-msgid "Activity Type"
-msgstr "نوع النشاط"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__activity_type_icon
-msgid "Activity Type Icon"
-msgstr "أيقونة نوع النشاط"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__amount
-msgid "Amount"
-msgstr "مبلغ"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__amount_currency
-msgid "Amount in Currency"
-msgstr "المبلغ بالعملة"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__message_attachment_count
-msgid "Attachment Count"
-msgstr "عدد المرفقات"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__auto_sync
-msgid "Automatic synchronization"
-msgstr "المزامنة الآلية "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_account__balance
-msgid "Balance"
-msgstr "الرصيد"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_account__balance
-msgid "Balance of the account sent by the third party provider"
-msgstr "رصيد الحساب الذي أرسله مزود الطرف الثالث "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.portal_renew_consent
-msgid "Bank"
-msgstr "البنك"
-
-#. module: odex30_account_online_sync
-#: model:ir.model,name:odex30_account_online_sync.model_account_online_link
-msgid "Bank Connection"
-msgstr "اتصال البنك"
-
-#. module: odex30_account_online_sync
-#: model:ir.model,name:odex30_account_online_sync.model_account_bank_statement_line
-msgid "Bank Statement Line"
-msgstr "بند كشف الحساب البنكي"
-
-#. module: odex30_account_online_sync
-#: model:mail.activity.type,name:odex30_account_online_sync.bank_sync_activity_update_consent
-msgid "Bank Synchronization: Update consent"
-msgstr "مزامنة البنك: تحديث الإذن "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_online.py:0
-msgid "Bank Synchronization: Update your consent"
-msgstr "مزامنة البنك: قم بتحديث الإذن "
-
-#. module: odex30_account_online_sync
-#: model:mail.template,name:odex30_account_online_sync.email_template_sync_reminder
-msgid "Bank connection expiration reminder"
-msgstr "تذكير انتهاء صلاحية اتصال البنك "
-
-#. module: odex30_account_online_sync
-#: model:ir.model,name:odex30_account_online_sync.model_bank_rec_widget
-msgid "Bank reconciliation widget for a single statement line"
-msgstr "أداة التسوية البنكية لبند كشف حساب واحد "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_missing_transaction_wizard_view_form
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.view_account_bank_selection_form_wizard
-msgid "Cancel"
-msgstr "إلغاء"
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_online.py:0
-msgid "Check the documentation"
-msgstr "ألقِ نظرة على الوثائق "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_duplicate_transaction_wizard_view_form
-msgid ""
-"Choose a date and a journal from which you want to check the transactions."
-msgstr "اختر التاريخ ودفتر اليومية الذي ترغب في التحقق من المعاملات فيه "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_missing_transaction_wizard_view_form
-msgid "Choose a date and a journal from which you want to fetch transactions"
-msgstr "اختر التاريخ ودفتر اليومية الذي ترغب في جلب المعاملات منه "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__client_id
-msgid "Client"
-msgstr "العميل"
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_online_link_view_form
-msgid "Client id"
-msgstr "معرف العميل "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_journal__renewal_contact_email
-msgid ""
-"Comma separated list of email addresses to send consent renewal "
-"notifications 15, 3 and 1 days before expiry"
-msgstr ""
-"قائمة مفصولة بفواصل بعناوين البريد الإلكترونية لإرسال إشعارات تجديد الإذن "
-"قبل 15، 3، و1 يوم من تاريخ انتهاء الصلاحية "
-
-#. module: odex30_account_online_sync
-#: model:ir.model,name:odex30_account_online_sync.model_res_company
-msgid "Companies"
-msgstr "الشركات"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__company_id
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_account__company_id
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__company_id
-msgid "Company"
-msgstr "الشركة "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_online_link_view_form
-msgid "Connect"
-msgstr "اتصل"
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.view_account_bank_selection_form_wizard
-msgid "Connect Bank"
-msgstr "توصيل البنك "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_journal_dashboard_inherit_online_sync
-msgid "Connect bank"
-msgstr "توصيل البنك "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.portal_renew_consent
-msgid "Connect my Bank"
-msgstr "توصيل بنكي "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.portal_renew_consent
-msgid "Connect your bank account to Odoo"
-msgstr "قم بتوصيل حسابك البنكي بأودو "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields.selection,name:odex30_account_online_sync.selection__account_online_link__state__connected
-msgid "Connected"
-msgstr "متصل "
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#: code:addons/odex30_account_online_sync/static/src/components/connected_until_widget/connected_until_widget.xml:0
-msgid "Connected until"
-msgstr "متصل حتى "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_journal__renewal_contact_email
-msgid "Connection Requests"
-msgstr "طلبات الاتصال "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__connection_state_details
-msgid "Connection State Details"
-msgstr "تفاصيل حالة الاتصال "
-
-#. module: odex30_account_online_sync
-#: model:mail.message.subtype,name:odex30_account_online_sync.bank_sync_consent_renewal
-msgid "Consent Renewal"
-msgstr "تجديد الإذن "
-
-#. module: odex30_account_online_sync
-#: model:ir.model,name:odex30_account_online_sync.model_res_partner
-msgid "Contact"
-msgstr "جهة الاتصال"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_selection__create_uid
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__create_uid
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_duplicate_transaction_wizard__create_uid
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_missing_transaction_wizard__create_uid
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_account__create_uid
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__create_uid
-msgid "Created by"
-msgstr "أنشئ بواسطة"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_selection__create_date
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__create_date
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_duplicate_transaction_wizard__create_date
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_missing_transaction_wizard__create_date
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_account__create_date
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__create_date
-msgid "Created on"
-msgstr "أنشئ في"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__currency_id
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_account__currency_id
-msgid "Currency"
-msgstr "العملة"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__date
-msgid "Date"
-msgstr "التاريخ"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_journal__expiring_synchronization_date
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_link__expiring_synchronization_date
-msgid "Date when the consent for this connection expires"
-msgstr "تاريخ انتهاء صلاحية إذن هذا الاتصال "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_selection__display_name
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__display_name
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_duplicate_transaction_wizard__display_name
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_missing_transaction_wizard__display_name
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_account__display_name
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__display_name
-msgid "Display Name"
-msgstr "اسم العرض "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields.selection,name:odex30_account_online_sync.selection__account_online_account__fetching_status__done
-msgid "Done"
-msgstr "منتهي "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_bank_statement.py:0
-#: model:ir.model.fields.selection,name:odex30_account_online_sync.selection__account_online_link__state__error
-msgid "Error"
-msgstr "خطأ"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_journal__expiring_synchronization_date
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__expiring_synchronization_date
-msgid "Expiring Synchronization Date"
-msgstr "تاريخ انتهاء صلاحية المزامنة "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_journal__expiring_synchronization_due_day
-msgid "Expiring Synchronization Due Day"
-msgstr "التاريخ الذي ستنتهي فيه صلاحية المزامنة "
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#: code:addons/odex30_account_online_sync/static/src/components/connected_until_widget/connected_until_widget.xml:0
-msgid "Extend Connection"
-msgstr "تمديد الإذن "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_account__account_data
-msgid "Extra information needed by third party provider"
-msgstr "معلومات إضافية مطلوبة من قِبَل مزود الطرف الثالث "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_missing_transaction_wizard_view_form
-msgid "Fetch"
-msgstr "جلب "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_duplicate_transaction_wizard_view_form
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_missing_transaction_wizard_view_form
-msgid "Fetch Missing Bank Statements Wizard"
-msgstr "معالج جلب كشوفات الحسابات المفقودة "
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#: code:addons/odex30_account_online_sync/static/src/components/refresh_spin_journal_widget/refresh_spin_journal_widget.xml:0
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_online_link_view_form
-msgid "Fetch Transactions"
-msgstr "جلب المعاملات "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_online.py:0
-msgid "Fetched Transactions"
-msgstr "المعاملات التي تم جلبها "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_journal__online_sync_fetching_status
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_account__fetching_status
-msgid "Fetching Status"
-msgstr "حالة الجلب "
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#: code:addons/odex30_account_online_sync/static/src/components/refresh_spin_journal_widget/refresh_spin_journal_widget.xml:0
-msgid "Fetching..."
-msgstr "جري جلب... "
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_journal.py:0
-#: code:addons/odex30_account_online_sync/static/src/components/bank_reconciliation/find_duplicate_transactions_cog_menu.xml:0
-msgid "Find Duplicate Transactions"
-msgstr "العثور على المعاملات المكررة "
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_journal.py:0
-#: code:addons/odex30_account_online_sync/static/src/components/bank_reconciliation/fetch_missing_transactions_cog_menu.xml:0
-msgid "Find Missing Transactions"
-msgstr "العثور على المعاملات المفقودة "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_duplicate_transaction_wizard__first_ids_in_group
-msgid "First Ids In Group"
-msgstr "المُعرّفات الأولى في المجموعة "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__message_follower_ids
-msgid "Followers"
-msgstr "المتابعين"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__message_partner_ids
-msgid "Followers (Partners)"
-msgstr "المتابعين (الشركاء) "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_link__activity_type_icon
-msgid "Font awesome icon e.g. fa-tasks"
-msgstr "أيقونة من Font awesome مثال: fa-tasks "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__foreign_currency_id
-msgid "Foreign Currency"
-msgstr "عملة أجنبية "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__has_message
-msgid "Has Message"
-msgstr "يحتوي على رسالة "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__has_unlinked_accounts
-msgid "Has Unlinked Accounts"
-msgstr "يحتوي على حسابات غير مرتبطة "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_online.py:0
-msgid "Here"
-msgstr "هنا "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_selection__id
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__id
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_duplicate_transaction_wizard__id
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_missing_transaction_wizard__id
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_account__id
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__id
-msgid "ID"
-msgstr "المُعرف"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__activity_exception_icon
-msgid "Icon"
-msgstr "الأيقونة"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_link__activity_exception_icon
-msgid "Icon to indicate an exception activity."
-msgstr "الأيقونة للإشارة إلى النشاط المستثنى. "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_account__online_identifier
-msgid "Id used to identify account by third party provider"
-msgstr "المعرف المستخدَم لتعرف الحساب من قِبَل مزود الطرف الثالث "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_link__message_needaction
-msgid "If checked, new messages require your attention."
-msgstr "إذا كان محددًا، فهناك رسائل جديدة عليك رؤيتها. "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_link__message_has_error
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_link__message_has_sms_error
-msgid "If checked, some messages have a delivery error."
-msgstr "إذا كان محددًا، فقد حدث خطأ في تسليم بعض الرسائل."
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_account__inverse_balance_sign
-msgid "If checked, the balance sign will be inverted"
-msgstr "إذا كان محدداً، سيتم عكس علامة الرصيد "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_account__inverse_transaction_sign
-msgid "If checked, the transaction sign will be inverted"
-msgstr "إذا كان محدداً، سيتم عكس علامة المعاملة "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_link__auto_sync
-msgid ""
-"If possible, we will try to automatically fetch new transactions for this record\n"
-" \n"
-"If the automatic sync is disabled. that will be due to security policy on the bank's end. So, they have to launch the sync manually"
-msgstr ""
-"إذا أمكن، سنحاول جلب المعاملات الجديدة تلقائياً لهذا السجل\n"
-" \n"
-"إذا كانت خاصية المزامنة التلقائية معطلة. سيكون ذلك بسبب سياسة الأمن التي يفرضها البنك. لذلك، سيتوجب عليهم تشغيل عملية المزامنة يدوياً "
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#: code:addons/odex30_account_online_sync/static/src/components/transient_bank_statement_line_list_view/transient_bank_statement_line_list_view.xml:0
-msgid "Import Transactions"
-msgstr "استيراد المعاملات "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_link__provider_data
-msgid "Information needed to interact with third party provider"
-msgstr "المعلومات المطلوبة للتفاعل مع مزود الطرف الثالث "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_bank_selection__institution_name
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_link__name
-msgid "Institution Name"
-msgstr "اسم المنشأة "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_online.py:0
-msgid "Internal Error"
-msgstr "خطأ داخلي "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_online.py:0
-msgid "Invalid URL"
-msgstr "رابط URL غير صحيح "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_online.py:0
-msgid "Invalid value for proxy_mode config parameter."
-msgstr "قيمة غير صالحة لمعيار تهيئة proxy_mode "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_account__inverse_balance_sign
-msgid "Inverse Balance Sign"
-msgstr "علامة عكس الرصيد "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_account__inverse_transaction_sign
-msgid "Inverse Transaction Sign"
-msgstr "عكس علامة المعاملة "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__message_is_follower
-msgid "Is Follower"
-msgstr "متابع"
-
-#. module: odex30_account_online_sync
-#: model:ir.model,name:odex30_account_online_sync.model_account_journal
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__journal_id
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_duplicate_transaction_wizard__journal_id
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_missing_transaction_wizard__journal_id
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_account__journal_ids
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__journal_ids
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.portal_renew_consent
-msgid "Journal"
-msgstr "دفتر اليومية"
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_online.py:0
-msgid ""
-"Journal %(journal_name)s has been set up with a different currency and "
-"already has existing entries. You can't link selected bank account in "
-"%(currency_name)s to it"
-msgstr ""
-"تم إعداد دفتر اليومية %(journal_name)s بعملة مختلفة ويحتوي بالفعل على قيود "
-"موجودة. لا يمكنك ربط الحساب البنكي المحدد %(currency_name)s به "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__last_refresh
-msgid "Last Refresh"
-msgstr "التحديث الأخير"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_selection__write_uid
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__write_uid
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_duplicate_transaction_wizard__write_uid
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_missing_transaction_wizard__write_uid
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_account__write_uid
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__write_uid
-msgid "Last Updated by"
-msgstr "آخر تحديث بواسطة"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_selection__write_date
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__write_date
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_duplicate_transaction_wizard__write_date
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_missing_transaction_wizard__write_date
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_account__write_date
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__write_date
-msgid "Last Updated on"
-msgstr "آخر تحديث في"
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_online_link_view_form
-msgid "Last refresh"
-msgstr "آخر تحديث "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_account__last_sync
-msgid "Last synchronization"
-msgstr "آخر مزامنة"
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.portal_renew_consent
-msgid "Latest Balance"
-msgstr "آخر رصيد "
-
-#. module: odex30_account_online_sync
-#: model:ir.model,name:odex30_account_online_sync.model_account_bank_selection
-msgid "Link a bank account to the selected journal"
-msgstr " قم بربط حساب بنكي واحد بدفتر اليومية المحدد "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/wizard/account_journal_missing_transactions.py:0
-msgid "Manual Bank Statement Lines"
-msgstr "بنود كشف الحساب البنكي اليدوية "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_online.py:0
-msgid "Message"
-msgstr "الرسالة"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__message_has_error
-msgid "Message Delivery error"
-msgstr "خطأ في تسليم الرسائل"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__message_ids
-msgid "Messages"
-msgstr "الرسائل"
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/wizard/account_journal_missing_transactions.py:0
-msgid "Missing and Pending Transactions"
-msgstr "المعاملات المفقودة وقيد الانتظار "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__my_activity_date_deadline
-msgid "My Activity Deadline"
-msgstr "الموعد النهائي لنشاطاتي "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_selection__institution_name
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__name
-msgid "Name"
-msgstr "الاسم"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__activity_calendar_event_id
-msgid "Next Activity Calendar Event"
-msgstr "الفعالية التالية في تقويم الأنشطة "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__activity_date_deadline
-msgid "Next Activity Deadline"
-msgstr "الموعد النهائي للنشاط التالي"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__activity_summary
-msgid "Next Activity Summary"
-msgstr "ملخص النشاط التالي"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__activity_type_id
-msgid "Next Activity Type"
-msgstr "نوع النشاط التالي"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__next_refresh
-msgid "Next synchronization"
-msgstr "المزامنة التالية"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields.selection,name:odex30_account_online_sync.selection__account_online_link__state__disconnected
-msgid "Not Connected"
-msgstr "غير متصل "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__message_needaction_counter
-msgid "Number of Actions"
-msgstr "عدد الإجراءات"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__message_has_error_counter
-msgid "Number of errors"
-msgstr "عدد الأخطاء "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_link__message_needaction_counter
-msgid "Number of messages requiring action"
-msgstr "عدد الرسائل التي تتطلب اتخاذ إجراء"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_link__message_has_error_counter
-msgid "Number of messages with delivery error"
-msgstr "عدد الرسائل الحادث بها خطأ في التسليم"
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.portal_renew_consent
-msgid "Odoo"
-msgstr "أودو"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line__online_account_id
-msgid "Online Account"
-msgstr "حساب عبر الإنترنت "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_online_link_view_form
-msgid "Online Accounts"
-msgstr "حسابات عبر الإنترنت "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_account__online_identifier
-msgid "Online Identifier"
-msgstr "معرف عبر الإنترنت "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_journal__next_link_synchronization
-msgid "Online Link Next synchronization"
-msgstr "ربط المزامنة التالية عبر الإنترنت "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line__online_partner_information
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_res_partner__online_partner_information
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_res_users__online_partner_information
-msgid "Online Partner Information"
-msgstr "معلومات الشريك عبر الإنترنت "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_journal.py:0
-#: model:ir.actions.act_window,name:odex30_account_online_sync.action_account_online_link_form
-#: model:ir.ui.menu,name:odex30_account_online_sync.menu_action_online_link_account
-#: model_terms:ir.actions.act_window,help:odex30_account_online_sync.action_account_online_link_form
-msgid "Online Synchronization"
-msgstr "المزامنة عبر الإنترنت "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line__online_transaction_identifier
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__online_transaction_identifier
-msgid "Online Transaction Identifier"
-msgstr "معرف المعاملات عبر الإنترنت "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_bank_statement.py:0
-msgid "Opening statement: first synchronization"
-msgstr "البيان الافتتاحي: المزامنة الأولى"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__partner_name
-msgid "Partner Name"
-msgstr "اسم الشريك"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__payment_ref
-msgid "Payment Ref"
-msgstr "مرجع الدفع "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields.selection,name:odex30_account_online_sync.selection__account_bank_statement_line_transient__state__pending
-msgid "Pending"
-msgstr "قيد الانتظار "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields.selection,name:odex30_account_online_sync.selection__account_online_account__fetching_status__planned
-msgid "Planned"
-msgstr "المخطط له "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/wizard/account_journal_missing_transactions.py:0
-msgid "Please enter a valid Starting Date to continue."
-msgstr "يرجى إدخال تاريخ بدء صالح للمتابعة. "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_online.py:0
-msgid "Please reconnect your online account."
-msgstr "الرجاء إعادة توصيل حسابك عبر الإنترنت. "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/wizard/account_bank_statement_line.py:0
-msgid "Please select first the transactions you wish to import."
-msgstr "يرجى أولاً تحديد المعاملات التي ترغب في استيرادها. "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields.selection,name:odex30_account_online_sync.selection__account_bank_statement_line_transient__state__posted
-msgid "Posted"
-msgstr "مُرحّل "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.missing_bank_statement_line_search
-msgid "Posted Transactions"
-msgstr "المعاملات التي تم ترحيلها "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields.selection,name:odex30_account_online_sync.selection__account_online_account__fetching_status__processing
-msgid "Processing"
-msgstr "معالجة "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__provider_data
-msgid "Provider Data"
-msgstr "بيانات المزود "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__provider_type
-msgid "Provider Type"
-msgstr "نوع Plaid"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__rating_ids
-msgid "Ratings"
-msgstr "التقييمات"
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_online_link_view_form
-msgid "Reconnect"
-msgstr "إعادة توصيل "
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#: code:addons/odex30_account_online_sync/static/src/components/connected_until_widget/connected_until_widget.xml:0
-#: code:addons/odex30_account_online_sync/static/src/components/refresh_spin_journal_widget/refresh_spin_journal_widget.xml:0
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_journal_dashboard_inherit_online_sync
-msgid "Reconnect Bank"
-msgstr "إعادة توصيل البنك "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_online.py:0
-msgid "Redirect"
-msgstr "إعادة توجيه"
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#: code:addons/odex30_account_online_sync/static/src/components/refresh_spin_journal_widget/refresh_spin_journal_widget.xml:0
-msgid "Refresh"
-msgstr "تحديث "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__refresh_token
-msgid "Refresh Token"
-msgstr "تحديث الرمز"
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_journal.py:0
-msgid "Report Issue"
-msgstr "إبلاغ عن إساءة "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_online.py:0
-msgid "Report issue"
-msgstr "إبلاغ عن مشكلة "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_link__client_id
-msgid "Represent a link for a given user towards a banking institution"
-msgstr "تقديم رابط لمستخدم معين إلى منشأة بنكية "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_online_link_view_form
-msgid "Reset"
-msgstr "إعادة الضبط "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__activity_user_id
-msgid "Responsible User"
-msgstr "المستخدم المسؤول"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__message_has_sms_error
-msgid "SMS Delivery error"
-msgstr "خطأ في تسليم الرسائل النصية القصيرة "
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#: code:addons/odex30_account_online_sync/static/src/components/bank_configure/bank_configure.xml:0
-msgid "Search over"
-msgstr "البحث "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.portal_renew_consent
-msgid ""
-"Security Tip: always check the domain name of this page, before clicking on "
-"the button."
-msgstr ""
-"نصيحة للأمان: تحقق دائماً من اسم النطاق لهذه الصفحة، عن طريق الضغط على الزر."
-" "
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#: code:addons/odex30_account_online_sync/static/src/components/refresh_spin_journal_widget/refresh_spin_journal_widget.xml:0
-msgid "See error"
-msgstr "انظر إلى الخطأ "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_online.py:0
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.view_account_bank_selection_form_wizard
-msgid "Select a Bank Account"
-msgstr "تحديد حساب بنكي "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.view_account_bank_selection_form_wizard
-msgid "Select the"
-msgstr "قم بتحديد "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_selection__selected_account
-msgid "Selected Account"
-msgstr "الحساب المحدد "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__sequence
-msgid "Sequence"
-msgstr "تسلسل "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_account__account_number
-msgid "Set if third party provider has the full account number"
-msgstr "التعيين إذا كان لدى مزود الطرف الثالث رقم الحساب الكامل "
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#: code:addons/odex30_account_online_sync/static/src/components/bank_configure/bank_configure.xml:0
-msgid "Setup Bank"
-msgstr "إعداد البنك "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_online.py:0
-msgid "Setup Bank Account"
-msgstr "إعداد الحساب البنكي "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__show_sync_actions
-msgid "Show Sync Actions"
-msgstr "عرض إجراءات المزامنة "
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#: code:addons/odex30_account_online_sync/static/src/components/views/account_online_authorization_kanban_controller.xml:0
-msgid "Some transactions"
-msgstr "بعض المعاملات "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_duplicate_transaction_wizard__date
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_missing_transaction_wizard__date
-msgid "Starting Date"
-msgstr "تاريخ البدء"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__state
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_journal__account_online_link_state
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__state
-msgid "State"
-msgstr "الحالة "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_link__activity_state
-msgid ""
-"Status based on activities\n"
-"Overdue: Due date is already passed\n"
-"Today: Activity date is today\n"
-"Planned: Future activities."
-msgstr ""
-"الأنشطة المعتمدة على الحالة\n"
-"المتأخرة: تاريخ الاستحقاق مر\n"
-"اليوم: تاريخ النشاط هو اليوم\n"
-"المخطط: الأنشطة المستقبلية."
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.portal_renew_consent
-msgid "Thank You!"
-msgstr "شكرا لك! "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_online.py:0
-msgid "The consent for the selected account has expired."
-msgstr "انتهت صلاحية الإذن للحساب المحدد. "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_online.py:0
-msgid ""
-"The online synchronization service is not available at the moment. Please "
-"try again later."
-msgstr ""
-"خدمة المزامنة عبر الإنترنت غير متوفرة في الوقت الحالي. الرجاء المحاولة "
-"مجدداً لاحقاً "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_link__provider_type
-msgid "Third Party Provider"
-msgstr "مزود الطرف الثالث "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_duplicate_transaction_wizard_view_form
-msgid ""
-"This action will delete all selected transactions. Are you sure you want to "
-"proceed?"
-msgstr ""
-"سيؤدي هذا الإجراء إلى حذف كافة المعاملات المحددة. هل أنت متأكد أنك ترغب في "
-"المتابعة؟ "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_online_link_view_form
-msgid "This button will reset the fetching status"
-msgstr "سيؤدي هذا الزر إلى إعادة ضبط حالة عملية البحث "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_online.py:0
-msgid ""
-"This version of Odoo appears to be outdated and does not support the '%s' "
-"sync mode. Installing the latest update might solve this."
-msgstr ""
-"يبدو أن هذه النسخة من أودو قديمة ولا تدعم وضع المزامنة '%s'. قد يحل تثبيت "
-"آخر تحديث هذه المشكلة. "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.actions.act_window,help:odex30_account_online_sync.action_account_online_link_form
-msgid ""
-"To create a synchronization with your banking institution, \n"
-" please click on Add a Bank Account."
-msgstr ""
-"لإنشاء مزامنة مع المنشأة البنكية، \n"
-" يرجى الضغط على إضافة حساب بنكي. "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_link__access_token
-msgid "Token used to access API."
-msgstr "الرمز المستخدَم للوصول إلى الواجهة البرمجية للتطبيق. "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_link__refresh_token
-msgid "Token used to sign API request, Never disclose it"
-msgstr ""
-"الرمز المستخدَم للتوقيع على طلب الواجهة البرمجية للتطبيق، لا تقم بالإفصاح "
-"عنه أبداً "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_duplicate_transaction_wizard__transaction_ids
-msgid "Transaction"
-msgstr "معاملة"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_bank_statement_line_transient__transaction_details
-msgid "Transaction Details"
-msgstr "تفاصيل المعاملة "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_duplicate_transaction_wizard_view_form
-msgid "Transactions"
-msgstr "المعاملات "
-
-#. module: odex30_account_online_sync
-#: model:ir.model,name:odex30_account_online_sync.model_account_bank_statement_line_transient
-msgid "Transient model for bank statement line"
-msgstr "Transient model for bank statement line"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_link__has_unlinked_accounts
-msgid ""
-"True if that connection still has accounts that are not linked to an Odoo "
-"journal"
-msgstr ""
-"تكون القيمة صحيحة إذا كان الاتصال لا يزال يحتوي على حسابات غير مرتبطة بدفتر "
-"يومية أودو "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_link__activity_exception_decoration
-msgid "Type of the exception activity on record."
-msgstr "نوع النشاط المستثنى في السجل. "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_online_link_view_form
-msgid "Update Credentials"
-msgstr "تحديث بيانات الاعتماد"
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields.selection,name:odex30_account_online_sync.selection__account_online_account__fetching_status__waiting
-msgid "Waiting"
-msgstr "قيد الانتظار "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,field_description:odex30_account_online_sync.field_account_online_link__website_message_ids
-msgid "Website Messages"
-msgstr "رسائل الموقع الإلكتروني "
-
-#. module: odex30_account_online_sync
-#: model:ir.model.fields,help:odex30_account_online_sync.field_account_online_link__website_message_ids
-msgid "Website communication history"
-msgstr "سجل تواصل الموقع الإلكتروني "
-
-#. module: odex30_account_online_sync
-#: model:ir.model,name:odex30_account_online_sync.model_account_duplicate_transaction_wizard
-msgid "Wizard for duplicate transactions"
-msgstr "معالج للمعاملات المكررة "
-
-#. module: odex30_account_online_sync
-#: model:ir.model,name:odex30_account_online_sync.model_account_missing_transaction_wizard
-msgid "Wizard for missing transactions"
-msgstr "معالج للمعاملات المفقودة "
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#: code:addons/odex30_account_online_sync/static/src/components/transient_bank_statement_line_list_view/transient_bank_statement_line_list_view.xml:0
-msgid ""
-"You are importing transactions before the creation of your online synchronization\n"
-" ("
-msgstr ""
-"أنت تقوم باستيراد المعاملات قبل إنشاء المزامنة عبر الإنترنت\n"
-" ("
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_online.py:0
-msgid "You can contact Odoo support"
-msgstr "يمكنك التواصل مع فريق الدعم لدى أودو "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_journal.py:0
-msgid "You can only execute this action for bank-synchronized journals."
-msgstr "لا يمكنك تنفيذ هذا الإجراء إلا لدفاتر اليومية المتزامنة مع البنك. "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_journal.py:0
-#: code:addons/odex30_account_online_sync/wizard/account_journal_missing_transactions.py:0
-msgid ""
-"You can't find missing transactions for a journal that isn't connected."
-msgstr "لا يمكنك العثور على المعاملات المفقودة لدفتر يومية غير متصل. "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/models/account_journal.py:0
-#: code:addons/odex30_account_online_sync/models/account_online.py:0
-msgid "You cannot have two journals associated with the same Online Account."
-msgstr "لا يمكن أن يكون لديك حسابان مرتبطان بنفس الحساب عبر الإنترنت. "
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/wizard/account_bank_statement_line.py:0
-msgid "You cannot import pending transactions."
-msgstr "لا يمكنك استيراد المعاملات المعلقة. "
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#: code:addons/odex30_account_online_sync/static/src/components/transient_bank_statement_line_list_view/transient_bank_statement_line_list_view.xml:0
-msgid "You have"
-msgstr "لديك"
-
-#. module: odex30_account_online_sync
-#. odoo-python
-#: code:addons/odex30_account_online_sync/wizard/account_journal_missing_transactions.py:0
-msgid "You have to select one journal to continue."
-msgstr "عليك تحديد دفتر يومية واحد للاستمرار. "
-
-#. module: odex30_account_online_sync
-#: model:mail.template,subject:odex30_account_online_sync.email_template_sync_reminder
-msgid "Your bank connection is expiring soon"
-msgstr "سوف ينتهي اتصال البنك الخاص بك قريباً "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.view_account_bank_selection_form_wizard
-msgid "account to connect:"
-msgstr "الحساب لربطه: "
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#: code:addons/odex30_account_online_sync/static/src/components/bank_configure/bank_configure.xml:0
-msgid "banks"
-msgstr "البنوك "
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#: code:addons/odex30_account_online_sync/static/src/components/transient_bank_statement_line_list_view/transient_bank_statement_line_list_view.xml:0
-msgid "entries"
-msgstr "القيود"
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#: code:addons/odex30_account_online_sync/static/src/components/bank_configure/bank_configure.xml:0
-msgid "loading..."
-msgstr "جاري التحميل..."
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#: code:addons/odex30_account_online_sync/static/src/components/views/account_online_authorization_kanban_controller.xml:0
-msgid "may be duplicates."
-msgstr "قد تكون عناك نُسخ مكررة "
-
-#. module: odex30_account_online_sync
-#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.portal_renew_consent
-msgid "on"
-msgstr "في"
-
-#. module: odex30_account_online_sync
-#: model:ir.model,name:odex30_account_online_sync.model_account_online_account
-msgid "representation of an online bank account"
-msgstr "تمثيل لحساب بنكي عبر الإنترنت "
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#: code:addons/odex30_account_online_sync/static/src/components/refresh_spin_journal_widget/refresh_spin_journal_widget.xml:0
-msgid "transactions fetched"
-msgstr "تم جلب المعاملات "
-
-#. module: odex30_account_online_sync
-#. odoo-javascript
-#: code:addons/odex30_account_online_sync/static/src/components/transient_bank_statement_line_list_view/transient_bank_statement_line_list_view.xml:0
-msgid ""
-"within this period that were not created using the online synchronization. "
-"This might cause duplicate entries."
-msgstr ""
-"خلال هذه الفترة والتي لم يتم إنشاؤها باستخدام المزامنة عبر الإنترنت. قد "
-"يتسبب هذا في استنساخ القيود. "
diff --git a/dev_odex30_accounting/odex30_account_online_sync/models/__init__.py b/dev_odex30_accounting/odex30_account_online_sync/models/__init__.py
deleted file mode 100644
index fa59fc5..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/models/__init__.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from . import account_bank_statement
-from . import account_journal
-from . import account_online
-from . import company
-from . import mail_activity_type
-from . import partner
-from . import bank_rec_widget
diff --git a/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/__init__.cpython-311.pyc b/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/__init__.cpython-311.pyc
deleted file mode 100644
index 807c970..0000000
Binary files a/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/__init__.cpython-311.pyc and /dev/null differ
diff --git a/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/account_bank_statement.cpython-311.pyc b/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/account_bank_statement.cpython-311.pyc
deleted file mode 100644
index 161301f..0000000
Binary files a/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/account_bank_statement.cpython-311.pyc and /dev/null differ
diff --git a/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/account_journal.cpython-311.pyc b/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/account_journal.cpython-311.pyc
deleted file mode 100644
index 65ca1e2..0000000
Binary files a/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/account_journal.cpython-311.pyc and /dev/null differ
diff --git a/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/account_online.cpython-311.pyc b/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/account_online.cpython-311.pyc
deleted file mode 100644
index eb8eff3..0000000
Binary files a/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/account_online.cpython-311.pyc and /dev/null differ
diff --git a/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/bank_rec_widget.cpython-311.pyc b/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/bank_rec_widget.cpython-311.pyc
deleted file mode 100644
index 23f8bc4..0000000
Binary files a/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/bank_rec_widget.cpython-311.pyc and /dev/null differ
diff --git a/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/company.cpython-311.pyc b/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/company.cpython-311.pyc
deleted file mode 100644
index 9dba055..0000000
Binary files a/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/company.cpython-311.pyc and /dev/null differ
diff --git a/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/mail_activity_type.cpython-311.pyc b/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/mail_activity_type.cpython-311.pyc
deleted file mode 100644
index 2aabcb1..0000000
Binary files a/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/mail_activity_type.cpython-311.pyc and /dev/null differ
diff --git a/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/odoofin_auth.cpython-311.pyc b/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/odoofin_auth.cpython-311.pyc
deleted file mode 100644
index 3a6a04c..0000000
Binary files a/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/odoofin_auth.cpython-311.pyc and /dev/null differ
diff --git a/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/partner.cpython-311.pyc b/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/partner.cpython-311.pyc
deleted file mode 100644
index a501c7f..0000000
Binary files a/dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/partner.cpython-311.pyc and /dev/null differ
diff --git a/dev_odex30_accounting/odex30_account_online_sync/models/account_bank_statement.py b/dev_odex30_accounting/odex30_account_online_sync/models/account_bank_statement.py
deleted file mode 100644
index bade26e..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/models/account_bank_statement.py
+++ /dev/null
@@ -1,114 +0,0 @@
-# -*- coding: utf-8 -*-
-import threading
-import time
-import json
-
-from odoo import api, fields, models, SUPERUSER_ID, tools, _
-from odoo.tools import date_utils
-from odoo.exceptions import UserError, ValidationError
-
-STATEMENT_LINE_CREATION_BATCH_SIZE = 500 # When importing transactions, batch the process to commit after importing batch_size
-
-
-class AccountBankStatementLine(models.Model):
- _inherit = 'account.bank.statement.line'
-
- online_transaction_identifier = fields.Char("Online Transaction Identifier", readonly=True)
- online_partner_information = fields.Char(readonly=True)
- online_account_id = fields.Many2one(comodel_name='account.online.account', readonly=True)
- online_link_id = fields.Many2one(
- comodel_name='account.online.link',
- related='online_account_id.account_online_link_id',
- store=True,
- readonly=True,
- )
-
- @api.model_create_multi
- def create(self, vals_list):
- """
- Some transactions can be marked as "Zero Balancing",
- which is a transaction used at the end of the day to summarize all the transactions
- of the day. As we already manage the details of all the transactions, this one is not
- useful and moreover create duplicates. To deal with that, we cancel the move and so
- the bank statement line.
- """
- # EXTEND account
- bank_statement_lines = super().create(vals_list)
- moves_to_cancel = self.env['account.move']
- for bank_statement_line in bank_statement_lines:
- transaction_details = json.loads(bank_statement_line.transaction_details) if bank_statement_line.transaction_details else {}
- if not transaction_details.get('is_zero_balancing'):
- continue
- moves_to_cancel |= bank_statement_line.move_id
- moves_to_cancel.button_cancel()
-
- return bank_statement_lines
-
- @api.model
- def _online_sync_bank_statement(self, transactions, online_account):
- """
- build bank statement lines from a list of transaction and post messages is also post in the online_account of the journal.
- :param transactions: A list of transactions that will be created.
- The format is : [{
- 'id': online id, (unique ID for the transaction)
- 'date': transaction date, (The date of the transaction)
- 'name': transaction description, (The description)
- 'amount': transaction amount, (The amount of the transaction. Negative for debit, positive for credit)
- }, ...]
- :param online_account: The online account for this statement
- Return: The number of imported transaction for the journal
- """
- start_time = time.time()
- lines_to_reconcile = self.env['account.bank.statement.line']
- try:
- for journal in online_account.journal_ids:
- # Since the synchronization succeeded, set it as the bank_statements_source of the journal
- journal.sudo().write({'bank_statements_source': 'online_sync'})
- if not transactions:
- continue
-
- sorted_transactions = sorted(transactions, key=lambda transaction: transaction['date'])
- total = self.env.context.get('transactions_total') or sum([transaction['amount'] for transaction in transactions])
-
- # For first synchronization, an opening line is created to fill the missing bank statement data
- any_st_line = self.search_count([('journal_id', '=', journal.id)], limit=1)
- journal_currency = journal.currency_id or journal.company_id.currency_id
- # If there are neither statement and the ending balance != 0, we create an opening bank statement at the day of the oldest transaction.
- # We set the sequence to >1 to ensure the computed internal_index will force its display before any other statement with the same date.
- if not any_st_line and not journal_currency.is_zero(online_account.balance - total):
- opening_st_line = self.with_context(skip_statement_line_cron_trigger=True).create({
- 'date': sorted_transactions[0]['date'],
- 'journal_id': journal.id,
- 'payment_ref': _("Opening statement: first synchronization"),
- 'amount': online_account.balance - total,
- 'sequence': 2,
- })
- lines_to_reconcile += opening_st_line
-
- filtered_transactions = online_account._get_filtered_transactions(sorted_transactions)
-
- do_commit = not (hasattr(threading.current_thread(), 'testing') and threading.current_thread().testing)
- if filtered_transactions:
- # split transactions import in batch and commit after each batch except in testing mode
- for index in range(0, len(filtered_transactions), STATEMENT_LINE_CREATION_BATCH_SIZE):
- lines_to_reconcile += self.with_user(SUPERUSER_ID).with_company(journal.company_id).with_context(skip_statement_line_cron_trigger=True).create(filtered_transactions[index:index + STATEMENT_LINE_CREATION_BATCH_SIZE])
- if do_commit:
- self.env.cr.commit()
- # Set last sync date as the last transaction date
- journal.account_online_account_id.sudo().write({'last_sync': filtered_transactions[-1]['date']})
-
- if lines_to_reconcile:
- # 'limit_time_real_cron' defaults to -1.
- # Manual fallback applied for non-POSIX systems where this key is disabled (set to None).
- cron_limit_time = tools.config['limit_time_real_cron'] or -1
- limit_time = (cron_limit_time if cron_limit_time > 0 else 180) - (time.time() - start_time)
- if limit_time > 0:
- lines_to_reconcile._cron_try_auto_reconcile_statement_lines(limit_time=limit_time)
- # Catch any configuration error that would prevent creating the entries, reset fetching_status flag and re-raise the error
- # Otherwise flag is never reset and user is under the impression that we are still fetching transactions
- except (UserError, ValidationError) as e:
- self.env.cr.rollback()
- online_account.account_online_link_id._log_information('error', subject=_("Error"), message=str(e))
- self.env.cr.commit()
- raise
- return lines_to_reconcile
diff --git a/dev_odex30_accounting/odex30_account_online_sync/models/account_journal.py b/dev_odex30_accounting/odex30_account_online_sync/models/account_journal.py
deleted file mode 100644
index afb0777..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/models/account_journal.py
+++ /dev/null
@@ -1,380 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import logging
-import requests
-from dateutil.relativedelta import relativedelta
-from requests.exceptions import RequestException, Timeout
-
-from odoo import api, fields, models, tools, _
-from odoo.exceptions import UserError, ValidationError, RedirectWarning
-from odoo.tools import SQL
-
-_logger = logging.getLogger(__name__)
-
-
-class AccountJournal(models.Model):
- _inherit = "account.journal"
-
- def __get_bank_statements_available_sources(self):
- rslt = super(AccountJournal, self).__get_bank_statements_available_sources()
- rslt.append(("online_sync", _("Online Synchronization")))
- return rslt
-
- next_link_synchronization = fields.Datetime("Online Link Next synchronization", related='account_online_link_id.next_refresh')
- expiring_synchronization_date = fields.Date(related='account_online_link_id.expiring_synchronization_date')
- expiring_synchronization_due_day = fields.Integer(compute='_compute_expiring_synchronization_due_day')
- account_online_account_id = fields.Many2one('account.online.account', copy=False, ondelete='set null')
- account_online_link_id = fields.Many2one('account.online.link', related='account_online_account_id.account_online_link_id', readonly=True, store=True)
- account_online_link_state = fields.Selection(related="account_online_link_id.state", readonly=True)
- renewal_contact_email = fields.Char(
- string='Connection Requests',
- help='Comma separated list of email addresses to send consent renewal notifications 15, 3 and 1 days before expiry',
- default=lambda self: self.env.user.email,
- )
- online_sync_fetching_status = fields.Selection(related="account_online_account_id.fetching_status", readonly=True)
-
- def write(self, vals):
- # When changing the bank_statement_source, unlink the connection if there is any
- if 'bank_statements_source' in vals and vals.get('bank_statements_source') != 'online_sync':
- for journal in self:
- if journal.bank_statements_source == 'online_sync':
- # unlink current connection
- vals['account_online_account_id'] = False
- journal.account_online_link_id.has_unlinked_accounts = True
- return super().write(vals)
-
- @api.depends('expiring_synchronization_date')
- def _compute_expiring_synchronization_due_day(self):
- for record in self:
- if record.expiring_synchronization_date:
- due_day_delta = record.expiring_synchronization_date - fields.Date.context_today(record)
- record.expiring_synchronization_due_day = due_day_delta.days
- else:
- record.expiring_synchronization_due_day = 0
-
- def _fill_bank_cash_dashboard_data(self, dashboard_data):
- super()._fill_bank_cash_dashboard_data(dashboard_data)
- # Caching data to avoid one call per journal
- self.browse(list(dashboard_data.keys())).fetch(['type', 'account_online_account_id'])
- for journal_id, journal_data in dashboard_data.items():
- journal = self.browse(journal_id)
- journal_data['display_connect_bank_in_dashboard'] = journal.type in ('bank', 'credit') \
- and not journal.account_online_account_id \
- and journal.company_id.id == self.env.company.id
-
- @api.constrains('account_online_account_id')
- def _check_account_online_account_id(self):
- for journal in self:
- if len(journal.account_online_account_id.journal_ids) > 1:
- raise ValidationError(_('You cannot have two journals associated with the same Online Account.'))
-
- def _fetch_online_transactions(self):
- for journal in self:
- try:
- journal.account_online_link_id._pop_connection_state_details(journal=journal)
- journal.manual_sync()
- # for cron jobs it is usually recommended committing after each iteration,
- # so that a later error or job timeout doesn't discard previous work
- self.env.cr.commit()
- except (UserError, RedirectWarning):
- # We need to rollback here otherwise the next iteration will still have the error when trying to commit
- self.env.cr.rollback()
-
- def fetch_online_sync_favorite_institutions(self):
- self.ensure_one()
- timeout = int(self.env['ir.config_parameter'].sudo().get_param('odex30_account_online_sync.request_timeout')) or 60
- endpoint_url = self.env['account.online.link']._get_odoofin_url('/proxy/v1/get_dashboard_institutions')
- params = {'country': self.sudo().company_id.account_fiscal_country_id.code, 'limit': 28}
- try:
- resp = requests.post(endpoint_url, json=params, timeout=timeout)
- resp_dict = resp.json()['result']
- for institution in resp_dict:
- if institution['picture'].startswith('/'):
- institution['picture'] = self.env['account.online.link']._get_odoofin_url(institution['picture'])
- return resp_dict
- except (Timeout, ConnectionError, RequestException, ValueError) as e:
- _logger.warning(e)
- return []
-
- @api.model
- def _cron_fetch_waiting_online_transactions(self):
- """ This method is only called when the user fetch transactions asynchronously.
- We only fetch transactions on synchronizations that are in "waiting" status.
- Once the synchronization is done, the status is changed for "done".
- We have to that to avoid having too much logic in the same cron function to do
- 2 different things. This cron should only be used for asynchronous fetchs.
- """
-
- # 'limit_time_real_cron' and 'limit_time_real' default respectively to -1 and 120.
- # Manual fallbacks applied for non-POSIX systems where this key is disabled (set to None).
- limit_time = tools.config['limit_time_real_cron'] or -1
- if limit_time <= 0:
- limit_time = tools.config['limit_time_real'] or 120
- journals = self.search([
- '|',
- ('online_sync_fetching_status', 'in', ('planned', 'waiting')),
- '&',
- ('online_sync_fetching_status', '=', 'processing'),
- ('account_online_link_id.last_refresh', '<', fields.Datetime.now() - relativedelta(seconds=limit_time)),
- ])
- journals.with_context(cron=True)._fetch_online_transactions()
-
- @api.model
- def _cron_fetch_online_transactions(self):
- """ This method is called by the cron (by default twice a day) to fetch (for all journals)
- the new transactions.
- """
- journals = self.search([('account_online_account_id', '!=', False)])
- journals.with_context(cron=True)._fetch_online_transactions()
-
- @api.model
- def _cron_send_reminder_email(self):
- for journal in self.search([('account_online_account_id', '!=', False)]):
- if journal.expiring_synchronization_due_day in {1, 3, 15}:
- journal.action_send_reminder()
-
- def manual_sync(self):
- self.ensure_one()
- if self.account_online_link_id:
- account = self.account_online_account_id
- return self.account_online_link_id._fetch_transactions(accounts=account)
-
- def unlink(self):
- """
- Override of the unlink method.
- That's useful to unlink account.online.account too.
- """
- if self.account_online_account_id:
- self.account_online_account_id.unlink()
- return super(AccountJournal, self).unlink()
-
- def action_configure_bank_journal(self):
- """
- Override the "action_configure_bank_journal" and change the flow for the
- "Configure" button in dashboard.
- """
- self.ensure_one()
- return self.env['account.online.link'].action_new_synchronization()
-
- def action_open_account_online_link(self):
- self.ensure_one()
- return {
- 'type': 'ir.actions.act_window',
- 'name': self.account_online_link_id.name,
- 'res_model': 'account.online.link',
- 'target': 'main',
- 'view_mode': 'form',
- 'views': [[False, 'form']],
- 'res_id': self.account_online_link_id.id,
- }
-
- def action_extend_consent(self):
- """
- Extend the consent of the user by redirecting him to update his credentials
- """
- self.ensure_one()
- return self.account_online_link_id._open_iframe(
- mode='updateCredentials',
- include_param={
- 'account_online_identifier': self.account_online_account_id.online_identifier,
- },
- )
-
- def action_reconnect_online_account(self):
- self.ensure_one()
- return self.account_online_link_id.action_reconnect_account()
-
- def action_send_reminder(self):
- self.ensure_one()
- self._portal_ensure_token()
- template = self.env.ref('odex30_account_online_sync.email_template_sync_reminder')
- subtype = self.env.ref('odex30_account_online_sync.bank_sync_consent_renewal')
- self.message_post_with_source(source_ref=template, subtype_id=subtype.id)
-
- def action_open_missing_transaction_wizard(self):
- """ This method allows to open the wizard to fetch the missing
- transactions and the pending ones.
- Depending on where the function is called, we'll receive
- one journal or none of them.
- If we receive more or less than one journal, we do not set
- it on the wizard, the user should select it by himself.
-
- :return: An action opening the wizard.
- """
- journal_id = None
- if len(self) == 1:
- if not self.account_online_account_id or self.account_online_link_state != 'connected':
- raise UserError(_("You can't find missing transactions for a journal that isn't connected."))
-
- journal_id = self.id
-
- wizard = self.env['account.missing.transaction.wizard'].create({'journal_id': journal_id})
- return {
- 'name': _("Find Missing Transactions"),
- 'type': 'ir.actions.act_window',
- 'res_model': 'account.missing.transaction.wizard',
- 'res_id': wizard.id,
- 'views': [(False, 'form')],
- 'target': 'new',
- }
-
- def action_open_duplicate_transaction_wizard(self, from_date=None):
- """ This method allows to open the wizard to find duplicate transactions.
- :param from_date: date from with we must check for duplicates.
-
- :return: An action opening the wizard.
- """
- wizard = self.env['account.duplicate.transaction.wizard'].create({
- 'journal_id': self.id if len(self) == 1 else None,
- **({'date': from_date} if from_date else {}),
- })
- return wizard._get_records_action(name=_("Find Duplicate Transactions"))
-
- def _has_duplicate_transactions(self, date_from):
- """ Has any transaction with
- - same amount &
- - same date &
- - same account number
- We do not check on online_transaction_identifier because this is called after the fetch
- where transitions would already have been filtered on existing online_transaction_identifier.
-
- :param from_date: date from with we must check for duplicates.
- """
- self.env.cr.execute(SQL.join(SQL(''), [
- self._get_duplicate_amount_date_account_transactions_query(date_from),
- SQL('LIMIT 1'),
- ]))
- return bool(self.env.cr.rowcount)
-
- def _get_duplicate_transactions(self, date_from):
- """Find all transaction with
- - same amount &
- - same date &
- - same account number
- or
- - same transaction id
-
- :param from_date: date from with we must check for duplicates.
- """
- query = SQL.join(SQL(''), [
- self._get_duplicate_amount_date_account_transactions_query(date_from),
- SQL('UNION'),
- self._get_duplicate_online_transaction_identifier_transactions_query(date_from),
- SQL('ORDER BY ids'),
- ])
- return [res[0] for res in self.env.execute_query(query)]
-
- def _get_duplicate_amount_date_account_transactions_query(self, date_from):
- self.ensure_one()
- return SQL('''
- SELECT ARRAY_AGG(st_line.id ORDER BY st_line.id) AS ids
- FROM account_bank_statement_line st_line
- JOIN account_move move ON move.id = st_line.move_id
- WHERE st_line.journal_id = %(journal_id)s AND move.date >= %(date_from)s
- GROUP BY st_line.currency_id, st_line.amount, st_line.account_number, move.date
- HAVING count(st_line.id) > 1
- ''',
- journal_id=self.id,
- date_from=date_from,
- )
-
- def _get_duplicate_online_transaction_identifier_transactions_query(self, date_from):
- return SQL('''
- SELECT ARRAY_AGG(st_line.id ORDER BY st_line.id) AS ids
- FROM account_bank_statement_line st_line
- JOIN account_move move ON move.id = st_line.move_id
- WHERE st_line.journal_id = %(journal_id)s AND
- move.date >= %(prior_date)s AND
- st_line.online_transaction_identifier IS NOT NULL
- GROUP BY st_line.online_transaction_identifier
- HAVING count(st_line.id) > 1 AND BOOL_OR(move.date >= %(date_from)s) -- at least one date is > date_from
- ''',
- journal_id=self.id,
- date_from=date_from,
- prior_date=date_from - relativedelta(months=3), # allow 1 of duplicate statements to be older than "from" date
- )
-
- def action_open_dashboard_asynchronous_action(self):
- """ This method allows to open action asynchronously
- during the fetching process.
- When a user clicks on the Fetch Transactions button in
- the dashboard, we fetch the transactions asynchronously
- and save connection state details on the synchronization.
- This action allows the user to open the action saved in
- the connection state details.
- """
- self.ensure_one()
-
- if not self.account_online_account_id:
- raise UserError(_("You can only execute this action for bank-synchronized journals."))
-
- connection_state_details = self.account_online_link_id._pop_connection_state_details(journal=self)
- if connection_state_details and connection_state_details.get('action'):
- if connection_state_details.get('error_type') == 'redirect_warning':
- self.env.cr.commit()
- raise RedirectWarning(connection_state_details['error_message'], connection_state_details['action'], _('Report Issue'))
- else:
- return connection_state_details['action']
-
- return {'type': 'ir.actions.client', 'tag': 'soft_reload'}
-
- def _get_journal_dashboard_data_batched(self):
- dashboard_data = super()._get_journal_dashboard_data_batched()
- for journal in self.filtered(lambda j: j.type in ('bank', 'credit')):
- if journal.account_online_account_id:
- if journal.company_id.id not in self.env.companies.ids:
- continue
- connection_state_details = journal.account_online_link_id._get_connection_state_details(journal=journal)
- if not connection_state_details and journal.account_online_account_id.fetching_status in ('waiting', 'processing'):
- connection_state_details = {'status': 'fetching'}
- dashboard_data[journal.id]['connection_state_details'] = connection_state_details
- dashboard_data[journal.id]['show_sync_actions'] = journal.account_online_link_id.show_sync_actions
- return dashboard_data
-
- def get_related_connection_state_details(self):
- """ This method allows JS widget to get the last connection state details
- It's useful if the user wasn't on the dashboard when we send the message
- by websocket that the asynchronous flow is finished.
- In case we don't have a connection state details and if the fetching
- status is set on "waiting" or "processing". We're returning that the sync
- is currently fetching.
- """
- self.ensure_one()
- connection_state_details = self.account_online_link_id._get_connection_state_details(journal=self)
- if not connection_state_details and self.account_online_account_id.fetching_status in ('waiting', 'processing'):
- connection_state_details = {'status': 'fetching'}
- return connection_state_details
-
- def _consume_connection_state_details(self):
- self.ensure_one()
- if self.account_online_link_id and self.env.user.has_group('account.group_account_manager'):
- # In case we have a bank synchronization connected to the journal
- # we want to remove the last connection state because it means that we
- # have "mark as read" this state, and we don't want to display it again to
- # the user.
- self.account_online_link_id._pop_connection_state_details(journal=self)
-
- def open_action(self):
- # Extends 'account_accountant'
- if not self._context.get('action_name') and self.type == 'bank' and self.bank_statements_source == 'online_sync':
- self._consume_connection_state_details()
- return self.env['account.bank.statement.line']._action_open_bank_reconciliation_widget(
- default_context={'search_default_journal_id': self.id},
- )
- return super().open_action()
-
- def action_open_reconcile(self):
- # Extends 'account_accountant'
- self._consume_connection_state_details()
- return super().action_open_reconcile()
-
- def action_open_bank_transactions(self):
- # Extends 'account_accountant'
- self._consume_connection_state_details()
- return super().action_open_bank_transactions()
-
- @api.model
- def _toggle_asynchronous_fetching_cron(self):
- cron = self.env.ref('odex30_account_online_sync.online_sync_cron_waiting_synchronization', raise_if_not_found=False)
- if cron:
- cron.sudo().toggle(model=self._name, domain=[('account_online_account_id', '!=', False)])
diff --git a/dev_odex30_accounting/odex30_account_online_sync/models/account_online.py b/dev_odex30_accounting/odex30_account_online_sync/models/account_online.py
deleted file mode 100644
index 63554d4..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/models/account_online.py
+++ /dev/null
@@ -1,1170 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import base64
-import datetime
-import requests
-import logging
-import re
-import uuid
-import urllib.parse
-import odoo
-import odoo.release
-from dateutil.relativedelta import relativedelta
-from markupsafe import Markup
-
-from requests.exceptions import RequestException, Timeout, ConnectionError
-from odoo import api, fields, models, modules, tools
-from odoo.exceptions import UserError, CacheMiss, MissingError, ValidationError, RedirectWarning
-from odoo.http import request
-from odoo.addons.odex30_account_online_sync.models.odoofin_auth import OdooFinAuth
-from odoo.tools.misc import format_amount, format_date, get_lang
-from odoo.tools import _, LazyTranslate
-
-_lt = LazyTranslate(__name__)
-_logger = logging.getLogger(__name__)
-pattern = re.compile("^[a-z0-9-_]+$")
-runbot_pattern = re.compile(r"^https:\/\/[a-z0-9-_]+\.[a-z0-9-_]+\.odoo\.com$")
-
-class OdooFinRedirectException(UserError):
- """ When we need to open the iframe in a given mode. """
-
- def __init__(self, message=_lt('Redirect'), mode='link'):
- self.mode = mode
- super().__init__(message)
-
-class AccountOnlineAccount(models.Model):
- _name = 'account.online.account'
- _description = 'representation of an online bank account'
-
- name = fields.Char(string="Account Name", help="Account Name as provided by third party provider")
- online_identifier = fields.Char(help='Id used to identify account by third party provider', readonly=True)
- balance = fields.Float(readonly=True, help='Balance of the account sent by the third party provider')
- account_number = fields.Char(help='Set if third party provider has the full account number')
- account_data = fields.Char(help='Extra information needed by third party provider', readonly=True)
-
- account_online_link_id = fields.Many2one('account.online.link', readonly=True, ondelete='cascade')
- journal_ids = fields.One2many('account.journal', 'account_online_account_id', string='Journal', domain="[('type', 'in', ('bank', 'credit')), ('company_id', '=', company_id)]")
- last_sync = fields.Date("Last synchronization")
- company_id = fields.Many2one('res.company', related='account_online_link_id.company_id')
- currency_id = fields.Many2one('res.currency')
- fetching_status = fields.Selection(
- selection=[
- ('planned', 'Planned'), # When all the transactions couldn't be imported in one go and is waiting for next batch
- ('waiting', 'Waiting'), # When waiting for the provider to fetch the transactions
- ('processing', 'Processing'), # When currently importing in odoo
- ('done', 'Done'), # When every transaction have been imported in odoo
- ]
- )
-
- inverse_balance_sign = fields.Boolean(
- string="Inverse Balance Sign",
- help="If checked, the balance sign will be inverted",
- )
- inverse_transaction_sign = fields.Boolean(
- string="Inverse Transaction Sign",
- help="If checked, the transaction sign will be inverted",
- )
-
- @api.constrains('journal_ids')
- def _check_journal_ids(self):
- for online_account in self:
- if len(online_account.journal_ids) > 1:
- raise ValidationError(_('You cannot have two journals associated with the same Online Account.'))
-
- @api.model_create_multi
- def create(self, vals):
- result = super().create(vals)
- if any(data.get('fetching_status') in {'waiting', 'processing', 'planned'} for data in vals):
- self.env['account.journal']._toggle_asynchronous_fetching_cron()
- return result
-
- def write(self, vals):
- result = super().write(vals)
- if vals.get('fetching_status') in {'waiting', 'processing', 'planned'}:
- self.env['account.journal']._toggle_asynchronous_fetching_cron()
- return result
-
- def unlink(self):
- result = super().unlink()
- self.env['account.journal']._toggle_asynchronous_fetching_cron()
- return result
-
- def _assign_journal(self, swift_code=False):
- """
- This method allows to link an online account to a journal with the following heuristics
- Also, Create and assign bank & swift/bic code if odoofin returns one
- If a journal is present in the context (active_model = account.journal and active_id), we assume that
- We started the journey from a journal and we assign the online_account to that particular journal.
- Otherwise we will create a new journal on the fly and assign the online_account to it.
- If an online_account was previously set on the journal, it will be removed and deleted.
- This will also set the 'online_sync' source on the journal and create an activity for the consent renewal
- The date to fetch transaction will also be set and have the following value:
- date of the latest statement line on the journal
- or date of the fiscalyear lock date
- or False (we fetch transactions as far as possible)
- """
- currency_id = self.currency_id.id if not self.currency_id.is_current_company_currency else False
- existing_journal = self.env['account.journal'].search([
- ('bank_acc_number', '=', self.account_number),
- ('currency_id', '=', currency_id),
- ('type', '=', 'bank'),
- ('account_online_account_id', '=', False),
- ], limit=1)
-
- self.ensure_one()
- if (active_id := self.env.context.get('active_id')) and self.env.context.get('active_model') == 'account.journal':
- journal = self.env['account.journal'].browse(active_id)
- # If we already have a linked account on that journal, it means we are in the process of relinking
- # it is due to an error that occured which require to redo the connection (can't fix it).
- # Hence we delete the previously linked account.online.link to prevent showing multiple
- # duplicate existing connections when opening the iframe
- if journal.account_online_link_id:
- journal.account_online_link_id.unlink()
-
- # Ensure the journal's currency matches the bank account's currency.
- if self.currency_id.id != journal.currency_id.id:
- # If the journal already has entries in a different currency, raise an error.
- statement_lines_in_other_currency = self.env['account.bank.statement.line'].search_count([
- ('journal_id', '=', journal.id),
- ('currency_id', 'not in', (False, self.currency_id.id)),
- ], limit=1)
- if statement_lines_in_other_currency:
- raise UserError(_("Journal %(journal_name)s has been set up with a different currency and already has existing entries. "
- "You can't link selected bank account in %(currency_name)s to it",
- journal_name=journal.name, currency_name=self.currency_id.name))
- else:
- # If the journal's default bank account has entries in a differente currency, silently do nothing to avoid an error.
- move_lines_in_other_currency = self.env['account.move.line'].search_count([
- ('account_id', '=', journal.default_account_id.id),
- ('currency_id', '!=', self.currency_id.id),
- ], limit=1)
- if not move_lines_in_other_currency:
- # If not set yet and there are no conflicting entries, set it.
- journal.sudo().currency_id = self.currency_id.id
- elif existing_journal:
- journal = existing_journal
- else:
- journal = self.env['account.journal'].create({
- 'name': self.account_number or self.display_name,
- 'code': self.env['account.journal'].get_next_bank_cash_default_code('bank', self.env.company),
- 'type': 'bank',
- 'company_id': self.env.company.id,
- 'currency_id': currency_id,
- })
-
- self.sudo().journal_ids = journal
-
- journal_vals = {
- 'bank_statements_source': 'online_sync',
- }
- if self.account_number and not self.journal_ids.bank_acc_number:
- journal_vals['bank_acc_number'] = self.account_number
- self.journal_ids.sudo().write(journal_vals)
- # Update connection status and get consent expiration date and create an activity on related journal
- self.account_online_link_id._update_connection_status()
-
- # Set last_sync date (date of latest statement or one day after accounting lock date or False)
- lock_date = self.env.company._get_user_fiscal_lock_date(journal)
- last_sync = lock_date + relativedelta(days=1) if lock_date and lock_date > datetime.date.min else None
- bnk_stmt_line = self.env['account.bank.statement.line'].search([('journal_id', 'in', self.journal_ids.ids)], order="date desc", limit=1)
- if bnk_stmt_line:
- last_sync = bnk_stmt_line.date
- self.last_sync = last_sync
-
- if swift_code:
- if self.journal_ids.bank_account_id.bank_id:
- if not self.journal_ids.bank_account_id.bank_id.bic:
- self.journal_ids.bank_account_id.bank_id.bic = swift_code
- else:
- bank_rec = self.env['res.bank'].search([('bic', '=', swift_code)], limit=1)
- if not bank_rec:
- bank_rec = self.env['res.bank'].create({'name': self.account_online_link_id.display_name, 'bic': swift_code})
- self.journal_ids.bank_account_id.bank_id = bank_rec.id
-
- def _refresh(self):
- """
- This method is called on an online_account in order to check the current refresh status of the
- account. If we are in manual mode and if the provider allows it, this will also trigger a
- manual refresh on the provider side. Call to /proxy/v1/refresh will return a boolean
- telling us if the refresh was successful or not. When not successful, we should avoid
- trying to fetch transactions. Cases where we can receive an unsuccessful response are as follow
- (non exhaustive list)
- - Another refresh was made too early and provider/bank limit the number of refresh allowed
- - Provider is in the process of importing the transactions so we should wait until he has
- finished before fetching them in Odoo
- :return: True if provider has refreshed the account and we can start fetching transactions
- """
- data = {'account_id': self.online_identifier}
- while True:
- # While this is kind of a bad practice to do, it can happen that provider_data/account_data change between
- # 2 calls, the reason is that those field contains the encrypted information needed to access the provider
- # and first call can result in an error due to the encrypted token inside provider_data being expired for example.
- # In such a case, we renew the token with the provider and send back the newly encrypted token inside provider_data
- # which result in the information having changed, henceforth why those fields are passed at every loop.
- data.update({
- 'provider_data': self.account_online_link_id.provider_data,
- 'account_data': self.account_data,
- 'fetching_status': self.fetching_status,
- })
- resp_json = self.account_online_link_id._fetch_odoo_fin('/proxy/v1/refresh', data=data)
- if resp_json.get('account_data'):
- self.account_data = resp_json['account_data']
- currently_fetching = resp_json.get('currently_fetching')
- success = resp_json.get('success', True)
- if currently_fetching:
- # Provider has not finished fetching transactions, set status to waiting
- self.fetching_status = 'waiting'
- if not resp_json.get('next_data'):
- break
- data['next_data'] = resp_json.get('next_data') or {}
- return {'success': not currently_fetching and success, 'data': resp_json.get('data', {})}
-
- def _retrieve_transactions(self, date=None, include_pendings=False):
- last_stmt_line = self.env['account.bank.statement.line'].search([
- ('date', '<=', self.last_sync or fields.Date().today()),
- ('online_transaction_identifier', '!=', False),
- ('journal_id', 'in', self.journal_ids.ids),
- ('online_account_id', '=', self.id)
- ], order="date desc", limit=1)
- transactions = []
-
- start_date = date or last_stmt_line.date or self.last_sync
- data = {
- # If we are in a new sync, we do not give a start date; We will fetch as much as possible. Otherwise, the last sync is the start date.
- 'start_date': start_date and format_date(self.env, start_date, date_format='yyyy-MM-dd'),
- 'account_id': self.online_identifier,
- 'last_transaction_identifier': last_stmt_line.online_transaction_identifier if not include_pendings else None,
- 'currency_code': self.currency_id.name or self.journal_ids[0].currency_id.name or self.company_id.currency_id.name,
- 'include_pendings': include_pendings,
- 'include_foreign_currency': True,
- }
- pendings = []
- while True:
- # While this is kind of a bad practice to do, it can happen that provider_data/account_data change between
- # 2 calls, the reason is that those field contains the encrypted information needed to access the provider
- # and first call can result in an error due to the encrypted token inside provider_data being expired for example.
- # In such a case, we renew the token with the provider and send back the newly encrypted token inside provider_data
- # which result in the information having changed, henceforth why those fields are passed at every loop.
- data.update({
- 'provider_data': self.account_online_link_id.provider_data,
- 'account_data': self.account_data,
- })
- resp_json = self.account_online_link_id._fetch_odoo_fin('/proxy/v1/transactions', data=data)
- if resp_json.get('balance'):
- sign = -1 if self.inverse_balance_sign else 1
- self.balance = sign * resp_json['balance']
- if resp_json.get('account_data'):
- self.account_data = resp_json['account_data']
- transactions += resp_json.get('transactions', [])
- pendings += resp_json.get('pendings', [])
- if not resp_json.get('next_data'):
- break
- data['next_data'] = resp_json.get('next_data') or {}
-
- return {
- 'transactions': self._format_transactions(transactions),
- 'pendings': self._format_transactions(pendings),
- }
-
- def get_formatted_balances(self):
- balances = {}
- for account in self:
- if account.currency_id:
- formatted_balance = format_amount(self.env, account.balance, account.currency_id)
- else:
- formatted_balance = '%.2f' % account.balance
- balances[account.id] = [formatted_balance, account.balance]
- return balances
-
- ###########
- # HELPERS #
- ###########
-
- def _get_filtered_transactions(self, new_transactions):
- """ This function will filter transaction to avoid duplicate transactions.
- To do that, we're comparing the received online_transaction_identifier with
- those in the database. If there is a match, the new transaction is ignored.
- """
- self.ensure_one()
-
- journal_id = self.journal_ids[0]
- existing_bank_statement_lines = self.env['account.bank.statement.line'].search_fetch(
- [
- ('journal_id', '=', journal_id.id),
- ('online_transaction_identifier', 'in', [
- transaction['online_transaction_identifier']
- for transaction in new_transactions
- if transaction.get('online_transaction_identifier')
- ]),
- ],
- ['online_transaction_identifier']
- )
- existing_online_transaction_identifier = set(existing_bank_statement_lines.mapped('online_transaction_identifier'))
-
- filtered_transactions = []
- # Remove transactions already imported in Odoo
- for transaction in new_transactions:
- if transaction_identifier := transaction['online_transaction_identifier']:
- if transaction_identifier in existing_online_transaction_identifier:
- continue
- existing_online_transaction_identifier.add(transaction_identifier)
-
- filtered_transactions.append(transaction)
- return filtered_transactions
-
- def _format_transactions(self, new_transactions):
- """ This function format transactions:
- It will:
- - Replace the foreign currency code with the corresponding currency id and activating the currencies that are not active
- - Change inverse the transaction sign if the setting is activated
- - Parsing the date
- - Setting the account online account and the account journal
- """
- self.ensure_one()
- transaction_sign = -1 if self.inverse_transaction_sign else 1
- currencies = self.env['res.currency'].with_context(active_test=False).search([])
- currency_code_mapping = {currency.name: currency for currency in currencies}
-
- formatted_transactions = []
- for transaction in new_transactions:
- if transaction.get('foreign_currency_code'):
- currency = currency_code_mapping.get(transaction.pop('foreign_currency_code'))
- if currency:
- transaction.update({'foreign_currency_id': currency.id})
- if not currency.active:
- currency.active = True
-
- formatted_transactions.append({
- **transaction,
- 'amount': transaction['amount'] * transaction_sign,
- 'date': fields.Date.from_string(transaction['date']),
- 'online_account_id': self.id,
- 'journal_id': self.journal_ids[0].id,
- 'company_id': self.company_id.id,
- })
- return formatted_transactions
-
- def action_reset_fetching_status(self):
- """
- This action will reset the fetching status to avoid the problem when there is an error during the
- synchronisation that would block the customer with his connection since we block the fetch due that value.
- With this he has a button that can reset the fetching status.
- """
- self.fetching_status = None
-
-
-class AccountOnlineLink(models.Model):
- _name = 'account.online.link'
- _description = 'Bank Connection'
- _inherit = ['mail.thread', 'mail.activity.mixin']
-
- def _compute_next_synchronization(self):
- for rec in self:
- rec.next_refresh = self.env['ir.cron'].sudo().search([('id', '=', self.env.ref('odex30_account_online_sync.online_sync_cron').id)], limit=1).nextcall
-
- account_online_account_ids = fields.One2many('account.online.account', 'account_online_link_id')
- last_refresh = fields.Datetime(readonly=True, default=fields.Datetime.now)
- next_refresh = fields.Datetime("Next synchronization", compute='_compute_next_synchronization')
- state = fields.Selection([('connected', 'Connected'), ('error', 'Error'), ('disconnected', 'Not Connected')],
- default='disconnected', tracking=True, required=True, readonly=True)
- connection_state_details = fields.Json()
- auto_sync = fields.Boolean(
- default=True,
- string="Automatic synchronization",
- help="""If possible, we will try to automatically fetch new transactions for this record
- \nIf the automatic sync is disabled. that will be due to security policy on the bank's end. So, they have to launch the sync manually""",
- )
- company_id = fields.Many2one('res.company', required=True, default=lambda self: self.env.company)
- has_unlinked_accounts = fields.Boolean(default=True, help="True if that connection still has accounts that are not linked to an Odoo journal")
- show_sync_actions = fields.Boolean(compute='_compute_show_sync_actions')
-
- # Information received from OdooFin, should not be tampered with
- name = fields.Char(help="Institution Name", readonly=True)
- client_id = fields.Char(help="Represent a link for a given user towards a banking institution", readonly=True)
- refresh_token = fields.Char(help="Token used to sign API request, Never disclose it",
- readonly=True, groups="base.group_system")
- access_token = fields.Char(help="Token used to access API.", readonly=True, groups="account.group_account_basic")
- provider_data = fields.Char(help="Information needed to interact with third party provider", readonly=True)
- expiring_synchronization_date = fields.Date(help="Date when the consent for this connection expires",
- readonly=True)
- journal_ids = fields.One2many('account.journal', compute='_compute_journal_ids')
- provider_type = fields.Char(help="Third Party Provider", readonly=True)
-
- ###################
- # Compute methods #
- ###################
-
- @api.depends('account_online_account_ids')
- def _compute_journal_ids(self):
- for online_link in self:
- online_link.journal_ids = online_link.account_online_account_ids.journal_ids
-
- @api.depends('company_id')
- @api.depends_context('allowed_company_ids')
- def _compute_show_sync_actions(self):
- for online_link in self:
- online_link.show_sync_actions = online_link.company_id in self.env.companies
-
- ##########################
- # Wizard opening actions #
- ##########################
- def create_new_bank_account_action(self, data=None):
- self.ensure_one()
- # We do return the bank account setup wizard if we don't have minimum info
- if not data or not data.get('account_number'):
- ctx = self.env.context
- # if this was called from kanban box, active_model is in context
- if ctx.get('active_model') == 'account.journal':
- ctx = {**ctx, 'default_linked_journal_id': ctx.get('active_id', False), 'dialog_size': 'medium'}
- return {
- 'type': 'ir.actions.act_window',
- 'name': _('Setup Bank Account'),
- 'res_model': 'account.setup.bank.manual.config',
- 'target': 'new',
- 'view_mode': 'form',
- 'context': ctx,
- 'views': [(False, 'form')],
- }
-
- bank = self.env['res.bank']
- if data.get('name'):
- bank = self.env['res.bank'].sudo().create({
- 'name': data['name'],
- 'bic': data.get('swift_code'),
- })
-
- bank_account = self.env['res.partner.bank'].sudo().create({
- 'acc_number': data.get('account_number'),
- 'bank_id': bank.id,
- 'partner_id': self.company_id.partner_id.id,
- })
-
- self.env['account.journal'].sudo().create({
- 'name': data.get('account_number'),
- 'type': data.get('journal_type') or 'bank',
- 'bank_account_id': bank_account.id,
- })
-
- return {'type': 'ir.actions.client', 'tag': 'soft_reload'}
-
- def _link_accounts_to_journals_action(self, swift_code):
- """
- This method opens a wizard allowing the user to link
- his bank accounts with new or existing journal.
- :return: An action openning a wizard to link bank accounts with account journal.
- """
- self.ensure_one()
- account_bank_selection_wizard = self.env['account.bank.selection'].create({
- 'account_online_link_id': self.id,
- })
-
- return {
- "name": _("Select a Bank Account"),
- "type": "ir.actions.act_window",
- "res_model": "account.bank.selection",
- "views": [[False, "form"]],
- "target": "new",
- "res_id": account_bank_selection_wizard.id,
- 'context': dict(self.env.context, swift_code=swift_code),
- }
-
- @api.model
- def _show_fetched_transactions_action(self, stmt_line_ids, duplicates_from_date):
- return self.env['account.bank.statement.line']._action_open_bank_reconciliation_widget(
- extra_domain=[('id', 'in', stmt_line_ids.ids)],
- name=_('Fetched Transactions'),
- **({'default_context': {'duplicates_from_date': duplicates_from_date}} if duplicates_from_date else {}),
- )
-
- def _get_connection_state_details(self, journal):
- self.ensure_one()
- if self.connection_state_details and self.connection_state_details.get(str(journal.id)):
- # We have to check that we have a key and a right value for this journal
- # Because if we have an empty dict, the JS part will handle it as a Proxy object.
- # To avoid that, we checked if we have a key in the dict and if the value is truthy.
- return self.connection_state_details[str(journal.id)]
- return None
-
- def _pop_connection_state_details(self, journal):
- self.ensure_one()
- if journal_connection_state_details := self._get_connection_state_details(journal):
- self._set_connection_state_details(journal, {})
- return journal_connection_state_details
- return None
-
- def _set_connection_state_details(self, journal, connection_state_details):
- self.ensure_one()
- existing_connection_state_details = self.connection_state_details or {}
- self.connection_state_details = {
- **existing_connection_state_details,
- str(journal.id): connection_state_details,
- }
-
- def _notify_connection_update(self, journal, connection_state_details):
- """ The aim of this function is saving the last connection state details
- (like if the status is success or in error) on the account.online.link
- object. At the same moment, we're sending a websocket message to
- accounting dashboard where we return the status of the connection.
- To make sure that we don't return sensitive information, we filtered
- the connection state details to only send by websocket information
- like the connection status, how many transactions we fetched, and
- the error type. In case of an error, the function is calling rollback
- on the cursor and is committing the save on the account online link.
- It's also usefull to commit in case of error to send the websocket message.
- The commit is only called if we aren't in test mode and if the connection is
- in error.
-
- :param journal: The journal for which we want to save the connection state details.
- :param connection_state_details: The information about the status of the connection (like how many transactions fetched, ...)
- """
- self.ensure_one()
-
- connection_state_details_status = connection_state_details['status'] # We're always waiting for a status in the dict.
- if connection_state_details_status == 'error':
- # In case the connection status is in error, we roll back everything before saving the status.
- self.env.cr.rollback()
- if not (connection_state_details_status == 'success' and connection_state_details.get('nb_fetched_transactions', 0) == 0):
- self._set_connection_state_details(
- journal=journal,
- connection_state_details=connection_state_details,
- )
- self.env.ref('account.group_account_user').users._bus_send(
- 'online_sync',
- {
- 'id': journal.id,
- 'connection_state_details': {
- key: value
- for key, value in connection_state_details.items()
- if key in ('status', 'error_type', 'nb_fetched_transactions')
- },
- },
- )
- if connection_state_details_status == 'error' and not tools.config['test_enable'] and not modules.module.current_test:
- # In case the status is in error, and we aren't in test mode, we commit to save the last connection state and to send the websocket message
- self.env.cr.commit()
-
- def _handle_odoofin_redirect_exception(self, mode='link'):
- if mode == 'link':
- return self.with_context({'redirect_reconnection': True}).action_new_synchronization()
- return self.with_context({'redirect_reconnection': True})._open_iframe(mode=mode)
-
- #######################################################
- # Generic methods to contact server and handle errors #
- #######################################################
-
- @api.model
- def _get_odoofin_url(self, url):
- proxy_mode = self.env['ir.config_parameter'].sudo().get_param('odex30_account_online_sync.proxy_mode') or 'production'
- if not pattern.match(proxy_mode) and not runbot_pattern.match(proxy_mode):
- raise UserError(_('Invalid value for proxy_mode config parameter.'))
- endpoint_url = 'https://%s.odoofin.com%s' % (proxy_mode, url)
- if runbot_pattern.match(proxy_mode):
- endpoint_url = '%s%s' % (proxy_mode, url)
- return endpoint_url
-
- def _fetch_odoo_fin(self, url, data=None, ignore_status=False):
- """
- Method used to fetch data from the Odoo Fin proxy.
- :param url: Proxy's URL end point.
- :param data: HTTP data request.
- :return: A dict containing all data.
- """
- if not data:
- data = {}
- if self.state == 'disconnected' and not ignore_status:
- raise UserError(_('Please reconnect your online account.'))
- if not url.startswith('/'):
- raise UserError(_('Invalid URL'))
-
- timeout = int(self.env['ir.config_parameter'].sudo().get_param('odex30_account_online_sync.request_timeout')) or 60
- endpoint_url = self._get_odoofin_url(url)
- cron = self.env.context.get('cron', False)
- data['utils'] = {
- 'request_timeout': timeout,
- 'lang': get_lang(self.env).code,
- 'server_version': odoo.release.serie,
- 'db_uuid': self.env['ir.config_parameter'].sudo().get_param('database.uuid'),
- 'cron': cron,
- }
- if request:
- # many banking institutions require the end-user IP/user_agent for traceability
- # of client-initiated actions. It won't be stored on odoofin side.
- data['utils']['psu_info'] = {
- 'ip': request.httprequest.remote_addr,
- 'user_agent': request.httprequest.user_agent.string,
- }
-
- try:
- # We have to use sudo to pass record as some fields are protected from read for common users.
- resp = requests.post(url=endpoint_url, json=data, timeout=timeout, auth=OdooFinAuth(record=self.sudo()))
- resp_json = resp.json()
- return self._handle_response(resp_json, url, data, ignore_status)
- except (Timeout, ConnectionError, RequestException, ValueError):
- _logger.warning('synchronization error')
- raise UserError(
- _("The online synchronization service is not available at the moment. "
- "Please try again later."))
-
- def _handle_response(self, resp_json, url, data, ignore_status=False):
- # Response is a json-rpc response, therefore data is encapsulated inside error in case of error
- # and inside result in case of success.
- if not resp_json.get('error'):
- result = resp_json.get('result')
- state = result.get('odoofin_state') or False
- message = result.get('display_message') or False
- subject = message and _('Message') or False
- self._log_information(state=state, message=message, subject=subject)
- if result.get('provider_data'):
- # Provider_data is extremely important and must be saved as soon as we received it
- # as it contains encrypted credentials from external provider and if we loose them we
- # loose access to the bank account, As it is possible that provider_data
- # are received during a transaction containing multiple calls to the proxy, we ensure
- # that provider_data is committed in database as soon as we received it.
- self.provider_data = result.get('provider_data')
- self.env.cr.commit()
- return result
- else:
- error = resp_json.get('error')
- # Not considered as error
- if error.get('code') == 101: # access token expired, not an error
- self._get_access_token()
- return self._fetch_odoo_fin(url, data, ignore_status)
- elif error.get('code') == 102: # refresh token expired, not an error
- self._get_refresh_token()
- self._get_access_token()
- # We need to commit here because if we got a new refresh token, and a new access token
- # It means that the token is active on the proxy and any further call resulting in an
- # error would lose the new refresh_token hence blocking the account ad vitam eternam
- self.env.cr.commit()
- if self.journal_ids: # We can't do it unless we already have a journal
- self._update_connection_status()
- return self._fetch_odoo_fin(url, data, ignore_status)
- elif error.get('code') == 300: # redirect, not an error
- raise OdooFinRedirectException(mode=error.get('data', {}).get('mode', 'link'))
- # If we are in the process of deleting the record ignore code 100 (invalid signature), 104 (account deleted)
- # 106 (provider_data corrupted) and allow user to delete his record from this side.
- elif error.get('code') in (100, 104, 106) and self.env.context.get('delete_sync'):
- return {'delete': True}
- # Log and raise error
- error_details = error.get('data')
- subject = error.get('message')
- message = error_details.get('message')
- state = error_details.get('odoofin_state') or 'error'
- ctx = self.env.context.copy()
- ctx['error_reference'] = error_details.get('error_reference')
- ctx['provider_type'] = error_details.get('provider_type')
- ctx['redirect_warning_url'] = error_details.get('redirect_warning_url')
-
- self.with_context(ctx)._log_information(state=state, subject=subject, message=message, reset_tx=True)
-
- def _log_information(self, state, subject=None, message=None, reset_tx=False):
- # If the reset_tx flag is passed, it means that we have an error, and we want to log it on the record
- # and then raise the error to the end user. To do that we first roll back the current transaction,
- # then we write the error on the record, we commit those changes, and finally we raise the error.
- if reset_tx:
- self.env.cr.rollback()
- try:
- # if state is disconnected, and new state is error: ignore it
- if state == 'error' and self.state == 'disconnected':
- state = 'disconnected'
- if state and self.state != state:
- self.write({'state': state})
- if state in ('error', 'disconnected'):
- self.account_online_account_ids.fetching_status = 'done'
- if reset_tx:
- context = self.env.context
- button_label = url = None
- if subject and message:
- message_post = message
- error_reference = context.get('error_reference')
- provider = context.get('provider_type')
- odoo_help_description = f'''ClientID: {self.client_id}\nInstitution: {self.name}\nError Reference: {error_reference}\nError Message: {message_post}\n'''
- odoo_help_summary = f'Bank sync error ref: {error_reference} - Provider: {provider} - Client ID: {self.client_id}'
- if context.get('redirect_warning_url'):
- if context['redirect_warning_url'] == 'odoo_support':
- url_params = urllib.parse.urlencode({'stage': 'bank_sync', 'summary': odoo_help_summary, 'description': odoo_help_description[:1500]})
- url = f'https://www.odoo.com/help?{url_params}'
- message += _("\n\nIf you've already opened a ticket for this issue, don't report it again: a support agent will contact you shortly.")
- message_post = Markup('%s %s %s') % (message, _("You can contact Odoo support"), url, _("Here"))
- button_label = _('Report issue')
- else:
- url = "https://www.odoo.com/documentation/18.0/applications/finance/accounting/bank/bank_synchronization.html#faq"
- message_post = Markup('%s %s %s') % (message_post, _("Check the documentation"), url, _("Here"))
- button_label = _('Check the documentation')
- self.message_post(body=message_post, subject=subject)
- # In case of reset_tx, we commit the changes in order to have the message post saved
- self.env.cr.commit()
- # and then raise either a redirectWarning error so that customer can easily open an issue with Odoo,
- # or eventually bring the user to the documentation if there's no need to contact the support.
- if url:
- action_id = {
- "type": "ir.actions.act_url",
- "url": url,
- }
- raise RedirectWarning(message, action_id, button_label) #pylint: disable=E0601
- # either a userError if there's no need to bother the support, or link to the doc.
- raise UserError(message)
- except (CacheMiss, MissingError):
- # This exception can happen if record was created and rollbacked due to error in same transaction
- # Therefore it is not possible to log information on it, in this case we just ignore it.
- pass
-
- ###############
- # API methods #
- ###############
-
- def _get_access_token(self):
- for link in self:
- resp_json = link._fetch_odoo_fin('/proxy/v1/get_access_token', ignore_status=True)
- link.access_token = resp_json.get('access_token', False)
-
- def _get_refresh_token(self):
- # Use sudo as refresh_token field is not accessible to most user
- for link in self.sudo():
- resp_json = link._fetch_odoo_fin('/proxy/v1/renew_token', ignore_status=True)
- link.refresh_token = resp_json.get('refresh_token', False)
-
- def unlink(self):
- to_unlink = self.env['account.online.link']
- for link in self:
- try:
- resp_json = link.with_context(delete_sync=True)._fetch_odoo_fin('/proxy/v1/delete_user', data={'provider_data': link.provider_data}, ignore_status=True) # delete proxy user
- if resp_json.get('delete', True) is True:
- to_unlink += link
- except OdooFinRedirectException:
- # Can happen that this call returns a redirect in mode link, in which case we delete the record
- to_unlink += link
- continue
- except (UserError, RedirectWarning):
- to_unlink += link
- continue
- result = super(AccountOnlineLink, to_unlink).unlink()
- self.env['account.journal']._toggle_asynchronous_fetching_cron()
- return result
-
- def _fetch_accounts(self, online_identifier=False):
- self.ensure_one()
- if online_identifier:
- matching_account = self.account_online_account_ids.filtered(lambda l: l.online_identifier == online_identifier)
- # Ignore account that is already there and linked to a journal as there is no need to fetch information for that one
- if matching_account and matching_account.journal_ids:
- return matching_account
- # If we have the account locally but didn't link it to a journal yet, delete it first.
- # This way, we'll get the information back from the proxy with updated balances. Avoiding potential issues.
- elif matching_account and not matching_account.journal_ids:
- matching_account.unlink()
- accounts = {}
- data = {
- 'currency_code': self.company_id.currency_id.name,
- }
- swift_code = False
- while True:
- # While this is kind of a bad practice to do, it can happen that provider_data changes between
- # 2 calls, the reason is that that field contains the encrypted information needed to access the provider
- # and first call can result in an error due to the encrypted token inside provider_data being expired for example.
- # In such a case, we renew the token with the provider and send back the newly encrypted token inside provider_data
- # which result in the information having changed, henceforth why that field is passed at every loop.
- data['provider_data'] = self.provider_data
- # Retrieve information about a specific account
- if online_identifier:
- data['online_identifier'] = online_identifier
-
- resp_json = self._fetch_odoo_fin('/proxy/v1/accounts', data)
- for acc in resp_json.get('accounts', []):
- acc['account_online_link_id'] = self.id
- currency_id = self.env['res.currency'].with_context(active_test=False).search([('name', '=', acc.pop('currency_code', ''))], limit=1)
- if currency_id:
- if not currency_id.active:
- currency_id.sudo().active = True
- acc['currency_id'] = currency_id.id
- accounts[str(acc.get('online_identifier'))] = acc
- swift_code = resp_json.get('swift_code')
- if not resp_json.get('next_data'):
- break
- data['next_data'] = resp_json.get('next_data')
-
- if accounts:
- self.has_unlinked_accounts = True
- return self.env['account.online.account'].create(accounts.values()), swift_code
- return False, False
-
- def _pre_check_fetch_transactions(self):
- self.ensure_one()
- # 'limit_time_real_cron' and 'limit_time_real' default respectively to -1 and 120.
- # Manual fallbacks applied for non-POSIX systems where this key is disabled (set to None).
- limit_time = tools.config['limit_time_real_cron'] or -1
- if limit_time <= 0:
- limit_time = tools.config['limit_time_real'] or 120
- limit_time += 20 # Add 20 seconds to be sure that the process will have been killed
- # if any account is actually creating entries and last_refresh was made less than cron_limit_time ago, skip fetching
- if (self.account_online_account_ids.filtered(lambda account: account.fetching_status == 'processing') and
- self.last_refresh + relativedelta(seconds=limit_time) > fields.Datetime.now()):
- return False
- # If not in the process of importing and auto_sync is not set, skip fetching
- if (self.env.context.get('cron') and
- not self.auto_sync and
- not self.account_online_account_ids.filtered(lambda acc: acc.fetching_status in ('planned', 'waiting', 'processing'))):
- return False
- return True
-
- def _fetch_transactions(self, refresh=True, accounts=False, check_duplicates=False):
- self.ensure_one()
- # return early if condition to fetch transactions are not met
- if not self._pre_check_fetch_transactions():
- return
-
- is_cron_running = self.env.context.get('cron')
- acc = (accounts or self.account_online_account_ids).filtered('journal_ids')
- self.last_refresh = fields.Datetime.now()
- try:
- # When manually fetching, refresh must still be done in case a redirect occurs
- # however since transactions are always fetched inside a cron, in case we are manually
- # fetching, trigger the cron and redirect customer to accounting dashboard
- accounts_to_synchronize = acc
- if not is_cron_running:
- accounts_not_to_synchronize = self.env['account.online.account']
- account_to_reauth = False
- for online_account in acc:
- # Only get transactions on account linked to a journal
- if refresh and online_account.fetching_status not in ('planned', 'processing'):
- refresh_res = online_account._refresh()
- if not refresh_res['success']:
- if refresh_res['data'].get('mode') == 'updateCredentials':
- account_to_reauth = online_account
- accounts_not_to_synchronize += online_account
- continue
- online_account.fetching_status = 'waiting'
- if account_to_reauth:
- return self._open_iframe(
- mode='updateCredentials',
- include_param={
- 'account_online_identifier': account_to_reauth.online_identifier,
- },
- )
- accounts_to_synchronize = acc - accounts_not_to_synchronize
- if not accounts_to_synchronize:
- return
-
- def get_duplicates_from_date(statement_lines, journal):
- if check_duplicates and statement_lines:
- from_date = fields.Date.to_string(statement_lines.sorted('date')[0].date)
- if journal._has_duplicate_transactions(from_date):
- return from_date
-
- for online_account in accounts_to_synchronize:
- journal = online_account.journal_ids[0]
- online_account.fetching_status = 'processing'
- # Committing here so that multiple thread calling this method won't execute in parallel and import duplicates transaction
- self.env.cr.commit()
- try:
- transactions = online_account._retrieve_transactions().get('transactions', [])
- except RedirectWarning as redirect_warning:
- self._notify_connection_update(
- journal=journal,
- connection_state_details={
- 'status': 'error',
- 'error_type': 'redirect_warning',
- 'error_message': redirect_warning.args[0],
- 'action': redirect_warning.args[1],
- },
- )
- raise
- except OdooFinRedirectException as redirect_exception:
- self._notify_connection_update(
- journal=journal,
- connection_state_details={
- 'status': 'error',
- 'error_type': 'odoofin_redirect',
- 'action': self._handle_odoofin_redirect_exception(mode=redirect_exception.mode),
- },
- )
- raise
-
- sorted_transactions = sorted(transactions, key=lambda transaction: transaction['date'])
- if not is_cron_running:
- # we want to import the first 100 transaction, show them to the user
- # and import the rest asynchronously with the 'online_sync_cron_waiting_synchronization' cron
- total = sum([transaction['amount'] for transaction in transactions])
- statement_lines = self.env['account.bank.statement.line'].with_context(transactions_total=total)._online_sync_bank_statement(sorted_transactions[:100], online_account)
- online_account.fetching_status = 'planned' if len(transactions) > 100 else 'done'
- domain = None
- if statement_lines:
- domain = [('id', 'in', statement_lines.ids)]
-
- duplicates_from_date = get_duplicates_from_date(statement_lines, journal)
- return self.env['account.bank.statement.line']._action_open_bank_reconciliation_widget(
- extra_domain=domain,
- name=_('Fetched Transactions'),
- default_context={**self.env.context, 'default_journal_id': journal.id, 'duplicates_from_date': duplicates_from_date},
- )
- else:
- statement_lines = self.env['account.bank.statement.line']._online_sync_bank_statement(sorted_transactions, online_account)
- online_account.fetching_status = 'done'
- duplicates_from_date = get_duplicates_from_date(statement_lines, journal)
- self._notify_connection_update(
- journal=journal,
- connection_state_details={
- 'status': 'success',
- 'nb_fetched_transactions': len(statement_lines),
- 'action': self._show_fetched_transactions_action(statement_lines, duplicates_from_date),
- },
- )
- return
- except OdooFinRedirectException as e:
- return self._handle_odoofin_redirect_exception(mode=e.mode)
-
- def _get_consent_expiring_date(self, data=None):
- self.ensure_one()
- if not data: # Small hack to avoid breaking the stable policy
- data = self._fetch_odoo_fin('/proxy/v1/consent_expiring_date', ignore_status=True)
-
- if data.get('consent_expiring_date'):
- expiring_synchronization_date = fields.Date.to_date(data['consent_expiring_date'])
- if expiring_synchronization_date != self.expiring_synchronization_date:
- # TDE TODO: master: use generic activity mixin methods instead
- bank_sync_activity_type_id = self.env.ref('odex30_account_online_sync.bank_sync_activity_update_consent')
- account_journal_model_id = self.env['ir.model']._get_id('account.journal')
-
- # Remove old activities
- self.env['mail.activity'].search([
- ('res_id', 'in', self.journal_ids.ids),
- ('res_model_id', '=', account_journal_model_id),
- ('activity_type_id', '=', bank_sync_activity_type_id.id),
- ('date_deadline', '<=', self.expiring_synchronization_date),
- ('user_id', '=', self.env.user.id),
- ]).unlink()
-
- # Create a new activity for each journals for this synch
- self.expiring_synchronization_date = expiring_synchronization_date
- new_activity_vals = []
- for journal in self.journal_ids:
- new_activity_vals.append({
- 'res_id': journal.id,
- 'res_model_id': account_journal_model_id,
- 'date_deadline': self.expiring_synchronization_date,
- 'summary': _("Bank Synchronization: Update your consent"),
- 'note': data.get('activity_message') or '',
- 'activity_type_id': bank_sync_activity_type_id.id,
- })
- self.env['mail.activity'].create(new_activity_vals)
- elif self.expiring_synchronization_date and self.expiring_synchronization_date < fields.Date.context_today(self):
- # Avoid an infinite "expired synchro" if the provider
- # doesn't send us a new consent expiring date
- self.expiring_synchronization_date = None
-
- def _update_connection_status(self):
- self.ensure_one()
- resp_json = self._fetch_odoo_fin('/proxy/v2/connection_status', ignore_status=True)
-
- self._get_consent_expiring_date(resp_json)
-
- # Returning what we receive from Odoo Fin to allow function extension
- return resp_json
-
- def _authorize_access(self, data_access_token):
- """
- This method is used to allow an existing connection to give temporary access
- to a new connection in order to see the list of available unlinked accounts.
- We pass as parameter the list of already linked account, so that if there are
- no more accounts to link, we will receive a response telling us so and we won't
- call authorize for that connection later on.
- """
- self.ensure_one()
- data = {
- 'linked_accounts': self.account_online_account_ids.filtered('journal_ids').mapped('online_identifier'),
- 'record_access_token': data_access_token,
- }
- try:
- resp_json = self._fetch_odoo_fin('/proxy/v1/authorize_access', data)
- self.has_unlinked_accounts = resp_json.get('has_unlinked_accounts')
- except UserError:
- # We don't want to throw an error to the customer so ignore error
- pass
-
- @api.model
- def _cron_delete_unused_connection(self):
- account_online_links = self.search([
- ('write_date', '<=', fields.Datetime.now() - relativedelta(months=1)),
- ])
- for link in account_online_links:
- if not link.account_online_account_ids.filtered('journal_ids'):
- link.unlink()
-
- @api.returns('mail.message', lambda value: value.id)
- def message_post(self, **kwargs):
- """Override to log all message to the linked journal as well."""
- for journal in self.journal_ids:
- journal.message_post(**kwargs)
- return super(AccountOnlineLink, self).message_post(**kwargs)
-
- ################################
- # Callback methods from iframe #
- ################################
-
- def success(self, mode, data):
- if data:
- self.write(data)
- # Provider_data is extremely important and must be saved as soon as we received it
- # as it contains encrypted credentials from external provider and if we loose them we
- # loose access to the bank account, As it is possible that provider_data
- # are received during a transaction containing multiple calls to the proxy, we ensure
- # that provider_data is committed in database as soon as we received it.
- if data.get('provider_data'):
- self.env.cr.commit()
-
- self._update_connection_status()
- # if for some reason we just have to update the record without doing anything else, the mode will be set to 'none'
- if mode == 'none':
- return {'type': 'ir.actions.client', 'tag': 'reload'}
- try:
- method_name = '_success_%s' % mode
- method = getattr(self, method_name)
- except AttributeError:
- message = _("This version of Odoo appears to be outdated and does not support the '%s' sync mode. "
- "Installing the latest update might solve this.", mode)
- _logger.info('Online sync: %s' % (message,))
- self.env.cr.rollback()
- self._log_information(state='error', subject=_('Internal Error'), message=message, reset_tx=True)
- raise UserError(message)
- action = method()
- return action or self.env['ir.actions.act_window']._for_xml_id('account.open_account_journal_dashboard_kanban')
-
- @api.model
- def connect_existing_account(self, data):
- # extract client_id and online_identifier from data and retrieve the account detail from the connection.
- # If we have a journal in context, assign to journal, otherwise create new journal then fetch transaction
- client_id = data.get('client_id')
- online_identifier = data.get('online_identifier')
- if client_id and online_identifier:
- online_link = self.search([('client_id', '=', client_id)], limit=1)
- if not online_link:
- return {'type': 'ir.actions.client', 'tag': 'reload'}
- new_account, swift_code = online_link._fetch_accounts(online_identifier=online_identifier)
- if new_account:
- new_account._assign_journal(swift_code)
- action = online_link._fetch_transactions(accounts=new_account, check_duplicates=True)
- return action or self.env['ir.actions.act_window']._for_xml_id('account.open_account_journal_dashboard_kanban')
- raise UserError(_("The consent for the selected account has expired."))
- return {'type': 'ir.actions.client', 'tag': 'reload'}
-
- def exchange_token(self, exchange_token):
- self.ensure_one()
- # Exchange token to retrieve client_id and refresh_token from proxy account
- data = {
- 'exchange_token': exchange_token,
- 'company_id': self.env.company.id,
- 'user_id': self.env.user.id
- }
- resp_json = self._fetch_odoo_fin('/proxy/v1/exchange_token', data=data, ignore_status=True)
- # Write in sudo mode as those fields are protected from users
- self.sudo().write({
- 'client_id': resp_json.get('client_id'),
- 'refresh_token': resp_json.get('refresh_token'),
- 'access_token': resp_json.get('access_token')
- })
- return True
-
- def _success_link(self):
- self.ensure_one()
- self._log_information(state='connected')
- account_online_accounts, swift_code = self._fetch_accounts()
- if account_online_accounts and len(account_online_accounts) == 1:
- account_online_accounts._assign_journal(swift_code)
- return self._fetch_transactions(accounts=account_online_accounts, check_duplicates=True)
- return self._link_accounts_to_journals_action(swift_code)
-
- def _success_updateCredentials(self):
- self.ensure_one()
- return self._fetch_transactions(refresh=False)
-
- def _success_refreshAccounts(self):
- self.ensure_one()
- return self._fetch_transactions(refresh=False)
-
- def _success_reconnect(self):
- self.ensure_one()
- self._log_information(state='connected')
- return self._fetch_transactions(check_duplicates=True)
-
- ##################
- # action buttons #
- ##################
-
- def action_new_synchronization(self, preferred_inst=None, journal_id=False):
- # Search for an existing link that was not fully connected
- online_link = self
- if not online_link or online_link.provider_data:
- online_link = self.search([('account_online_account_ids', '=', False)], limit=1)
- # If not found, create a new one
- if not online_link or online_link.provider_data:
- online_link = self.create({})
- return online_link._open_iframe('link', preferred_institution=preferred_inst, journal_id=journal_id)
-
- def action_update_credentials(self):
- return self._open_iframe('updateCredentials')
-
- def action_fetch_transactions(self):
- self.account_online_account_ids.fetching_status = None
- action = self._fetch_transactions()
- return action or self.env['ir.actions.act_window']._for_xml_id('account.open_account_journal_dashboard_kanban')
-
- def action_reconnect_account(self):
- return self._open_iframe('reconnect')
-
- def _open_iframe(self, mode='link', include_param=None, preferred_institution=False, journal_id=False):
- self.ensure_one()
- if self.client_id and self.sudo().refresh_token:
- try:
- self._get_access_token()
- except OdooFinRedirectException:
- # Delete record and open iframe in a new one
- self.unlink()
- return self.create({})._open_iframe('link')
-
- proxy_mode = self.env['ir.config_parameter'].sudo().get_param('odex30_account_online_sync.proxy_mode') or 'production'
- country = self.env['account.journal'].browse(journal_id).company_id.account_fiscal_country_id or self.env.company.country_id
- action = {
- 'type': 'ir.actions.client',
- 'tag': 'odoo_fin_connector',
- 'id': self.id,
- 'params': {
- 'proxyMode': proxy_mode,
- 'clientId': self.client_id,
- 'accessToken': self.access_token,
- 'mode': mode,
- 'includeParam': {
- 'lang': get_lang(self.env).code,
- 'countryCode': country.code,
- 'countryName': country.display_name,
- 'redirect_reconnection': self.env.context.get('redirect_reconnection'),
- 'serverVersion': odoo.release.serie,
- 'mfa_type': self.env.user._mfa_type(),
- }
- },
- 'context': {
- 'dialog_size': 'medium',
- },
- }
- if self.provider_data:
- action['params']['providerData'] = self.provider_data
- if preferred_institution:
- action['params']['includeParam']['clickedInstitution'] = preferred_institution
- if journal_id:
- action['context']['active_model'] = 'account.journal'
- action['context']['active_id'] = journal_id
-
- if mode == 'link':
- user_email = self.env.user.email or self.env.ref('base.user_admin').email or '' # Necessary for some providers onboarding
- action['params']['includeParam']['dbUuid'] = self.env['ir.config_parameter'].sudo().get_param('database.uuid')
- action['params']['includeParam']['userEmail'] = user_email
- # Compute a hash of a random string for each connection in success
- existing_link = self.search([('state', '!=', 'disconnected'), ('has_unlinked_accounts', '=', True)])
- if existing_link:
- record_access_token = base64.b64encode(uuid.uuid4().bytes).decode('utf-8')
- for link in existing_link:
- link._authorize_access(record_access_token)
- action['params']['includeParam']['recordAccessToken'] = record_access_token
-
- if include_param:
- action['params']['includeParam'].update(include_param)
- return action
diff --git a/dev_odex30_accounting/odex30_account_online_sync/models/bank_rec_widget.py b/dev_odex30_accounting/odex30_account_online_sync/models/bank_rec_widget.py
deleted file mode 100644
index 273c43f..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/models/bank_rec_widget.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from odoo import models
-
-
-class BankRecWidget(models.Model):
- _inherit = 'bank.rec.widget'
-
- def _action_validate(self):
- # EXTENDS account_accountant
- super()._action_validate()
- line = self.st_line_id
- if line.partner_id and line.online_partner_information:
- # write value for account and merchant on partner only if partner has no value,
- # in case value are different write False
- value_merchant = line.partner_id.online_partner_information or line.online_partner_information
- value_merchant = value_merchant if value_merchant == line.online_partner_information else False
- line.partner_id.online_partner_information = value_merchant
diff --git a/dev_odex30_accounting/odex30_account_online_sync/models/company.py b/dev_odex30_accounting/odex30_account_online_sync/models/company.py
deleted file mode 100644
index 84feee2..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/models/company.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from odoo import api, models
-
-
-class ResCompany(models.Model):
- _inherit = "res.company"
-
- @api.model
- def setting_init_bank_account_action(self):
- """
- Override the "setting_init_bank_account_action" in accounting menu
- and change the flow for the "Add a bank account" menu item in dashboard.
- """
- return self.env['account.online.link'].action_new_synchronization()
diff --git a/dev_odex30_accounting/odex30_account_online_sync/models/mail_activity_type.py b/dev_odex30_accounting/odex30_account_online_sync/models/mail_activity_type.py
deleted file mode 100644
index 93e61bf..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/models/mail_activity_type.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from odoo import api, models
-
-
-class MailActivityType(models.Model):
- _inherit = "mail.activity.type"
-
- @api.model
- def _get_model_info_by_xmlid(self):
- info = super()._get_model_info_by_xmlid()
- info['odex30_account_online_sync.bank_sync_activity_update_consent'] = {
- 'res_model': 'account.journal',
- 'unlink': False,
- }
- return info
diff --git a/dev_odex30_accounting/odex30_account_online_sync/models/odoofin_auth.py b/dev_odex30_accounting/odex30_account_online_sync/models/odoofin_auth.py
deleted file mode 100644
index 19288f6..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/models/odoofin_auth.py
+++ /dev/null
@@ -1,46 +0,0 @@
-import base64
-import hashlib
-import hmac
-import json
-import requests
-import time
-import werkzeug.urls
-
-
-class OdooFinAuth(requests.auth.AuthBase):
-
- def __init__(self, record=None):
- self.access_token = record and record.access_token or False
- self.refresh_token = record and record.refresh_token or False
- self.client_id = record and record.client_id or False
-
- def __call__(self, request):
- # We don't sign request that still don't have a client_id/refresh_token
- if not self.client_id or not self.refresh_token:
- return request
- # craft the message (timestamp|url path|client_id|access_token|query params|body content)
- msg_timestamp = int(time.time())
- parsed_url = werkzeug.urls.url_parse(request.path_url)
-
- body = request.body
- if isinstance(body, bytes):
- body = body.decode('utf-8')
- body = json.loads(body)
-
- message = '%s|%s|%s|%s|%s|%s' % (
- msg_timestamp, # timestamp
- parsed_url.path, # url path
- self.client_id,
- self.access_token,
- json.dumps(werkzeug.urls.url_decode(parsed_url.query), sort_keys=True), # url query params sorted by key
- json.dumps(body, sort_keys=True)) # http request body
-
- h = hmac.new(base64.b64decode(self.refresh_token), message.encode('utf-8'), digestmod=hashlib.sha256)
-
- request.headers.update({
- 'odoofin-client-id': self.client_id,
- 'odoofin-access-token': self.access_token,
- 'odoofin-signature': base64.b64encode(h.digest()),
- 'odoofin-timestamp': msg_timestamp,
- })
- return request
diff --git a/dev_odex30_accounting/odex30_account_online_sync/models/partner.py b/dev_odex30_accounting/odex30_account_online_sync/models/partner.py
deleted file mode 100644
index 7cb6bc9..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/models/partner.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from odoo import models, fields
-
-
-class ResPartner(models.Model):
- _inherit = 'res.partner'
-
- online_partner_information = fields.Char(readonly=True)
diff --git a/dev_odex30_accounting/odex30_account_online_sync/security/account_online_sync_security.xml b/dev_odex30_accounting/odex30_account_online_sync/security/account_online_sync_security.xml
deleted file mode 100644
index 9a4c704..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/security/account_online_sync_security.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
- Account online link company rule
-
-
- [('company_id', 'parent_of', company_ids)]
-
-
- Online account company rule
-
-
- [('account_online_link_id.company_id','parent_of', company_ids)]
-
-
diff --git a/dev_odex30_accounting/odex30_account_online_sync/security/ir.model.access.csv b/dev_odex30_accounting/odex30_account_online_sync/security/ir.model.access.csv
deleted file mode 100644
index adca78d..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/security/ir.model.access.csv
+++ /dev/null
@@ -1,12 +0,0 @@
-id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
-access_account_online_link_id,access_account_online_link_id,model_account_online_link,account.group_account_basic,1,1,1,0
-access_account_online_link_id_readonly,access_account_online_link_id_readonly,model_account_online_link,account.group_account_readonly,1,0,0,0
-access_account_online_link_id_manager,access_account_online_link_id manager,model_account_online_link,account.group_account_manager,1,1,1,1
-access_account_online_account_id,access_account_online_account_id,model_account_online_account,account.group_account_basic,1,1,1,0
-access_account_online_account_id_readonly,access_account_online_account_id_readonly,model_account_online_account,account.group_account_readonly,1,0,0,0
-access_account_online_account_id_manager,access_account_online_account_id manager,model_account_online_account,account.group_account_manager,1,1,1,1
-access_account_bank_selection_manager,access.account.bank.selection manager,model_account_bank_selection,account.group_account_manager,1,1,1,1
-access_account_bank_selection,access.account.bank.selection basic,model_account_bank_selection,account.group_account_basic,1,1,1,0
-access_account_bank_statement_line_transient,access_account_bank_statement_line_transient,model_account_bank_statement_line_transient,account.group_account_manager,1,1,1,1
-access_account_missing_transaction_wizard,access_account_missing_transaction_wizard,model_account_missing_transaction_wizard,account.group_account_manager,1,1,1,1
-access_account_duplicate_transaction_wizard,access_account_duplicate_transaction_wizard,model_account_duplicate_transaction_wizard,account.group_account_user,1,1,1,0
diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transaction_form.js b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transaction_form.js
deleted file mode 100644
index 403bbe4..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transaction_form.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import { formView } from "@web/views/form/form_view";
-import { FormController } from "@web/views/form/form_controller";
-import { registry } from "@web/core/registry";
-import { useCheckDuplicateService } from "./account_duplicate_transaction_hook";
-
-export class AccountDuplicateTransactionsFormController extends FormController {
- setup() {
- super.setup();
- this.duplicateCheckService = useCheckDuplicateService();
- }
-
- async beforeExecuteActionButton(clickParams) {
- if (clickParams.name === "delete_selected_transactions") {
- const selected = this.duplicateCheckService.selectedLines;
- if (selected.size) {
- await this.orm.call(
- "account.bank.statement.line",
- "unlink",
- [Array.from(selected)],
- );
- this.env.services.action.doAction({type: 'ir.actions.client', tag: 'reload'});
- }
- return false;
- }
- return super.beforeExecuteActionButton(...arguments);
- }
-
- get cogMenuProps() {
- const props = super.cogMenuProps;
- props.items.action = [];
- return props;
- }
-}
-
-export const form = { ...formView, Controller: AccountDuplicateTransactionsFormController };
-
-registry.category("views").add("account_duplicate_transactions_form", form);
diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transaction_hook.js b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transaction_hook.js
deleted file mode 100644
index 64b1604..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transaction_hook.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import { useService } from "@web/core/utils/hooks";
-import { useState } from "@odoo/owl";
-
-export function useCheckDuplicateService() {
- return useState(useService("odex30_account_online_sync.duplicate_check_service"));
-}
diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transaction_service.js b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transaction_service.js
deleted file mode 100644
index b5571d2..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transaction_service.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import { registry } from "@web/core/registry";
-
-class AccountDuplicateTransactionsServiceModel {
- constructor() {
- this.selectedLines = new Set();
- }
-
- updateLIne(selected, id) {
- this.selectedLines[selected ? "add" : "delete"](id);
- }
-}
-
-const duplicateCheckService = {
- start(env, services) {
- return new AccountDuplicateTransactionsServiceModel();
- },
-};
-
-registry
- .category("services")
- .add("odex30_account_online_sync.duplicate_check_service", duplicateCheckService);
diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transactions_x2many.js b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transactions_x2many.js
deleted file mode 100644
index d1d2a8e..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transactions_x2many.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import { onMounted } from "@odoo/owl";
-import { registry } from "@web/core/registry";
-import { ListRenderer } from "@web/views/list/list_renderer";
-import { X2ManyField, x2ManyField } from "@web/views/fields/x2many/x2many_field";
-import { useCheckDuplicateService } from "./account_duplicate_transaction_hook";
-
-export class AccountDuplicateTransactionsListRenderer extends ListRenderer {
- static template = "odex30_account_online_sync.AccountDuplicateTransactionsListRenderer";
- static recordRowTemplate = "odex30_account_online_sync.AccountDuplicateTransactionsRecordRow";
-
- setup() {
- super.setup();
- this.duplicateCheckService = useCheckDuplicateService();
-
- onMounted(() => {
- this.deleteButton = document.querySelector('button[name="delete_selected_transactions"]');
- this.deleteButton.disabled = true;
- });
- }
-
- toggleRecordSelection(selected, record) {
- this.duplicateCheckService.updateLIne(selected, record.data.id);
- this.deleteButton.disabled = this.duplicateCheckService.selectedLines.size === 0;
- }
-
- get hasSelectors() {
- return true;
- }
-
- getRowClass(record) {
- let classes = super.getRowClass(record);
- const firstIdsInGroup = this.env.model.root.data.first_ids_in_group;
- if (firstIdsInGroup instanceof Array && firstIdsInGroup.includes(record.data.id)) {
- classes += " account_duplicate_transactions_lines_list_x2many_group_line";
- }
- return classes;
- }
-}
-
-export class AccountDuplicateTransactionsLinesListX2ManyField extends X2ManyField {
- static components = {
- ...X2ManyField.components,
- ListRenderer: AccountDuplicateTransactionsListRenderer,
- };
-}
-
-registry.category("fields").add("account_duplicate_transactions_lines_list_x2many", {
- ...x2ManyField,
- component: AccountDuplicateTransactionsLinesListX2ManyField,
-});
diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transactions_x2many.scss b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transactions_x2many.scss
deleted file mode 100644
index 4b147a5..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transactions_x2many.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.account_duplicate_transactions_lines_list_x2many_group_line {
- border-top-width: thick;
-}
diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transactions_x2many.xml b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transactions_x2many.xml
deleted file mode 100644
index 52f66ff..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transactions_x2many.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/js/odoo_fin_connector.js b/dev_odex30_accounting/odex30_account_online_sync/static/src/js/odoo_fin_connector.js
deleted file mode 100644
index b8d18ca..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/static/src/js/odoo_fin_connector.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/** @odoo-module **/
-
-import { registry } from "@web/core/registry";
-import { loadJS } from "@web/core/assets";
-import { cookie } from "@web/core/browser/cookie";
-import { markup } from "@odoo/owl";
-const actionRegistry = registry.category('actions');
-/* global OdooFin */
-
-function OdooFinConnector(parent, action) {
- const orm = parent.services.orm;
- const actionService = parent.services.action;
- const notificationService = parent.services.notification;
- const debugMode = parent.debug;
-
- const id = action.id;
- action.params.colorScheme = cookie.get("color_scheme");
- let mode = action.params.mode || 'link';
- // Ensure that the proxyMode is valid
- const modeRegexp = /^[a-z0-9-_]+$/;
- const runbotRegexp = /^https:\/\/[a-z0-9-_]+\.[a-z0-9-_]+\.odoo\.com$/;
- if (!modeRegexp.test(action.params.proxyMode) && !runbotRegexp.test(action.params.proxyMode)) {
- return;
- }
- let url = 'https://' + action.params.proxyMode + '.odoofin.com/proxy/v1/odoofin_link';
- if (runbotRegexp.test(action.params.proxyMode)) {
- url = action.params.proxyMode + '/proxy/v1/odoofin_link';
- }
- let actionResult = false;
-
- loadJS(url)
- .then(function () {
- // Create and open the iframe
- const params = {
- data: action.params,
- proxyMode: action.params.proxyMode,
- onEvent: async function (event, data) {
- switch (event) {
- case 'close':
- return;
- case 'reload':
- return actionService.doAction({type: 'ir.actions.client', tag: 'reload'});
- case 'notification':
- notificationService.add(data.message, data);
- break;
- case 'exchange_token':
- await orm.call('account.online.link', 'exchange_token',
- [[id], data], {context: action.context});
- break;
- case 'success':
- mode = data.mode || mode;
- actionResult = await orm.call('account.online.link', 'success', [[id], mode, data], {context: action.context});
- actionResult.help = markup(actionResult.help)
- return actionService.doAction(actionResult);
- case 'connect_existing_account':
- actionResult = await orm.call('account.online.link', 'connect_existing_account', [data], {context: action.context});
- actionResult.help = markup(actionResult.help)
- return actionService.doAction(actionResult);
- default:
- return;
- }
- },
- onAddBank: async function (data) {
- // If the user doesn't find his bank
- actionResult = await orm.call(
- "account.online.link",
- "create_new_bank_account_action",
- [[id], data],
- { context: action.context }
- );
- return actionService.doAction(actionResult);
- }
- };
- // propagate parent debug mode to iframe
- if (typeof debugMode !== "undefined" && debugMode) {
- params.data["debug"] = debugMode;
- }
- OdooFin.create(params);
- OdooFin.open();
- });
- return;
-}
-
-actionRegistry.add('odoo_fin_connector', OdooFinConnector);
-
-export default OdooFinConnector;
diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/js/online_sync_portal.js b/dev_odex30_accounting/odex30_account_online_sync/static/src/js/online_sync_portal.js
deleted file mode 100644
index 8dde787..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/static/src/js/online_sync_portal.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/** @odoo-module **/
-
- import publicWidget from "@web/legacy/js/public/public_widget";
- import { loadJS } from "@web/core/assets";
- /* global OdooFin */
-
- publicWidget.registry.OnlineSyncPortal = publicWidget.Widget.extend({
- selector: '.oe_online_sync',
- events: Object.assign({}, {
- 'click #renew_consent_button': '_onRenewConsent',
- }),
-
- OdooFinConnector: function (parent, action) {
- // Ensure that the proxyMode is valid
- const modeRegexp = /^[a-z0-9-_]+$/i;
- if (!modeRegexp.test(action.params.proxyMode)) {
- return;
- }
- const url = 'https://' + action.params.proxyMode + '.odoofin.com/proxy/v1/odoofin_link';
-
- loadJS(url)
- .then(() => {
- // Create and open the iframe
- const params = {
- data: action.params,
- proxyMode: action.params.proxyMode,
- onEvent: function (event, data) {
- switch (event) {
- case 'success':
- const processUrl = window.location.pathname + '/complete' + window.location.search;
- $('.js_reconnect').toggleClass('d-none');
- $.post(processUrl, {csrf_token: odoo.csrf_token});
- default:
- return;
- }
- },
- };
- OdooFin.create(params);
- OdooFin.open();
- });
- return;
- },
-
- /**
- * @private
- * @param {Event} ev
- */
- _onRenewConsent: async function (ev) {
- ev.preventDefault();
- const action = JSON.parse($(ev.currentTarget).attr('iframe-params'));
- return this.OdooFinConnector(this, action);
- },
- });
-
- export default {
- OnlineSyncPortal: publicWidget.registry.OnlineSyncPortal,
- };
diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/tests/helpers/model_definitions_setup.js b/dev_odex30_accounting/odex30_account_online_sync/static/tests/helpers/model_definitions_setup.js
deleted file mode 100644
index 6434c51..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/static/tests/helpers/model_definitions_setup.js
+++ /dev/null
@@ -1,5 +0,0 @@
-/** @odoo-module **/
-
-import { addModelNamesToFetch } from '@bus/../tests/helpers/model_definitions_helpers';
-
-addModelNamesToFetch(["account.online.link", "account.online.account", "account.bank.selection"]);
diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/tests/online_account_radio_test.js b/dev_odex30_accounting/odex30_account_online_sync/static/tests/online_account_radio_test.js
deleted file mode 100644
index 3be8ad5..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/static/tests/online_account_radio_test.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/* @odoo-module */
-
-import { startServer } from "@bus/../tests/helpers/mock_python_environment";
-
-import { openFormView, start } from "@mail/../tests/helpers/test_utils";
-
-import { click, contains } from "@web/../tests/utils";
-
-QUnit.module("Views", {}, function () {
- QUnit.module("AccountOnlineSynchronizationAccountRadio");
-
- QUnit.test("can be rendered", async () => {
- const pyEnv = await startServer();
- const onlineLink = pyEnv["account.online.link"].create([
- {
- state: "connected",
- name: "Fake Bank",
- },
- ]);
- pyEnv["account.online.account"].create([
- {
- name: "account_1",
- online_identifier: "abcd",
- balance: 10.0,
- account_number: "account_number_1",
- account_online_link_id: onlineLink,
- },
- {
- name: "account_2",
- online_identifier: "efgh",
- balance: 20.0,
- account_number: "account_number_2",
- account_online_link_id: onlineLink,
- },
- ]);
- const bankSelection = pyEnv["account.bank.selection"].create([
- {
- account_online_link_id: onlineLink,
- },
- ]);
-
- const views = {
- "account.bank.selection,false,form": ``,
- };
- await start({
- serverData: { views },
- mockRPC: function (route, args) {
- if (
- route === "/web/dataset/call_kw/account.online.account/get_formatted_balances"
- ) {
- return {
- 1: ["$ 10.0", 10.0],
- 2: ["$ 20.0", 20.0],
- };
- }
- },
- });
- await openFormView("account.bank.selection", bankSelection);
- await contains(".o_radio_item", { count: 2 });
- await contains(":nth-child(1 of .o_radio_item)", {
- contains: [
- ["p", { text: "$ 10.0" }],
- ["label", { text: "account_1" }],
- [".o_radio_input:checked"],
- ],
- });
- await contains(":nth-child(2 of .o_radio_item)", {
- contains: [
- ["p", { text: "$ 20.0" }],
- ["label", { text: "account_2" }],
- [".o_radio_input:not(:checked)"],
- ],
- });
- await click(":nth-child(2 of .o_radio_item) .o_radio_input");
- await contains(":nth-child(1 of .o_radio_item) .o_radio_input:not(:checked)");
- await contains(":nth-child(2 of .o_radio_item) .o_radio_input:checked");
- });
-});
diff --git a/dev_odex30_accounting/odex30_account_online_sync/tests/__init__.py b/dev_odex30_accounting/odex30_account_online_sync/tests/__init__.py
deleted file mode 100644
index eacefb9..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/tests/__init__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-# -*- encoding: utf-8 -*-
-
-from . import common
-from . import test_account_online_account
-from . import test_online_sync_creation_statement
-from . import test_account_missing_transactions_wizard
-from . import test_online_sync_branch_companies
diff --git a/dev_odex30_accounting/odex30_account_online_sync/tests/common.py b/dev_odex30_accounting/odex30_account_online_sync/tests/common.py
deleted file mode 100644
index ffcbc53..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/tests/common.py
+++ /dev/null
@@ -1,110 +0,0 @@
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from odoo import Command, fields
-from odoo.addons.account.tests.common import AccountTestInvoicingCommon
-from odoo.tests import tagged
-from unittest.mock import MagicMock
-
-
-@tagged('post_install', '-at_install')
-class AccountOnlineSynchronizationCommon(AccountTestInvoicingCommon):
-
- @classmethod
- def setUpClass(cls):
- super().setUpClass()
- cls.other_currency = cls.setup_other_currency('EUR')
-
- cls.euro_bank_journal = cls.env['account.journal'].create({
- 'name': 'Euro Bank Journal',
- 'type': 'bank',
- 'code': 'EURB',
- 'currency_id': cls.other_currency.id,
- })
- cls.account_online_link = cls.env['account.online.link'].create({
- 'name': 'Test Bank',
- 'client_id': 'client_id_1',
- 'refresh_token': 'refresh_token',
- 'access_token': 'access_token',
- })
- cls.account_online_account = cls.env['account.online.account'].create({
- 'name': 'MyBankAccount',
- 'account_online_link_id': cls.account_online_link.id,
- 'journal_ids': [Command.set(cls.euro_bank_journal.id)]
- })
- cls.BankStatementLine = cls.env['account.bank.statement.line']
-
- def setUp(self):
- super().setUp()
- self.transaction_id = 1
- self.account_online_account.balance = 0.0
-
- def _create_one_online_transaction(self, transaction_identifier=None, date=None, payment_ref=None, amount=10.0, partner_name=None, foreign_currency_code=None, amount_currency=8.0):
- """ This method allows to create an online transaction granularly
-
- :param transaction_identifier: Online identifier of the transaction, by default transaction_id from the
- setUp. If used, transaction_id is not incremented.
- :param date: Date of the transaction, by default the date of today
- :param payment_ref: Label of the transaction
- :param amount: Amount of the transaction, by default equals 10.0
- :param foreign_currency_code: Code of transaction's foreign currency
- :param amount_currency: Amount of transaction in foreign currency, update transaction only if foreign_currency_code is given, by default equals 8.0
- :return: A dictionnary representing an online transaction (not formatted)
- """
- transaction_identifier = transaction_identifier if transaction_identifier is not None else self.transaction_id
- if date:
- date = date if isinstance(date, str) else fields.Date.to_string(date)
- else:
- date = fields.Date.to_string(fields.Date.today())
-
- payment_ref = payment_ref or f'transaction_{transaction_identifier}'
- transaction = {
- 'online_transaction_identifier': transaction_identifier,
- 'date': date,
- 'payment_ref': payment_ref,
- 'amount': amount,
- 'partner_name': partner_name,
- }
- if foreign_currency_code:
- transaction.update({
- 'foreign_currency_code': foreign_currency_code,
- 'amount_currency': amount_currency
- })
- return transaction
-
- def _create_online_transactions(self, dates):
- """ This method returns a list of transactions with the
- given dates.
- All amounts equals 10.0
-
- :param dates: A list of dates, one transaction is created for each given date.
- :return: A formatted list of transactions
- """
- transactions = []
- for date in dates:
- transactions.append(self._create_one_online_transaction(date=date))
- self.transaction_id += 1
- return self.account_online_account._format_transactions(transactions)
-
- def _mock_odoofin_response(self, data=None):
- if not data:
- data = {}
- mock_response = MagicMock()
- mock_response.status_code = 200
- mock_response.json.return_value = {
- 'result': data,
- }
- return mock_response
-
- def _mock_odoofin_error_response(self, code=200, message='Default', data=None):
- if not data:
- data = {}
- mock_response = MagicMock()
- mock_response.status_code = 200
- mock_response.json.return_value = {
- 'error': {
- 'code': code,
- 'message': message,
- 'data': data,
- },
- }
- return mock_response
diff --git a/dev_odex30_accounting/odex30_account_online_sync/tests/test_account_missing_transactions_wizard.py b/dev_odex30_accounting/odex30_account_online_sync/tests/test_account_missing_transactions_wizard.py
deleted file mode 100644
index 1d9989f..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/tests/test_account_missing_transactions_wizard.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from odoo import fields
-from odoo.addons.odex30_account_online_sync.tests.common import AccountOnlineSynchronizationCommon
-from odoo.tests import tagged
-from unittest.mock import patch
-
-
-@tagged('post_install', '-at_install')
-class TestAccountMissingTransactionsWizard(AccountOnlineSynchronizationCommon):
- """ Tests the account journal missing transactions wizard. """
-
- @patch('odoo.addons.odex30_account_online_sync.models.account_online.AccountOnlineLink._fetch_odoo_fin')
- def test_fetch_missing_transaction(self, patched_fetch_odoofin):
- self.account_online_link.state = 'connected'
- patched_fetch_odoofin.side_effect = [{
- 'transactions': [
- self._create_one_online_transaction(transaction_identifier='ABCD01', date='2023-07-06', foreign_currency_code='EGP', amount_currency=8.0),
- ],
- 'pendings': [
- self._create_one_online_transaction(transaction_identifier='ABCD02_pending', date='2023-07-25', foreign_currency_code='GBP', amount_currency=8.0),
- ]
- }]
- start_date = fields.Date.from_string('2023-07-01')
- wizard = self.env['account.missing.transaction.wizard'].new({
- 'date': start_date,
- 'journal_id': self.euro_bank_journal.id,
- })
-
- action = wizard.action_fetch_missing_transaction()
- transient_transactions = self.env['account.bank.statement.line.transient'].search(domain=action['domain'])
- egp_currency = self.env['res.currency'].search([('name', '=', 'EGP')])
- gbp_currency = self.env['res.currency'].search([('name', '=', 'GBP')])
-
- self.assertEqual(2, len(transient_transactions))
- # Posted Transaction
- self.assertEqual(transient_transactions[0]['online_transaction_identifier'], 'ABCD01')
- self.assertEqual(transient_transactions[0]['date'], fields.Date.from_string('2023-07-06'))
- self.assertEqual(transient_transactions[0]['state'], 'posted')
- self.assertEqual(transient_transactions[0]['foreign_currency_id'], egp_currency)
- self.assertEqual(transient_transactions[0]['amount_currency'], 8.0)
- # Pending Transaction
- self.assertEqual(transient_transactions[1]['online_transaction_identifier'], 'ABCD02_pending')
- self.assertEqual(transient_transactions[1]['date'], fields.Date.from_string('2023-07-25'))
- self.assertEqual(transient_transactions[1]['state'], 'pending')
- self.assertEqual(transient_transactions[1]['foreign_currency_id'], gbp_currency)
- self.assertEqual(transient_transactions[1]['amount_currency'], 8.0)
diff --git a/dev_odex30_accounting/odex30_account_online_sync/tests/test_account_online_account.py b/dev_odex30_accounting/odex30_account_online_sync/tests/test_account_online_account.py
deleted file mode 100644
index 35c5bcd..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/tests/test_account_online_account.py
+++ /dev/null
@@ -1,491 +0,0 @@
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-import logging
-from datetime import datetime, timedelta
-from freezegun import freeze_time
-from unittest.mock import patch
-
-from odoo import Command, fields, tools
-from odoo.addons.odex30_account_online_sync.tests.common import AccountOnlineSynchronizationCommon
-from odoo.tests import tagged
-
-_logger = logging.getLogger(__name__)
-
-@tagged('post_install', '-at_install')
-class TestAccountOnlineAccount(AccountOnlineSynchronizationCommon):
-
- @classmethod
- def setUpClass(cls):
- super().setUpClass()
-
- cls.bank_account_id = cls.env['account.account'].create({
- 'name': 'Bank Account',
- 'account_type': 'asset_cash',
- 'code': cls.env['account.account']._search_new_account_code('BNK100'),
- })
- cls.bank_journal = cls.env['account.journal'].create({
- 'name': 'A bank journal',
- 'default_account_id': cls.bank_account_id.id,
- 'type': 'bank',
- 'code': cls.env['account.journal'].get_next_bank_cash_default_code('bank', cls.company_data['company']),
- })
-
- @freeze_time('2023-08-01')
- def test_get_filtered_transactions(self):
- """ This test verifies that duplicate transactions are filtered """
- self.BankStatementLine.with_context(skip_statement_line_cron_trigger=True).create({
- 'date': '2023-08-01',
- 'journal_id': self.euro_bank_journal.id,
- 'online_transaction_identifier': 'ABCD01',
- 'payment_ref': 'transaction_ABCD01',
- 'amount': 10.0,
- })
-
- transactions_to_filtered = [
- self._create_one_online_transaction(transaction_identifier='ABCD01'),
- self._create_one_online_transaction(transaction_identifier='ABCD02'),
- ]
-
- filtered_transactions = self.account_online_account._get_filtered_transactions(transactions_to_filtered)
-
- self.assertEqual(
- filtered_transactions,
- [
- {
- 'payment_ref': 'transaction_ABCD02',
- 'date': '2023-08-01',
- 'online_transaction_identifier': 'ABCD02',
- 'amount': 10.0,
- 'partner_name': None,
- }
- ]
- )
-
- @freeze_time('2023-08-01')
- def test_get_filtered_transactions_with_empty_transaction_identifier(self):
- """ This test verifies that transactions without a transaction identifier
- are not filtered due to their empty transaction identifier.
- """
- self.BankStatementLine.with_context(skip_statement_line_cron_trigger=True).create({
- 'date': '2023-08-01',
- 'journal_id': self.euro_bank_journal.id,
- 'online_transaction_identifier': '',
- 'payment_ref': 'transaction_ABCD01',
- 'amount': 10.0,
- })
-
- transactions_to_filtered = [
- self._create_one_online_transaction(transaction_identifier=''),
- self._create_one_online_transaction(transaction_identifier=''),
- ]
-
- filtered_transactions = self.account_online_account._get_filtered_transactions(transactions_to_filtered)
-
- self.assertEqual(
- filtered_transactions,
- [
- {
- 'payment_ref': 'transaction_',
- 'date': '2023-08-01',
- 'online_transaction_identifier': '',
- 'amount': 10.0,
- 'partner_name': None,
- },
- {
- 'payment_ref': 'transaction_',
- 'date': '2023-08-01',
- 'online_transaction_identifier': '',
- 'amount': 10.0,
- 'partner_name': None,
- },
- ]
- )
-
- @freeze_time('2023-08-01')
- def test_format_transactions(self):
- transactions_to_format = [
- self._create_one_online_transaction(transaction_identifier='ABCD01'),
- self._create_one_online_transaction(transaction_identifier='ABCD02'),
- ]
- formatted_transactions = self.account_online_account._format_transactions(transactions_to_format)
- self.assertEqual(
- formatted_transactions,
- [
- {
- 'payment_ref': 'transaction_ABCD01',
- 'date': fields.Date.from_string('2023-08-01'),
- 'online_transaction_identifier': 'ABCD01',
- 'amount': 10.0,
- 'online_account_id': self.account_online_account.id,
- 'journal_id': self.euro_bank_journal.id,
- 'company_id': self.euro_bank_journal.company_id.id,
- 'partner_name': None,
- },
- {
- 'payment_ref': 'transaction_ABCD02',
- 'date': fields.Date.from_string('2023-08-01'),
- 'online_transaction_identifier': 'ABCD02',
- 'amount': 10.0,
- 'online_account_id': self.account_online_account.id,
- 'journal_id': self.euro_bank_journal.id,
- 'company_id': self.euro_bank_journal.company_id.id,
- 'partner_name': None,
- },
- ]
- )
-
- @freeze_time('2023-08-01')
- def test_format_transactions_invert_sign(self):
- transactions_to_format = [
- self._create_one_online_transaction(transaction_identifier='ABCD01', amount=25.0),
- ]
- self.account_online_account.inverse_transaction_sign = True
- formatted_transactions = self.account_online_account._format_transactions(transactions_to_format)
- self.assertEqual(
- formatted_transactions,
- [
- {
- 'payment_ref': 'transaction_ABCD01',
- 'date': fields.Date.from_string('2023-08-01'),
- 'online_transaction_identifier': 'ABCD01',
- 'amount': -25.0,
- 'online_account_id': self.account_online_account.id,
- 'journal_id': self.euro_bank_journal.id,
- 'company_id': self.euro_bank_journal.company_id.id,
- 'partner_name': None,
- },
- ]
- )
-
- @freeze_time('2023-08-01')
- def test_format_transactions_foreign_currency_code_to_id_with_activation(self):
- """ This test ensures conversion of foreign currency code to foreign currency id and activates foreign currency if not already activate """
- gbp_currency = self.env['res.currency'].with_context(active_test=False).search([('name', '=', 'GBP')])
- egp_currency = self.env['res.currency'].with_context(active_test=False).search([('name', '=', 'EGP')])
-
- transactions_to_format = [
- self._create_one_online_transaction(transaction_identifier='ABCD01', foreign_currency_code='GBP'),
- self._create_one_online_transaction(transaction_identifier='ABCD02', foreign_currency_code='EGP', amount_currency=500.0),
- ]
- formatted_transactions = self.account_online_account._format_transactions(transactions_to_format)
-
- self.assertTrue(gbp_currency.active)
- self.assertTrue(egp_currency.active)
-
- self.assertEqual(
- formatted_transactions,
- [
- {
- 'payment_ref': 'transaction_ABCD01',
- 'date': fields.Date.from_string('2023-08-01'),
- 'online_transaction_identifier': 'ABCD01',
- 'amount': 10.0,
- 'online_account_id': self.account_online_account.id,
- 'journal_id': self.euro_bank_journal.id,
- 'company_id': self.euro_bank_journal.company_id.id,
- 'partner_name': None,
- 'foreign_currency_id': gbp_currency.id,
- 'amount_currency': 8.0,
- },
- {
- 'payment_ref': 'transaction_ABCD02',
- 'date': fields.Date.from_string('2023-08-01'),
- 'online_transaction_identifier': 'ABCD02',
- 'amount': 10.0,
- 'online_account_id': self.account_online_account.id,
- 'journal_id': self.euro_bank_journal.id,
- 'company_id': self.euro_bank_journal.company_id.id,
- 'partner_name': None,
- 'foreign_currency_id': egp_currency.id,
- 'amount_currency': 500.0,
- },
- ]
- )
-
- @freeze_time('2023-07-25')
- @patch('odoo.addons.odex30_account_online_sync.models.account_online.AccountOnlineLink._fetch_odoo_fin')
- def test_retrieve_pending_transactions(self, patched_fetch_odoofin):
- self.account_online_link.state = 'connected'
- patched_fetch_odoofin.side_effect = [{
- 'transactions': [
- self._create_one_online_transaction(transaction_identifier='ABCD01', date='2023-07-06'),
- self._create_one_online_transaction(transaction_identifier='ABCD02', date='2023-07-22'),
- ],
- 'pendings': [
- self._create_one_online_transaction(transaction_identifier='ABCD03_pending', date='2023-07-25'),
- self._create_one_online_transaction(transaction_identifier='ABCD04_pending', date='2023-07-25'),
- ]
- }]
-
- start_date = fields.Date.from_string('2023-07-01')
- result = self.account_online_account._retrieve_transactions(date=start_date, include_pendings=True)
- self.assertEqual(
- result,
- {
- 'transactions': [
- {
- 'payment_ref': 'transaction_ABCD01',
- 'date': fields.Date.from_string('2023-07-06'),
- 'online_transaction_identifier': 'ABCD01',
- 'amount': 10.0,
- 'partner_name': None,
- 'online_account_id': self.account_online_account.id,
- 'journal_id': self.euro_bank_journal.id,
- 'company_id': self.euro_bank_journal.company_id.id,
- },
- {
- 'payment_ref': 'transaction_ABCD02',
- 'date': fields.Date.from_string('2023-07-22'),
- 'online_transaction_identifier': 'ABCD02',
- 'amount': 10.0,
- 'partner_name': None,
- 'online_account_id': self.account_online_account.id,
- 'journal_id': self.euro_bank_journal.id,
- 'company_id': self.euro_bank_journal.company_id.id,
- }
- ],
- 'pendings': [
- {
- 'payment_ref': 'transaction_ABCD03_pending',
- 'date': fields.Date.from_string('2023-07-25'),
- 'online_transaction_identifier': 'ABCD03_pending',
- 'amount': 10.0,
- 'partner_name': None,
- 'online_account_id': self.account_online_account.id,
- 'journal_id': self.euro_bank_journal.id,
- 'company_id': self.euro_bank_journal.company_id.id,
- },
- {
- 'payment_ref': 'transaction_ABCD04_pending',
- 'date': fields.Date.from_string('2023-07-25'),
- 'online_transaction_identifier': 'ABCD04_pending',
- 'amount': 10.0,
- 'partner_name': None,
- 'online_account_id': self.account_online_account.id,
- 'journal_id': self.euro_bank_journal.id,
- 'company_id': self.euro_bank_journal.company_id.id,
- }
- ]
- }
- )
-
- @freeze_time('2023-01-01 01:10:15')
- @patch('odoo.addons.odex30_account_online_sync.models.account_online.AccountOnlineAccount._retrieve_transactions', return_value={})
- @patch('odoo.addons.odex30_account_online_sync.models.account_online.AccountOnlineAccount._refresh', return_value={'success': True, 'data': {}})
- def test_basic_flow_manual_fetching_transactions(self, patched_refresh, patched_transactions):
- self.addCleanup(self.env.registry.leave_test_mode)
- # flush and clear everything for the new "transaction"
- self.env.invalidate_all()
-
- self.env.registry.enter_test_mode(self.cr)
- with self.env.registry.cursor() as test_cr:
- test_env = self.env(cr=test_cr)
- test_link_account = self.account_online_link.with_env(test_env)
- test_link_account.state = 'connected'
- # Call fetch_transaction in manual mode and check that a call was made to refresh and to transaction
- test_link_account._fetch_transactions()
- patched_refresh.assert_called_once()
- patched_transactions.assert_called_once()
- self.assertEqual(test_link_account.account_online_account_ids[0].fetching_status, 'done')
-
- @freeze_time('2023-01-01 01:10:15')
- @patch('odoo.addons.odex30_account_online_sync.models.account_online.AccountOnlineAccount._retrieve_transactions', return_value={})
- @patch('odoo.addons.odex30_account_online_sync.models.account_online.AccountOnlineLink._fetch_odoo_fin')
- def test_refresh_incomplete_fetching_transactions(self, patched_refresh, patched_transactions):
- patched_refresh.return_value = {'success': False}
- # Call fetch_transaction and if call result is false, don't call transaction
- self.account_online_link._fetch_transactions()
- patched_transactions.assert_not_called()
-
- patched_refresh.return_value = {'success': False, 'currently_fetching': True}
- # Call fetch_transaction and if call result is false but in the process of fetching, don't call transaction
- # and wait for the async cron to try again
- self.account_online_link._fetch_transactions()
- patched_transactions.assert_not_called()
- self.assertEqual(self.account_online_account.fetching_status, 'waiting')
-
- @freeze_time('2023-01-01 01:10:15')
- @patch('odoo.addons.odex30_account_online_sync.models.account_online.AccountOnlineAccount._retrieve_transactions', return_value={})
- @patch('odoo.addons.odex30_account_online_sync.models.account_online.AccountOnlineAccount._refresh', return_value={'success': True, 'data': {}})
- def test_currently_processing_fetching_transactions(self, patched_refresh, patched_transactions):
- self.account_online_account.fetching_status = 'processing' # simulate the fact that we are currently creating entries in odoo
- limit_time = tools.config['limit_time_real_cron'] if tools.config['limit_time_real_cron'] > 0 else tools.config['limit_time_real']
- self.account_online_link.last_refresh = datetime.now()
- with freeze_time(datetime.now() + timedelta(seconds=(limit_time - 10))):
- # Call to fetch_transaction should be skipped, and the cron should not try to fetch either
- self.account_online_link._fetch_transactions()
- self.euro_bank_journal._cron_fetch_waiting_online_transactions()
- patched_refresh.assert_not_called()
- patched_transactions.assert_not_called()
-
- self.addCleanup(self.env.registry.leave_test_mode)
- # flush and clear everything for the new "transaction"
- self.env.invalidate_all()
-
- self.env.registry.enter_test_mode(self.cr)
- with self.env.registry.cursor() as test_cr:
- test_env = self.env(cr=test_cr)
- with freeze_time(datetime.now() + timedelta(seconds=(limit_time + 100))):
- # Call to fetch_transaction should be started by the cron when the time limit is exceeded and still in processing
- self.euro_bank_journal.with_env(test_env)._cron_fetch_waiting_online_transactions()
- patched_refresh.assert_not_called()
- patched_transactions.assert_called_once()
-
- @patch('odoo.addons.odex30_account_online_sync.models.account_online.requests')
- def test_delete_with_redirect_error(self, patched_request):
- # Use case being tested: call delete on a record, first call returns token expired exception
- # Which trigger a call to get a new token, which result in a 104 user_deleted_error, since version 17,
- # such error are returned as a OdooFinRedirectException with mode link to reopen the iframe and link with a new
- # bank. In our case we don't want that and want to be able to delete the record instead.
- # Such use case happen when db_uuid has changed as the check for db_uuid is done after the check for token_validity
- account_online_link = self.env['account.online.link'].create({
- 'name': 'Test Delete',
- 'client_id': 'client_id_test',
- 'refresh_token': 'refresh_token',
- 'access_token': 'access_token',
- })
- first_call = self._mock_odoofin_error_response(code=102)
- second_call = self._mock_odoofin_error_response(code=300, data={'mode': 'link'})
- patched_request.post.side_effect = [first_call, second_call]
- nb_connections = len(self.env['account.online.link'].search([]))
- # Try to delete record
- account_online_link.unlink()
- # Record should be deleted
- self.assertEqual(len(self.env['account.online.link'].search([])), nb_connections - 1)
-
- @patch('odoo.addons.odex30_account_online_sync.models.account_online.requests')
- def test_redirect_mode_link(self, patched_request):
- # Use case being tested: Call to open the iframe which result in a OdoofinRedirectException in link mode
- # This should not trigger a traceback but delete the current online.link and reopen the iframe
- account_online_link = self.env['account.online.link'].create({
- 'name': 'Test Delete',
- 'client_id': 'client_id_test',
- 'refresh_token': 'refresh_token',
- 'access_token': 'access_token',
- })
- link_id = account_online_link.id
- first_call = self._mock_odoofin_error_response(code=300, data={'mode': 'link'})
- second_call = self._mock_odoofin_response(data={'delete': True})
- patched_request.post.side_effect = [first_call, second_call]
- # Try to open iframe with broken connection
- action = account_online_link.action_new_synchronization()
- # Iframe should open in mode link and with a different record (old one should have been deleted)
- self.assertEqual(action['params']['mode'], 'link')
- self.assertNotEqual(action['id'], link_id)
- self.assertEqual(len(self.env['account.online.link'].search([('id', '=', link_id)])), 0)
-
- @patch("odoo.addons.odex30_account_online_sync.models.account_online.AccountOnlineLink._update_connection_status", return_value={})
- def test_assign_journal_with_currency_on_account_online_account(self, patched_update_connection_status):
- self.env['account.move'].create([
- {
- 'move_type': 'entry',
- 'date': fields.Date.from_string('2025-06-25'),
- 'journal_id': self.bank_journal.id,
- 'invoice_line_ids': [
- Command.create({
- 'name': 'a line',
- 'account_id': self.bank_account_id.id,
- 'debit': 100,
- 'currency_id': self.company_data['currency'].id,
- }),
- Command.create({
- 'name': 'another line',
- 'account_id': self.company_data['default_account_expense'].id,
- 'credit': 100,
- 'currency_id': self.company_data['currency'].id,
- }),
- ],
- },
- {
- 'move_type': 'entry',
- 'date': fields.Date.from_string('2025-06-26'),
- 'journal_id': self.bank_journal.id,
- 'invoice_line_ids': [
- Command.create({
- 'name': 'a line',
- 'account_id': self.bank_account_id.id,
- 'debit': 220,
- 'currency_id': self.company_data['currency'].id,
- }),
- Command.create({
- 'name': 'another line',
- 'account_id': self.company_data['default_account_expense'].id,
- 'credit': 220,
- 'currency_id': self.company_data['currency'].id,
- }),
- ],
- },
- ])
-
- self.account_online_account.currency_id = self.company_data['currency'].id
- self.account_online_account.with_context(active_id=self.bank_journal.id, active_model='account.journal')._assign_journal()
- self.assertEqual(
- self.bank_journal.currency_id.id,
- self.company_data['currency'].id,
- )
- self.assertEqual(
- self.bank_journal.default_account_id.currency_id.id,
- self.company_data['currency'].id,
- )
-
- @patch("odoo.addons.odex30_account_online_sync.models.account_online.AccountOnlineLink._update_connection_status", return_value={})
- def test_set_currency_on_journal_when_existing_currencies_on_move_lines(self, patched_update_connection_status):
- bank_account_id = self.env['account.account'].create({
- 'name': 'Bank Account',
- 'account_type': 'asset_cash',
- 'code': self.env['account.account']._search_new_account_code('BNK100'),
- })
- bank_journal = self.env['account.journal'].create({
- 'name': 'A bank journal',
- 'default_account_id': bank_account_id.id,
- 'type': 'bank',
- 'code': self.env['account.journal'].get_next_bank_cash_default_code('bank', self.company_data['company']),
- })
-
- self.env['account.move'].create([
- {
- 'move_type': 'entry',
- 'date': fields.Date.from_string('2025-06-25'),
- 'journal_id': bank_journal.id,
- 'invoice_line_ids': [
- Command.create({
- 'name': 'a line',
- 'account_id': bank_account_id.id,
- 'debit': 100,
- 'currency_id': self.other_currency.id,
- }),
- Command.create({
- 'name': 'another line',
- 'account_id': self.company_data['default_account_expense'].id,
- 'credit': 100,
- 'currency_id': self.other_currency.id,
- }),
- ],
- },
- {
- 'move_type': 'entry',
- 'date': fields.Date.from_string('2025-06-26'),
- 'journal_id': bank_journal.id,
- 'invoice_line_ids': [
- Command.create({
- 'name': 'a line',
- 'account_id': bank_account_id.id,
- 'debit': 220,
- 'currency_id': self.company_data['currency'].id,
- }),
- Command.create({
- 'name': 'another line',
- 'account_id': self.company_data['default_account_expense'].id,
- 'credit': 220,
- 'currency_id': self.company_data['currency'].id,
- }),
- ],
- },
- ])
-
- self.account_online_account.currency_id = self.company_data['currency'].id
- self.account_online_account.with_context(active_id=bank_journal.id, active_model='account.journal')._assign_journal()
-
- # Silently ignore the error and don't set currency on the journal and on the account
- self.assertEqual(bank_journal.currency_id.id, False)
- self.assertEqual(bank_journal.default_account_id.currency_id.id, False)
diff --git a/dev_odex30_accounting/odex30_account_online_sync/tests/test_online_sync_branch_companies.py b/dev_odex30_accounting/odex30_account_online_sync/tests/test_online_sync_branch_companies.py
deleted file mode 100644
index 16cb0ef..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/tests/test_online_sync_branch_companies.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from odoo.addons.odex30_account_online_sync.tests.common import AccountOnlineSynchronizationCommon
-from odoo.tests import tagged
-
-
-@tagged('post_install', '-at_install')
-class TestSynchInBranches(AccountOnlineSynchronizationCommon):
-
- @classmethod
- def setUpClass(cls):
- super().setUpClass()
-
- cls.mother_company = cls.env['res.company'].create({'name': 'Mother company 2000'})
- cls.branch_company = cls.env['res.company'].create({'name': 'Branch company', 'parent_id': cls.mother_company.id})
-
- cls.mother_bank_journal = cls.env['account.journal'].create({
- 'name': 'Mother Bank Journal',
- 'type': 'bank',
- 'code': 'MBJ',
- 'company_id': cls.mother_company.id,
- })
- cls.mother_account_online_link = cls.env['account.online.link'].create({
- 'name': 'Test Bank',
- 'client_id': 'client_id_1',
- 'refresh_token': 'refresh_token',
- 'access_token': 'access_token',
- 'company_id': cls.mother_company.id,
- })
-
- def test_show_sync_actions(self):
- """We test if the sync actions are correctly displayed based on the selected and enabled companies.
-
- Let's have company A with an online link, and a branch of that company: company B.
-
- - If we only have company A enabled and selected, the sync actions should be shown.
- - If company A and B are enabled, no matter which company is selected, the sync actions should be shown.
- - If we only have company B enabled and selected, the sync actions should be hidden.
- """
- self.assertTrue(
- self.mother_account_online_link
- .with_context(allowed_company_ids=(self.mother_company)._ids)
- .with_company(self.mother_company)
- .show_sync_actions
- )
-
- self.assertTrue(
- self.mother_account_online_link
- .with_context(allowed_company_ids=(self.branch_company + self.mother_company)._ids)
- .with_company(self.mother_company)
- .show_sync_actions
- )
-
- self.assertTrue(
- self.mother_account_online_link
- .with_context(allowed_company_ids=(self.branch_company + self.mother_company)._ids)
- .with_company(self.branch_company)
- .show_sync_actions
- )
-
- self.assertFalse(
- self.mother_account_online_link
- .with_context(allowed_company_ids=(self.branch_company)._ids)
- .with_company(self.branch_company)
- .show_sync_actions
- )
-
- def test_show_bank_connect(self):
- """We test if the 'connect' bank button appears on the journal on the dashboard given the selected company.
-
- Let's have company A with an bank journal, and a branch of that company: company B.
-
- - On the dashboard of company A, the connect bank button should appear on the journal.
- - On the dashboard of company B, the connect bank button should not appear on the journal, even with company A enabled.
- """
- dashboard_data = self.mother_bank_journal\
- .with_context(allowed_company_ids=(self.mother_company)._ids)\
- .with_company(self.mother_company)\
- ._get_journal_dashboard_data_batched()
- self.assertTrue(dashboard_data[self.mother_bank_journal.id].get('display_connect_bank_in_dashboard'))
-
- dashboard_data = self.mother_bank_journal\
- .with_context(allowed_company_ids=(self.branch_company + self.mother_company)._ids)\
- .with_company(self.branch_company)\
- ._get_journal_dashboard_data_batched()
- self.assertFalse(dashboard_data[self.mother_bank_journal.id].get('display_connect_bank_in_dashboard'))
diff --git a/dev_odex30_accounting/odex30_account_online_sync/tests/test_online_sync_creation_statement.py b/dev_odex30_accounting/odex30_account_online_sync/tests/test_online_sync_creation_statement.py
deleted file mode 100644
index a68e1da..0000000
--- a/dev_odex30_accounting/odex30_account_online_sync/tests/test_online_sync_creation_statement.py
+++ /dev/null
@@ -1,374 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from unittest.mock import MagicMock, patch
-
-from odoo.addons.base.models.res_bank import sanitize_account_number
-from odoo.addons.odex30_account_online_sync.tests.common import AccountOnlineSynchronizationCommon
-from odoo.exceptions import RedirectWarning
-from odoo.tests import tagged
-from odoo import fields, Command
-
-
-@tagged('post_install', '-at_install')
-class TestSynchStatementCreation(AccountOnlineSynchronizationCommon):
-
- @classmethod
- def setUpClass(cls):
- super().setUpClass()
-
- cls.account = cls.env['account.account'].create({
- 'name': 'Fixed Asset Account',
- 'code': 'AA',
- 'account_type': 'asset_fixed',
- })
-
- def reconcile_st_lines(self, st_lines):
- for line in st_lines:
- wizard = self.env['bank.rec.widget'].with_context(default_st_line_id=line.id).new({})
- line = wizard.line_ids.filtered(lambda x: x.flag == 'auto_balance')
- wizard._js_action_mount_line_in_edit(line.index)
- line.name = "toto"
- wizard._line_value_changed_name(line)
- line.account_id = self.account
- wizard._line_value_changed_account_id(line)
- wizard._action_validate()
-
- # Tests
- def test_creation_initial_sync_statement(self):
- transactions = self._create_online_transactions(['2016-01-01', '2016-01-03'])
- self.account_online_account.balance = 1000
- self.BankStatementLine._online_sync_bank_statement(transactions, self.account_online_account)
- # Since ending balance is 1000$ and we only have 20$ of transactions and that it is the first statement
- # it should create a statement before this one with the initial statement line
- created_st_lines = self.BankStatementLine.search([('journal_id', '=', self.euro_bank_journal.id)], order='internal_index asc')
- self.assertEqual(len(created_st_lines), 3, 'Should have created an initial bank statement line and two for the synchronization')
- transactions = self._create_online_transactions(['2016-01-05'])
- self.account_online_account.balance = 2000
- self.BankStatementLine._online_sync_bank_statement(transactions, self.account_online_account)
- created_st_lines = self.BankStatementLine.search([('journal_id', '=', self.euro_bank_journal.id)], order='internal_index asc')
- self.assertRecordValues(
- created_st_lines,
- [
- {'date': fields.Date.from_string('2016-01-01'), 'amount': 980.0},
- {'date': fields.Date.from_string('2016-01-01'), 'amount': 10.0},
- {'date': fields.Date.from_string('2016-01-03'), 'amount': 10.0},
- {'date': fields.Date.from_string('2016-01-05'), 'amount': 10.0},
- ]
- )
-
- def test_creation_initial_sync_statement_bis(self):
- transactions = self._create_online_transactions(['2016-01-01', '2016-01-03'])
- self.account_online_account.balance = 20
- self.BankStatementLine._online_sync_bank_statement(transactions, self.account_online_account)
- # Since ending balance is 20$ and we only have 20$ of transactions and that it is the first statement
- # it should NOT create a initial statement before this one
- created_st_lines = self.BankStatementLine.search([('journal_id', '=', self.euro_bank_journal.id)], order='internal_index asc')
- self.assertRecordValues(
- created_st_lines,
- [
- {'date': fields.Date.from_string('2016-01-01'), 'amount': 10.0},
- {'date': fields.Date.from_string('2016-01-03'), 'amount': 10.0},
- ]
- )
-
- def test_creation_initial_sync_statement_invert_sign(self):
- self.account_online_account.balance = -20
- self.account_online_account.inverse_transaction_sign = True
- self.account_online_account.inverse_balance_sign = True
- transactions = self._create_online_transactions(['2016-01-01', '2016-01-03'])
- self.BankStatementLine._online_sync_bank_statement(transactions, self.account_online_account)
- # Since ending balance is 1000$ and we only have 20$ of transactions and that it is the first statement
- # it should create a statement before this one with the initial statement line
- created_st_lines = self.BankStatementLine.search([('journal_id', '=', self.euro_bank_journal.id)], order='internal_index asc')
- self.assertEqual(len(created_st_lines), 2, 'Should have created two bank statement lines for the synchronization')
- transactions = self._create_online_transactions(['2016-01-05'])
- self.account_online_account.balance = -30
- self.BankStatementLine._online_sync_bank_statement(transactions, self.account_online_account)
- created_st_lines = self.BankStatementLine.search([('journal_id', '=', self.euro_bank_journal.id)], order='internal_index asc')
- self.assertRecordValues(
- created_st_lines,
- [
- {'date': fields.Date.from_string('2016-01-01'), 'amount': -10.0},
- {'date': fields.Date.from_string('2016-01-03'), 'amount': -10.0},
- {'date': fields.Date.from_string('2016-01-05'), 'amount': -10.0},
- ]
- )
-
- @patch('odoo.addons.odex30_account_online_sync.models.account_online.AccountOnlineLink._fetch_transactions')
- @patch('odoo.addons.odex30_account_online_sync.models.account_online.AccountOnlineLink._update_connection_status')
- def test_automatic_journal_assignment(self, patched_update_connection_status, patched_fetch_transactions):
- def create_online_account(name, link_id, iban, currency_id):
- return self.env['account.online.account'].create({
- 'name': name,
- 'account_online_link_id': link_id,
- 'account_number': iban,
- 'currency_id' : currency_id,
- })
-
- def create_bank_account(account_number, partner_id):
- return self.env['res.partner.bank'].create({
- 'acc_number': account_number,
- 'partner_id': partner_id,
- })
-
- def create_journal(name, journal_type, code, currency_id=False, bank_account_id=False):
- return self.env['account.journal'].create({
- 'name': name,
- 'type': journal_type,
- 'code': code,
- 'currency_id': currency_id,
- 'bank_account_id': bank_account_id,
- })
-
- bank_account_1 = create_bank_account('BE48485444456727', self.company_data['company'].partner_id.id)
- bank_account_2 = create_bank_account('BE23798242487491', self.company_data['company'].partner_id.id)
-
- bank_journal_with_account_gol = create_journal('Bank with account', 'bank', 'BJWA1', self.other_currency.id)
- bank_journal_with_account_usd = create_journal('Bank with account USD', 'bank', 'BJWA3', self.env.ref('base.USD').id, bank_account_2.id)
-
- online_account_1 = create_online_account('OnlineAccount1', self.account_online_link.id, 'BE48485444456727', self.other_currency.id)
- online_account_2 = create_online_account('OnlineAccount2', self.account_online_link.id, 'BE61954856342317', self.other_currency.id)
- online_account_3 = create_online_account('OnlineAccount3', self.account_online_link.id, 'BE23798242487495', self.other_currency.id)
-
- patched_fetch_transactions.return_value = True
- patched_update_connection_status.return_value = {
- 'consent_expiring_date': None,
- 'is_payment_enabled': False,
- 'is_payment_activated': False,
- }
-
- account_link_journal_wizard = self.env['account.bank.selection'].create({'account_online_link_id': self.account_online_link.id})
- account_link_journal_wizard.with_context(active_model='account.journal', active_id=bank_journal_with_account_gol.id).sync_now()
- self.assertEqual(
- online_account_1.id, bank_journal_with_account_gol.account_online_account_id.id,
- "The wizard should have linked the online account to the journal with the same account."
- )
- self.assertEqual(bank_journal_with_account_gol.bank_account_id, bank_account_1, "Account should be set on the journal")
-
- # Test with no context present, should create a new journal
- previous_number = self.env['account.journal'].search_count([])
- account_link_journal_wizard.selected_account = online_account_2
- account_link_journal_wizard.sync_now()
- actual_number = self.env['account.journal'].search_count([])
- self.assertEqual(actual_number, previous_number+1, "should have created a new journal")
- self.assertEqual(online_account_2.journal_ids.currency_id, self.other_currency)
- self.assertEqual(online_account_2.journal_ids.bank_account_id.sanitized_acc_number, sanitize_account_number('BE61954856342317'))
-
- # Test assigning to a journal in another currency
- account_link_journal_wizard.selected_account = online_account_3
- account_link_journal_wizard.with_context(active_model='account.journal', active_id=bank_journal_with_account_usd.id).sync_now()
- self.assertEqual(online_account_3.id, bank_journal_with_account_usd.account_online_account_id.id)
- self.assertEqual(bank_journal_with_account_usd.bank_account_id, bank_account_2, "Bank Account should not have changed")
- self.assertEqual(bank_journal_with_account_usd.currency_id, self.other_currency, "Currency should have changed")
-
- @patch('odoo.addons.odex30_account_online_sync.models.account_online.AccountOnlineLink._fetch_odoo_fin')
- def test_fetch_transaction_date_start(self, patched_fetch):
- """ This test verifies that the start_date params used when fetching transaction is correct """
- patched_fetch.return_value = {'transactions': []}
- # Since no transactions exists in db, we should fetch transactions without a starting_date
- self.account_online_account._retrieve_transactions()
- data = {
- 'start_date': False,
- 'account_id': False,
- 'last_transaction_identifier': False,
- 'currency_code': 'EUR',
- 'provider_data': False,
- 'account_data': False,
- 'include_pendings': False,
- 'include_foreign_currency': True,
- }
- patched_fetch.assert_called_with('/proxy/v1/transactions', data=data)
-
- # No transaction exists in db but we have a value for last_sync on the online_account, we should use that date
- self.account_online_account.last_sync = '2020-03-04'
- data['start_date'] = '2020-03-04'
- self.account_online_account._retrieve_transactions()
- patched_fetch.assert_called_with('/proxy/v1/transactions', data=data)
-
- # We have transactions, we should use the date of the latest one instead of the last_sync date
- transactions = self._create_online_transactions(['2016-01-01', '2016-01-03'])
- self.BankStatementLine._online_sync_bank_statement(transactions, self.account_online_account)
- self.account_online_account.last_sync = '2020-03-04'
- data['start_date'] = '2016-01-03'
- data['last_transaction_identifier'] = '2'
- self.account_online_account._retrieve_transactions()
- patched_fetch.assert_called_with('/proxy/v1/transactions', data=data)
-
- def test_multiple_transaction_identifier_fetched(self):
- # Ensure that if we receive twice the same transaction within the same call, it won't be created twice
- transactions = self._create_online_transactions(['2016-01-01', '2016-01-03'])
- # Add first transactions to the list again
- transactions.append(transactions[0])
- self.BankStatementLine._online_sync_bank_statement(transactions, self.account_online_account)
- bnk_stmt_lines = self.BankStatementLine.search([('online_transaction_identifier', '!=', False), ('journal_id', '=', self.euro_bank_journal.id)])
- self.assertEqual(len(bnk_stmt_lines), 2, 'Should only have created two lines')
-
- @patch('odoo.addons.odex30_account_online_sync.models.account_online.AccountOnlineLink._fetch_odoo_fin')
- def test_fetch_transactions_reauth(self, patched_refresh):
- patched_refresh.side_effect = [
- {
- 'success': False,
- 'code': 300,
- 'data': {'mode': 'updateCredentials'},
- },
- {
- 'access_token': 'open_sesame',
- },
- ]
- self.account_online_account.account_online_link_id.state = 'connected'
- res = self.account_online_account.account_online_link_id._fetch_transactions()
- self.assertTrue('account_online_identifier' in res.get('params', {}).get('includeParam', {}))
-
- def test_duplicate_transaction_date_amount_account(self):
- """ This test verifies that the duplicate transaction wizard is detects transactions with
- same date, amount, account_number and currency
- """
- # Create 2 groups of respectively 2 and 3 duplicate transactions. We create one transaction the day before so the opening statement does not interfere with the test.
- transactions = self._create_online_transactions([
- '2024-01-01',
- '2024-01-02', '2024-01-02',
- '2024-01-03', '2024-01-03', '2024-01-03',
- ])
- bsls = self.BankStatementLine._online_sync_bank_statement(transactions, self.account_online_account)
- self.env.flush_all() # _get_duplicate_transactions make sql request, must write to db
- duplicate_transactions = self.euro_bank_journal._get_duplicate_transactions(
- fields.Date.to_date('2000-01-01')
- )
- group_1 = bsls.filtered(lambda bsl: bsl.date == fields.Date.from_string('2024-01-02')).ids
- group_2 = bsls.filtered(lambda bsl: bsl.date == fields.Date.from_string('2024-01-03')).ids
-
- self.assertEqual(duplicate_transactions, [group_1, group_2])
-
- # check has_duplicate_transactions
- has_duplicate_transactions = self.euro_bank_journal._has_duplicate_transactions(
- fields.Date.to_date('2000-01-01')
- )
- self.assertTrue(has_duplicate_transactions is True) # explicit check on bool type
-
- def test_duplicate_transaction_online_transaction_identifier(self):
- """ This test verifies that the duplicate transaction wizard is detects transactions with
- same online_transaction_identifier
- """
- # Create transactions
- transactions = self._create_online_transactions([
- '2024-01-01', '2024-01-02', '2024-01-03', '2024-01-04', '2024-01-05'
- ])
- bsls = self.BankStatementLine._online_sync_bank_statement(transactions, self.account_online_account)
-
- group_1, group_2 = [], []
- for bsl in bsls:
- # have to update the online_transaction_identifier after to force duplicates
- if bsl.payment_ref in ('transaction_1', 'transaction_2'):
- group_1.append(bsl.id)
- bsl.online_transaction_identifier = 'same_oti_1'
- if bsl.payment_ref in ('transaction_3, transaction_4, transaction_5'):
- group_2.append(bsl.id)
- bsl.online_transaction_identifier = 'same_oti_2'
-
- self.env.flush_all() # _get_duplicate_transactions make sql request, must write to db
- duplicate_transactions = self.euro_bank_journal._get_duplicate_transactions(
- fields.Date.to_date('2000-01-01')
- )
- self.assertEqual(duplicate_transactions, [group_1, group_2])
-
- @patch('odoo.addons.odex30_account_online_sync.models.account_online.requests')
- def test_fetch_receive_error_message(self, patched_request):
- # We want to test that when we receive an error, a redirectWarning with the correct parameter is thrown
- # However the method _log_information that we need to test for that is performing a rollback as it needs
- # to save the message error on the record as well (so it rollback, save message, commit, raise error).
- # So in order to test the method, we need to use a "test cursor".
- mock_response = MagicMock()
- mock_response.status_code = 200
- mock_response.json.return_value = {
- 'error': {
- 'code': 400,
- 'message': 'Shit Happened',
- 'data': {
- 'exception_type': 'random',
- 'message': 'This kind of things can happen.',
- 'error_reference': 'abc123',
- 'provider_type': 'theonlyone',
- 'redirect_warning_url': 'odoo_support',
- },
- },
- }
- patched_request.post.return_value = mock_response
-
- generated_url = 'https://www.odoo.com/help?stage=bank_sync&summary=Bank+sync+error+ref%3A+abc123+-+Provider%3A+theonlyone+-+Client+ID%3A+client_id_1&description=ClientID%3A+client_id_1%0AInstitution%3A+Test+Bank%0AError+Reference%3A+abc123%0AError+Message%3A+This+kind+of+things+can+happen.%0A'
- return_act_url = {
- 'type': 'ir.actions.act_url',
- 'url': generated_url
- }
- body_generated_url = generated_url.replace('&', '&') #in post_message, & has been escaped to &
- message_body = f"""
This kind of things can happen.
-
-If you've already opened a ticket for this issue, don't report it again: a support agent will contact you shortly. You can contact Odoo support Here
"""
-
- # flush and clear everything for the new "transaction"
- self.env.invalidate_all()
- try:
- self.env.registry.enter_test_mode(self.cr)
- with self.env.registry.cursor() as test_cr:
- test_env = self.env(cr=test_cr)
- test_link_account = self.account_online_link.with_env(test_env)
- test_link_account.state = 'connected'
-
- # this hand-written self.assertRaises() does not roll back self.cr,
- # which is necessary below to inspect the message being posted
- try:
- test_link_account._fetch_odoo_fin('/testthisurl')
- except RedirectWarning as exception:
- self.assertEqual(exception.args[0], "This kind of things can happen.\n\nIf you've already opened a ticket for this issue, don't report it again: a support agent will contact you shortly.")
- self.assertEqual(exception.args[1], return_act_url)
- self.assertEqual(exception.args[2], 'Report issue')
- else:
- self.fail("Expected RedirectWarning not raised")
- self.assertEqual(test_link_account.message_ids[0].body, message_body)
- finally:
- self.env.registry.leave_test_mode()
-
- def test_account_online_link_having_journal_ids(self):
- """ This test verifies that the account online link object
- has all the journal in the field journal_ids.
- It's important to handle these journals because we need
- them to add the consent expiring date.
- """
- # Create a bank sync connection having 2 online accounts (with one journal connected for each account)
- online_link = self.env['account.online.link'].create({
- 'name': 'My New Bank connection',
- })
- online_accounts = self.env['account.online.account'].create([
- {
- 'name': 'Account 1',
- 'account_online_link_id': online_link.id,
- 'journal_ids': [Command.create({
- 'name': 'Account 1',
- 'code': 'BK1',
- 'type': 'bank',
- })],
- },
- {
- 'name': 'Account 2',
- 'account_online_link_id': online_link.id,
- 'journal_ids': [Command.create({
- 'name': 'Account 2',
- 'code': 'BK2',
- 'type': 'bank',
- })],
- },
- ])
- self.assertEqual(online_link.account_online_account_ids, online_accounts)
- self.assertEqual(len(online_link.journal_ids), 2) # Our online link connections should have 2 journals.
-
- def test_transaction_details_json_compatibility_from_html(self):
- """ This test checks that, after being imported from the transient model
- the records of account.bank.statement.line will have the
- 'transaction_details' field able to be decoded to a JSON,
- i.e. it is not encapsulated in