From 7ff50ca7262b62f18d9b0314310bec7b628e1da8 Mon Sep 17 00:00:00 2001 From: mohammed-alkhazrji Date: Mon, 19 Jan 2026 16:45:37 +0300 Subject: [PATCH] fetch report --- .../account_chart_of_accounts/__manifest__.py | 7 +- .../account_chart_of_accounts/i18n/ar_001.po | 317 ++++ .../account_account.cpython-311.pyc | Bin 9611 -> 9948 bytes .../models/account_account.py | 53 +- .../static/src/js/account_list_renderer.js | 31 + .../static/src/js/search_panel_bold.js | 61 + .../static/src/scss/account_hierarchy.scss | 74 +- .../views/account_account_view.xml | 5 + dev_odex30_accounting/accountant/__init__.py | 67 + .../accountant/__manifest__.py | 37 + .../data/account_accountant_data.xml | 32 + .../demo/account_accountant_demo.xml | 6 + .../accountant/i18n/accountant.pot | 28 + dev_odex30_accounting/accountant/i18n/ar.po | 32 + .../accountant/models/__init__.py | 1 + .../accountant/models/account_move.py | 9 + .../security/accounting_security.xml | 38 + .../accountant/static/description/icon.png | Bin 0 -> 2217 bytes .../accountant/static/description/icon.svg | 1 + .../static/src/js/tours/accountant.js | 12 + .../tests/tours/account_merge_wizard_tour.js | 61 + .../static/tests/tours/account_tours.js | 10 + .../accountant/tests/__init__.py | 1 + .../accountant/tests/test_tour.py | 48 + .../accountant/views/partner_views.xml | 16 + .../accountant/views/res_config_settings.xml | 15 + .../odex30_account_online_sync/__init__.py | 5 - .../__manifest__.py | 51 - .../__pycache__/__init__.cpython-311.pyc | Bin 383 -> 0 bytes .../controllers/__init__.py | 1 - .../__pycache__/__init__.cpython-311.pyc | Bin 298 -> 0 bytes .../__pycache__/portal.cpython-311.pyc | Bin 4280 -> 0 bytes .../controllers/portal.py | 58 - .../data/config_parameter.xml | 13 - .../data/ir_cron.xml | 48 - .../data/mail_activity_type_data.xml | 21 - .../data/neutralize.sql | 4 - .../data/sync_reminder_email_template.xml | 71 - .../i18n/account_online_synchronization.pot | 1322 --------------- .../odex30_account_online_sync/i18n/ar.po | 1418 ----------------- .../models/__init__.py | 9 - .../__pycache__/__init__.cpython-311.pyc | Bin 621 -> 0 bytes .../account_bank_statement.cpython-311.pyc | Bin 8104 -> 0 bytes .../account_journal.cpython-311.pyc | Bin 26737 -> 0 bytes .../account_online.cpython-311.pyc | Bin 69362 -> 0 bytes .../bank_rec_widget.cpython-311.pyc | Bin 1257 -> 0 bytes .../__pycache__/company.cpython-311.pyc | Bin 1153 -> 0 bytes .../mail_activity_type.cpython-311.pyc | Bin 1161 -> 0 bytes .../__pycache__/odoofin_auth.cpython-311.pyc | Bin 2789 -> 0 bytes .../__pycache__/partner.cpython-311.pyc | Bin 709 -> 0 bytes .../models/account_bank_statement.py | 114 -- .../models/account_journal.py | 380 ----- .../models/account_online.py | 1170 -------------- .../models/bank_rec_widget.py | 16 - .../models/company.py | 15 - .../models/mail_activity_type.py | 14 - .../models/odoofin_auth.py | 46 - .../models/partner.py | 7 - .../security/account_online_sync_security.xml | 15 - .../security/ir.model.access.csv | 12 - .../account_duplicate_transaction_form.js | 37 - .../account_duplicate_transaction_hook.js | 6 - .../account_duplicate_transaction_service.js | 21 - .../account_duplicate_transactions_x2many.js | 50 - ...account_duplicate_transactions_x2many.scss | 3 - .../account_duplicate_transactions_x2many.xml | 14 - .../bank_configure/bank_configure.js | 84 - .../bank_configure/bank_configure.scss | 15 - .../bank_configure/bank_configure.xml | 29 - .../fetch_missing_transactions_cog_menu.js | 68 - .../fetch_missing_transactions_cog_menu.xml | 8 - .../find_duplicate_transactions_cog_menu.js | 64 - .../find_duplicate_transactions_cog_menu.xml | 8 - .../connected_until_widget.js | 61 - .../connected_until_widget.xml | 23 - .../online_account_radio.js | 41 - .../online_account_radio.xml | 29 - .../refresh_spin_journal_widget.js | 99 -- .../refresh_spin_journal_widget.xml | 42 - ...transient_bank_statement_line_list_view.js | 51 - ...ransient_bank_statement_line_list_view.xml | 28 - .../account_online_authorization_kanban.js | 19 - ...online_authorization_kanban_controller.xml | 12 - .../static/src/js/odoo_fin_connector.js | 86 - .../static/src/js/online_sync_portal.js | 57 - .../tests/helpers/model_definitions_setup.js | 5 - .../static/tests/online_account_radio_test.js | 83 - .../tests/__init__.py | 7 - .../tests/common.py | 110 -- ...est_account_missing_transactions_wizard.py | 45 - .../tests/test_account_online_account.py | 491 ------ .../test_online_sync_branch_companies.py | 86 - .../test_online_sync_creation_statement.py | 374 ----- .../views/account_bank_statement_view.xml | 27 - .../views/account_journal_dashboard_view.xml | 86 - .../views/account_journal_view.xml | 22 - .../account_online_sync_portal_templates.xml | 59 - .../views/account_online_sync_views.xml | 126 -- .../wizard/__init__.py | 6 - .../__pycache__/__init__.cpython-311.pyc | Bin 536 -> 0 bytes ...ount_bank_selection_wizard.cpython-311.pyc | Bin 2700 -> 0 bytes ...ccount_bank_statement_line.cpython-311.pyc | Bin 4318 -> 0 bytes ...nal_duplicate_transactions.cpython-311.pyc | Bin 4348 -> 0 bytes ...urnal_missing_transactions.cpython-311.pyc | Bin 5691 -> 0 bytes .../wizard/account_bank_selection_wizard.py | 29 - .../wizard/account_bank_selection_wizard.xml | 26 - .../wizard/account_bank_statement_line.py | 89 -- .../wizard/account_bank_statement_line.xml | 34 - .../account_journal_duplicate_transactions.py | 66 - ...account_journal_duplicate_transactions.xml | 50 - .../account_journal_missing_transactions.py | 78 - .../account_journal_missing_transactions.xml | 22 - .../odex30_account_reports/__manifest__.py | 7 +- 113 files changed, 927 insertions(+), 7728 deletions(-) create mode 100644 dev_odex30_accounting/account_chart_of_accounts/i18n/ar_001.po create mode 100644 dev_odex30_accounting/account_chart_of_accounts/static/src/js/account_list_renderer.js create mode 100644 dev_odex30_accounting/account_chart_of_accounts/static/src/js/search_panel_bold.js create mode 100644 dev_odex30_accounting/accountant/__init__.py create mode 100644 dev_odex30_accounting/accountant/__manifest__.py create mode 100644 dev_odex30_accounting/accountant/data/account_accountant_data.xml create mode 100644 dev_odex30_accounting/accountant/demo/account_accountant_demo.xml create mode 100644 dev_odex30_accounting/accountant/i18n/accountant.pot create mode 100644 dev_odex30_accounting/accountant/i18n/ar.po create mode 100644 dev_odex30_accounting/accountant/models/__init__.py create mode 100644 dev_odex30_accounting/accountant/models/account_move.py create mode 100644 dev_odex30_accounting/accountant/security/accounting_security.xml create mode 100644 dev_odex30_accounting/accountant/static/description/icon.png create mode 100644 dev_odex30_accounting/accountant/static/description/icon.svg create mode 100644 dev_odex30_accounting/accountant/static/src/js/tours/accountant.js create mode 100644 dev_odex30_accounting/accountant/static/tests/tours/account_merge_wizard_tour.js create mode 100644 dev_odex30_accounting/accountant/static/tests/tours/account_tours.js create mode 100644 dev_odex30_accounting/accountant/tests/__init__.py create mode 100644 dev_odex30_accounting/accountant/tests/test_tour.py create mode 100644 dev_odex30_accounting/accountant/views/partner_views.xml create mode 100644 dev_odex30_accounting/accountant/views/res_config_settings.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/__init__.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/__manifest__.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/__pycache__/__init__.cpython-311.pyc delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/controllers/__init__.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/controllers/__pycache__/__init__.cpython-311.pyc delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/controllers/__pycache__/portal.cpython-311.pyc delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/controllers/portal.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/data/config_parameter.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/data/ir_cron.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/data/mail_activity_type_data.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/data/neutralize.sql delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/data/sync_reminder_email_template.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/i18n/account_online_synchronization.pot delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/i18n/ar.po delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/models/__init__.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/__init__.cpython-311.pyc delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/account_bank_statement.cpython-311.pyc delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/account_journal.cpython-311.pyc delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/account_online.cpython-311.pyc delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/bank_rec_widget.cpython-311.pyc delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/company.cpython-311.pyc delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/mail_activity_type.cpython-311.pyc delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/odoofin_auth.cpython-311.pyc delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/models/__pycache__/partner.cpython-311.pyc delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/models/account_bank_statement.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/models/account_journal.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/models/account_online.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/models/bank_rec_widget.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/models/company.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/models/mail_activity_type.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/models/odoofin_auth.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/models/partner.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/security/account_online_sync_security.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/security/ir.model.access.csv delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transaction_form.js delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transaction_hook.js delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transaction_service.js delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transactions_x2many.js delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transactions_x2many.scss delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/account_duplicate_transaction/account_duplicate_transactions_x2many.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_configure/bank_configure.js delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_configure/bank_configure.scss delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_configure/bank_configure.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_reconciliation/fetch_missing_transactions_cog_menu.js delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_reconciliation/fetch_missing_transactions_cog_menu.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_reconciliation/find_duplicate_transactions_cog_menu.js delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_reconciliation/find_duplicate_transactions_cog_menu.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/connected_until_widget/connected_until_widget.js delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/connected_until_widget/connected_until_widget.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/online_account_radio/online_account_radio.js delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/online_account_radio/online_account_radio.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/refresh_spin_journal_widget/refresh_spin_journal_widget.js delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/refresh_spin_journal_widget/refresh_spin_journal_widget.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/transient_bank_statement_line_list_view/transient_bank_statement_line_list_view.js delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/transient_bank_statement_line_list_view/transient_bank_statement_line_list_view.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/views/account_online_authorization_kanban.js delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/components/views/account_online_authorization_kanban_controller.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/js/odoo_fin_connector.js delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/src/js/online_sync_portal.js delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/tests/helpers/model_definitions_setup.js delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/static/tests/online_account_radio_test.js delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/tests/__init__.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/tests/common.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/tests/test_account_missing_transactions_wizard.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/tests/test_account_online_account.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/tests/test_online_sync_branch_companies.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/tests/test_online_sync_creation_statement.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/views/account_bank_statement_view.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/views/account_journal_dashboard_view.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/views/account_journal_view.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/views/account_online_sync_portal_templates.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/views/account_online_sync_views.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/wizard/__init__.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/wizard/__pycache__/__init__.cpython-311.pyc delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/wizard/__pycache__/account_bank_selection_wizard.cpython-311.pyc delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/wizard/__pycache__/account_bank_statement_line.cpython-311.pyc delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/wizard/__pycache__/account_journal_duplicate_transactions.cpython-311.pyc delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/wizard/__pycache__/account_journal_missing_transactions.cpython-311.pyc delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/wizard/account_bank_selection_wizard.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/wizard/account_bank_selection_wizard.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/wizard/account_bank_statement_line.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/wizard/account_bank_statement_line.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/wizard/account_journal_duplicate_transactions.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/wizard/account_journal_duplicate_transactions.xml delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/wizard/account_journal_missing_transactions.py delete mode 100644 dev_odex30_accounting/odex30_account_online_sync/wizard/account_journal_missing_transactions.xml diff --git a/dev_odex30_accounting/account_chart_of_accounts/__manifest__.py b/dev_odex30_accounting/account_chart_of_accounts/__manifest__.py index 2d53a00..c2f07aa 100644 --- a/dev_odex30_accounting/account_chart_of_accounts/__manifest__.py +++ b/dev_odex30_accounting/account_chart_of_accounts/__manifest__.py @@ -41,11 +41,14 @@ 'account_chart_of_accounts/static/src/js/account_type_selection_extend.js', 'account_chart_of_accounts/static/src/js/filters_patch.js', 'account_chart_of_accounts/static/src/js/account_report.js', + 'account_chart_of_accounts/static/src/js/account_list_renderer.js', + 'account_chart_of_accounts/static/src/js/search_panel_bold.js', 'account_chart_of_accounts/static/src/xml/filter_full_hierarchy.xml', - ], - 'web.assets_frontend': [ + # CRITICAL: SCSS must be in backend, not frontend! 'account_chart_of_accounts/static/src/scss/account_hierarchy.scss', + ], + }, 'installable': True, diff --git a/dev_odex30_accounting/account_chart_of_accounts/i18n/ar_001.po b/dev_odex30_accounting/account_chart_of_accounts/i18n/ar_001.po new file mode 100644 index 0000000..84c3335 --- /dev/null +++ b/dev_odex30_accounting/account_chart_of_accounts/i18n/ar_001.po @@ -0,0 +1,317 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_chart_of_accounts +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-01-18 03:36+0000\n" +"PO-Revision-Date: 2026-01-18 03:36+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: account_chart_of_accounts +#: model_terms:ir.ui.view,arch_db:account_chart_of_accounts.view_account_form_inherit_hierarchy +msgid "" +"\n" +" Auto\n" +" " +msgstr "" + +#. module: account_chart_of_accounts +#: model:ir.model,name:account_chart_of_accounts.model_account_account +msgid "Account" +msgstr "الحساب " + +#. module: account_chart_of_accounts +#: model_terms:ir.ui.view,arch_db:account_chart_of_accounts.res_config_settings_view_form_inherit +msgid "Account Code Padding" +msgstr "حشو كود الحساب" + +#. module: account_chart_of_accounts +#: model:ir.model.fields,help:account_chart_of_accounts.field_account_account__account_type +msgid "" +"Account Type is used for information purpose, to generate country-specific " +"legal reports, and set the rules to close a fiscal year and generate opening" +" entries." +msgstr "" +"يُستخدم نوع الحساب في إنشاء التقارير القانونية المخصصة لكل دولة، وتضع " +"القواعد اللازمة لإقفال السنة المالية وإنشاء القيود الافتتاحية للسنة المالية " +"اللاحقة. " + +#. module: account_chart_of_accounts +#: model:ir.model,name:account_chart_of_accounts.model_account_report +msgid "Accounting Report" +msgstr "تقرير المحاسبة " + +#. module: account_chart_of_accounts +#. odoo-javascript +#: code:addons/account_chart_of_accounts/static/src/js/account_type_selection_extend.js:0 +msgid "Assets" +msgstr "الأصول" + +#. module: account_chart_of_accounts +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_account_account__auto_code +msgid "Auto Code" +msgstr "كود آلي" + +#. module: account_chart_of_accounts +#: model_terms:ir.ui.view,arch_db:account_chart_of_accounts.res_config_settings_view_form_inherit +msgid "Auto Generate Account Codes" +msgstr "توليد أكواد الحسابات تلقائياً" + +#. module: account_chart_of_accounts +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_account_account__automticAccountsCodes +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_res_company__automticAccountsCodes +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_res_config_settings__automticAccountsCodes +msgid "Automatically Generate Accounts Codes" +msgstr "توليد أكواد الحسابات تلقائياً" + +#. module: account_chart_of_accounts +#: model_terms:ir.ui.view,arch_db:account_chart_of_accounts.res_config_settings_view_form_inherit +msgid "Automatically generate account codes based on parent account" +msgstr "توليد أكواد الحسابات تلقائياً بناءً على الحساب الأب" + +#. module: account_chart_of_accounts +#. odoo-javascript +#: code:addons/account_chart_of_accounts/static/src/js/account_type_selection_extend.js:0 +msgid "Balance Sheet" +msgstr "الميزانية العمومية" + +#. module: account_chart_of_accounts +#: model_terms:ir.ui.view,arch_db:account_chart_of_accounts.res_config_settings_view_form_inherit +msgid "Bank Account Prefix" +msgstr "بادئة حساب البنك" + +#. module: account_chart_of_accounts +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_res_config_settings__bank_account_code_prefix +msgid "Bank Prefix" +msgstr "بادئة البنك" + +#. module: account_chart_of_accounts +#. odoo-python +#: code:addons/account_chart_of_accounts/models/account_journal.py:0 +msgid "Can not find account with code (%s)." +msgstr "لا يمكن إيجاد حساب بالكود (%s)." + +#. module: account_chart_of_accounts +#. odoo-python +#: code:addons/account_chart_of_accounts/models/account_journal.py:0 +msgid "" +"Cannot generate an unused journal code. Please fill the 'Shortcode' field." +msgstr "لا يمكن توليد كود دفتر يومية غير مستخدم. يرجى ملء حقل 'الرمز المختصر'." + +#. module: account_chart_of_accounts +#: model_terms:ir.ui.view,arch_db:account_chart_of_accounts.res_config_settings_view_form_inherit +msgid "Cash Account Prefix" +msgstr "بادئة حساب النقدية" + +#. module: account_chart_of_accounts +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_res_config_settings__cash_account_code_prefix +msgid "Cash Prefix" +msgstr "بادئة النقدية" + +#. module: account_chart_of_accounts +#. odoo-python +#: code:addons/account_chart_of_accounts/models/res_config_settings.py:0 +msgid "Chart of Accounts" +msgstr "دليل الحسابات" + +#. module: account_chart_of_accounts +#: model_terms:ir.ui.view,arch_db:account_chart_of_accounts.res_config_settings_view_form_inherit +msgid "Chart of Accounts Hierarchy" +msgstr "التسلسل الهرمي لدليل الحسابات" + +#. module: account_chart_of_accounts +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_res_company__chart_account_padding +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_res_config_settings__chart_account_padding +msgid "Chart of accounts Padding" +msgstr "حشو دليل الحسابات" + +#. module: account_chart_of_accounts +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_res_company__chart_account_length +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_res_config_settings__chart_account_length +msgid "Chart of accounts length" +msgstr "طول دليل الحسابات" + +#. module: account_chart_of_accounts +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_account_account__child_ids +msgid "Child Accounts" +msgstr "الحسابات الفرعية" + +#. module: account_chart_of_accounts +#: model_terms:ir.ui.view,arch_db:account_chart_of_accounts.view_account_form_inherit_hierarchy +msgid "Code" +msgstr "الكود" + +#. module: account_chart_of_accounts +#: model:ir.model,name:account_chart_of_accounts.model_res_company +msgid "Companies" +msgstr "الشركات" + +#. module: account_chart_of_accounts +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_account_account__company_id +msgid "Company" +msgstr "الشركة" + +#. module: account_chart_of_accounts +#: model:ir.model,name:account_chart_of_accounts.model_res_config_settings +msgid "Config Settings" +msgstr "تهيئة الإعدادات " + +#. module: account_chart_of_accounts +#: model:ir.model.fields,help:account_chart_of_accounts.field_account_report__filter_show_full_hierarchy +msgid "Enable filter to display accounts in full hierarchical structure" +msgstr "تفعيل الفلتر لعرض الحسابات في هيكل هرمي كامل" + +#. module: account_chart_of_accounts +#. odoo-javascript +#: code:addons/account_chart_of_accounts/static/src/js/account_type_selection_extend.js:0 +msgid "Equity" +msgstr "حقوق الملكية" + +#. module: account_chart_of_accounts +#. odoo-javascript +#: code:addons/account_chart_of_accounts/static/src/js/account_type_selection_extend.js:0 +msgid "Expense" +msgstr "المصروفات" + +#. module: account_chart_of_accounts +#. odoo-javascript +#: code:addons/account_chart_of_accounts/static/src/xml/filter_full_hierarchy.xml:0 +msgid "Full Hierarchy" +msgstr "التسلسل الهرمي الكامل" + +#. module: account_chart_of_accounts +#: model_terms:ir.ui.view,arch_db:account_chart_of_accounts.view_account_search_inherit +msgid "Hierarchical Chart" +msgstr "الدليل الهرمي" + +#. module: account_chart_of_accounts +#. odoo-javascript +#: code:addons/account_chart_of_accounts/static/src/js/account_type_selection_extend.js:0 +msgid "Income" +msgstr "الإيرادات" + +#. module: account_chart_of_accounts +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_account_account__internal_group +msgid "Internal Group" +msgstr "مجموعة داخلية" + +#. module: account_chart_of_accounts +#: model:ir.model,name:account_chart_of_accounts.model_account_journal +msgid "Journal" +msgstr "دفتر اليومية" + +#. module: account_chart_of_accounts +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_account_account__level +msgid "Level" +msgstr "المستوى" + +#. module: account_chart_of_accounts +#. odoo-javascript +#: code:addons/account_chart_of_accounts/static/src/js/account_type_selection_extend.js:0 +msgid "Liabilities" +msgstr "الخصوم" + +#. module: account_chart_of_accounts +#: model_terms:ir.ui.view,arch_db:account_chart_of_accounts.res_config_settings_view_form_inherit +msgid "Number of digits for account code padding" +msgstr "عدد الأرقام لحشو كود الحساب" + +#. module: account_chart_of_accounts +#. odoo-javascript +#: code:addons/account_chart_of_accounts/static/src/js/account_type_selection_extend.js:0 +msgid "Other" +msgstr "أخرى" + +#. module: account_chart_of_accounts +#. odoo-python +#: code:addons/account_chart_of_accounts/models/account_journal.py:0 +msgid "Outstanding Payments" +msgstr "مدفوعات معلقة" + +#. module: account_chart_of_accounts +#. odoo-python +#: code:addons/account_chart_of_accounts/models/account_journal.py:0 +msgid "Outstanding Receipts" +msgstr "مقبوضات معلقة" + +#. module: account_chart_of_accounts +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_account_account__parent_id +msgid "Parent Account" +msgstr "الحساب الأب" + +#. module: account_chart_of_accounts +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_res_company__parent_bank_cash_account_id +msgid "Parent Bank Cash Account" +msgstr "حساب البنك النقدي الأب" + +#. module: account_chart_of_accounts +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_account_account__parent_path +msgid "Parent Path" +msgstr "المسار الأب" + +#. module: account_chart_of_accounts +#: model_terms:ir.ui.view,arch_db:account_chart_of_accounts.res_config_settings_view_form_inherit +msgid "Prefix for bank account codes" +msgstr "بادئة لأكواد حسابات البنك" + +#. module: account_chart_of_accounts +#: model_terms:ir.ui.view,arch_db:account_chart_of_accounts.res_config_settings_view_form_inherit +msgid "Prefix for cash account codes" +msgstr "بادئة لأكواد حسابات النقدية" + +#. module: account_chart_of_accounts +#. odoo-javascript +#: code:addons/account_chart_of_accounts/static/src/js/account_type_selection_extend.js:0 +msgid "Profit & Loss" +msgstr "الأرباح والخسائر" + +#. module: account_chart_of_accounts +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_account_report__filter_show_full_hierarchy +msgid "Show Full Hierarchy Filter" +msgstr "عرض فلتر التسلسل الهرمي الكامل" + +#. module: account_chart_of_accounts +#. odoo-python +#: code:addons/account_chart_of_accounts/models/account_account.py:0 +msgid "This account level is greater than the chart of account length." +msgstr "مستوى هذا الحساب أكبر من طول دليل الحسابات." + +#. module: account_chart_of_accounts +#: model:ir.model,name:account_chart_of_accounts.model_account_trial_balance_report_handler +msgid "Trial Balance Custom Handler" +msgstr "معالج مخصص لميزان المراجعة " + +#. module: account_chart_of_accounts +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_account_account__account_type +msgid "Type" +msgstr "النوع" + +#. module: account_chart_of_accounts +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_res_company__useFiexedTree +#: model:ir.model.fields,field_description:account_chart_of_accounts.field_res_config_settings__useFiexedTree +msgid "Use Fixed Length Chart of accounts" +msgstr "استخدام دليل حسابات بطول ثابت" + +#. module: account_chart_of_accounts +#: model_terms:ir.ui.view,arch_db:account_chart_of_accounts.res_config_settings_view_form_inherit +msgid "Use fixed length chart of accounts structure" +msgstr "استخدام هيكل دليل حسابات بطول ثابت" + +#. module: account_chart_of_accounts +#: model:ir.model.fields.selection,name:account_chart_of_accounts.selection__account_account__account_type__view +#: model:ir.model.fields.selection,name:account_chart_of_accounts.selection__account_account__internal_group__view +msgid "View" +msgstr "إجمالي" + +#. module: account_chart_of_accounts +#: model_terms:ir.ui.view,arch_db:account_chart_of_accounts.view_account_form_inherit_hierarchy +msgid "e.g. 101000" +msgstr "مثال: 101000" \ No newline at end of file diff --git a/dev_odex30_accounting/account_chart_of_accounts/models/__pycache__/account_account.cpython-311.pyc b/dev_odex30_accounting/account_chart_of_accounts/models/__pycache__/account_account.cpython-311.pyc index 9787f8426bebcbde819e71d7ed3400c4e0d26741..4be39215ecd12599ce104ef3b9ae2df37f73b5e8 100644 GIT binary patch delta 1855 zcmZ`)UrbY17(b___tJk9kWmC~JqQ`%v_J^Lx4K~wxR zI8Fo1QRBq#CYx&<27HZm)qYuX0?DS^!VLpLx8Tcx^>H9`o@ zi7_KR7-I?1$$qRVcg>Z?N(a!f?m2VJJYgzYT4EO38WY)kZFNb^G9j>g^))O}C&oq2 z70h~p0Dgnpoz@FIC(xWVCO}xKhb`Je3SJ?7(2D1|D@e8gUs}*!K_45#!t)ZKA&-0A z*_LPX+_MFRv22vBSB-Nv*rltmezU&*%~qRi(^qU=GP5CTRXk?fSRafoXflpjW&2gb zoIPeAMlEG$zqat>t?>;JmwAhD}!t*}Ehb zT5uNoH6D@V?^Y#{XS>;=YujOWuNr|0YkffYPX%Ou-x4Ub7Xjwc<@T#RiFnF6yxe}f z@9xOzOZ`AU#NS&})xp&0k@RSA9#Osz$`?lY70Y4*%LuSj(kn&_?PSZ+mKHBphBRNF=8UyAhsU_ zoe6Kwgq)J@_!LrCRxJn_XLAl&%ndp$46ap67bh|U`yRZNB7vo@>%A%R8vO1wf4(D6 zaU4089tmVRd()lXlr(hta9VPwrS?>t)c$a9PAqw$EQbIV_U3G;d`k{t`ykFVzWk`M zYqhcKu6WOK|MitdZ@SUTCW(Xv_CDDvyb2Yjee4V3$78IfLBbPkSHt!oJ}cnXek5r6x#qO43RVOgWQxTuGVOs68UqS8Sw;5Ufw;J|y9o&(6*eZHXT*R-Gl z4djChxI6h>L)_5TVXDrd4e;2A1jqR!Il~5N!HAu18mpCgSiQ;l|I%Njy^wrLRr=@> c`>koLf7XB@=zn8kf+R-}q$I_?5w+B~bga=I!LYhHs6g3i319SdprnO>!U(60=LO44cXLI}7(JCX}QP zO6ot!j>4}rN0I^fn$~aWMA=y-0*1K_%%}%l2$sF@ciK|e)T$JSx25l?mw9TL$Dnq^ z9V+~-|3N|ZW0=*9z-Iyz@bmgf zFNYkF%5r$5AdVg7eEdUEwfsRcmGaS`5@$>m+!m99+=uPf?-*7!L$cIk^_GGlzCrI{ z%d3(^p5m3@V_33U;Hb^)SM%yHF_q5KJUv%&%mdt6Z%%a_&ue%J#_X0JmOd}cYL*d) zr+7^d?$1b_isrSv7PQI&^()#_(HCl+;mE4m^IF@jcQP|XUdOA@1VCQT>rXY2O&t#! zn9q9m30BMN$L8=B$?ZPzP?@F>7bq|zz16s|-!$~!t8Nh!h z#T!_?JPF+<4deFO3x+Kc?F@Pk8%f;BvLvr#4G$D~BX1lhe8xB7y2IPzQ-W5+0q&hbbZy^xP@l-0dnB;E7;)z6NA)U*LCb^HjzZAO}2V6QQ>M`f!v--%A{~^-xbb`y~ zG63*cjyg(FEyhy|oJavKyO7EO`KaL1#Rr99%!eJqqeGL2TBcpIkJjkSzI42*&~8IQluLWtoBt?S!b09ohDeeIUjXzbZ_QM zwhqD8v3m8u)+`aUAt=L|x?F7)sxL@{+8iu1%>v^Wn84a_+26P45AXWJPou@Lw@Uu& zg8zE)Mx^AAtX!z>i$GVawQ1)CBJRR$_q#|q{IqkWmC8}yxsd60}m{#MGVRh)B&VDyp z?!WTmr6M!E8T_KV$n>ITyY7iiwyt8YI35+or^|sZArLM)uB}G|$18%Pt>|dmX_C|x zwML0RusSH|iAtwLkcL6B>}lWg1b02b?ZqccPhBNXSn!15Pc8>Z!#`YZ3KuDO6&l=e zasp=D4l)TpbzfW&HHq0|ij5`NESST4svb_~xET(BC{DmPgs5h@n_LLiZXo*6bC#h&_QPoMr zIe4ZdDAPw#NpUWEOoBf*O L{`S`k7_#ucVGx>t diff --git a/dev_odex30_accounting/account_chart_of_accounts/models/account_account.py b/dev_odex30_accounting/account_chart_of_accounts/models/account_account.py index 0b1a7ed..0ed4c60 100644 --- a/dev_odex30_accounting/account_chart_of_accounts/models/account_account.py +++ b/dev_odex30_accounting/account_chart_of_accounts/models/account_account.py @@ -139,49 +139,56 @@ class AccountAccount(models.Model): @api.model def search_panel_select_range(self, field_name, **kwargs): - + """ + Override to show hierarchical accounts in search panel. + Root level: Only show view accounts with children + Sub-levels: Show all children when parent is expanded + """ if field_name != 'parent_id': return super().search_panel_select_range(field_name, **kwargs) - enable_counters = kwargs.get('enable_counters', False) search_domain = kwargs.get('search_domain', []) + # Get ALL accounts for hierarchy + all_accounts = self.search([], order='code') + + # Filter accounts to show: + # - Root level (parent_id = False): ONLY view accounts WITH children + # - Sub levels: ALL accounts (shown when parent is expanded) + accounts_to_show = all_accounts.filtered( + lambda a: (not a.parent_id and a.account_type == 'view' and a.child_ids) or a.parent_id + ) - all_view_accounts = self.search([('account_type', '=', 'view')], order='code') - - count_by_parent = {} + count_by_account = {} if enable_counters: - all_accounts = self.search(search_domain) - - for account in all_accounts: - if account.parent_id: - parent_id = account.parent_id.id - count_by_parent[parent_id] = count_by_parent.get(parent_id, 0) + 1 - - ancestor = account.parent_id.parent_id - while ancestor: - count_by_parent[ancestor.id] = count_by_parent.get(ancestor.id, 0) + 1 - ancestor = ancestor.parent_id - + # Count records that match the search domain for each account + filtered_accounts = self.search(search_domain) + + for account in filtered_accounts: + # ✅ ONLY count in parent (and ancestors), not self + parent = account.parent_id + while parent: + count_by_account[parent.id] = count_by_account.get(parent.id, 0) + 1 + parent = parent.parent_id values = [] - for parent in all_view_accounts: + for account in accounts_to_show: value = { - 'id': parent.id, - 'display_name': f"{parent.code} {parent.name}" if parent.code else parent.name, - 'parent_id': parent.parent_id.id if parent.parent_id else False, + 'id': account.id, + 'display_name': f"{account.code} {account.name}" if account.code else account.name, + 'parent_id': account.parent_id.id if account.parent_id else False, } if enable_counters: - value['__count'] = count_by_parent.get(parent.id, 0) + value['__count'] = count_by_account.get(account.id, 0) values.append(value) - result = { 'parent_field': 'parent_id', 'values': values, } return result + diff --git a/dev_odex30_accounting/account_chart_of_accounts/static/src/js/account_list_renderer.js b/dev_odex30_accounting/account_chart_of_accounts/static/src/js/account_list_renderer.js new file mode 100644 index 0000000..d4998fc --- /dev/null +++ b/dev_odex30_accounting/account_chart_of_accounts/static/src/js/account_list_renderer.js @@ -0,0 +1,31 @@ +/** @odoo-module **/ + +import { ListRenderer } from "@web/views/list/list_renderer"; +import { patch } from "@web/core/utils/patch"; + +patch(ListRenderer.prototype, { + /** + * Add custom class to rows with account_type='view' to make them bold + */ + setup() { + super.setup(...arguments); + }, + + async onCellClicked(record, column, ev) { + await super.onCellClicked(...arguments); + }, + + getRowClass(record) { + const classes = super.getRowClass(record) || ""; + + // Add bold class if account_type is 'view' + if (this.props.list.resModel === 'account.account') { + const accountType = record.data.account_type; + if (accountType === 'view') { + return `${classes} fw-bold account-view-type`; + } + } + + return classes; + } +}); diff --git a/dev_odex30_accounting/account_chart_of_accounts/static/src/js/search_panel_bold.js b/dev_odex30_accounting/account_chart_of_accounts/static/src/js/search_panel_bold.js new file mode 100644 index 0000000..45aa5fb --- /dev/null +++ b/dev_odex30_accounting/account_chart_of_accounts/static/src/js/search_panel_bold.js @@ -0,0 +1,61 @@ +/** @odoo-module **/ + +import { patch } from "@web/core/utils/patch"; +import { SearchPanel } from "@web/search/search_panel/search_panel"; + +patch(SearchPanel.prototype, { + /** + * Override to add bold class to view accounts with children in search panel + */ + setup() { + super.setup(...arguments); + }, + + /** + * After rendering, add bold class to accounts with toggle button + */ + async onMounted() { + if (super.onMounted) { + await super.onMounted(...arguments); + } + this.makeBoldAccountsWithChildren(); + }, + + /** + * Make accounts with children (that have toggle button) bold + */ + makeBoldAccountsWithChildren() { + // CRITICAL: HTML uses class "account_root" not "account_account" + const searchPanel = document.querySelector('.o_search_panel.account_root'); + if (!searchPanel) return; + + // Find ALL category values + const allItems = searchPanel.querySelectorAll('.o_search_panel_category_value'); + + allItems.forEach(item => { + // Check if this item has a toggle button (means it has children) + const toggleButton = item.querySelector('.o_toggle_fold'); + + // Only make bold if it has a toggle button (has children) + if (toggleButton && toggleButton.querySelector('i.fa-caret-down, i.fa-caret-right')) { + // Add bold class + item.classList.add('has-children-bold'); + + // Also add to header and label + const header = item.querySelector('header'); + const label = item.querySelector('.o_search_panel_label_title'); + + if (header) header.classList.add('has-children-bold'); + if (label) label.classList.add('has-children-bold'); + } else { + // Remove bold class if it doesn't have children + item.classList.remove('has-children-bold'); + const header = item.querySelector('header'); + const label = item.querySelector('.o_search_panel_label_title'); + + if (header) header.classList.remove('has-children-bold'); + if (label) label.classList.remove('has-children-bold'); + } + }); + }, +}); diff --git a/dev_odex30_accounting/account_chart_of_accounts/static/src/scss/account_hierarchy.scss b/dev_odex30_accounting/account_chart_of_accounts/static/src/scss/account_hierarchy.scss index bb42e73..94adca9 100644 --- a/dev_odex30_accounting/account_chart_of_accounts/static/src/scss/account_hierarchy.scss +++ b/dev_odex30_accounting/account_chart_of_accounts/static/src/scss/account_hierarchy.scss @@ -1,5 +1,6 @@ -.o_search_panel.account_account { - .o_search_panel_field.parent_id { +// CRITICAL: HTML uses class "account_root" not "account_account" +.o_search_panel.account_root { + .o_search_panel_field { .o_search_panel_category_value { .o_toggle_fold { margin-right: 15px; @@ -11,9 +12,9 @@ align-items: center; padding: 3px 8px; - &:hover { - color: #f0f0f0; - } +// &:hover { +// color: #f0f0f0; +// } .o_search_panel_label_title { flex: 1; @@ -25,15 +26,64 @@ } } - // Levels - &[data-level="0"] { - font-weight: bold; + // ONLY use JavaScript-added class (not :has() selector) + // Only accounts with actual toggle buttons get this class + &.has-children-bold { + font-weight: 900 !important; + + header, + .o_search_panel_label_title { + font-weight: 900 !important; + } } - &[data-level="1"] header { padding-left: 20px; } - &[data-level="2"] header { padding-left: 35px; } - &[data-level="3"] header { padding-left: 50px; } - &[data-level="4"] header { padding-left: 65px; } + // Levels indentation + &[data-level="1"] header { + padding-left: 20px; + } + + &[data-level="2"] header { + padding-left: 35px; + } + + &[data-level="3"] header { + padding-left: 50px; + } + + &[data-level="4"] header { + padding-left: 65px; + } + + &[data-level="5"] header { + padding-left: 80px; + } + + &[data-level="6"] header { + padding-left: 95px; + } } } } + +// Make view accounts bold - using custom class from JavaScript +.o_list_view { + + tr.o_data_row.account-view-type, + tr.o_data_row.fw-bold { + font-weight: 900 !important; + + td.o_data_cell { + font-weight: 900 !important; + // Add text shadow to make it appear even bolder + // Slightly darker color for more contrast + color: #2c3e50 !important; + } + + // Optional: Add background color like Odoo 14 + // Uncomment the lines below if you want a background color + // background-color: #f5f5f5 !important; + // &:hover { + // background-color: #ebebeb !important; + // } + } +} \ No newline at end of file diff --git a/dev_odex30_accounting/account_chart_of_accounts/views/account_account_view.xml b/dev_odex30_accounting/account_chart_of_accounts/views/account_account_view.xml index 2565afb..8bdeac0 100644 --- a/dev_odex30_accounting/account_chart_of_accounts/views/account_account_view.xml +++ b/dev_odex30_accounting/account_chart_of_accounts/views/account_account_view.xml @@ -75,6 +75,11 @@ + + + + account_type_field + diff --git a/dev_odex30_accounting/accountant/__init__.py b/dev_odex30_accounting/accountant/__init__.py new file mode 100644 index 0000000..9067a53 --- /dev/null +++ b/dev_odex30_accounting/accountant/__init__.py @@ -0,0 +1,67 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging + +from . import models + +_logger = logging.getLogger(__name__) + + +def _accounting_post_init(env): + country_code = env.company.country_id.code + if country_code: + module_list = [] + + if country_code in ('AU', 'CA', 'US'): + module_list.append('account_reports_cash_basis') + + module_ids = env['ir.module.module'].search([('name', 'in', module_list), ('state', '=', 'uninstalled')]) + if module_ids: + module_ids.sudo().button_install() + + +def uninstall_hook(env): + try: + group_user = env.ref("account.group_account_user") + group_user.write({ + 'name': "Show Full Accounting Features", + 'implied_ids': [(3, env.ref('account.group_account_invoice').id)], + 'category_id': env.ref("base.module_category_hidden").id, + }) + group_readonly = env.ref("account.group_account_readonly") + group_readonly.write({ + 'name': "Show Full Accounting Features - Readonly", + 'category_id': env.ref("base.module_category_hidden").id, + }) + except ValueError as e: + _logger.warning(e) + + try: + group_manager = env.ref("account.group_account_manager") + group_manager.write({'name': "Billing Manager", + 'implied_ids': [(4, env.ref("account.group_account_invoice").id), + (3, env.ref("account.group_account_readonly").id), + (3, env.ref("account.group_account_user").id)]}) + except ValueError as e: + _logger.warning(e) + + # make the account_accountant features disappear (magic) + env.ref("account.group_account_user").write({'users': [(5, False, False)]}) + env.ref("account.group_account_readonly").write({'users': [(5, False, False)]}) + + # this menu should always be there, as the module depends on account. + # if it's not, there is something wrong with the db that should be investigated. + invoicing_menu = env.ref("account.menu_finance") + menus_to_move = [ + "account.menu_finance_receivables", + "account.menu_finance_payables", + "account.menu_finance_entries", + "account.menu_finance_reports", + "account.menu_finance_configuration", + "account.menu_board_journal_1", + ] + for menu_xmlids in menus_to_move: + try: + env.ref(menu_xmlids).parent_id = invoicing_menu + except ValueError as e: + _logger.warning(e) diff --git a/dev_odex30_accounting/accountant/__manifest__.py b/dev_odex30_accounting/accountant/__manifest__.py new file mode 100644 index 0000000..676fd80 --- /dev/null +++ b/dev_odex30_accounting/accountant/__manifest__.py @@ -0,0 +1,37 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. +{ + 'name': 'Accounting', + 'version': '1.1', + 'category': 'Accounting/Accounting', + 'sequence': 30, + 'summary': 'Manage financial and analytic accounting', + 'description': """ +Accounting Access Rights +======================== +It gives the Administrator user access to all accounting features such as journal items and the chart of accounts. + +It assigns manager and user access rights to the Administrator for the accounting application and only user rights to the Demo user. +""", + 'website': 'https://www.odoo.com/app/accounting', + 'depends': ['odex30_account_accountant'], + 'data': [ + 'data/account_accountant_data.xml', + 'security/accounting_security.xml', + 'views/res_config_settings.xml', + 'views/partner_views.xml', + ], + 'demo': ['demo/account_accountant_demo.xml'], + 'installable': True, + 'application': True, + 'post_init_hook': '_accounting_post_init', + 'uninstall_hook': "uninstall_hook", + 'license': 'OEEL-1', + 'assets': { + 'web.assets_backend': [ + 'accountant/static/src/js/tours/accountant.js', + ], + 'web.assets_tests': [ + 'accountant/static/tests/tours/*', + ], + } +} diff --git a/dev_odex30_accounting/accountant/data/account_accountant_data.xml b/dev_odex30_accounting/accountant/data/account_accountant_data.xml new file mode 100644 index 0000000..fbc7362 --- /dev/null +++ b/dev_odex30_accounting/accountant/data/account_accountant_data.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev_odex30_accounting/accountant/demo/account_accountant_demo.xml b/dev_odex30_accounting/accountant/demo/account_accountant_demo.xml new file mode 100644 index 0000000..b5e67c6 --- /dev/null +++ b/dev_odex30_accounting/accountant/demo/account_accountant_demo.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/dev_odex30_accounting/accountant/i18n/accountant.pot b/dev_odex30_accounting/accountant/i18n/accountant.pot new file mode 100644 index 0000000..ff8a02c --- /dev/null +++ b/dev_odex30_accounting/accountant/i18n/accountant.pot @@ -0,0 +1,28 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * accountant +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 09:51+0000\n" +"PO-Revision-Date: 2024-12-19 09:51+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: accountant +#: model:ir.ui.menu,name:accountant.menu_accounting +#: model_terms:ir.ui.view,arch_db:accountant.res_config_settings_view_form +#: model_terms:ir.ui.view,arch_db:accountant.res_partner_view_form +msgid "Accounting" +msgstr "" + +#. module: accountant +#: model:ir.model,name:accountant.model_account_move +msgid "Journal Entry" +msgstr "" diff --git a/dev_odex30_accounting/accountant/i18n/ar.po b/dev_odex30_accounting/accountant/i18n/ar.po new file mode 100644 index 0000000..eec72f5 --- /dev/null +++ b/dev_odex30_accounting/accountant/i18n/ar.po @@ -0,0 +1,32 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * accountant +# +# Translators: +# Wil Odoo, 2024 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-12-19 09:51+0000\n" +"PO-Revision-Date: 2024-09-25 09:44+0000\n" +"Last-Translator: Wil Odoo, 2024\n" +"Language-Team: Arabic (https://app.transifex.com/odoo/teams/41243/ar/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Language: ar\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#. module: accountant +#: model:ir.ui.menu,name:accountant.menu_accounting +#: model_terms:ir.ui.view,arch_db:accountant.res_config_settings_view_form +#: model_terms:ir.ui.view,arch_db:accountant.res_partner_view_form +msgid "Accounting" +msgstr "المحاسبة " + +#. module: accountant +#: model:ir.model,name:accountant.model_account_move +msgid "Journal Entry" +msgstr "قيد اليومية" diff --git a/dev_odex30_accounting/accountant/models/__init__.py b/dev_odex30_accounting/accountant/models/__init__.py new file mode 100644 index 0000000..9c0a421 --- /dev/null +++ b/dev_odex30_accounting/accountant/models/__init__.py @@ -0,0 +1 @@ +from . import account_move diff --git a/dev_odex30_accounting/accountant/models/account_move.py b/dev_odex30_accounting/accountant/models/account_move.py new file mode 100644 index 0000000..b31f91a --- /dev/null +++ b/dev_odex30_accounting/accountant/models/account_move.py @@ -0,0 +1,9 @@ +from odoo import models + + +class AccountMove(models.Model): + _inherit = 'account.move' + + def _get_invoice_in_payment_state(self): + # OVERRIDE to enable the 'in_payment' state on invoices. + return 'in_payment' diff --git a/dev_odex30_accounting/accountant/security/accounting_security.xml b/dev_odex30_accounting/accountant/security/accounting_security.xml new file mode 100644 index 0000000..ae6944f --- /dev/null +++ b/dev_odex30_accounting/accountant/security/accounting_security.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + Accounting + Helps you handle your invoices and accounting actions. + + Invoicing: Invoices, payments and basic invoice reporting. + Invoicing & Banks: adds the accounting dashboard, bank management and follow-up reports. + Bookkeeper: access to all Accounting features, including reporting, asset management, analytic accounting, without configuration rights. + Administrator: full access including configuration rights and accounting data management. + Readonly: access to all the accounting data but in readonly mode, no actions allowed. + + + + + Read-only + + + + + Bookkeeper + + + + + + + + diff --git a/dev_odex30_accounting/accountant/static/description/icon.png b/dev_odex30_accounting/accountant/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e64a0f250033fcd887f372076d3f00798f94fffc GIT binary patch literal 2217 zcmV;a2v+xrP)K`U~15r$A=Y@sDSj!_|T zOA6dVJ}ma%T3&$$CS{D6REn`8>!a1@s=gO&B=1{TBI2bqok<%qc9XRDGL}O;5wvob zAt6D(#Dv(Ep-?$x8{Xw{uQMV<yzutiPMsWjA6Pyh#kbii;1(?FlH&)}B`(TPXy*_QVIA%va|wN8zkj+a zL!n<}39iFI1mTfFT$Z8G79uZd0S=-ncfMTz#0_^{hQdIlPR63Rrs*FpRY&{NAVZ-g zL{;c;nQ##DpB5PkEg@bM=H3w;$27@M7!NMG1P?Y-BSWDjBov;JaJMC>+daB4O)?Zd zG$@z1w}M^?E^=?Phd;gj`Il@CiJF$7(8?jSBnO0uoy#Rk#?@clMel*u=j}jj$WZ9l zArTo(kS-$`52Oq#Jq8=tlA+KR5;4C6N7#|E9Z4C-U;~>n6xu@M0oLIFyJaY7_HmfT zwhV=~kT{~s`AXR<12h9Fn-!9U426+?2s3h<;`xva40DH+Vv4L}D2%lVd)FHsooS+J zc$5r<_qlJ$ha)gSmNFE^dg>JMX=KQLGU)K))XGp86EYZ`<2Ev2KN%25AP$=eKIb7r zVKkQI&jhDAo(OG^ePzfx!<&B&Cdf;M!l)3{a}OhL$(EFTWk|$lzG9@P%2S5Ic(ix! zzi*mPg7Mwxnq8DVV?|8H!97eGZnl;fy?ObKGkm zk|A@1C_iOPgu*o$icE!!P}6h5LtZ@!MV$l|m{8rFd*0u~*6)A)pUQ)v0wG1jaVdkL zkT?wv%24DYp(mm>ZkMXZ&Ac%Nqd31#eo=#mt3Uid-hy=NM*LGD6q&6~O-BD_BL0QbaTG7y!e zj2ARye3F(Fat;>ARfwfEeR1*ho7vvZbB1M%&UE>HQ2KsZHV#Y{VYvyhCFAOBA8*d| zcb>B&W9R6dDDa-$20B}Yq~tPl(N-~)tqcV}DN4a_<<2||k+~3CGTxOHceXNyFluvt z@VhVGz6KLyD#VryRdHXB{Ly5XQ3+@mReIuArjutU?8%ci4dDI zR8gh=6gkNlF;9MQ@z`JAf)3V&*j}G;b0lLnR~dse42rHgG_Wefwv4}dA^zn>`aN)S z5P_Q&jk?5|kSta%y;}-hk`Y6o??U5xtq93#8QI?tlT$K=RLXvkdM=W2Az8`z_aP7T zokiDVDB@xZDKRP}%LSHyUVi+XgEC@>`3a=Nn2@YjyWUMv6;A|#Kc$FlNt zcS$yLewBJ&>%b$hyF&76Wlh_n{AnPyki2D(tH8Upr7d=3N%mfXNW6oTBog9mYbwGO zIEKs}-S=zD_P#I6<)-ayru^(Q7#xdZ+FAECim zAs+EBT<-ME1pWnikPnbQEQ5+~Y}QV8p}|-oA93(adDAvv0TVs&xN>KlN6yIH(Rpv- zg!c5(u|m$aTU*;495*e+WH{vRLgRuwp8g!1^e~6vvoJuSR+ecgHeyp81m9DiQe+P^ z;jC^^H6n38#FP|UfkJgd)TcCKDU2vOclYUrv4+J&J#}3v8ANS68A^#|n5Z{)lWO++ zO0!idldqb>wWROKDujM)V^wXQ6uz$}K_^b%M95a1v~l|Gmroe)n5JHiFo~2r#xuS& zm_gW*C;ySWN>j7bS%qs diff --git a/dev_odex30_accounting/accountant/static/src/js/tours/accountant.js b/dev_odex30_accounting/accountant/static/src/js/tours/accountant.js new file mode 100644 index 0000000..cc316e8 --- /dev/null +++ b/dev_odex30_accounting/accountant/static/src/js/tours/accountant.js @@ -0,0 +1,12 @@ +/** @odoo-module **/ + +import { patch } from "@web/core/utils/patch"; +import { accountTourSteps } from "@account/js/tours/account"; +import { stepUtils } from "@web_tour/tour_service/tour_utils"; + + +patch(accountTourSteps, { + goToAccountMenu(description="Open Accounting Menu") { + return stepUtils.goToAppSteps('accountant.menu_accounting', description); + }, +}); diff --git a/dev_odex30_accounting/accountant/static/tests/tours/account_merge_wizard_tour.js b/dev_odex30_accounting/accountant/static/tests/tours/account_merge_wizard_tour.js new file mode 100644 index 0000000..0b338d8 --- /dev/null +++ b/dev_odex30_accounting/accountant/static/tests/tours/account_merge_wizard_tour.js @@ -0,0 +1,61 @@ +/** @odoo-module */ + +import { accountTourSteps } from "@account/js/tours/account"; +import { registry } from "@web/core/registry"; + +registry.category("web_tour.tours").add("account_merge_wizard_tour", { + url: "/odoo", + steps: () => [ + ...accountTourSteps.goToAccountMenu("Go to Accounting"), + { + content: "Go to Configuration", + trigger: 'span:contains("Configuration")', + run: "click", + }, + { + content: "Go to Chart of Accounts", + trigger: 'a:contains("Chart of Accounts")', + run: "click", + }, + { + trigger: '.o_breadcrumb .text-truncate:contains("Chart of Accounts")', + }, + { + content: "Select accounts", + trigger: "thead .o_list_record_selector", + run: "click", + }, + { + content: "Check that exactly 4 accounts are present and selected", + trigger: ".o_list_selection_box:contains(4):contains(selected)", + }, + { + content: "Open Actions menu", + trigger: ".o_cp_action_menus .dropdown-toggle", + run: "click", + }, + { + content: "Open Merge accounts wizard", + trigger: 'span:contains("Merge accounts")', + run: "click", + }, + { + content: "Group by name", + trigger: 'div[name="is_group_by_name"] input', + run: "click", + }, + { + content: "Wait for content to be updated", + trigger: 'td:contains("Current Assets (Current Assets)")', + }, + { + content: "Merge accounts", + trigger: 'button:not([disabled]) span:contains("Merge")', + run: "click", + }, + { + content: "Check that there are now exactly 2 accounts", + trigger: ".o_pager_limit:contains(2)", + }, + ], +}); diff --git a/dev_odex30_accounting/accountant/static/tests/tours/account_tours.js b/dev_odex30_accounting/accountant/static/tests/tours/account_tours.js new file mode 100644 index 0000000..aec8e4f --- /dev/null +++ b/dev_odex30_accounting/accountant/static/tests/tours/account_tours.js @@ -0,0 +1,10 @@ +/** @odoo-module **/ +import { patch } from "@web/core/utils/patch"; +import { accountTourSteps } from "@account/js/tours/account"; +import { stepUtils } from "@web_tour/tour_service/tour_utils"; + +patch(accountTourSteps, { + goToAccountMenu(description="Open Accounting Menu") { + return stepUtils.goToAppSteps('accountant.menu_accounting', description); + } +}); diff --git a/dev_odex30_accounting/accountant/tests/__init__.py b/dev_odex30_accounting/accountant/tests/__init__.py new file mode 100644 index 0000000..f49429e --- /dev/null +++ b/dev_odex30_accounting/accountant/tests/__init__.py @@ -0,0 +1 @@ +from . import test_tour diff --git a/dev_odex30_accounting/accountant/tests/test_tour.py b/dev_odex30_accounting/accountant/tests/test_tour.py new file mode 100644 index 0000000..3033b44 --- /dev/null +++ b/dev_odex30_accounting/accountant/tests/test_tour.py @@ -0,0 +1,48 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import odoo.tests + +from odoo import Command +from odoo.addons.account.tests.common import AccountTestInvoicingHttpCommon + + +@odoo.tests.tagged('post_install_l10n', 'post_install', '-at_install') +class TestAccountantTours(AccountTestInvoicingHttpCommon): + def test_account_merge_wizard_tour(self): + companies = self.env['res.company'].create([ + {'name': 'tour_company_1'}, + {'name': 'tour_company_2'}, + ]) + + self.env['account.account'].create([ + { + 'company_ids': [Command.set(companies[0].ids)], + 'code': "100001", + 'name': "Current Assets", + 'account_type': 'asset_current', + }, + { + 'company_ids': [Command.set(companies[0].ids)], + 'code': "100002", + 'name': "Non-Current Assets", + 'account_type': 'asset_non_current', + }, + { + 'company_ids': [Command.set(companies[1].ids)], + 'code': "200001", + 'name': "Current Assets", + 'account_type': 'asset_current', + }, + { + 'company_ids': [Command.set(companies[1].ids)], + 'code': "200002", + 'name': "Non-Current Assets", + 'account_type': 'asset_non_current', + }, + ]) + + self.env.ref('base.user_admin').write({ + 'company_id': companies[0].id, + 'company_ids': [Command.set(companies.ids)], + }) + self.start_tour("/odoo", 'account_merge_wizard_tour', login="admin", cookies={"cids": f"{companies[0].id}-{companies[1].id}"}) diff --git a/dev_odex30_accounting/accountant/views/partner_views.xml b/dev_odex30_accounting/accountant/views/partner_views.xml new file mode 100644 index 0000000..8058394 --- /dev/null +++ b/dev_odex30_accounting/accountant/views/partner_views.xml @@ -0,0 +1,16 @@ + + + + res.partner.form + res.partner + + + + Accounting + + + Accounting + + + + diff --git a/dev_odex30_accounting/accountant/views/res_config_settings.xml b/dev_odex30_accounting/accountant/views/res_config_settings.xml new file mode 100644 index 0000000..fcd7ee4 --- /dev/null +++ b/dev_odex30_accounting/accountant/views/res_config_settings.xml @@ -0,0 +1,15 @@ + + + + res.config.settings.view.form.inherit.accountant + res.config.settings + + + + Accounting + Accounting + /accountant/static/description/icon.png + + + + diff --git a/dev_odex30_accounting/odex30_account_online_sync/__init__.py b/dev_odex30_accounting/odex30_account_online_sync/__init__.py deleted file mode 100644 index bbc5580..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# -*- coding: utf-8 -*- - -from . import controllers -from . import models -from . import wizard diff --git a/dev_odex30_accounting/odex30_account_online_sync/__manifest__.py b/dev_odex30_accounting/odex30_account_online_sync/__manifest__.py deleted file mode 100644 index 5946b4a..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/__manifest__.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- -{ - 'name': "Online Bank Statement Synchronization", - 'summary': """ - This module is used for Online bank synchronization.""", - - 'description': """ - This module is used for Online bank synchronization. It provides basic methods to synchronize bank statement. - """, - 'author': "Expert Co. Ltd.", - 'website': "http://www.exp-sa.com", - 'category': 'Odex30-Accounting/Odex30-Accounting', - 'version': '2.0', - 'depends': ['odex30_account_accountant'], - - 'data': [ - 'data/config_parameter.xml', - 'data/ir_cron.xml', - 'data/mail_activity_type_data.xml', - 'data/sync_reminder_email_template.xml', - - 'security/ir.model.access.csv', - 'security/account_online_sync_security.xml', - - 'views/account_online_sync_views.xml', - 'views/account_bank_statement_view.xml', - 'views/account_journal_view.xml', - 'views/account_online_sync_portal_templates.xml', - 'views/account_journal_dashboard_view.xml', - - 'wizard/account_bank_selection_wizard.xml', - 'wizard/account_journal_missing_transactions.xml', - 'wizard/account_journal_duplicate_transactions.xml', - 'wizard/account_bank_statement_line.xml', - ], - 'license': 'OEEL-1', - 'auto_install': True, - 'assets': { - 'web.assets_backend': [ - 'odex30_account_online_sync/static/src/components/**/*', - 'odex30_account_online_sync/static/src/js/odoo_fin_connector.js', - ], - 'web.assets_frontend': [ - 'odex30_account_online_sync/static/src/js/online_sync_portal.js', - ], - 'web.qunit_suite_tests': [ - 'odex30_account_online_sync/static/tests/helpers/*.js', - 'odex30_account_online_sync/static/tests/*.js', - ], - } -} diff --git a/dev_odex30_accounting/odex30_account_online_sync/__pycache__/__init__.cpython-311.pyc b/dev_odex30_accounting/odex30_account_online_sync/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index e37619d868650a8758440e24b60d06f77f6efb43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 383 zcmZ3^%ge<81lK2oX2t^P#~=<2FhLogwSbK23@HpLj5!Rsj8Tk?AT|?_%@oB1W-|lX z%u&ohHcK#rChJQ^paxCGTinU{c_l^pIXS6C#Xv4wZhlH?4wzM*S(R9n;-|@Si=l`a zsJMs)M6d#hl?!qO45)fXd{I#G>4QqWrAXKVB8#Gm#OLSbWag#D7gy#b>&M4u z=4F<|$LkeT{^GC!y16tb)vgE>OdxL+I|GRi%#4hTAJ`Zer7kc?BBL7&A{S5*rU*nq I5eLv508r>^jsO4v diff --git a/dev_odex30_accounting/odex30_account_online_sync/controllers/__init__.py b/dev_odex30_accounting/odex30_account_online_sync/controllers/__init__.py deleted file mode 100644 index 8c3feb6..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/controllers/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import portal diff --git a/dev_odex30_accounting/odex30_account_online_sync/controllers/__pycache__/__init__.cpython-311.pyc b/dev_odex30_accounting/odex30_account_online_sync/controllers/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 6439cb3207fd5d40dc0dbf311a2f20b4adee7579..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 298 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 Ys#p_-8S3EO>FUsPf9B;I=KU}GCmn@5$N&HU diff --git a/dev_odex30_accounting/odex30_account_online_sync/controllers/portal.py b/dev_odex30_accounting/odex30_account_online_sync/controllers/portal.py deleted file mode 100644 index 9556ea4..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/controllers/portal.py +++ /dev/null @@ -1,58 +0,0 @@ -import json - -from odoo import http -from odoo.http import request -from odoo.addons.portal.controllers.portal import CustomerPortal -from odoo.tools import format_amount, format_date -from odoo.exceptions import AccessError, MissingError, UserError - - -class OnlineSynchronizationPortal(CustomerPortal): - - @http.route(['/renew_consent/'], type='http', auth="public", website=True, sitemap=False) - def portal_online_sync_renew_consent(self, journal_id, access_token=None, **kw): - # Display a page to the user allowing to renew the consent for his bank sync. - # Requires the same rights as the button in odoo. - try: - journal_sudo = self._document_check_access('account.journal', journal_id, access_token) - except (AccessError, MissingError): - return request.redirect('/my') - values = self._prepare_portal_layout_values() - # Ignore the route if the journal isn't one using bank sync. - if not journal_sudo.account_online_account_id: - raise request.not_found() - - balance = journal_sudo.account_online_account_id.balance - if journal_sudo.account_online_account_id.currency_id: - formatted_balance = format_amount(request.env, balance, journal_sudo.account_online_account_id.currency_id) - else: - formatted_balance = format_amount(request.env, balance, journal_sudo.currency_id or journal_sudo.company_id.currency_id) - - values.update({ - 'bank': journal_sudo.bank_account_id.bank_name or journal_sudo.account_online_account_id.name, - 'bank_account': journal_sudo.bank_account_id.acc_number, - 'journal': journal_sudo.name, - 'latest_balance_formatted': formatted_balance, - 'latest_balance': balance, - 'latest_sync': format_date(request.env, journal_sudo.account_online_account_id.last_sync, date_format="MMM dd, YYYY"), - 'iframe_params': json.dumps(journal_sudo.action_extend_consent()), - }) - return request.render("account_online_synchronization.portal_renew_consent", values) - - - @http.route(['/renew_consent//complete'], type='http', auth="public", methods=['POST'], website=True) - def portal_online_sync_action_complete(self, journal_id, access_token=None, **kw): - # Complete the consent renewal process - try: - journal_sudo = self._document_check_access('account.journal', journal_id, access_token) - except (AccessError, MissingError): - return request.redirect('/my') - # Ignore the route if the journal isn't one using bank sync. - if not journal_sudo.account_online_link_id: - raise request.not_found() - try: - journal_sudo.account_online_link_id._update_connection_status() - journal_sudo.manual_sync() - except UserError: - pass - return request.make_response(json.dumps({'status': 'done'})) diff --git a/dev_odex30_accounting/odex30_account_online_sync/data/config_parameter.xml b/dev_odex30_accounting/odex30_account_online_sync/data/config_parameter.xml deleted file mode 100644 index 5a8a8fa..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/data/config_parameter.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - odex30_account_online_sync.proxy_mode - production - - - odex30_account_online_sync.request_timeout - 60 - - - diff --git a/dev_odex30_accounting/odex30_account_online_sync/data/ir_cron.xml b/dev_odex30_accounting/odex30_account_online_sync/data/ir_cron.xml deleted file mode 100644 index 5ed932b..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/data/ir_cron.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - Account: Journal online sync - - code - model._cron_fetch_online_transactions() - - - 12 - hours - - - Account: Journal online Waiting Synchronization - - code - model._cron_fetch_waiting_online_transactions() - - - 5 - minutes - - - - Account: Journal online sync reminder - - code - model._cron_send_reminder_email() - - - 1 - days - - - - Account: Journal online sync cleanup unused connections - - code - model._cron_delete_unused_connection() - - - 1 - days - - - diff --git a/dev_odex30_accounting/odex30_account_online_sync/data/mail_activity_type_data.xml b/dev_odex30_accounting/odex30_account_online_sync/data/mail_activity_type_data.xml deleted file mode 100644 index 0a96bcc..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/data/mail_activity_type_data.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - Bank Synchronization: Update consent - fa-university - warning - account.journal - 0 - - - - Consent Renewal - - - account.journal - - - - - diff --git a/dev_odex30_accounting/odex30_account_online_sync/data/neutralize.sql b/dev_odex30_accounting/odex30_account_online_sync/data/neutralize.sql deleted file mode 100644 index 465aaa7..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/data/neutralize.sql +++ /dev/null @@ -1,4 +0,0 @@ --- disable bank synchronisation links -UPDATE account_online_link - SET provider_data = '', - client_id = 'duplicate'; diff --git a/dev_odex30_accounting/odex30_account_online_sync/data/sync_reminder_email_template.xml b/dev_odex30_accounting/odex30_account_online_sync/data/sync_reminder_email_template.xml deleted file mode 100644 index abf2b28..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/data/sync_reminder_email_template.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - Bank connection expiration reminder - Your bank connection is expiring soon - {{ object.company_id.email_formatted or user.email_formatted }} - {{ object.renewal_contact_email }} - - - - - - - - - - - -
- - - - - - - -
- - - - - - - -
-
- Hello,

- The connection between https://yourcompany.odoo.com and Belfius expired.expires in 10 days.
- - Security Tip: Check that the domain name you are redirected to is: https://yourcompany.odoo.com -
-
-
-
-
-
- - - - -
- Powered by Odoo -
- - - - -
- PS: This is an automated email sent by Odoo Accounting to remind you before a bank sync consent expiration. -
-
-
-
-
-
diff --git a/dev_odex30_accounting/odex30_account_online_sync/i18n/account_online_synchronization.pot b/dev_odex30_accounting/odex30_account_online_sync/i18n/account_online_synchronization.pot deleted file mode 100644 index b437820..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/i18n/account_online_synchronization.pot +++ /dev/null @@ -1,1322 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * odex30_account_online_sync -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 18.0+e\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-07-11 18:50+0000\n" -"PO-Revision-Date: 2025-07-11 18:50+0000\n" -"Last-Translator: \n" -"Language-Team: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: \n" - -#. module: odex30_account_online_sync -#. odoo-python -#: code:addons/odex30_account_online_sync/models/account_online.py:0 -msgid "" -"\n" -"\n" -"If you've already opened a ticket for this issue, don't report it again: a support agent will contact you shortly." -msgstr "" - -#. module: odex30_account_online_sync -#. odoo-python -#: code:addons/odex30_account_online_sync/wizard/account_journal_duplicate_transactions.py:0 -msgid "%s duplicate 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 "" -").\n" -" This might cause duplicate entries." -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 "0 transaction fetched" -msgstr "" - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.account_duplicate_transaction_wizard_view_form -msgid " Delete Selected" -msgstr "" - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.account_journal_form -msgid " Send Now" -msgstr "" - -#. module: odex30_account_online_sync -#: model:mail.template,body_html:account_online_synchronization.email_template_sync_reminder -msgid "" -"\n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -"
\n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -"
\n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -"
\n" -"
\n" -" Hello,

\n" -" The connection between https://yourcompany.odoo.com and Belfius expired.expires in 10 days.
\n" -" \n" -" Security Tip: Check that the domain name you are redirected to is: https://yourcompany.odoo.com\n" -"
\n" -"
\n" -"
\n" -"
\n" -"
\n" -"
\n" -" \n" -" \n" -" \n" -" \n" -"
\n" -" Powered by Odoo\n" -"
\n" -" \n" -" \n" -" \n" -" \n" -"
\n" -" PS: This is an automated email sent by Odoo Accounting to remind you before a bank sync consent expiration.\n" -"
\n" -"
\n" -" " -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__access_token -msgid "Access Token" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_account__account_data -msgid "Account Data" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_account__name -msgid "Account Name" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,help:account_online_synchronization.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:account_online_synchronization.field_account_bank_statement_line_transient__account_number -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_account__account_number -msgid "Account Number" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_selection__account_online_account_ids -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line_transient__online_account_id -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_journal__account_online_account_id -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__account_online_account_ids -msgid "Account Online Account" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_selection__account_online_link_id -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line__online_link_id -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_journal__account_online_link_id -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_account__account_online_link_id -msgid "Account Online Link" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.actions.server,name:account_online_synchronization.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:account_online_synchronization.online_sync_cron_ir_actions_server -msgid "Account: Journal online sync" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.actions.server,name:account_online_synchronization.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:account_online_synchronization.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:account_online_synchronization.field_account_online_link__message_needaction -msgid "Action Needed" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__activity_ids -msgid "Activities" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__activity_exception_decoration -msgid "Activity Exception Decoration" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__activity_state -msgid "Activity State" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model,name:account_online_synchronization.model_mail_activity_type -msgid "Activity Type" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__activity_type_icon -msgid "Activity Type Icon" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line_transient__amount -msgid "Amount" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line_transient__amount_currency -msgid "Amount in Currency" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__message_attachment_count -msgid "Attachment Count" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__auto_sync -msgid "Automatic synchronization" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_account__balance -msgid "Balance" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,help:account_online_synchronization.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:account_online_synchronization.portal_renew_consent -msgid "Bank" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model,name:account_online_synchronization.model_account_online_link -msgid "Bank Connection" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model,name:account_online_synchronization.model_account_bank_statement_line -msgid "Bank Statement Line" -msgstr "" - -#. module: odex30_account_online_sync -#: model:mail.activity.type,name:account_online_synchronization.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:account_online_synchronization.email_template_sync_reminder -msgid "Bank connection expiration reminder" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model,name:account_online_synchronization.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:account_online_synchronization.account_missing_transaction_wizard_view_form -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.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:account_online_synchronization.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:account_online_synchronization.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:account_online_synchronization.field_account_online_link__client_id -msgid "Client" -msgstr "" - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.account_online_link_view_form -msgid "Client id" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,help:account_online_synchronization.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 "" - -#. module: odex30_account_online_sync -#: model:ir.model,name:account_online_synchronization.model_res_company -msgid "Companies" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line_transient__company_id -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_account__company_id -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__company_id -msgid "Company" -msgstr "" - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.account_online_link_view_form -msgid "Connect" -msgstr "" - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.view_account_bank_selection_form_wizard -msgid "Connect Bank" -msgstr "" - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.account_journal_dashboard_inherit_online_sync -msgid "Connect bank" -msgstr "" - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.portal_renew_consent -msgid "Connect my Bank" -msgstr "" - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.portal_renew_consent -msgid "Connect your bank account to Odoo" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields.selection,name:account_online_synchronization.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:account_online_synchronization.field_account_journal__renewal_contact_email -msgid "Connection Requests" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__connection_state_details -msgid "Connection State Details" -msgstr "" - -#. module: odex30_account_online_sync -#: model:mail.message.subtype,name:account_online_synchronization.bank_sync_consent_renewal -msgid "Consent Renewal" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model,name:account_online_synchronization.model_res_partner -msgid "Contact" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_selection__create_uid -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line_transient__create_uid -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_duplicate_transaction_wizard__create_uid -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_missing_transaction_wizard__create_uid -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_account__create_uid -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__create_uid -msgid "Created by" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_selection__create_date -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line_transient__create_date -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_duplicate_transaction_wizard__create_date -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_missing_transaction_wizard__create_date -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_account__create_date -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__create_date -msgid "Created on" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line_transient__currency_id -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_account__currency_id -msgid "Currency" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line_transient__date -msgid "Date" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,help:account_online_synchronization.field_account_journal__expiring_synchronization_date -#: model:ir.model.fields,help:account_online_synchronization.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:account_online_synchronization.field_account_bank_selection__display_name -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line_transient__display_name -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_duplicate_transaction_wizard__display_name -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_missing_transaction_wizard__display_name -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_account__display_name -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__display_name -msgid "Display Name" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields.selection,name:account_online_synchronization.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:account_online_synchronization.selection__account_online_link__state__error -msgid "Error" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_journal__expiring_synchronization_date -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__expiring_synchronization_date -msgid "Expiring Synchronization Date" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.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:account_online_synchronization.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:account_online_synchronization.account_missing_transaction_wizard_view_form -msgid "Fetch" -msgstr "" - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.account_duplicate_transaction_wizard_view_form -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.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:account_online_synchronization.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:account_online_synchronization.field_account_journal__online_sync_fetching_status -#: model:ir.model.fields,field_description:account_online_synchronization.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:account_online_synchronization.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:account_online_synchronization.field_account_online_link__message_follower_ids -msgid "Followers" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__message_partner_ids -msgid "Followers (Partners)" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,help:account_online_synchronization.field_account_online_link__activity_type_icon -msgid "Font awesome icon e.g. fa-tasks" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line_transient__foreign_currency_id -msgid "Foreign Currency" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__has_message -msgid "Has Message" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.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:account_online_synchronization.field_account_bank_selection__id -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line_transient__id -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_duplicate_transaction_wizard__id -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_missing_transaction_wizard__id -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_account__id -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__id -msgid "ID" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__activity_exception_icon -msgid "Icon" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,help:account_online_synchronization.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:account_online_synchronization.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:account_online_synchronization.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:account_online_synchronization.field_account_online_link__message_has_error -#: model:ir.model.fields,help:account_online_synchronization.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:account_online_synchronization.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:account_online_synchronization.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:account_online_synchronization.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 "" - -#. 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:account_online_synchronization.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:account_online_synchronization.field_account_bank_selection__institution_name -#: model:ir.model.fields,help:account_online_synchronization.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 "" - -#. 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 "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_account__inverse_balance_sign -msgid "Inverse Balance Sign" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_account__inverse_transaction_sign -msgid "Inverse Transaction Sign" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__message_is_follower -msgid "Is Follower" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model,name:account_online_synchronization.model_account_journal -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line_transient__journal_id -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_duplicate_transaction_wizard__journal_id -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_missing_transaction_wizard__journal_id -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_account__journal_ids -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__journal_ids -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.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 "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__last_refresh -msgid "Last Refresh" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_selection__write_uid -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line_transient__write_uid -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_duplicate_transaction_wizard__write_uid -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_missing_transaction_wizard__write_uid -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_account__write_uid -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__write_uid -msgid "Last Updated by" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_selection__write_date -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line_transient__write_date -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_duplicate_transaction_wizard__write_date -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_missing_transaction_wizard__write_date -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_account__write_date -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__write_date -msgid "Last Updated on" -msgstr "" - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.account_online_link_view_form -msgid "Last refresh" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_account__last_sync -msgid "Last synchronization" -msgstr "" - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.portal_renew_consent -msgid "Latest Balance" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model,name:account_online_synchronization.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:account_online_synchronization.field_account_online_link__message_has_error -msgid "Message Delivery error" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.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:account_online_synchronization.field_account_online_link__my_activity_date_deadline -msgid "My Activity Deadline" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_selection__institution_name -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__name -msgid "Name" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.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:account_online_synchronization.field_account_online_link__activity_date_deadline -msgid "Next Activity Deadline" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__activity_summary -msgid "Next Activity Summary" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__activity_type_id -msgid "Next Activity Type" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__next_refresh -msgid "Next synchronization" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields.selection,name:account_online_synchronization.selection__account_online_link__state__disconnected -msgid "Not Connected" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__message_needaction_counter -msgid "Number of Actions" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__message_has_error_counter -msgid "Number of errors" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,help:account_online_synchronization.field_account_online_link__message_needaction_counter -msgid "Number of messages requiring action" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,help:account_online_synchronization.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:account_online_synchronization.portal_renew_consent -msgid "Odoo" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line__online_account_id -msgid "Online Account" -msgstr "" - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.account_online_link_view_form -msgid "Online Accounts" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_account__online_identifier -msgid "Online Identifier" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_journal__next_link_synchronization -msgid "Online Link Next synchronization" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line__online_partner_information -#: model:ir.model.fields,field_description:account_online_synchronization.field_res_partner__online_partner_information -#: model:ir.model.fields,field_description:account_online_synchronization.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:account_online_synchronization.action_account_online_link_form -#: model:ir.ui.menu,name:account_online_synchronization.menu_action_online_link_account -#: model_terms:ir.actions.act_window,help:account_online_synchronization.action_account_online_link_form -msgid "Online Synchronization" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line__online_transaction_identifier -#: model:ir.model.fields,field_description:account_online_synchronization.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:account_online_synchronization.field_account_bank_statement_line_transient__partner_name -msgid "Partner Name" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line_transient__payment_ref -msgid "Payment Ref" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields.selection,name:account_online_synchronization.selection__account_bank_statement_line_transient__state__pending -msgid "Pending" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields.selection,name:account_online_synchronization.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:account_online_synchronization.selection__account_bank_statement_line_transient__state__posted -msgid "Posted" -msgstr "" - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.missing_bank_statement_line_search -msgid "Posted Transactions" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields.selection,name:account_online_synchronization.selection__account_online_account__fetching_status__processing -msgid "Processing" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__provider_data -msgid "Provider Data" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__provider_type -msgid "Provider Type" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__rating_ids -msgid "Ratings" -msgstr "" - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.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:account_online_synchronization.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:account_online_synchronization.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:account_online_synchronization.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:account_online_synchronization.account_online_link_view_form -msgid "Reset" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__activity_user_id -msgid "Responsible User" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.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:account_online_synchronization.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:account_online_synchronization.view_account_bank_selection_form_wizard -msgid "Select a Bank Account" -msgstr "" - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.view_account_bank_selection_form_wizard -msgid "Select the" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_selection__selected_account -msgid "Selected Account" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line_transient__sequence -msgid "Sequence" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,help:account_online_synchronization.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:account_online_synchronization.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:account_online_synchronization.field_account_duplicate_transaction_wizard__date -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_missing_transaction_wizard__date -msgid "Starting Date" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line_transient__state -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_journal__account_online_link_state -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__state -msgid "State" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,help:account_online_synchronization.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 "" - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.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:account_online_synchronization.field_account_online_link__provider_type -msgid "Third Party Provider" -msgstr "" - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.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:account_online_synchronization.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 "" - -#. module: odex30_account_online_sync -#: model_terms:ir.actions.act_window,help:account_online_synchronization.action_account_online_link_form -msgid "" -"To create a synchronization with your banking institution,
\n" -" please click on Add a Bank Account." -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,help:account_online_synchronization.field_account_online_link__access_token -msgid "Token used to access API." -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,help:account_online_synchronization.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:account_online_synchronization.field_account_duplicate_transaction_wizard__transaction_ids -msgid "Transaction" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_bank_statement_line_transient__transaction_details -msgid "Transaction Details" -msgstr "" - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.account_duplicate_transaction_wizard_view_form -msgid "Transactions" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model,name:account_online_synchronization.model_account_bank_statement_line_transient -msgid "Transient model for bank statement line" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,help:account_online_synchronization.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:account_online_synchronization.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:account_online_synchronization.account_online_link_view_form -msgid "Update Credentials" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields.selection,name:account_online_synchronization.selection__account_online_account__fetching_status__waiting -msgid "Waiting" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,field_description:account_online_synchronization.field_account_online_link__website_message_ids -msgid "Website Messages" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model.fields,help:account_online_synchronization.field_account_online_link__website_message_ids -msgid "Website communication history" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model,name:account_online_synchronization.model_account_duplicate_transaction_wizard -msgid "Wizard for duplicate transactions" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model,name:account_online_synchronization.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 "" - -#. 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:account_online_synchronization.email_template_sync_reminder -msgid "Your bank connection is expiring soon" -msgstr "" - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:account_online_synchronization.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:account_online_synchronization.portal_renew_consent -msgid "on" -msgstr "" - -#. module: odex30_account_online_sync -#: model:ir.model,name:account_online_synchronization.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/i18n/ar.po b/dev_odex30_accounting/odex30_account_online_sync/i18n/ar.po deleted file mode 100644 index 61d8a10..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/i18n/ar.po +++ /dev/null @@ -1,1418 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * odex30_account_online_sync -# -# Translators: -# Mustafa J. Kadhem , 2024 -# Malaz Abuidris , 2025 -# Wil Odoo, 2025 -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 18.0+e\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-07-11 18:50+0000\n" -"PO-Revision-Date: 2024-09-25 09:44+0000\n" -"Last-Translator: Wil Odoo, 2025\n" -"Language-Team: Arabic (https://app.transifex.com/odoo/teams/41243/ar/)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Language: ar\n" -"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" - -#. module: odex30_account_online_sync -#. odoo-python -#: code:addons/odex30_account_online_sync/models/account_online.py:0 -msgid "" -"\n" -"\n" -"If you've already opened a ticket for this issue, don't report it again: a support agent will contact you shortly." -msgstr "" -"\n" -"\n" -"إذا قمت بفتح تذكرة دعم لهذه المشكلة بالفعل، لا تقم بالإبلاغ عنها مجدداً: سيتواصل معك أحد موظفي الدعم عما قريب. " - -#. module: odex30_account_online_sync -#. odoo-python -#: code:addons/odex30_account_online_sync/wizard/account_journal_duplicate_transactions.py:0 -msgid "%s duplicate transactions" -msgstr "%s معاملات مكررة " - -#. 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 "" -").\n" -" This might cause duplicate entries." -msgstr "" -").\n" -" قد يتسبب هذا في تكرار القيود. " - -#. 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 "0 transaction fetched" -msgstr "تم جلب 0 معاملات " - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_duplicate_transaction_wizard_view_form -msgid " Delete Selected" -msgstr " حذف العناصر المحددة " - -#. module: odex30_account_online_sync -#: model_terms:ir.ui.view,arch_db:odex30_account_online_sync.account_journal_form -msgid " Send Now" -msgstr " إرسال الآن " - -#. module: odex30_account_online_sync -#: model:mail.template,body_html:odex30_account_online_sync.email_template_sync_reminder -msgid "" -"\n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -"
\n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -"
\n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -"
\n" -"
\n" -" Hello,

\n" -" The connection between https://yourcompany.odoo.com and Belfius expired.expires in 10 days.
\n" -" \n" -" Security Tip: Check that the domain name you are redirected to is: https://yourcompany.odoo.com\n" -"
\n" -"
\n" -"
\n" -"
\n" -"
\n" -"
\n" -" \n" -" \n" -" \n" -" \n" -"
\n" -" Powered by Odoo\n" -"
\n" -" \n" -" \n" -" \n" -" \n" -"
\n" -" PS: This is an automated email sent by Odoo Accounting to remind you before a bank sync consent expiration.\n" -"
\n" -"
\n" -" " -msgstr "" -"\n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -"
\n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -"
\n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -" \n" -"
\n" -"
\n" -" مرحباً،

\n" -" الاتصال بين https://yourcompany.odoo.com و Belfius قد انتهت صلاحيته.تنتهي صلاحيته في 10 أيام.
\n" -" \n" -" نصيحة للأمان: تحقق من أن اسم النطاق الذي تمت إعادة توجيهك إليه هو: https://yourcompany.odoo.com\n" -"
\n" -"
\n" -"
\n" -"
\n" -"
\n" -"
\n" -" \n" -" \n" -" \n" -" \n" -"
\n" -" مشغل بواسطة أودو\n" -"
\n" -" \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 807c97008dcaff1314dc2fce1c21682189b3c56f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 621 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
|L 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 QQ?Kv;@N7T-;o&9z4M46s0{{R3 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 9dba055d4f7b0e73cf6d5ae738a743d609e5c3a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1153 zcmah{%}>-o6rbtGx-5{IV2Bzt4Y!SP3vvfq;&7UId>pLOFW%VV3lCoe2?8W~M$f zCli*Y0bd~j4^pz^@>Sw`Ufkv>$TW!gq#1CtG(nshG2R}Oayd0U-{mVsvE?w{FqdOd zRK8EC>6)@+l|Pwhn?cGVS(UnpzZAQIT7!b!v9gsF^DyAd0=6o*!T5T1wLnBT+aO7Y zd%lQy&~+usCeE~kh0BS&4)f%V?Z*+b-Eh@+yP_4?iyeR|qD2w6n3pCttabC&G>AB! zx_R3wwA?x~U0nc7Yg4vPir7D`XDy_FnnSXS2V<8TwbnYBdEO0oZ=Ajd>%8D0m@l~w zYzy^9(8Wt{HxDjP9U9h=G56J&`(n%=8uJJB`M!J#3(X5zoe=IujF7BB2!LycQXe7Y zRoe|qPaT%;GZCahKDbQJXAmQxe?npugcz2rRxzn0cYtjTzHi3F)}!xwefwok(e=l; zha^8%QO!7ODjY>sS!bcCXPR6?-dkv3%Je62!C}#F3=F3jPU^G840pviIOMVu0<5QE ijC(bNuN|O?zyDrs2un==<~!r>CXRF8um42k0Dc3Qx-K^W 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 2aabcb1570606bc9f4fed21c910f4dff068433ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1161 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 DbfJYj 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 a501c7f51477735953aacdcc2509074dded1b8b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 709 zcmZuuy>HYo6t|OHdO9dYs??#aKsN@#rF;xXck04GmsTeVOBTzDT`osr7bjQsI(1;` z)S*%r28OCt1p|M|At6zDLSkY&bWELiJ|`83zBteC)4!je&t_*E2;|%0tK>cGKa5jJ ztu!VbFpd#J45ui=5zd^*!N@_+5p&)l=5pM3tEN-1K3;i|$GlAx)P9NPg~ZSMjx#Z8pm=VibT8)8Q&qc_V4Jw5mNHWhawK`biJF7Axc|LSnzX!)0k|Gp+U1^ojWe7k%A 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/components/bank_configure/bank_configure.js b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_configure/bank_configure.js deleted file mode 100644 index 0469a8d..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_configure/bank_configure.js +++ /dev/null @@ -1,84 +0,0 @@ -/** @odoo-module */ - -import { registry } from "@web/core/registry"; -import { standardWidgetProps } from "@web/views/widgets/standard_widget_props"; -import { useService, useBus } from "@web/core/utils/hooks"; -import { SIZES } from "@web/core/ui/ui_service"; -import { Component, useState, useRef, onWillStart } from "@odoo/owl"; - -class BankConfigureWidget extends Component { - static template = "account.BankConfigureWidget"; - static props = { - ...standardWidgetProps, - } - setup() { - this.container = useRef("container"); - this.allInstitutions = []; - this.state = useState({ - isLoading: true, - institutions: [], - gridStyle: "grid-template-columns: repeat(5, minmax(90px, 1fr));" - }); - this.orm = useService("orm"); - this.action = useService("action"); - this.ui = useService("ui"); - onWillStart(this.fetchInstitutions); - useBus(this.ui.bus, "resize", this.computeGrid); - } - - computeGrid() { - if (this.allInstitutions.length > 4) { - let containerWidth = this.container.el ? this.container.el.offsetWidth - 32 : 0; - // when the container width can't be computed, use the screen size and number of journals. - if (!containerWidth) { - if (this.ui.size >= SIZES.XXL) { - containerWidth = window.innerWidth / (this.props.record.model.root.count < 6 ? 2 : 3); - } else { - containerWidth = Math.max(this.ui.size * 100, 400); - } - } - const canFit = Math.floor(containerWidth / 100); - const numberOfRows = (Math.floor((this.allInstitutions.length + 1) / 2) >= canFit) + 1; - this.state.gridStyle = `grid-template-columns: repeat(${canFit}, minmax(90px, 1fr)); - grid-template-rows: repeat(${numberOfRows}, 1fr); - grid-auto-rows: 0px; - `; - } - this.state.institutions = this.allInstitutions; - } - - async fetchInstitutions() { - this.orm.silent.call(this.props.record.resModel, "fetch_online_sync_favorite_institutions", [this.props.record.resId]) - .then((response) => { - this.allInstitutions = response; - }) - .finally(() => { - this.state.isLoading = false; - this.computeGrid(); - }); - } - - async connectBank(institutionId=null) { - const action = await this.orm.call("account.online.link", "action_new_synchronization", [[]], { - preferred_inst: institutionId, - journal_id: this.props.record.resId, - }) - this.action.doAction(action); - } - - async fallbackConnectBank() { - const action = await this.orm.call('account.online.link', 'create_new_bank_account_action', [], { - context: { - active_model: 'account.journal', - active_id: this.props.record.resId, - } - }); - this.action.doAction(action); - } -} - -export const bankConfigureWidget = { - component: BankConfigureWidget, -} - -registry.category("view_widgets").add("bank_configure", bankConfigureWidget); diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_configure/bank_configure.scss b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_configure/bank_configure.scss deleted file mode 100644 index 3b96ed6..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_configure/bank_configure.scss +++ /dev/null @@ -1,15 +0,0 @@ -.bank_configure_container { - .d-grid { - overflow: hidden; - column-gap: 0.25rem; - } - .dashboard_bank { - aspect-ratio: 1 / 1; - .align-self-center { - background-color: $gray-100; - border: 1px solid $gray-100; - } - margin-bottom: 0.25rem; - overflow: hidden; - } -} diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_configure/bank_configure.xml b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_configure/bank_configure.xml deleted file mode 100644 index 5068148..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_configure/bank_configure.xml +++ /dev/null @@ -1,29 +0,0 @@ - - diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_reconciliation/fetch_missing_transactions_cog_menu.js b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_reconciliation/fetch_missing_transactions_cog_menu.js deleted file mode 100644 index 6899842..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_reconciliation/fetch_missing_transactions_cog_menu.js +++ /dev/null @@ -1,68 +0,0 @@ -/** @odoo-module **/ - -import { Component } from "@odoo/owl"; -import { DropdownItem } from "@web/core/dropdown/dropdown_item"; -import { registry } from "@web/core/registry"; -import { useService } from "@web/core/utils/hooks"; - -const cogMenuRegistry = registry.category("cogMenu"); - -/** - * 'Fetch Missing Transactions' menu - * - * This component is used to open a wizard allowing the user to fetch their missing/pending - * transaction since a specific date. - * It's only available in the bank reconciliation widget. - * By default, if there is only one selected journal, this journal is directly selected. - * In case there is no selected journal or more than one, we let the user choose which - * journal he/she wants. This part is handled by the server. - * @extends Component - */ -export class FetchMissingTransactions extends Component { - static template = "odex30_account_online_sync.FetchMissingTransactions"; - static components = { DropdownItem }; - static props = {}; - - setup() { - this.action = useService("action"); - } - - //--------------------------------------------------------------------- - // Protected - //--------------------------------------------------------------------- - - async openFetchMissingTransactionsWizard() { - const { context } = this.env.searchModel; - const activeModel = context.active_model; - let activeIds = []; - if (activeModel === "account.journal") { - activeIds = context.active_ids; - } else if (!!context.default_journal_id) { - activeIds = context.default_journal_id; - } - // We have to use this.env.services.orm.call instead of using useService - // for a specific reason. useService implies that function calls with - // are "protected", it means that if the component is closed the - // response will be pending and the code stop their execution. - // By passing directly from the env, this protection is not activated. - const action = await this.env.services.orm.call( - "account.journal", - "action_open_missing_transaction_wizard", - [activeIds] - ); - return this.action.doAction(action); - } -} - -export const fetchMissingTransactionItem = { - Component: FetchMissingTransactions, - groupNumber: 5, - isDisplayed: ({ config, isSmall }) => { - return !isSmall && - config.actionType === "ir.actions.act_window" && - ["kanban", "list"].includes(config.viewType) && - ["bank_rec_widget_kanban", "bank_rec_list"].includes(config.viewSubType); - }, -}; - -cogMenuRegistry.add("fetch-missing-transaction-menu", fetchMissingTransactionItem, { sequence: 1 }); diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_reconciliation/fetch_missing_transactions_cog_menu.xml b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_reconciliation/fetch_missing_transactions_cog_menu.xml deleted file mode 100644 index 07256ff..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_reconciliation/fetch_missing_transactions_cog_menu.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - Find Missing Transactions - - - diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_reconciliation/find_duplicate_transactions_cog_menu.js b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_reconciliation/find_duplicate_transactions_cog_menu.js deleted file mode 100644 index dc2968e..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_reconciliation/find_duplicate_transactions_cog_menu.js +++ /dev/null @@ -1,64 +0,0 @@ -import { Component } from "@odoo/owl"; -import { DropdownItem } from "@web/core/dropdown/dropdown_item"; -import { registry } from "@web/core/registry"; -import { useService } from "@web/core/utils/hooks"; - -/** - * 'Find Duplicate Transactions' menu - * - * This component is used to open a wizard allowing the user to find duplicate - * transactions since a specific date. - * It's only available in the bank reconciliation widget. - * By default, if there is only one selected journal, this journal is directly selected. - * In case there is no selected journal or more than one, we let the user choose. - * @extends Component - */ -export class FindDuplicateTransactions extends Component { - static template = "odex30_account_online_sync.FindDuplicateTransactions"; - static components = { DropdownItem }; - static props = {}; - - setup() { - this.action = useService("action"); - } - - //--------------------------------------------------------------------- - // Protected - //--------------------------------------------------------------------- - - async openFindDuplicateTransactionsWizard() { - const { context } = this.env.searchModel; - const activeModel = context.active_model; - let activeIds = []; - if (activeModel === "account.journal") { - activeIds = context.active_ids; - } else if (context.default_journal_id) { - activeIds = context.default_journal_id; - } - return this.action.doActionButton({ - type: "object", - resModel: "account.journal", - name:"action_open_duplicate_transaction_wizard", - resIds: activeIds, - }) - } -} - -export const findDuplicateTransactionItem = { - Component: FindDuplicateTransactions, - groupNumber: 5, // same group as fetch missing transactions - isDisplayed: ({ config, isSmall }) => { - return ( - !isSmall && - config.actionType === "ir.actions.act_window" && - ["kanban", "list"].includes(config.viewType) && - ["bank_rec_widget_kanban", "bank_rec_list"].includes(config.viewSubType) - ) - }, -}; - -registry.category("cogMenu").add( - "find-duplicate-transaction-menu", - findDuplicateTransactionItem, - { sequence: 3 }, // after fetch missing transactions -); diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_reconciliation/find_duplicate_transactions_cog_menu.xml b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_reconciliation/find_duplicate_transactions_cog_menu.xml deleted file mode 100644 index 1e75c99..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/bank_reconciliation/find_duplicate_transactions_cog_menu.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - Find Duplicate Transactions - - - diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/connected_until_widget/connected_until_widget.js b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/connected_until_widget/connected_until_widget.js deleted file mode 100644 index 4792386..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/connected_until_widget/connected_until_widget.js +++ /dev/null @@ -1,61 +0,0 @@ -/** @odoo-module **/ - -import { registry } from "@web/core/registry"; -import { standardWidgetProps } from "@web/views/widgets/standard_widget_props"; -import { useService } from "@web/core/utils/hooks"; -import { Component, useState } from "@odoo/owl"; - -class ConnectedUntil extends Component { - static template = "odex30_account_online_sync.ConnectedUntil"; - static props = { ...standardWidgetProps }; - - setup() { - this.state = useState({ - isHovered: false, - displayReconnectButton: false, - }); - - if (this.isConnectionExpiredIn(0)) { - this.state.displayReconnectButton = true; - } - - this.action = useService("action"); - this.orm = useService("orm"); - } - - get cssClasses() { - let cssClasses = "text-nowrap w-100"; - if (this.isConnectionExpiredIn(7)) { - cssClasses += this.isConnectionExpiredIn(3) ? " text-danger" : " text-warning"; - } - return cssClasses; - } - - onMouseEnter() { - this.state.isHovered = true; - } - - onMouseLeave() { - this.state.isHovered = false; - } - - isConnectionExpiredIn(nbDays) { - return this.props.record.data.expiring_synchronization_due_day <= nbDays; - } - - async extendConnection() { - const action = await this.orm.call( - "account.journal", - "action_extend_consent", - [this.props.record.resId], - {} - ); - this.action.doAction(action); - } -} - -export const connectedUntil = { - component: ConnectedUntil, -}; - -registry.category("view_widgets").add("connected_until_widget", connectedUntil); diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/connected_until_widget/connected_until_widget.xml b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/connected_until_widget/connected_until_widget.xml deleted file mode 100644 index 18714c9..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/connected_until_widget/connected_until_widget.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - -
- - - - - - - Connected until - - - - Extend Connection - - - -
-
-
diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/online_account_radio/online_account_radio.js b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/online_account_radio/online_account_radio.js deleted file mode 100644 index a0e1aa0..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/online_account_radio/online_account_radio.js +++ /dev/null @@ -1,41 +0,0 @@ -/** @odoo-module **/ - -import { onMounted, useState } from "@odoo/owl"; -import { registry } from "@web/core/registry"; -import { RadioField, radioField } from "@web/views/fields/radio/radio_field"; -import { useService } from '@web/core/utils/hooks'; - - -class OnlineAccountRadio extends RadioField { - static template = "odex30_account_online_sync.OnlineAccountRadio"; - setup() { - super.setup(); - this.orm = useService("orm"); - this.state = useState({balances: {}}); - - onMounted(async () => { - this.state.balances = await this.loadData(); - // Make sure the first option is selected by default. - this.onChange(this.items[0]); - }); - } - - async loadData() { - const ids = this.items.map(i => i[0]); - return await this.orm.call("account.online.account", "get_formatted_balances", [ids]); - } - - getBalanceName(itemID) { - return this.state.balances?.[itemID]?.[0] ?? "Loading ..."; - } - - isNegativeAmount(itemID) { - // In case of the value is undefined, it will return false as intended. - return this.state.balances?.[itemID]?.[1] < 0; - } -} - -registry.category("fields").add("online_account_radio", { - ...radioField, - component: OnlineAccountRadio, -}); diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/online_account_radio/online_account_radio.xml b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/online_account_radio/online_account_radio.xml deleted file mode 100644 index 2407b88..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/online_account_radio/online_account_radio.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - -
- -
- -
-
-
-
-
diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/refresh_spin_journal_widget/refresh_spin_journal_widget.js b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/refresh_spin_journal_widget/refresh_spin_journal_widget.js deleted file mode 100644 index 8944dfc..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/refresh_spin_journal_widget/refresh_spin_journal_widget.js +++ /dev/null @@ -1,99 +0,0 @@ -/** @odoo-module **/ - -import { registry } from "@web/core/registry"; -import { standardWidgetProps } from "@web/views/widgets/standard_widget_props"; -import { useService } from "@web/core/utils/hooks"; -import { Component, useState, onWillStart, markup } from "@odoo/owl"; - -class RefreshSpin extends Component { - static template = "odex30_account_online_sync.RefreshSpin"; - static props = { ...standardWidgetProps }; - - setup() { - this.state = useState({ - isHovered: false, - fetchingStatus: false, - connectionStateDetails: null, - }); - - this.actionService = useService("action"); - this.busService = this.env.services.bus_service; - this.orm = useService("orm"); - this.state.fetchingStatus = this.props.record.data.online_sync_fetching_status; - - this.busService.subscribe("online_sync", (notification) => { - if (notification?.id === this.recordId && notification?.connection_state_details) { - this.state.connectionStateDetails = notification.connection_state_details; - } - }); - - onWillStart(() => { - this._initConnectionStateDetails(); - }); - } - - refresh() { - this.actionService.restore(this.actionService.currentController.jsId); - } - - onMouseEnter() { - this.state.isHovered = true; - } - - onMouseLeave() { - this.state.isHovered = false; - } - - async openAction() { - /** - * This function is used to open the action that the asynchronous process saved - * on the databsase. It allows users to call the action when they want and not when - * the process is over. - */ - const action = await this.orm.call( - "account.journal", - "action_open_dashboard_asynchronous_action", - [this.recordId], - ); - this.actionService.doAction(action); - this.state.connectionStateDetails = null; - } - - async fetchTransactions() { - /** - * This function call the function to fetch transactions. - * In the main case, we don't do anything after calling the function. - * The idea is that websockets will update the status by themselves. - * In one specific case, we have to return an action to the user to open - * the Odoo Fin iframe to refresh the connection. - */ - this.state.connectionStateDetails = { status: "fetching" }; - const action = await this.orm.call("account.journal", "manual_sync", [this.recordId]); - if (action) { - action.help = markup(action.help); - this.actionService.doAction(action); - } - } - - _initConnectionStateDetails() { - /** - * This function is used to get the last state of the connection (if there is one) - */ - const kanbanDashboardData = JSON.parse(this.props.record.data.kanban_dashboard); - this.state.connectionStateDetails = kanbanDashboardData?.connection_state_details; - } - - get recordId() { - return this.props.record.data.id; - } - - get connectionStatus() { - return this.state.connectionStateDetails?.status; - } -} - -export const refreshSpin = { - component: RefreshSpin, -}; - -registry.category("view_widgets").add("refresh_spin_widget", refreshSpin); diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/refresh_spin_journal_widget/refresh_spin_journal_widget.xml b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/refresh_spin_journal_widget/refresh_spin_journal_widget.xml deleted file mode 100644 index 869292b..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/refresh_spin_journal_widget/refresh_spin_journal_widget.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - -
- - - transactions fetched - - - - 0 transaction fetched - -
-
- -
- - See error - - -
-
- -
- - Refresh - - - Fetching... - -
-
- - - Fetch Transactions - - -
-
diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/transient_bank_statement_line_list_view/transient_bank_statement_line_list_view.js b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/transient_bank_statement_line_list_view/transient_bank_statement_line_list_view.js deleted file mode 100644 index 6b9993e..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/transient_bank_statement_line_list_view/transient_bank_statement_line_list_view.js +++ /dev/null @@ -1,51 +0,0 @@ -/** @odoo-module **/ - -import { ListRenderer } from "@web/views/list/list_renderer"; -import { ListController } from "@web/views/list/list_controller"; -import { registry } from "@web/core/registry"; -import { listView } from "@web/views/list/list_view"; -import { useService } from "@web/core/utils/hooks"; - -export class TransientBankStatementLineListController extends ListController { - - setup() { - super.setup(); - this.orm = useService("orm"); - this.action = useService("action"); - } - - async onClickImportTransactions() { - const resIds = await this.getSelectedResIds(); - const resultAction = await this.orm.call("account.bank.statement.line.transient", "action_import_transactions", [resIds]); - this.action.doAction(resultAction); - } -} - -export class TransientBankStatementLineListRenderer extends ListRenderer { - - static template = "odex30_account_online_sync.TransientBankStatementLineRenderer"; - - setup() { - super.setup(); - this.orm = useService("orm"); - this.action = useService("action"); - } - - async openManualEntries() { - if (this.env.searchModel.context.active_model === "account.missing.transaction.wizard" && this.env.searchModel.context.active_ids) { - const activeIds = this.env.searchModel.context.active_ids; - const action = await this.orm.call("account.missing.transaction.wizard", "action_open_manual_bank_statement_lines", activeIds); - this.action.doAction(action); - } - } - -} - -export const TransientBankStatementLineListView = { - ...listView, - Renderer: TransientBankStatementLineListRenderer, - Controller: TransientBankStatementLineListController, - buttonTemplate: "TransientBankStatementLineButtonTemplate", -} - -registry.category("views").add("transient_bank_statement_line_list_view", TransientBankStatementLineListView); diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/transient_bank_statement_line_list_view/transient_bank_statement_line_list_view.xml b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/transient_bank_statement_line_list_view/transient_bank_statement_line_list_view.xml deleted file mode 100644 index 1e9c17c..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/transient_bank_statement_line_list_view/transient_bank_statement_line_list_view.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/views/account_online_authorization_kanban.js b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/views/account_online_authorization_kanban.js deleted file mode 100644 index bd42d29..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/views/account_online_authorization_kanban.js +++ /dev/null @@ -1,19 +0,0 @@ -import { patch } from "@web/core/utils/patch"; -import { BankRecKanbanController } from "@odex30_account_accountant/components/bank_reconciliation/kanban"; - -patch(BankRecKanbanController.prototype, { - setup() { - super.setup(); - this.displayDuplicateWarning = !!this.props.context.duplicates_from_date; - }, - async onWarningClick () { - const { context } = this.env.searchModel; - return this.action.doActionButton({ - type: "object", - resModel: "account.journal", - name:"action_open_duplicate_transaction_wizard", - resId: this.state.journalId, - args: JSON.stringify([context.duplicates_from_date]), - }) - }, -}) diff --git a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/views/account_online_authorization_kanban_controller.xml b/dev_odex30_accounting/odex30_account_online_sync/static/src/components/views/account_online_authorization_kanban_controller.xml deleted file mode 100644 index 6cdfe99..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/static/src/components/views/account_online_authorization_kanban_controller.xml +++ /dev/null @@ -1,12 +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

tags. - """ - transaction = self._create_one_online_transaction() - transaction['transaction_details'] = '{\n "account_id": "1",\n "status": "posted"\n}' - transient_transaction = self.env['account.bank.statement.line.transient'].create(transaction) - transaction_details = transient_transaction.read(fields=['transaction_details'], load=None)[0]['transaction_details'] - self.assertFalse(transaction_details.startswith('

'), 'Transient transaction details should not start with

when read.') - self.assertFalse(transaction_details.endswith('

'), 'Transient transaction details should not end with

when read.') diff --git a/dev_odex30_accounting/odex30_account_online_sync/views/account_bank_statement_view.xml b/dev_odex30_accounting/odex30_account_online_sync/views/account_bank_statement_view.xml deleted file mode 100644 index 678dc78..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/views/account_bank_statement_view.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - bank.statement.line.list.inherit - account.bank.statement.line - - - - - - - - - - - - account.bank.statement.line.form.bank_rec_widget.inherit - account.bank.statement.line - primary - - - - - - - diff --git a/dev_odex30_accounting/odex30_account_online_sync/views/account_journal_dashboard_view.xml b/dev_odex30_accounting/odex30_account_online_sync/views/account_journal_dashboard_view.xml deleted file mode 100644 index ebe51e5..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/views/account_journal_dashboard_view.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - account.journal.dashboard.inherit.online.sync - account.journal - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - -
-
- - - - - - - account.group_account_manager - - dashboard.display_connect_bank_in_dashboard ? 'col-4' : 'col-6' - - - - account.group_account_manager - - dashboard.display_connect_bank_in_dashboard ? 'col-4' : 'col-6' - - - - - - - - - - - - - - - - - -
-
-
-
diff --git a/dev_odex30_accounting/odex30_account_online_sync/views/account_journal_view.xml b/dev_odex30_accounting/odex30_account_online_sync/views/account_journal_view.xml deleted file mode 100644 index 4e356f0..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/views/account_journal_view.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - account.journal.form.online.sync - account.journal - - - - - - -
- - - - - - diff --git a/dev_odex30_accounting/odex30_account_online_sync/views/account_online_sync_portal_templates.xml b/dev_odex30_accounting/odex30_account_online_sync/views/account_online_sync_portal_templates.xml deleted file mode 100644 index c0cd687..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/views/account_online_sync_portal_templates.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - diff --git a/dev_odex30_accounting/odex30_account_online_sync/views/account_online_sync_views.xml b/dev_odex30_accounting/odex30_account_online_sync/views/account_online_sync_views.xml deleted file mode 100644 index e43ecef..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/views/account_online_sync_views.xml +++ /dev/null @@ -1,126 +0,0 @@ - - - - - account.online.link.form - account.online.link - -
-
-
- -
-

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
diff --git a/dev_odex30_accounting/odex30_account_online_sync/wizard/account_journal_missing_transactions.py b/dev_odex30_accounting/odex30_account_online_sync/wizard/account_journal_missing_transactions.py deleted file mode 100644 index f6e0c2c..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/wizard/account_journal_missing_transactions.py +++ /dev/null @@ -1,78 +0,0 @@ -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from dateutil.relativedelta import relativedelta - -from odoo import fields, models, _ -from odoo.exceptions import UserError -from odoo.tools import format_date - - -class AccountMissingTransaction(models.TransientModel): - _name = 'account.missing.transaction.wizard' - _description = 'Wizard for missing transactions' - - date = fields.Date( - string="Starting Date", - default=lambda self: fields.Date.today() - relativedelta(months=1), - ) - journal_id = fields.Many2one( - comodel_name='account.journal', - domain="[('type', '=', 'bank'), ('account_online_account_id', '!=', 'False'), ('account_online_link_state', '=', 'connected')]" - ) - - def _get_manual_bank_statement_lines(self): - return self.env['account.bank.statement.line'].search( - domain=[ - ('date', '>=', self.date), - ('journal_id', '=', self.journal_id.id), - ('online_transaction_identifier', '=', False), - ], - ) - - def action_fetch_missing_transaction(self): - self.ensure_one() - - if not self.journal_id: - raise UserError(_("You have to select one journal to continue.")) - - if not self.date: - raise UserError(_("Please enter a valid Starting Date to continue.")) - - if self.journal_id.account_online_link_state != 'connected': - raise UserError(_("You can't find missing transactions for a journal that isn't connected.")) - - fetched_transactions = self.journal_id.account_online_account_id._retrieve_transactions(date=self.date, include_pendings=True) - transactions = fetched_transactions.get('transactions') or [] - pendings = fetched_transactions.get('pendings') or [] - - pendings = [{**pending, 'state': 'pending'} for pending in pendings] - filtered_transactions = self.journal_id.account_online_account_id._get_filtered_transactions(transactions + pendings) - - transient_transactions_ids = self.env['account.bank.statement.line.transient'].create(filtered_transactions) - - return { - 'name': _("Missing and Pending Transactions"), - 'type': 'ir.actions.act_window', - 'res_model': 'account.bank.statement.line.transient', - 'view_mode': 'list', - 'views': [(False, 'list')], - 'domain': [('id', 'in', transient_transactions_ids.ids)], - 'context': { - 'has_manual_entries': bool(self._get_manual_bank_statement_lines()), - 'is_fetch_before_creation': self.date < self.journal_id.account_online_link_id.create_date.date(), - 'account_online_link_create_date': format_date(self.env, self.journal_id.account_online_link_id.create_date), - 'search_default_filter_posted': bool([transaction for transaction in filtered_transactions if transaction.get('state') != 'pending']), # Activate this default filter only if we have posted transactions - }, - } - - def action_open_manual_bank_statement_lines(self): - self.ensure_one() - bank_statement_lines = self._get_manual_bank_statement_lines() - - return { - 'name': _("Manual Bank Statement Lines"), - 'type': 'ir.actions.act_window', - 'res_model': 'account.bank.statement.line', - 'views': [(False, 'list'), (False, 'form')], - 'domain': [('id', 'in', bank_statement_lines.ids)], - } diff --git a/dev_odex30_accounting/odex30_account_online_sync/wizard/account_journal_missing_transactions.xml b/dev_odex30_accounting/odex30_account_online_sync/wizard/account_journal_missing_transactions.xml deleted file mode 100644 index 5d85de2..0000000 --- a/dev_odex30_accounting/odex30_account_online_sync/wizard/account_journal_missing_transactions.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - account.missing.transaction.wizard.form - account.missing.transaction.wizard - -
-

- Choose a date and a journal from which you want to fetch transactions -

- - - - - -
-
-
-
diff --git a/dev_odex30_accounting/odex30_account_reports/__manifest__.py b/dev_odex30_accounting/odex30_account_reports/__manifest__.py index 881f1d9..1cba83b 100644 --- a/dev_odex30_accounting/odex30_account_reports/__manifest__.py +++ b/dev_odex30_accounting/odex30_account_reports/__manifest__.py @@ -10,7 +10,7 @@ Accounting Reports ================== """, - 'depends': ['account'], + 'depends': ['accountant'], 'data': [ 'security/ir.model.access.csv', 'data/pdf_export_templates.xml', @@ -73,10 +73,7 @@ Accounting Reports ], 'web.assets_backend': [ - 'odex30_account_reports/static/src/components/account_report/account_report.xml', # أضف هذا أولاً - 'odex30_account_reports/static/src/components/account_report/account_report.js', - 'odex30_account_reports/static/src/components/**/*.xml', # أضف هذا - 'odex30_account_reports/static/src/components/**/*.js', + 'odex30_account_reports/static/src/components/**/*', 'odex30_account_reports/static/src/js/**/*', 'odex30_account_reports/static/src/widgets/**/*', ],