From 7c506d3ba8a6210d46c7ab3725fc75db8f5d213a Mon Sep 17 00:00:00 2001 From: expert Date: Mon, 24 Jun 2024 14:07:12 +0300 Subject: [PATCH] Add odex25_helpdesk --- odex25_helpdesk/odex25_helpdesk/__init__.py | 5 + .../odex25_helpdesk/__manifest__.py | 61 + .../odex25_helpdesk/controllers/__init__.py | 4 + .../odex25_helpdesk/controllers/portal.py | 192 + .../odex25_helpdesk/controllers/rating.py | 50 + .../odex25_helpdesk/data/digest_data.xml | 27 + .../odex25_helpdesk/data/mail_data.xml | 184 + .../data/odex25_helpdesk_data.xml | 83 + .../data/odex25_helpdesk_demo.xml | 384 ++ odex25_helpdesk/odex25_helpdesk/i18n/ar.po | 3489 +++++++++++++++++ .../migrations/10.0.1.2/pre-nonulls.py | 9 + .../odex25_helpdesk/models/__init__.py | 8 + .../odex25_helpdesk/models/digest.py | 28 + .../odex25_helpdesk/models/odex25_helpdesk.py | 534 +++ .../models/odex25_helpdesk_ticket.py | 970 +++++ .../odex25_helpdesk/models/res_partner.py | 33 + .../odex25_helpdesk/models/res_setting.py | 40 + .../odex25_helpdesk/models/res_users.py | 48 + .../odex25_helpdesk/report/__init__.py | 3 + .../odex25_helpdesk_sla_report_analysis.py | 76 + ...x25_helpdesk_sla_report_analysis_views.xml | 100 + .../security/ir.model.access.csv | 21 + .../security/odex25_helpdesk_security.xml | 120 + .../static/description/icon.png | Bin 0 -> 11811 bytes .../static/description/icon.svg | 22 + .../static/src/css/portal_odex25_helpdesk.css | 11 + .../static/src/img/menu-navigation.gif | Bin 0 -> 32619 bytes .../src/js/odex25_helpdesk_dashboard.js | 264 ++ .../static/src/js/tours/odex25_helpdesk.js | 75 + .../static/src/scss/odex25_helpdesk.scss | 153 + .../xml/odex25_helpdesk_team_templates.xml | 202 + .../tests/odex25_helpdesk_dashboard_tests.js | 150 + .../odex25_helpdesk/tests/__init__.py | 6 + .../odex25_helpdesk/tests/common.py | 170 + .../odex25_helpdesk/tests/test_doc_links.py | 56 + .../tests/test_odex25_helpdesk_flow.py | 325 ++ .../tests/test_odex25_helpdesk_sla.py | 353 ++ .../odex25_helpdesk/tests/test_ui.py | 10 + .../odex25_helpdesk/views/assets.xml | 17 + .../odex25_helpdesk/views/digest_views.xml | 15 + .../views/mail_activity_views.xml | 23 + .../odex25_helpdesk_portal_templates.xml | 256 ++ .../views/odex25_helpdesk_team_views.xml | 635 +++ .../views/odex25_helpdesk_views.xml | 1036 +++++ .../views/res_partner_views.xml | 23 + .../views/zfp_config_setting.xml | 115 + .../__init__.py | 4 + .../__manifest__.py | 20 + .../i18n/ar_001.po | 132 + .../models/__init__.py | 4 + .../models/helpdesk_team.py | 109 + .../security/ir.model.access.csv | 2 + .../static/description/icon.png | Bin 0 -> 4615 bytes .../static/description/index.html | 83 + .../static/description/internet.png | Bin 0 -> 35772 bytes .../static/description/linkedin.png | Bin 0 -> 12914 bytes .../static/description/mail.png | Bin 0 -> 24437 bytes .../static/description/twitter.png | Bin 0 -> 20535 bytes .../views/helpdesk_views.xml | 50 + .../odex25_helpdesk_reopen/__init__.py | 4 + .../odex25_helpdesk_reopen/__manifest__.py | 21 + .../odex25_helpdesk_reopen/i18n/ar_001.po | 102 + .../odex25_helpdesk_reopen/models/__init__.py | 4 + .../odex25_helpdesk_reopen/models/helpdesk.py | 82 + .../views/cron_repair.xml | 13 + .../views/helpdesk_views.xml | 20 + .../odex25_helpdesk_sale/__init__.py | 3 + .../odex25_helpdesk_sale/__manifest__.py | 18 + .../data/odex25_helpdesk_sale_demo.xml | 53 + .../odex25_helpdesk_sale/i18n/ar.po | 59 + .../odex25_helpdesk_sale/models/__init__.py | 3 + .../models/odex25_helpdesk.py | 23 + .../static/description/icon.png | Bin 0 -> 32929 bytes .../views/odex25_helpdesk_views.xml | 44 + .../odex25_helpdesk_security/__init__.py | 5 + .../odex25_helpdesk_security/__manifest__.py | 25 + .../controllers/__init__.py | 1 + .../controllers/controller.py | 33 + .../odex25_helpdesk_security/i18n/ar_001.po | 163 + .../models/__init__.py | 1 + .../models/odex25_helpdesk_ticket.py | 61 + .../security/helpdesk_security.xml | 31 + .../security/ir.model.access.csv | 5 + .../static/src/xml/template.xml | 30 + .../odex25_helpdesk_security/views/view.xml | 340 ++ .../__init__.py | 4 + .../__manifest__.py | 22 + .../data/reminder_cron.xml | 13 + .../data/reminder_templates.xml | 63 + .../i18n/ar_001.po | 282 ++ .../models/__init__.py | 4 + .../models/helpdesk_reminder_policy.py | 204 + .../security/ir.model.access.csv | 2 + .../static/description/icon.png | Bin 0 -> 4615 bytes .../static/description/index.html | 83 + .../static/description/internet.png | Bin 0 -> 35772 bytes .../static/description/linkedin.png | Bin 0 -> 12914 bytes .../static/description/mail.png | Bin 0 -> 24437 bytes .../static/description/twitter.png | Bin 0 -> 20535 bytes .../views/helpdesk_sla_views.xml | 35 + .../odex25_helpdesk_stock/__init__.py | 4 + .../odex25_helpdesk_stock/__manifest__.py | 19 + .../data/odex25_helpdesk_stock_demo.xml | 91 + .../odex25_helpdesk_stock/i18n/ar.po | 142 + .../odex25_helpdesk_stock/models/__init__.py | 3 + .../models/odex25_helpdesk.py | 36 + .../static/description/icon.png | Bin 0 -> 32929 bytes .../odex25_helpdesk_stock/tests/__init__.py | 3 + .../tests/test_odex25_helpdesk_stock.py | 70 + .../views/odex25_helpdesk_views.xml | 51 + .../odex25_helpdesk_stock/wizard/__init__.py | 3 + .../wizard/stock_picking_return.py | 35 + .../wizard/stock_picking_return_views.xml | 22 + .../odex25_helpdesk_timesheet/__init__.py | 15 + .../odex25_helpdesk_timesheet/__manifest__.py | 24 + .../data/odex25_helpdesk_timesheet_data.xml | 12 + .../data/odex25_helpdesk_timesheet_demo.xml | 25 + .../odex25_helpdesk_timesheet/i18n/ar.po | 404 ++ .../models/__init__.py | 5 + .../models/analytic.py | 45 + .../models/odex25_helpdesk.py | 253 ++ .../models/project.py | 30 + .../security/ir.model.access.csv | 6 + .../odex25_helpdesk_timesheet_security.xml | 26 + .../static/description/icon.png | Bin 0 -> 32929 bytes .../tests/__init__.py | 4 + .../tests/test_project.py | 111 + .../views/odex25_helpdesk_views.xml | 188 + .../views/project_views.xml | 47 + .../wizard/__init__.py | 3 + ...odex25_helpdesk_ticket_create_timesheet.py | 38 + ...helpdesk_ticket_create_timesheet_views.xml | 22 + odex25_helpdesk/odex25_hr_gantt/__init__.py | 1 + .../odex25_hr_gantt/__manifest__.py | 13 + .../static/src/js/odex25_hr_gantt_renderer.js | 14 + .../static/src/js/odex25_hr_gantt_row.js | 59 + .../static/src/js/odex25_hr_gantt_view.js | 16 + .../static/src/xml/odex25_hr_gantt.xml | 10 + .../static/tests/odex25_hr_gantt_tests.js | 123 + .../odex25_hr_gantt/views/assets.xml | 16 + odex25_helpdesk/odex25_timer/__init__.py | 3 + odex25_helpdesk/odex25_timer/__manifest__.py | 28 + odex25_helpdesk/odex25_timer/i18n/ar.po | 158 + .../odex25_timer/models/__init__.py | 4 + odex25_helpdesk/odex25_timer/models/timer.py | 69 + .../odex25_timer/models/timer_mixin.py | 133 + .../odex25_timer/security/ir.model.access.csv | 2 + .../odex25_timer/security/timer_security.xml | 30 + .../odex25_timer/static/description/icon.png | Bin 0 -> 32929 bytes .../odex25_timer/static/src/js/timer.js | 63 + .../odex25_timer/static/src/js/timer_mixin.js | 119 + .../static/src/js/timer_toggle_button.js | 79 + .../static/src/scss/timer_button.scss | 26 + odex25_helpdesk/odex25_timer/views/assets.xml | 11 + .../odex25_timesheet_grid/__init__.py | 40 + .../odex25_timesheet_grid/__manifest__.py | 35 + .../odex25_timesheet_grid/data/mail_data.xml | 219 ++ .../data/odex25_timesheet_grid_demo.xml | 10 + .../odex25_timesheet_grid/i18n/ar.po | 1233 ++++++ .../odex25_timesheet_grid/models/__init__.py | 9 + .../odex25_timesheet_grid/models/analytic.py | 725 ++++ .../models/hr_employee.py | 73 + .../odex25_timesheet_grid/models/project.py | 32 + .../models/res_company.py | 187 + .../models/res_config_settings.py | 20 + .../odex25_timesheet_grid/models/res_users.py | 19 + .../odex25_timesheet_grid/models/task.py | 71 + .../security/ir.model.access.csv | 2 + .../security/timesheet_security.xml | 34 + .../static/description/icon.png | Bin 0 -> 8995 bytes .../static/description/icon.svg | 1 + .../static/img/logo_apple_store.png | Bin 0 -> 2982 bytes .../static/img/logo_chrome_store.png | Bin 0 -> 10090 bytes .../static/img/logo_google_play.png | Bin 0 -> 7478 bytes .../odex25_timesheet_grid_model.js | 28 + .../odex25_timesheet_grid_renderer.js | 55 + .../odex25_timesheet_grid_view.js | 35 + .../timer_header_component.js | 258 ++ .../src/js/odex25_timesheet_grid/timer_m2o.js | 143 + .../timer_start_component.js | 58 + .../timesheet_grid_controller.js | 48 + .../timesheet_timer_grid_controller.js | 59 + .../timesheet_timer_grid_model.js | 196 + .../timesheet_timer_grid_renderer.js | 338 ++ .../timesheet_timer_grid_view.js | 34 + .../static/src/js/timesheet_config_mixin.js | 52 + .../timesheet_kanban/timesheet_kanban_view.js | 40 + .../js/timesheet_list/timesheet_list_view.js | 40 + .../timesheet_pivot/timesheet_pivot_view.js | 31 + .../static/src/js/timesheet_uom.js | 79 + .../static/src/js/timesheet_uom_timer.js | 175 + .../src/js/tours/odex25_timesheet_grid.js | 44 + .../src/scss/odex25_timesheet_grid.scss | 82 + .../static/src/xml/odex25_timesheet_grid.xml | 142 + .../static/src/xml/timer_m2o.xml | 13 + .../tests/odex25_timesheet_grid_tests.js | 212 + .../tests/timesheet_timer_grid_tests.js | 563 +++ .../odex25_timesheet_grid/tests/__init__.py | 5 + .../tests/test_access_rights.py | 251 ++ .../tests/test_merge_timesheet.py | 62 + .../tests/test_timesheet.py | 221 ++ .../odex25_timesheet_grid/views/assets.xml | 34 + .../views/hr_timesheet_views.xml | 718 ++++ .../views/res_config_settings_views.xml | 57 + .../odex25_timesheet_grid/wizard/__init__.py | 3 + .../wizard/timesheet_merge_wizard.py | 77 + .../wizard/timesheet_merge_wizard_views.xml | 38 + .../odex25_website_helpdesk/__init__.py | 4 + .../odex25_website_helpdesk/__manifest__.py | 22 + .../controllers/__init__.py | 3 + .../controllers/main.py | 449 +++ .../data/helpdesk_data.xml | 3 + .../odex25_website_helpdesk/i18n/ar.po | 109 + .../odex25_website_helpdesk/i18n/ar_001.po | 375 ++ .../models/__init__.py | 4 + .../models/helpdesk.py | 34 + .../models/res_users.py | 9 + .../odex25_website_helpdesk/models/website.py | 13 + .../static/description/icon.png | Bin 0 -> 32929 bytes .../static/src/js/website_helpdesk.menu.js | 23 + .../odex25_website_helpdesk/views/assets.xml | 15 + .../views/helpdesk_templates.xml | 264 ++ .../views/helpdesk_views.xml | 13 + .../views/portal_user_views.xml | 389 ++ .../views/portal_user_views_bak.xml | 240 ++ .../odex25_website_helpdesk_form/__init__.py | 19 + .../__manifest__.py | 24 + .../controller/__init__.py | 3 + .../controller/main.py | 22 + .../data/odex25_website_helpdesk.xml | 23 + .../odex25_website_helpdesk_form/i18n/ar.po | 121 + .../models/__init__.py | 3 + .../models/helpdesk.py | 58 + .../static/description/icon.png | Bin 0 -> 10118 bytes .../static/description/icon.svg | 1 + .../static/src/js/script_tickets.js | 16 + .../src/js/website_helpdesk_form_editor.js | 43 + .../tests/__init__.py | 3 + .../tests/test_helpdesk_portal.py | 45 + .../views/helpdesk_templates.xml | 176 + .../views/helpdesk_views.xml | 19 + .../__init__.py | 3 + .../__manifest__.py | 28 + .../i18n/ar.po | 192 + .../models/__init__.py | 3 + .../models/helpdesk.py | 103 + .../static/description/icon.png | Bin 0 -> 32929 bytes .../views/helpdesk_view.xml | 42 + .../__init__.py | 4 + .../__manifest__.py | 26 + .../controllers/__init__.py | 3 + .../controllers/main.py | 21 + .../odex25_website_helpdesk_slides/i18n/ar.po | 83 + .../models/__init__.py | 3 + .../models/helpdesk.py | 15 + .../static/description/icon.png | Bin 0 -> 32929 bytes .../views/helpdesk_templates.xml | 35 + .../views/helpdesk_views.xml | 23 + .../odex_subscription_service/__init__.py | 4 + .../odex_subscription_service/__manifest__.py | 48 + .../controllers/__init__.py | 4 + .../controllers/portal.py | 130 + .../data/ir_cron.xml | 48 + .../data/subscription_service_data.xml | 28 + .../odex_subscription_service/i18n/ar_001.po | 1310 +++++++ .../models/__init__.py | 6 + .../models/account_move.py | 23 + .../models/product_template.py | 9 + .../models/res_partner.py | 65 + .../models/res_settings.py | 22 + .../models/subscription_service.py | 578 +++ .../security/ir.model.access.csv | 9 + .../security/sale_subscription_security.xml | 37 + .../description/Subscription_invoice.png | Bin 0 -> 110882 bytes .../static/description/Subscription_list.png | Bin 0 -> 59977 bytes .../description/Subscription_portal1.png | Bin 0 -> 17701 bytes .../description/Subscription_portal2.png | Bin 0 -> 50638 bytes .../description/Subscription_subscription.png | Bin 0 -> 101100 bytes .../static/description/banner.png | Bin 0 -> 64559 bytes .../static/description/icon.png | Bin 0 -> 13984 bytes .../static/description/icon2.png | Bin 0 -> 8535 bytes .../static/description/index.html | 85 + .../static/description/logoMDLU.png | Bin 0 -> 65968 bytes .../static/src/js/portal_subscription.js | 14 + .../static/src/scss/portal_subscription.scss | 14 + .../views/account_invoice_views.xml | 35 + .../views/assets.xml | 9 + .../views/product_template_views.xml | 37 + .../views/res_partner_views.xml | 35 + .../views/res_settings.xml | 37 + .../views/subscription_portal_templates.xml | 312 ++ .../views/subscription_service_views.xml | 250 ++ 292 files changed, 28539 insertions(+) create mode 100644 odex25_helpdesk/odex25_helpdesk/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk/__manifest__.py create mode 100644 odex25_helpdesk/odex25_helpdesk/controllers/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk/controllers/portal.py create mode 100644 odex25_helpdesk/odex25_helpdesk/controllers/rating.py create mode 100644 odex25_helpdesk/odex25_helpdesk/data/digest_data.xml create mode 100644 odex25_helpdesk/odex25_helpdesk/data/mail_data.xml create mode 100644 odex25_helpdesk/odex25_helpdesk/data/odex25_helpdesk_data.xml create mode 100644 odex25_helpdesk/odex25_helpdesk/data/odex25_helpdesk_demo.xml create mode 100644 odex25_helpdesk/odex25_helpdesk/i18n/ar.po create mode 100644 odex25_helpdesk/odex25_helpdesk/migrations/10.0.1.2/pre-nonulls.py create mode 100644 odex25_helpdesk/odex25_helpdesk/models/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk/models/digest.py create mode 100644 odex25_helpdesk/odex25_helpdesk/models/odex25_helpdesk.py create mode 100644 odex25_helpdesk/odex25_helpdesk/models/odex25_helpdesk_ticket.py create mode 100644 odex25_helpdesk/odex25_helpdesk/models/res_partner.py create mode 100644 odex25_helpdesk/odex25_helpdesk/models/res_setting.py create mode 100644 odex25_helpdesk/odex25_helpdesk/models/res_users.py create mode 100644 odex25_helpdesk/odex25_helpdesk/report/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk/report/odex25_helpdesk_sla_report_analysis.py create mode 100644 odex25_helpdesk/odex25_helpdesk/report/odex25_helpdesk_sla_report_analysis_views.xml create mode 100644 odex25_helpdesk/odex25_helpdesk/security/ir.model.access.csv create mode 100644 odex25_helpdesk/odex25_helpdesk/security/odex25_helpdesk_security.xml create mode 100644 odex25_helpdesk/odex25_helpdesk/static/description/icon.png create mode 100644 odex25_helpdesk/odex25_helpdesk/static/description/icon.svg create mode 100644 odex25_helpdesk/odex25_helpdesk/static/src/css/portal_odex25_helpdesk.css create mode 100644 odex25_helpdesk/odex25_helpdesk/static/src/img/menu-navigation.gif create mode 100644 odex25_helpdesk/odex25_helpdesk/static/src/js/odex25_helpdesk_dashboard.js create mode 100644 odex25_helpdesk/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js create mode 100644 odex25_helpdesk/odex25_helpdesk/static/src/scss/odex25_helpdesk.scss create mode 100644 odex25_helpdesk/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml create mode 100644 odex25_helpdesk/odex25_helpdesk/static/tests/odex25_helpdesk_dashboard_tests.js create mode 100644 odex25_helpdesk/odex25_helpdesk/tests/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk/tests/common.py create mode 100644 odex25_helpdesk/odex25_helpdesk/tests/test_doc_links.py create mode 100644 odex25_helpdesk/odex25_helpdesk/tests/test_odex25_helpdesk_flow.py create mode 100644 odex25_helpdesk/odex25_helpdesk/tests/test_odex25_helpdesk_sla.py create mode 100644 odex25_helpdesk/odex25_helpdesk/tests/test_ui.py create mode 100644 odex25_helpdesk/odex25_helpdesk/views/assets.xml create mode 100644 odex25_helpdesk/odex25_helpdesk/views/digest_views.xml create mode 100644 odex25_helpdesk/odex25_helpdesk/views/mail_activity_views.xml create mode 100644 odex25_helpdesk/odex25_helpdesk/views/odex25_helpdesk_portal_templates.xml create mode 100644 odex25_helpdesk/odex25_helpdesk/views/odex25_helpdesk_team_views.xml create mode 100644 odex25_helpdesk/odex25_helpdesk/views/odex25_helpdesk_views.xml create mode 100644 odex25_helpdesk/odex25_helpdesk/views/res_partner_views.xml create mode 100644 odex25_helpdesk/odex25_helpdesk/views/zfp_config_setting.xml create mode 100644 odex25_helpdesk/odex25_helpdesk_assignation_method/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_assignation_method/__manifest__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_assignation_method/i18n/ar_001.po create mode 100644 odex25_helpdesk/odex25_helpdesk_assignation_method/models/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_assignation_method/models/helpdesk_team.py create mode 100644 odex25_helpdesk/odex25_helpdesk_assignation_method/security/ir.model.access.csv create mode 100644 odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/icon.png create mode 100644 odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/index.html create mode 100644 odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/internet.png create mode 100644 odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/linkedin.png create mode 100644 odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/mail.png create mode 100644 odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/twitter.png create mode 100644 odex25_helpdesk/odex25_helpdesk_assignation_method/views/helpdesk_views.xml create mode 100644 odex25_helpdesk/odex25_helpdesk_reopen/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_reopen/__manifest__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_reopen/i18n/ar_001.po create mode 100644 odex25_helpdesk/odex25_helpdesk_reopen/models/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_reopen/models/helpdesk.py create mode 100644 odex25_helpdesk/odex25_helpdesk_reopen/views/cron_repair.xml create mode 100644 odex25_helpdesk/odex25_helpdesk_reopen/views/helpdesk_views.xml create mode 100644 odex25_helpdesk/odex25_helpdesk_sale/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_sale/__manifest__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_sale/data/odex25_helpdesk_sale_demo.xml create mode 100644 odex25_helpdesk/odex25_helpdesk_sale/i18n/ar.po create mode 100644 odex25_helpdesk/odex25_helpdesk_sale/models/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_sale/models/odex25_helpdesk.py create mode 100644 odex25_helpdesk/odex25_helpdesk_sale/static/description/icon.png create mode 100644 odex25_helpdesk/odex25_helpdesk_sale/views/odex25_helpdesk_views.xml create mode 100644 odex25_helpdesk/odex25_helpdesk_security/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_security/__manifest__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_security/controllers/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_security/controllers/controller.py create mode 100644 odex25_helpdesk/odex25_helpdesk_security/i18n/ar_001.po create mode 100644 odex25_helpdesk/odex25_helpdesk_security/models/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_security/models/odex25_helpdesk_ticket.py create mode 100644 odex25_helpdesk/odex25_helpdesk_security/security/helpdesk_security.xml create mode 100644 odex25_helpdesk/odex25_helpdesk_security/security/ir.model.access.csv create mode 100644 odex25_helpdesk/odex25_helpdesk_security/static/src/xml/template.xml create mode 100644 odex25_helpdesk/odex25_helpdesk_security/views/view.xml create mode 100644 odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/__manifest__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/data/reminder_cron.xml create mode 100644 odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/data/reminder_templates.xml create mode 100644 odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/i18n/ar_001.po create mode 100644 odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/models/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py create mode 100644 odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/security/ir.model.access.csv create mode 100644 odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/icon.png create mode 100644 odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/index.html create mode 100644 odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/internet.png create mode 100644 odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/linkedin.png create mode 100644 odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/mail.png create mode 100644 odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/twitter.png create mode 100644 odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/views/helpdesk_sla_views.xml create mode 100644 odex25_helpdesk/odex25_helpdesk_stock/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_stock/__manifest__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_stock/data/odex25_helpdesk_stock_demo.xml create mode 100644 odex25_helpdesk/odex25_helpdesk_stock/i18n/ar.po create mode 100644 odex25_helpdesk/odex25_helpdesk_stock/models/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_stock/models/odex25_helpdesk.py create mode 100644 odex25_helpdesk/odex25_helpdesk_stock/static/description/icon.png create mode 100644 odex25_helpdesk/odex25_helpdesk_stock/tests/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_stock/tests/test_odex25_helpdesk_stock.py create mode 100644 odex25_helpdesk/odex25_helpdesk_stock/views/odex25_helpdesk_views.xml create mode 100644 odex25_helpdesk/odex25_helpdesk_stock/wizard/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_stock/wizard/stock_picking_return.py create mode 100644 odex25_helpdesk/odex25_helpdesk_stock/wizard/stock_picking_return_views.xml create mode 100644 odex25_helpdesk/odex25_helpdesk_timesheet/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_timesheet/__manifest__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_timesheet/data/odex25_helpdesk_timesheet_data.xml create mode 100644 odex25_helpdesk/odex25_helpdesk_timesheet/data/odex25_helpdesk_timesheet_demo.xml create mode 100644 odex25_helpdesk/odex25_helpdesk_timesheet/i18n/ar.po create mode 100644 odex25_helpdesk/odex25_helpdesk_timesheet/models/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_timesheet/models/analytic.py create mode 100644 odex25_helpdesk/odex25_helpdesk_timesheet/models/odex25_helpdesk.py create mode 100644 odex25_helpdesk/odex25_helpdesk_timesheet/models/project.py create mode 100644 odex25_helpdesk/odex25_helpdesk_timesheet/security/ir.model.access.csv create mode 100644 odex25_helpdesk/odex25_helpdesk_timesheet/security/odex25_helpdesk_timesheet_security.xml create mode 100644 odex25_helpdesk/odex25_helpdesk_timesheet/static/description/icon.png create mode 100644 odex25_helpdesk/odex25_helpdesk_timesheet/tests/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_timesheet/tests/test_project.py create mode 100644 odex25_helpdesk/odex25_helpdesk_timesheet/views/odex25_helpdesk_views.xml create mode 100644 odex25_helpdesk/odex25_helpdesk_timesheet/views/project_views.xml create mode 100644 odex25_helpdesk/odex25_helpdesk_timesheet/wizard/__init__.py create mode 100644 odex25_helpdesk/odex25_helpdesk_timesheet/wizard/odex25_helpdesk_ticket_create_timesheet.py create mode 100644 odex25_helpdesk/odex25_helpdesk_timesheet/wizard/odex25_helpdesk_ticket_create_timesheet_views.xml create mode 100644 odex25_helpdesk/odex25_hr_gantt/__init__.py create mode 100644 odex25_helpdesk/odex25_hr_gantt/__manifest__.py create mode 100644 odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_renderer.js create mode 100644 odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_row.js create mode 100644 odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_view.js create mode 100644 odex25_helpdesk/odex25_hr_gantt/static/src/xml/odex25_hr_gantt.xml create mode 100644 odex25_helpdesk/odex25_hr_gantt/static/tests/odex25_hr_gantt_tests.js create mode 100644 odex25_helpdesk/odex25_hr_gantt/views/assets.xml create mode 100644 odex25_helpdesk/odex25_timer/__init__.py create mode 100644 odex25_helpdesk/odex25_timer/__manifest__.py create mode 100644 odex25_helpdesk/odex25_timer/i18n/ar.po create mode 100644 odex25_helpdesk/odex25_timer/models/__init__.py create mode 100644 odex25_helpdesk/odex25_timer/models/timer.py create mode 100644 odex25_helpdesk/odex25_timer/models/timer_mixin.py create mode 100644 odex25_helpdesk/odex25_timer/security/ir.model.access.csv create mode 100644 odex25_helpdesk/odex25_timer/security/timer_security.xml create mode 100644 odex25_helpdesk/odex25_timer/static/description/icon.png create mode 100644 odex25_helpdesk/odex25_timer/static/src/js/timer.js create mode 100644 odex25_helpdesk/odex25_timer/static/src/js/timer_mixin.js create mode 100644 odex25_helpdesk/odex25_timer/static/src/js/timer_toggle_button.js create mode 100644 odex25_helpdesk/odex25_timer/static/src/scss/timer_button.scss create mode 100644 odex25_helpdesk/odex25_timer/views/assets.xml create mode 100644 odex25_helpdesk/odex25_timesheet_grid/__init__.py create mode 100644 odex25_helpdesk/odex25_timesheet_grid/__manifest__.py create mode 100644 odex25_helpdesk/odex25_timesheet_grid/data/mail_data.xml create mode 100644 odex25_helpdesk/odex25_timesheet_grid/data/odex25_timesheet_grid_demo.xml create mode 100644 odex25_helpdesk/odex25_timesheet_grid/i18n/ar.po create mode 100644 odex25_helpdesk/odex25_timesheet_grid/models/__init__.py create mode 100644 odex25_helpdesk/odex25_timesheet_grid/models/analytic.py create mode 100644 odex25_helpdesk/odex25_timesheet_grid/models/hr_employee.py create mode 100644 odex25_helpdesk/odex25_timesheet_grid/models/project.py create mode 100644 odex25_helpdesk/odex25_timesheet_grid/models/res_company.py create mode 100644 odex25_helpdesk/odex25_timesheet_grid/models/res_config_settings.py create mode 100644 odex25_helpdesk/odex25_timesheet_grid/models/res_users.py create mode 100644 odex25_helpdesk/odex25_timesheet_grid/models/task.py create mode 100644 odex25_helpdesk/odex25_timesheet_grid/security/ir.model.access.csv create mode 100644 odex25_helpdesk/odex25_timesheet_grid/security/timesheet_security.xml create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/description/icon.png create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/description/icon.svg create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/img/logo_apple_store.png create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/img/logo_chrome_store.png create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/img/logo_google_play.png create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/js/odex25_timesheet_grid/odex25_timesheet_grid_model.js create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/js/odex25_timesheet_grid/odex25_timesheet_grid_renderer.js create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/js/odex25_timesheet_grid/odex25_timesheet_grid_view.js create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/js/odex25_timesheet_grid/timer_header_component.js create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/js/odex25_timesheet_grid/timer_m2o.js create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/js/odex25_timesheet_grid/timer_start_component.js create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/js/odex25_timesheet_grid/timesheet_grid_controller.js create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/js/odex25_timesheet_grid/timesheet_timer_grid_controller.js create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/js/odex25_timesheet_grid/timesheet_timer_grid_model.js create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/js/odex25_timesheet_grid/timesheet_timer_grid_renderer.js create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/js/odex25_timesheet_grid/timesheet_timer_grid_view.js create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/js/timesheet_config_mixin.js create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/js/timesheet_kanban/timesheet_kanban_view.js create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/js/timesheet_list/timesheet_list_view.js create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/js/timesheet_pivot/timesheet_pivot_view.js create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/js/timesheet_uom.js create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/js/timesheet_uom_timer.js create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/js/tours/odex25_timesheet_grid.js create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/scss/odex25_timesheet_grid.scss create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/xml/odex25_timesheet_grid.xml create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/src/xml/timer_m2o.xml create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/tests/odex25_timesheet_grid_tests.js create mode 100644 odex25_helpdesk/odex25_timesheet_grid/static/tests/timesheet_timer_grid_tests.js create mode 100644 odex25_helpdesk/odex25_timesheet_grid/tests/__init__.py create mode 100644 odex25_helpdesk/odex25_timesheet_grid/tests/test_access_rights.py create mode 100644 odex25_helpdesk/odex25_timesheet_grid/tests/test_merge_timesheet.py create mode 100644 odex25_helpdesk/odex25_timesheet_grid/tests/test_timesheet.py create mode 100644 odex25_helpdesk/odex25_timesheet_grid/views/assets.xml create mode 100644 odex25_helpdesk/odex25_timesheet_grid/views/hr_timesheet_views.xml create mode 100644 odex25_helpdesk/odex25_timesheet_grid/views/res_config_settings_views.xml create mode 100644 odex25_helpdesk/odex25_timesheet_grid/wizard/__init__.py create mode 100644 odex25_helpdesk/odex25_timesheet_grid/wizard/timesheet_merge_wizard.py create mode 100644 odex25_helpdesk/odex25_timesheet_grid/wizard/timesheet_merge_wizard_views.xml create mode 100644 odex25_helpdesk/odex25_website_helpdesk/__init__.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk/__manifest__.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk/controllers/__init__.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk/controllers/main.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk/data/helpdesk_data.xml create mode 100644 odex25_helpdesk/odex25_website_helpdesk/i18n/ar.po create mode 100644 odex25_helpdesk/odex25_website_helpdesk/i18n/ar_001.po create mode 100644 odex25_helpdesk/odex25_website_helpdesk/models/__init__.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk/models/helpdesk.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk/models/res_users.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk/models/website.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk/static/description/icon.png create mode 100644 odex25_helpdesk/odex25_website_helpdesk/static/src/js/website_helpdesk.menu.js create mode 100644 odex25_helpdesk/odex25_website_helpdesk/views/assets.xml create mode 100644 odex25_helpdesk/odex25_website_helpdesk/views/helpdesk_templates.xml create mode 100644 odex25_helpdesk/odex25_website_helpdesk/views/helpdesk_views.xml create mode 100644 odex25_helpdesk/odex25_website_helpdesk/views/portal_user_views.xml create mode 100644 odex25_helpdesk/odex25_website_helpdesk/views/portal_user_views_bak.xml create mode 100644 odex25_helpdesk/odex25_website_helpdesk_form/__init__.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk_form/__manifest__.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk_form/controller/__init__.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk_form/controller/main.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk_form/data/odex25_website_helpdesk.xml create mode 100644 odex25_helpdesk/odex25_website_helpdesk_form/i18n/ar.po create mode 100644 odex25_helpdesk/odex25_website_helpdesk_form/models/__init__.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk_form/models/helpdesk.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk_form/static/description/icon.png create mode 100644 odex25_helpdesk/odex25_website_helpdesk_form/static/description/icon.svg create mode 100644 odex25_helpdesk/odex25_website_helpdesk_form/static/src/js/script_tickets.js create mode 100644 odex25_helpdesk/odex25_website_helpdesk_form/static/src/js/website_helpdesk_form_editor.js create mode 100644 odex25_helpdesk/odex25_website_helpdesk_form/tests/__init__.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk_form/tests/test_helpdesk_portal.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk_form/views/helpdesk_templates.xml create mode 100644 odex25_helpdesk/odex25_website_helpdesk_form/views/helpdesk_views.xml create mode 100644 odex25_helpdesk/odex25_website_helpdesk_livechat/__init__.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk_livechat/__manifest__.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk_livechat/i18n/ar.po create mode 100644 odex25_helpdesk/odex25_website_helpdesk_livechat/models/__init__.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk_livechat/models/helpdesk.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk_livechat/static/description/icon.png create mode 100644 odex25_helpdesk/odex25_website_helpdesk_livechat/views/helpdesk_view.xml create mode 100644 odex25_helpdesk/odex25_website_helpdesk_slides/__init__.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk_slides/__manifest__.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk_slides/controllers/__init__.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk_slides/controllers/main.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk_slides/i18n/ar.po create mode 100644 odex25_helpdesk/odex25_website_helpdesk_slides/models/__init__.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk_slides/models/helpdesk.py create mode 100644 odex25_helpdesk/odex25_website_helpdesk_slides/static/description/icon.png create mode 100644 odex25_helpdesk/odex25_website_helpdesk_slides/views/helpdesk_templates.xml create mode 100644 odex25_helpdesk/odex25_website_helpdesk_slides/views/helpdesk_views.xml create mode 100644 odex25_helpdesk/odex_subscription_service/__init__.py create mode 100644 odex25_helpdesk/odex_subscription_service/__manifest__.py create mode 100644 odex25_helpdesk/odex_subscription_service/controllers/__init__.py create mode 100644 odex25_helpdesk/odex_subscription_service/controllers/portal.py create mode 100644 odex25_helpdesk/odex_subscription_service/data/ir_cron.xml create mode 100644 odex25_helpdesk/odex_subscription_service/data/subscription_service_data.xml create mode 100644 odex25_helpdesk/odex_subscription_service/i18n/ar_001.po create mode 100644 odex25_helpdesk/odex_subscription_service/models/__init__.py create mode 100644 odex25_helpdesk/odex_subscription_service/models/account_move.py create mode 100644 odex25_helpdesk/odex_subscription_service/models/product_template.py create mode 100644 odex25_helpdesk/odex_subscription_service/models/res_partner.py create mode 100644 odex25_helpdesk/odex_subscription_service/models/res_settings.py create mode 100644 odex25_helpdesk/odex_subscription_service/models/subscription_service.py create mode 100644 odex25_helpdesk/odex_subscription_service/security/ir.model.access.csv create mode 100644 odex25_helpdesk/odex_subscription_service/security/sale_subscription_security.xml create mode 100644 odex25_helpdesk/odex_subscription_service/static/description/Subscription_invoice.png create mode 100644 odex25_helpdesk/odex_subscription_service/static/description/Subscription_list.png create mode 100644 odex25_helpdesk/odex_subscription_service/static/description/Subscription_portal1.png create mode 100644 odex25_helpdesk/odex_subscription_service/static/description/Subscription_portal2.png create mode 100644 odex25_helpdesk/odex_subscription_service/static/description/Subscription_subscription.png create mode 100644 odex25_helpdesk/odex_subscription_service/static/description/banner.png create mode 100644 odex25_helpdesk/odex_subscription_service/static/description/icon.png create mode 100644 odex25_helpdesk/odex_subscription_service/static/description/icon2.png create mode 100644 odex25_helpdesk/odex_subscription_service/static/description/index.html create mode 100644 odex25_helpdesk/odex_subscription_service/static/description/logoMDLU.png create mode 100644 odex25_helpdesk/odex_subscription_service/static/src/js/portal_subscription.js create mode 100644 odex25_helpdesk/odex_subscription_service/static/src/scss/portal_subscription.scss create mode 100644 odex25_helpdesk/odex_subscription_service/views/account_invoice_views.xml create mode 100644 odex25_helpdesk/odex_subscription_service/views/assets.xml create mode 100644 odex25_helpdesk/odex_subscription_service/views/product_template_views.xml create mode 100644 odex25_helpdesk/odex_subscription_service/views/res_partner_views.xml create mode 100644 odex25_helpdesk/odex_subscription_service/views/res_settings.xml create mode 100644 odex25_helpdesk/odex_subscription_service/views/subscription_portal_templates.xml create mode 100644 odex25_helpdesk/odex_subscription_service/views/subscription_service_views.xml diff --git a/odex25_helpdesk/odex25_helpdesk/__init__.py b/odex25_helpdesk/odex25_helpdesk/__init__.py new file mode 100644 index 000000000..29fd41cc7 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +from . import controllers +from . import models +from . import report diff --git a/odex25_helpdesk/odex25_helpdesk/__manifest__.py b/odex25_helpdesk/odex25_helpdesk/__manifest__.py new file mode 100644 index 000000000..015eec16b --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/__manifest__.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- + +{ + 'name': 'Helpdesk', + 'version': '1.3', + 'sequence': 110, + 'summary': 'Track, prioritize, and solve customer tickets', + 'author': "Expert Co Ltd", + 'website': "http://www.ex.com", + 'category': 'Odex25-Helpdesk/Odex25-Helpdesk', + 'depends': [ + 'base_setup', + 'mail', + 'utm', + 'rating', + 'web_tour', + 'resource', + 'portal', + 'digest', + ], + 'description': """ +Odex25 helpdesk - Ticket Management App +================================ + +Features: + + - Process tickets through different stages to solve them. + - Add priorities, types, descriptions and tags to define your tickets. + - Use the chatter to communicate additional information and ping co-workers on tickets. + - Enjoy the use of an adapted dashboard, and an easy-to-use kanban view to handle your tickets. + - Make an in-depth analysis of your tickets through the pivot view in the reports menu. + - Create a team and define its members, use an automatic assignment method if you wish. + - Use a mail alias to automatically create tickets and communicate with your customers. + - Add Service Level Agreement deadlines automatically to your tickets. + - Get customer feedback by using ratings. + - Install additional features easily using your team form view. + + """, + 'data': [ + 'security/odex25_helpdesk_security.xml', + 'security/ir.model.access.csv', + 'data/digest_data.xml', + 'data/mail_data.xml', + # 'data/odex25_helpdesk_data.xml', + 'views/odex25_helpdesk_views.xml', + 'views/odex25_helpdesk_views.xml', + 'views/zfp_config_setting.xml', + 'views/odex25_helpdesk_team_views.xml', + 'views/assets.xml', + 'views/digest_views.xml', + 'views/odex25_helpdesk_portal_templates.xml', + # 'views/res_partner_views.xml', + 'views/mail_activity_views.xml', + 'report/odex25_helpdesk_sla_report_analysis_views.xml', + ], + 'qweb': [ + "static/src/xml/odex25_helpdesk_team_templates.xml", + ], + # 'demo': ['data/odex25_helpdesk_demo.xml'], + 'application': True, +} diff --git a/odex25_helpdesk/odex25_helpdesk/controllers/__init__.py b/odex25_helpdesk/odex25_helpdesk/controllers/__init__.py new file mode 100644 index 000000000..38a97908f --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/controllers/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import portal +from . import rating diff --git a/odex25_helpdesk/odex25_helpdesk/controllers/portal.py b/odex25_helpdesk/odex25_helpdesk/controllers/portal.py new file mode 100644 index 000000000..0a9a5662c --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/controllers/portal.py @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- + +from operator import itemgetter + +from odoo import http +from odoo.exceptions import AccessError, MissingError, UserError +from odoo.http import request +from odoo.tools.translate import _ +from odoo.tools import groupby as groupbyelem +from odoo.addons.portal.controllers.portal import pager as portal_pager, CustomerPortal +from odoo.osv.expression import OR + + +class CustomerPortal(CustomerPortal): + + def _prepare_portal_layout_values(self): + values = super(CustomerPortal, self)._prepare_portal_layout_values() + if values.get('sales_user', False): + values['title'] = _("Salesperson") + return values + + def _prepare_home_portal_values(self, counters): + values = super()._prepare_home_portal_values(counters) + if 'ticket_count' in counters: + values['ticket_count'] = request.env['odex25_helpdesk.ticket'].search_count([]) + return values + + def _ticket_get_page_view_values(self, ticket, access_token, **kwargs): + values = { + 'page_name': 'ticket', + 'ticket': ticket, + } + return self._get_page_view_values(ticket, access_token, values, 'my_tickets_history', False, **kwargs) + + @http.route(['/my/tickets', '/my/tickets/page/'], type='http', auth="user", website=True) + def my_odex25_helpdesk_tickets(self, page=1, date_begin=None, date_end=None, sortby=None, filterby='all', search=None, groupby='none', search_in='content', **kw): + values = self._prepare_portal_layout_values() + + searchbar_sortings = { + 'date': {'label': _('Newest'), 'order': 'create_date desc'}, + 'name': {'label': _('Subject'), 'order': 'name'}, + 'stage': {'label': _('Stage'), 'order': 'stage_id'}, + 'reference': {'label': _('Reference'), 'order': 'id'}, + 'update': {'label': _('Last Stage Update'), 'order': 'date_last_stage_update desc'}, + } + searchbar_filters = { + 'all': {'label': _('All'), 'domain': []}, + 'assigned': {'label': _('Assigned'), 'domain': [('user_id', '!=', False)]}, + 'unassigned': {'label': _('Unassigned'), 'domain': [('user_id', '=', False)]}, + 'open': {'label': _('Open'), 'domain': [('close_date', '=', False)]}, + 'closed': {'label': _('Closed'), 'domain': [('close_date', '!=', False)]}, + 'last_message_sup': {'label': _('Last message is from support')}, + 'last_message_cust': {'label': _('Last message is from customer')}, + } + searchbar_inputs = { + 'content': {'input': 'content', 'label': _('Search (in Content)')}, + 'message': {'input': 'message', 'label': _('Search in Messages')}, + 'customer': {'input': 'customer', 'label': _('Search in Customer')}, + 'id': {'input': 'id', 'label': _('Search in Reference')}, + 'status': {'input': 'status', 'label': _('Search in Stage')}, + 'all': {'input': 'all', 'label': _('Search in All')}, + } + searchbar_groupby = { + 'none': {'input': 'none', 'label': _('None')}, + 'stage': {'input': 'stage_id', 'label': _('Stage')}, + } + + # default sort by value + if not sortby: + sortby = 'date' + order = searchbar_sortings[sortby]['order'] + + if filterby in ['last_message_sup', 'last_message_cust']: + discussion_subtype_id = request.env.ref('mail.mt_comment').id + messages = request.env['mail.message'].search_read([('model', '=', 'odex25_helpdesk.ticket'), ('subtype_id', '=', discussion_subtype_id)], fields=['res_id', 'author_id'], order='date desc') + last_author_dict = {} + for message in messages: + if message['res_id'] not in last_author_dict: + last_author_dict[message['res_id']] = message['author_id'][0] + + ticket_author_list = request.env['odex25_helpdesk.ticket'].search_read(fields=['id', 'partner_id']) + ticket_author_dict = dict([(ticket_author['id'], ticket_author['partner_id'][0] if ticket_author['partner_id'] else False) for ticket_author in ticket_author_list]) + + last_message_cust = [] + last_message_sup = [] + for ticket_id in last_author_dict.keys(): + if last_author_dict[ticket_id] == ticket_author_dict[ticket_id]: + last_message_cust.append(ticket_id) + else: + last_message_sup.append(ticket_id) + + if filterby == 'last_message_cust': + domain = [('id', 'in', last_message_cust)] + else: + domain = [('id', 'in', last_message_sup)] + + else: + domain = searchbar_filters[filterby]['domain'] + + if date_begin and date_end: + domain += [('create_date', '>', date_begin), ('create_date', '<=', date_end)] + + # search + if search and search_in: + search_domain = [] + if search_in in ('id', 'all'): + search_domain = OR([search_domain, [('id', 'ilike', search)]]) + if search_in in ('content', 'all'): + search_domain = OR([search_domain, ['|', ('name', 'ilike', search), ('description', 'ilike', search)]]) + if search_in in ('customer', 'all'): + search_domain = OR([search_domain, [('partner_id', 'ilike', search)]]) + if search_in in ('message', 'all'): + discussion_subtype_id = request.env.ref('mail.mt_comment').id + search_domain = OR([search_domain, [('message_ids.body', 'ilike', search), ('message_ids.subtype_id', '=', discussion_subtype_id)]]) + if search_in in ('status', 'all'): + search_domain = OR([search_domain, [('stage_id', 'ilike', search)]]) + domain += search_domain + + # pager + tickets_count = len(request.env['odex25_helpdesk.ticket'].search(domain)) + pager = portal_pager( + url="/my/tickets", + url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby, 'search_in': search_in, 'search': search}, + total=tickets_count, + page=page, + step=self._items_per_page + ) + + tickets = request.env['odex25_helpdesk.ticket'].search(domain, order=order, limit=self._items_per_page, offset=pager['offset']) + request.session['my_tickets_history'] = tickets.ids[:100] + + if groupby == 'stage': + grouped_tickets = [request.env['odex25_helpdesk.ticket'].concat(*g) for k, g in groupbyelem(tickets, itemgetter('stage_id'))] + else: + grouped_tickets = [tickets] + + values.update({ + 'date': date_begin, + 'grouped_tickets': grouped_tickets, + 'page_name': 'ticket', + 'default_url': '/my/tickets', + 'pager': pager, + 'searchbar_sortings': searchbar_sortings, + 'searchbar_filters': searchbar_filters, + 'searchbar_inputs': searchbar_inputs, + 'searchbar_groupby': searchbar_groupby, + 'sortby': sortby, + 'groupby': groupby, + 'search_in': search_in, + 'search': search, + 'filterby': filterby, + }) + return request.render("odex25_helpdesk.portal_odex25_helpdesk_ticket", values) + + @http.route([ + "/odex25_helpdesk/ticket/", + "/odex25_helpdesk/ticket//", + '/my/ticket/', + '/my/ticket//' + ], type='http', auth="public", website=True) + def tickets_followup(self, ticket_id=None, access_token=None, **kw): + try: + ticket_sudo = self._document_check_access('odex25_helpdesk.ticket', ticket_id, access_token) + except (AccessError, MissingError): + return request.redirect('/my') + + values = self._ticket_get_page_view_values(ticket_sudo, access_token, **kw) + return request.render("odex25_helpdesk.tickets_followup", values) + + @http.route([ + '/my/ticket/close/', + '/my/ticket/close//', + ], type='http', auth="public", website=True) + def ticket_close(self, ticket_id=None, access_token=None, **kw): + try: + ticket_sudo = self._document_check_access('odex25_helpdesk.ticket', ticket_id, access_token) + except (AccessError, MissingError): + return request.redirect('/my') + + if not ticket_sudo.team_id.allow_portal_ticket_closing: + raise UserError(_("The team does not allow ticket closing through portal")) + + if not ticket_sudo.closed_by_partner: + closing_stage = ticket_sudo.team_id._get_closing_stage() + if ticket_sudo.stage_id != closing_stage: + ticket_sudo.write({'stage_id': closing_stage[0].id, 'closed_by_partner': True}) + else: + ticket_sudo.write({'closed_by_partner': True}) + body = _('Ticket closed by the customer') + ticket_sudo.with_context(mail_create_nosubscribe=True).message_post(body=body, message_type='comment', subtype_xmlid='mail.mt_note') + + return request.redirect('/my/ticket/%s/%s' % (ticket_id, access_token or '')) diff --git a/odex25_helpdesk/odex25_helpdesk/controllers/rating.py b/odex25_helpdesk/odex25_helpdesk/controllers/rating.py new file mode 100644 index 000000000..bfa11c6d8 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/controllers/rating.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- + +import datetime + +from odoo import http +from odoo.http import request +from odoo.osv.expression import AND + + +class Websiteodex25_helpdesk(http.Controller): + + @http.route(['/odex25_helpdesk/rating', '/odex25_helpdesk/rating/'], type='http', auth="public", website=True, sitemap=True) + def page(self, team=False, **kw): + # to avoid giving any access rights on Helpdesk team to the public user, let's use sudo + # and check if the user should be able to view the team (team managers only if it's not published or has no rating) + user = request.env.user + team_domain = [('id', '=', team.id)] if team else [] + if user.has_group('odex25_helpdesk.group_heldpesk_manager'): + domain = AND([[('use_rating', '=', True)], team_domain]) + else: + domain = AND([[('use_rating', '=', True), ('portal_show_rating', '=', True)], team_domain]) + teams = request.env['odex25_helpdesk.team'].search(domain) + team_values = [] + for team in teams: + tickets = request.env['odex25_helpdesk.ticket'].sudo().search([('team_id', '=', team.id)]) + domain = [ + ('res_model', '=', 'odex25_helpdesk.ticket'), ('res_id', 'in', tickets.ids), + ('consumed', '=', True), ('rating', '>=', 1), + ] + ratings = request.env['rating.rating'].sudo().search(domain, order="id desc", limit=100) + + yesterday = (datetime.date.today()-datetime.timedelta(days=-1)).strftime('%Y-%m-%d 23:59:59') + stats = {} + any_rating = False + for x in (7, 30, 90): + todate = (datetime.date.today()-datetime.timedelta(days=x)).strftime('%Y-%m-%d 00:00:00') + domdate = domain + [('create_date', '<=', yesterday), ('create_date', '>=', todate)] + stats[x] = {1: 0, 3: 0, 5: 0} + rating_stats = request.env['rating.rating'].sudo().read_group(domdate, [], ['rating']) + total = sum(st['rating_count'] for st in rating_stats) + for rate in rating_stats: + any_rating = True + stats[x][rate['rating']] = (rate['rating_count'] * 100) / total + values = { + 'team': team, + 'ratings': ratings if any_rating else False, + 'stats': stats, + } + team_values.append(values) + return request.render('odex25_helpdesk.team_rating_page', {'page_name': 'rating', 'teams': team_values}) diff --git a/odex25_helpdesk/odex25_helpdesk/data/digest_data.xml b/odex25_helpdesk/odex25_helpdesk/data/digest_data.xml new file mode 100644 index 000000000..171c8292e --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/data/digest_data.xml @@ -0,0 +1,27 @@ + + + + + True + + + + + + Tip: Create tickets from incoming emails + 1800 + + +
+ % set record = object.env['odex25_helpdesk.team'].search([('alias_name', '!=', False)],limit=1) + Tip: Create tickets from incoming emails + % if record and record.alias_domain +

Emails sent to ${record.alias_id.display_name} generate tickets in your pipeline.

+ % else +

Emails sent to a Helpdesk Team alias generate tickets in your pipeline.

+ % endif +
+
+
+
+
diff --git a/odex25_helpdesk/odex25_helpdesk/data/mail_data.xml b/odex25_helpdesk/odex25_helpdesk/data/mail_data.xml new file mode 100644 index 000000000..deb125225 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/data/mail_data.xml @@ -0,0 +1,184 @@ + + + + + + Handle Ticket + fa-ticket + + + + + + Ticket Created + 0 + odex25_helpdesk.ticket + + + Ticket created + + + Ticket Rated + 5 + odex25_helpdesk.ticket + + + Ticket rated + + + Stage Changed + 10 + odex25_helpdesk.ticket + + + Stage Changed + + + + + Ticket Created + 0 + odex25_helpdesk.team + + + team_id + + + Ticket Rated + 5 + odex25_helpdesk.team + + + team_id + + + Ticket Stage Changed + 10 + odex25_helpdesk.team + + + + team_id + + + + Ticket: Reception Acknowledgment + + ${object.display_name} + ${(object.user_id.email_formatted or user.email_formatted) | safe} + ${(object.partner_email if not object.sudo().partner_id.email or object.sudo().partner_id.email != object.partner_email else '') | safe} + ${object.partner_id.id if object.sudo().partner_id.email and object.sudo().partner_id.email == object.partner_email else ''} + +
+ Dear ${object.sudo().partner_id.name or 'Madam/Sir'},

+ Your request + % if object.get_portal_url(): + ${object.name} + % endif + has been received and is being reviewed by our ${object.team_id.name or ''} team. + The reference of your ticket is ${object.id}.

+ + + + To add additional comments, reply to this email.

+ + Thank you,

+ ${object.team_id.name or 'Helpdesk'} Team. +
+
+ ${object.partner_id.lang or object.user_id.lang or user.lang} + +
+ + + Ticket: Solved + + ${object.display_name} + ${(object.user_id.email_formatted or user.email_formatted) | safe} + ${(object.partner_email if not object.sudo().partner_id.email or object.sudo().partner_id.email != object.partner_email else '') | safe} + ${object.partner_id.id if object.sudo().partner_id.email and object.sudo().partner_id.email == object.partner_email else ''} + +
+ Dear ${object.sudo().partner_id.name or 'Madam/Sir'},

+ This automatic message informs you that we have closed your ticket (reference ${object.id}). + We hope that the services provided have met your expectations. + If you have any more questions or comments, don't hesitate to reply to this e-mail to re-open your ticket.

+ Thank you for your cooperation.
+ Kind regards,

+ ${object.team_id.name or 'Helpdesk'} Team. +
+
+ ${object.partner_id.lang or object.user_id.lang or user.lang} + +
+ + + Ticket: Rating Request (requires rating enabled on team) + + ${object.company_id.name or object.user_id.company_id.name or 'Helpdesk'}: Service Rating Request + ${object.rating_get_rated_partner_id().email_formatted | safe} + ${(object.partner_email if not object.sudo().partner_id.email or object.sudo().partner_id.email != object.partner_email else '') | safe} + ${object.partner_id.id if object.sudo().partner_id.email and object.sudo().partner_id.email == object.partner_email else ''} + +
+ % set access_token = object.rating_get_access_token() + % set partner = object.rating_get_partner_id() + + + + + + +
+ % if partner.name: + Hello ${partner.name},
+ % else: + Hello,
+ % endif + Please take a moment to rate our services related to the ticket "${object.name}" + % if object.rating_get_rated_partner_id().name: + assigned to ${object.rating_get_rated_partner_id().name}.
+ % else: + .
+ % endif +
+ + + +
+ Tell us how you feel about our service
+ (click on one of these smileys) +
+ + + + + + +
+ + Satisfied + + + + Not satisfied + + + + Highly Dissatisfied + +
+
+
+ We appreciate your feedback. It helps us to improve continuously. +
This customer survey has been sent because your ticket has been moved to the stage ${object.stage_id.name} +
+
+
+ ${object.partner_id.lang or object.user_id.lang or user.lang} + +
+
+
diff --git a/odex25_helpdesk/odex25_helpdesk/data/odex25_helpdesk_data.xml b/odex25_helpdesk/odex25_helpdesk/data/odex25_helpdesk_data.xml new file mode 100644 index 000000000..ca2004aed --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/data/odex25_helpdesk_data.xml @@ -0,0 +1,83 @@ + + + + + Customer Care + support + + + + + + + New + 0 + + + + + + In Progress + 1 + + + + + Solved + + 2 + + + + + Cancelled + 3 + + + + + + + Question + + + Issue + + + + + Share + + + form + code + action = records.action_share() + + + + + Status Per Deadline + odex25_helpdesk.sla.report.analysis + { + 'pivot_column_groupby': ['sla_deadline:day'], + 'pivot_row_groupby': ['team_id', 'ticket_id', 'sla_id'] + } + [] + + + + + + Failed SLA Stage per Month + odex25_helpdesk.sla.report.analysis + { + 'pivot_measures': ['__count'], + 'pivot_column_groupby': ['create_date:month'], + 'pivot_row_groupby': ['team_id', 'sla_stage_id'] + } + [] + + + + + diff --git a/odex25_helpdesk/odex25_helpdesk/data/odex25_helpdesk_demo.xml b/odex25_helpdesk/odex25_helpdesk/data/odex25_helpdesk_demo.xml new file mode 100644 index 000000000..b17232eef --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/data/odex25_helpdesk_demo.xml @@ -0,0 +1,384 @@ + + + + CRM + + + Website + + + Service + + + Repair + + + + + + + + + + Done + 2 + + + + + + + + VIP Support + + + + + + + + 2 days to start + + + 2 + + + + 7 days to finish + + + 7 + + + + Assigned within 24 hrs + + + 1 + 3 + + + assigning + + + + + Kitchen collapsing + + 3 + + + + + Where can I download a catalog ? + 0 + + + + + Warranty + + + + 2 + + + +Hello, + +I would like to know what kind of warranties you are offering for your products. + +Here is my contact number: 123456789 + +Thank you, +Chester Reed + + + + + + + + + + Wood Treatment + + + + + + +Hello, + +Is the wood from your furniture treated with a particular product? What would you recommend to maintain the quality of a dining table? + +Your assistance would be greatly appreciated. + +Thanks in Advance, +Azure Interior + + + + Chair dimensions + + + + + + +Can you please tell me the dimensions of your “Office chair Black”? Also I am unable to find the information on your official site. + +I look forward to your kind response. + +Thank you! + + + + Lost key + + + + + + +Hello, + +I bought a locker a few years ago and I, unfortunately, lost the key. I cannot retrieve the documents I had left in there without damaging the furniture item. What solution do you offer? + +Thanks in advance for your help. +Kind regards, +Gemini Furniture + + + + Furniture delivery + + + + + +Hi, + +I was wondering if you were delivering the furniture or if we needed to pick it up at your warehouse? +If you do take care of the delivery, are there any extra costs? + +Regards, +Deco Addict + + + + Cabinets in kit + + + + + +Hello, + +I would like to know if your cabinets come in a kit? They seem quite large and I am not sure they will fit through my front door. + +Thank you for your help. +Best regards, +Jackson Group + + + + Missing user manual + + + + + +Hello, + +I recently purchased one of your wardrobes in a kit. Unfortunately, I didn’t receive the user manual, so I cannot assemble the item. Could you send me this document? + +Thank you. +Kind regards, + + + + Ugly Chair + + + + + + +Hello, + +I purchased a chair from you last week. I now realize it doesn’t go well with the rest of my furniture, so I would like to return it and to get a refund. + +Regards, +Deco Addict + + + + Couch + + + + + + +Hello, + +The couch I ordered was scratched during the delivery. Would it be possible to have a gesture of goodwill? + +Thank you for considering my request. +Best regards, + + + + Chair wheels aren’t working + + + + + 3 + + + +The chair I bought last year isn't turning correctly anymore. Are you selling spare parts for the wheels? + +Thank you in advance for your help. +Chester Reed + + + + + + + + + + Cabinet Colour and Lock aren't proper + + + + + + 3 + + +Hi, + +I purchased a "Cabinet With Doors" from your store a few days ago. The lock is not working properly and the color is wrong. This is unacceptable! I am asking for a product that corresponds to my order and that matches the quality you are advertising. + +Regards, +The Jackson Group + + + + Lamp Stand is bent + + + + + + 2 + +Hello, + +Yesterday I purchased a lamp stand from your site but the product I received is bent. + +Would it be possible to get a replacement? + +Regards, +Ready Mat + + + + Table legs are unbalanced + + + + + + 3 + + +Hi, + +A few days ago, I bought a Four Persons Desk. While assembling it in my office, I found that the legs of the table were not properly balanced. Could you please come and fix this? + +Kindly do this as early as possible. + +Best, +Azure Interior + + + + Drawer’s slides and handle have a defect + + + + + + +Hi, + +I have purchased a "Drawer" from your store but the slides and the handle seem to have a defect. + +Would it be possible for you to fix it? + +Regards, +Deco + + + + Want to change the place of the dining area + + + + + + +Hello, + +I want to change the location of the dining area and would like your advice. + +Hope to hear from you soon. + +Best, +Gemini Furniture + + + + Received Product is damaged + + + + + + +Hi, + +I ordered a "Table Kit" from your store but the delivered product is damaged. I demand a refund as soon as possible. + +Regards, + + + + Delivered wood panel is not what I ordered + + + + + + +Hello, + +I ordered a wood panel from your online store, but the delivered product is not what I had ordered. + +Could you please replace it with the right product? +Waiting for your response. + +Best, +Wood Corner + + + + + + diff --git a/odex25_helpdesk/odex25_helpdesk/i18n/ar.po b/odex25_helpdesk/odex25_helpdesk/i18n/ar.po new file mode 100644 index 000000000..056288563 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/i18n/ar.po @@ -0,0 +1,3489 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * odex25_helpdesk +# +# Translators: +# Sherif Abd Ekmoniem , 2020 +# Yihya Hugirat , 2020 +# Sadig Adam , 2020 +# Ghaith Gammar , 2020 +# Zuhair Hammadi , 2020 +# Talal Kamal , 2020 +# Akram Alfusayal , 2020 +# amrnegm , 2020 +# Martin Trigaux, 2020 +# hoxhe Aits , 2020 +# Ali zuaby , 2020 +# Osoul , 2020 +# Mohammed Ibrahim , 2020 +# Osama Ahmaro , 2020 +# amal ahmed , 2020 +# Shaima Safar , 2020 +# Amer Hazaa , 2020 +# Mostafa Hanafy , 2020 +# Nisrine Tagri , 2020 +# Tasneem Sarhan , 2020 +# Talal Albahra , 2020 +# Mustafa Rawi , 2020 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-29 14:04+0000\n" +"PO-Revision-Date: 2020-09-07 08:20+0000\n" +"Last-Translator: Mustafa Rawi , 2020\n" +"Language-Team: Arabic (https://www.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: odex25_helpdesk +#: model:mail.template,subject:odex25_helpdesk.rating_ticket_request_email_template +msgid "" +"${object.company_id.name or object.user_id.company_id.name or 'Helpdesk'}: " +"Service Rating Request" +msgstr "" +"${object.company_id.name or object.user_id.company_id.name or 'مكتب الدعم'}:" +" طلب تقييم الخدمة" + +#. module: odex25_helpdesk +#: model:mail.template,subject:odex25_helpdesk.new_ticket_request_email_template +#: model:mail.template,subject:odex25_helpdesk.solved_ticket_request_email_template +msgid "${object.display_name}" +msgstr "${object.display_name}" + +#. module: odex25_helpdesk +#: model_terms:digest.tip,tip_description:odex25_helpdesk.digest_tip_odex25_helpdesk_0 +msgid "${record.alias_id.display_name}" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:digest.tip,tip_description:odex25_helpdesk.digest_tip_odex25_helpdesk_0 +msgid "% else" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:digest.tip,tip_description:odex25_helpdesk.digest_tip_odex25_helpdesk_0 +msgid "% endif" +msgstr "% endif" + +#. module: odex25_helpdesk +#: model_terms:digest.tip,tip_description:odex25_helpdesk.digest_tip_odex25_helpdesk_0 +msgid "" +"% set record = object.env['odex25_helpdesk.team'].search([('alias_name', '!=', False)],limit=1)\n" +" Tip: Create tickets from incoming emails\n" +" % if record and record.alias_domain" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup +msgid "&times;" +msgstr "&times;" + +#. module: odex25_helpdesk +#: model:mail.template,body_html:odex25_helpdesk.rating_ticket_request_email_template +msgid "" +"\n" +"
\n" +" % set access_token = object.rating_get_access_token()\n" +" % set partner = object.rating_get_partner_id()\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" % if partner.name:\n" +" Hello ${partner.name},
\n" +" % else:\n" +" Hello,
\n" +" % endif\n" +" Please take a moment to rate our services related to the ticket \"${object.name}\"\n" +" % if object.rating_get_rated_partner_id().name:\n" +" assigned to ${object.rating_get_rated_partner_id().name}.
\n" +" % else:\n" +" .
\n" +" % endif\n" +"
\n" +" \n" +" \n" +" \n" +"
\n" +" Tell us how you feel about our service
\n" +" (click on one of these smileys)\n" +"
\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"
\n" +" \n" +" \"Satisfied\"\n" +" \n" +" \n" +" \n" +" \"Not\n" +" \n" +" \n" +" \n" +" \"Highly\n" +" \n" +"
\n" +"
\n" +"
\n" +" We appreciate your feedback. It helps us to improve continuously.\n" +"
This customer survey has been sent because your ticket has been moved to the stage ${object.stage_id.name}\n" +"
\n" +"
\n" +" " +msgstr "" + +#. module: odex25_helpdesk +#: model:mail.template,body_html:odex25_helpdesk.solved_ticket_request_email_template +msgid "" +"\n" +"
\n" +" Dear ${object.sudo().partner_id.name or 'Madam/Sir'},

\n" +" This automatic message informs you that we have closed your ticket (reference ${object.id}).\n" +" We hope that the services provided have met your expectations.\n" +" If you have any more questions or comments, don't hesitate to reply to this e-mail to re-open your ticket.

\n" +" Thank you for your cooperation.
\n" +" Kind regards,

\n" +" ${object.team_id.name or 'Helpdesk'} Team.\n" +"
\n" +" " +msgstr "" +"\n" +"
\n" +" عزيزنا ${object.sudo().partner_id.name or 'Madam/Sir'}،

\n" +" هذه رسالة تلقائية لإخطاركم أننا أغلقنا تذكرتكم (رقم الإشارة: ${object.id}).\n" +" نأمل أن تكون خدماتنا قد ارتقت لمستوى توقعاتكم.\n" +" إذا كان لديكم أية تساؤلات أو تعليقات، لا تترددوا في الرد على هذه الرسالة لإعادة فتح التذكرة.

\n" +" شكرًا على تعاونكم.
\n" +" مع أطيب تحياتنا،

\n" +" فريق ${object.team_id.name or 'Helpdesk'}.\n" +"
\n" +" " + +#. module: odex25_helpdesk +#: model:mail.template,body_html:odex25_helpdesk.new_ticket_request_email_template +msgid "" +"\n" +"
\n" +" Dear ${object.sudo().partner_id.name or 'Madam/Sir'},

\n" +" Your request\n" +" % if object.get_portal_url():\n" +" ${object.name}\n" +" % endif\n" +" has been received and is being reviewed by our ${object.team_id.name or ''} team.\n" +" The reference of your ticket is ${object.id}.

\n" +"\n" +"
\n" +" View the ticket
\n" +"
\n" +"\n" +" To add additional comments, reply to this email.

\n" +"\n" +" Thank you,

\n" +" ${object.team_id.name or 'Helpdesk'} Team.\n" +"
\n" +" " +msgstr "" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0 +#, python-format +msgid "" +"Stars mark the ticket priority. You can change it directly " +"from here!" +msgstr "" +"عدد النجوم يحدد أولوية التذكرة. يمكنك تغييرهم مباشرة من هنا!" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.portal_odex25_helpdesk_ticket +msgid "Tickets in stage:" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup +msgid "No description" +msgstr "لا يوجد وصف" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_kanban +msgid "" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_kanban +msgid "" +"&nbsp;" +msgstr "" +"&nbsp;" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "" +" Enable the External Email " +"Servers feature in the General Settings and indicate an alias domain" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup +msgid "Status:" +msgstr "الحالة:" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup +msgid "" +"\n" +" If the issue has been solved, you can close the request.\n" +" " +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup +msgid "Close this ticket" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_kanban +msgid "" +"" +msgstr "" +"" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.view_partner_form_inherit_odex25_helpdesk +msgid " Tickets" +msgstr " التذاكر" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup +msgid "Assigned to" +msgstr "مُسند إلى" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup +msgid "Description" +msgstr "الوصف" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup +msgid "Managed by" +msgstr "يديره" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup +msgid "Reported by" +msgstr "قُدم التقرير بواسطة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup +msgid "Reported on" +msgstr "قُدم التقرير عن" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__alias_defaults +msgid "" +"A Python dictionary that will be evaluated to provide default values when " +"creating new records for this alias." +msgstr "" +"قاموس بايثون سيتم تقديره لتوفير قيم افتراضية عند إنشاء سجلات جديدة لهذا " +"اللقب." + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__description +msgid "About Team" +msgstr "تعريف بالفريق" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__access_warning +msgid "Access warning" +msgstr "تحذير من خطأ بالوصول" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_needaction +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_needaction +msgid "Action Needed" +msgstr "إجراء مطلوب" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__active +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__active +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__active +msgid "Active" +msgstr "نشط" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_ids +msgid "Activities" +msgstr "الأنشطة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_exception_decoration +msgid "Activity Exception Decoration" +msgstr "زخرفة استثناء النشاط" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_state +msgid "Activity State" +msgstr "حالة النشاط" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_type_icon +msgid "Activity Type Icon" +msgstr "أيقونة نوع النشاط" + +#. module: odex25_helpdesk +#: model:ir.actions.act_window,name:odex25_helpdesk.mail_activity_type_action_config_odex25_helpdesk +#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_menu_config_activity_type +msgid "Activity Types" +msgstr "أنواع النشاطات" + +#. module: odex25_helpdesk +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_stage_action +msgid "" +"Adapt your pipeline to your workflow and easily track the progress of your " +"tickets." +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_stage_view_form +msgid "Add a description..." +msgstr "إضافة وصف..." + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0 +#, python-format +msgid "" +"Add columns to configure stages for your tickets.
e.g. " +"Awaiting Customer Feedback, Customer Followup, ..." +msgstr "" +"إضافة أعمدة لإعداد مراحل تذاكرك.
مثلًا: بانتظار ملاحظات " +"العميل، ردود العميل, ..." + +#. module: odex25_helpdesk +#: model:res.groups,name:odex25_helpdesk.group_odex25_helpdesk_manager +msgid "Administrator" +msgstr "مدير" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "After Sales" +msgstr "بعد البيع" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_id +msgid "Alias" +msgstr "لقب" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_contact +msgid "Alias Contact Security" +msgstr "لقب الاتصال الآمن" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_name +msgid "Alias Name" +msgstr "اسم اللقب" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_domain +msgid "Alias domain" +msgstr "نطاق اللقب" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_model_id +msgid "Aliased Model" +msgstr "الكائن الملقب" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla__priority__0 +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla_report_analysis__priority__0 +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_ticket__priority__0 +#, python-format +msgid "All" +msgstr "الكل" + +#. module: odex25_helpdesk +#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_ticket_menu_all +msgid "All Tickets" +msgstr "كافة التذاكر" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__allow_portal_ticket_closing +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Allow customers to close their tickets" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Allow product returns from tickets" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "" +"Allow your customers to easily rate your services. Activate this option will" +" add a default email template on non folded closing stages" +msgstr "" +"اسمح لعملائك بتقييم خدماتك بسهولة. تفعيل هذا الخيار سيضيف قالب بريد إلكتروني" +" افتراضي في مراحل الإغلاق غير المطوية" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__is_self_assigned +msgid "Am I assigned" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form +msgid "Applies To Stage" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form +msgid "Apply on" +msgstr "يُطبق على" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_kanban +msgid "Archive" +msgstr "أرشفة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_search +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_search +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_form +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis_closed +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search +msgid "Archived" +msgstr "مؤرشف" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup +msgid "Are you sure you wish to proceed?" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_form +msgid "Assign To Me" +msgstr "إسنادها لي" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0 +#, python-format +msgid "Assign the ticket to someone." +msgstr "إسناد التذكرة لشخص." + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/models/odex25_helpdesk_ticket.py:0 +#, python-format +msgid "Assign to me" +msgstr "إسنادها لي" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#, python-format +msgid "Assigned" +msgstr "إسناد" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__user_id +msgid "Assigned To" +msgstr "أسندت إلى" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__user_id +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search +msgid "Assigned to" +msgstr "مسند إلى" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis_closed +msgid "Assignee" +msgstr "المُسند إليه" + +#. module: odex25_helpdesk +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla__target_type__assigning +msgid "Assigning Ticket" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__assign_method +msgid "Assignment Method" +msgstr "طريقة التعيين" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_stage_view_form +msgid "" +"At each stage employees can block or make task/issue ready for next stage.\n" +" You can define here labels that will be displayed for the state instead\n" +" of the default labels." +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_attachment_count +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_attachment_count +msgid "Attachment Count" +msgstr "عدد المرفقات" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_stage__template_id +msgid "" +"Automated email sent to the ticket's customer when the ticket reaches this " +"stage." +msgstr "" +"رسالة الرد الآلي المرسلة للعميل مُقدم التذكرة عند وصولها لهذه المرحلة." + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__assign_method +msgid "" +"Automatic assignment method for new tickets:\n" +"\tManually: manual\n" +"\tRandomly: randomly but everyone gets the same amount\n" +"\tBalanced: to the person with the least amount of open tickets" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.team_rating_progress_data +msgid "Average" +msgstr "متوسط" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "Avg 7 Days Happy Rating" +msgstr "" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "Avg 7 Success Rate" +msgstr "" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "Avg 7 days" +msgstr "متوسط 7 أيام" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "Avg Open Hours" +msgstr "متوسط ساعات الفتح" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.team_rating_progress_data +msgid "Bad" +msgstr "سيئ" + +#. module: odex25_helpdesk +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_team__assign_method__balanced +msgid "Balanced" +msgstr "متوازن" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/models/odex25_helpdesk.py:0 +#: model:odex25_helpdesk.stage,legend_blocked:odex25_helpdesk.stage_cancelled +#: model:odex25_helpdesk.stage,legend_blocked:odex25_helpdesk.stage_done +#: model:odex25_helpdesk.stage,legend_blocked:odex25_helpdesk.stage_in_progress +#: model:odex25_helpdesk.stage,legend_blocked:odex25_helpdesk.stage_new +#: model:odex25_helpdesk.stage,legend_blocked:odex25_helpdesk.stage_solved +#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_1 +#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_10 +#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_11 +#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_12 +#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_13 +#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_14 +#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_15 +#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_16 +#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_17 +#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_18 +#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_19 +#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_2 +#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_3 +#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_4 +#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_5 +#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_6 +#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_7 +#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_8 +#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_9 +#, python-format +msgid "Blocked" +msgstr "محجوب" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__campaign_id +msgid "Campaign" +msgstr "الحملة" + +#. module: odex25_helpdesk +#: model:odex25_helpdesk.stage,name:odex25_helpdesk.stage_cancelled +msgid "Cancelled" +msgstr "ملغي" + +#. module: odex25_helpdesk +#: model:ir.model.constraint,message:odex25_helpdesk.constraint_odex25_helpdesk_team_not_portal_show_rating_if_not_use_rating +msgid "Cannot show ratings in portal if not using them" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Channels" +msgstr "القنوات" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0 +#, python-format +msgid "Click here to view this team's tickets." +msgstr "اضغط هنا لعرض تذاكر هذا الفريق." + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0 +#, python-format +msgid "" +"Click these cards to open their form view, or drag & drop them " +"through the different stages of this team." +msgstr "" +"اضغط على هذه البطاقات لفتح واجهة عروضهم، أو اسحبهم وأسقطهم لنقلهم عبر" +" المراحل المختلفة لهذا الفريق." + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "Click to set" +msgstr "انقر لضبط" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup +msgid "Close" +msgstr "اغلاق" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__close_date +msgid "Close date" +msgstr "تاريخ الإقفال" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup +msgid "Close the ticket" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup +msgid "Close ticket" +msgstr "" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search +#, python-format +msgid "Closed" +msgstr "مغلق" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search +msgid "Closed Ticket" +msgstr "" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis +#, python-format +msgid "Closed Tickets" +msgstr "التذاكر المقفلة" + +#. module: odex25_helpdesk +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_7days_analysis +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_close_analysis +msgid "Closed Tickets Analysis" +msgstr "تحليل التذاكر المقفلة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__closed_by_partner +msgid "Closed by Partner" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__is_close +msgid "Closing Stage" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_tag__color +msgid "Color" +msgstr "اللون" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__color +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__color +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__color +msgid "Color Index" +msgstr "معرف اللون" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__kanban_state_label +msgid "Column Status" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__company_id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__company_id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__company_id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__company_id +msgid "Company" +msgstr "شركة" + +#. module: odex25_helpdesk +#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_menu_config +msgid "Configuration" +msgstr "إعدادات التكوين" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Configure SLA Policies" +msgstr "إعداد سياسات اتفاق مستوى الخدمة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Configure a custom domain" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.action_upcoming_sla_fail_all_tickets +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_sla +msgid "Congratulations!" +msgstr "تهانينا!" + +#. module: odex25_helpdesk +#: model:ir.model,name:odex25_helpdesk.model_res_partner +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup +msgid "Contact" +msgstr "جهة الاتصال" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_coupons +msgid "Coupons" +msgstr "الكوبونات" + +#. module: odex25_helpdesk +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.email_template_action_odex25_helpdesk +msgid "Create a new template" +msgstr "إنشاء قالب جديد" + +#. module: odex25_helpdesk +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_action +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_7days_analysis +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_7dayssuccess +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_close_analysis +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_dashboard +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_success +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_team_performance +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_analysis_action +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_team_analysis_action +msgid "Create tickets to get statistics." +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__create_uid +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__create_uid +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__create_uid +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_tag__create_uid +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__create_uid +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__create_uid +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket_type__create_uid +msgid "Created by" +msgstr "أنشئ بواسطة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__create_date +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__create_date +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__create_date +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_tag__create_date +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__create_date +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__create_date +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket_type__create_date +msgid "Created on" +msgstr "أنشئ في" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search +msgid "Creation Date" +msgstr "تاريخ الإنشاء" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup +msgid "Current stage of this ticket" +msgstr "المرحلة الحالية لهذه التذكرة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_bounced_content +msgid "Custom Bounced Message" +msgstr "" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/models/odex25_helpdesk_ticket.py:0 +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__partner_id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__partner_id +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search +#, python-format +msgid "Customer" +msgstr "الموظف / العميل" + +#. module: odex25_helpdesk +#: model:odex25_helpdesk.team,name:odex25_helpdesk.odex25_helpdesk_team1 +msgid "Customer Care" +msgstr "خدمة العملاء" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/models/odex25_helpdesk_ticket.py:0 +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__partner_email +#, python-format +msgid "Customer Email" +msgstr "البريد الإلكتروني" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__partner_name +msgid "Customer Name" +msgstr "اسم العميل" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__access_url +msgid "Customer Portal URL" +msgstr "رابط بوابة العميل" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/models/odex25_helpdesk_ticket.py:0 +#, python-format +msgid "Customer Tickets" +msgstr "تذاكر العميل" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "Daily Target" +msgstr "الهدف اليومي" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/js/odex25_helpdesk_dashboard.js:0 +#, python-format +msgid "Dashboard" +msgstr "لوحة المعلومات" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla_status__reached_datetime +msgid "Datetime at which the SLA stage was reached for the first time" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__sla_exceeded_days +msgid "Day to reach SLA" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__sla_exceeded_days +msgid "" +"Day to reach the stage of the SLA, without taking the working calendar into " +"account" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__time_days +msgid "Days" +msgstr "الأيام" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla__time_days +msgid "Days to reach given stage based on ticket creation date" +msgstr "عدد الأيام المتبقية للوصول لمرحلة معينة حسب تاريخ إنشاء التذكرة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__deadline +msgid "Deadline" +msgstr "الموعد النهائي" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_defaults +msgid "Default Values" +msgstr "القيم الافتراضية" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_kanban +msgid "Delete" +msgstr "حذف" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__description +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__description +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.portal_odex25_helpdesk_ticket +msgid "Description" +msgstr "الوصف" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Description for customer portal" +msgstr "وصف بوابة العميل" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form +msgid "Description of the policy..." +msgstr "وصف السياسة..." + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_form +msgid "Description of the ticket..." +msgstr "وصف التذكرة..." + +#. module: odex25_helpdesk +#: model:ir.model,name:odex25_helpdesk.model_digest_digest +msgid "Digest" +msgstr "الملخص" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup +msgid "Discard" +msgstr "تجاهل" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_digest_digest__display_name +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__display_name +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__display_name +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__display_name +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__display_name +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_tag__display_name +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__display_name +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__display_name +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket_type__display_name +#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_partner__display_name +#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_users__display_name +msgid "Display Name" +msgstr "الاسم المعروض" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__portal_show_rating +msgid "Display Rating on Customer Portal" +msgstr "عرض التقييم على بوابة العميل" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/models/digest.py:0 +#, python-format +msgid "Do not have access, skip this data for user's digest email" +msgstr "لا تملك صلاحيات الوصول. تخطِ هذه البيانات لبريد الملخص للمستخدم." + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Documentation" +msgstr "التوثيق" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_form +msgid "Documents" +msgstr "المستندات" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__domain_user_ids +msgid "Domain User" +msgstr "" + +#. module: odex25_helpdesk +#: model:odex25_helpdesk.stage,name:odex25_helpdesk.stage_done +msgid "Done" +msgstr "المنتهية" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_kanban +msgid "Dropdown menu" +msgstr "القائمة المنسدلة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_kanban +msgid "Edit" +msgstr "تحرير" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__template_id +msgid "Email Template" +msgstr "قالب البريد الإلكتروني" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_alias +msgid "Email alias" +msgstr "لقب البريد الإلكتروني" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__email_cc +msgid "Email cc" +msgstr "البريد الإلكتروني cc" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__email +msgid "Email on Customer" +msgstr "البريد الإلكتروني" + +#. module: odex25_helpdesk +#: model_terms:digest.tip,tip_description:odex25_helpdesk.digest_tip_odex25_helpdesk_0 +msgid "Emails sent to" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:digest.tip,tip_description:odex25_helpdesk.digest_tip_odex25_helpdesk_0 +msgid "" +"Emails sent to a Helpdesk Team alias generate tickets in your pipeline." +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_website_helpdesk_slides +msgid "Enable eLearning" +msgstr "تفعيل eLearning" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0 +#, python-format +msgid "" +"Enter a subject or title for this ticket.
(e.g. Problem with " +"installation, Wrong order, Can't understand bill, etc.)" +msgstr "" +"أدخل موضوع أو عنوان لهذه التذكرة.
(مثلًا: مشكلة أثناء التثبيت، طلب " +"خطأ، فاتورة غير مفهومة، إلخ.)" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0 +#, python-format +msgid "Enter the customer. Feel free to create it on the fly." +msgstr "أدخل اسم العميل. يمكنك إنشاء واحد جديد سريعًا." + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__exceeded_days +msgid "Excedeed Working Days" +msgstr "أيام عمل متقطعة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__exclude_stage_ids +msgid "Exclude Stages" +msgstr "استبعاد المراحل" + +#. module: odex25_helpdesk +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla_report_analysis__sla_status__failed +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla_status__status__failed +msgid "Failed" +msgstr "فشل" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__sla_fail +msgid "Failed SLA Policy" +msgstr "حدث فشل باتفاق مستوى الخدمة" + +#. module: odex25_helpdesk +#: model:ir.filters,name:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_filter_stage_failed +msgid "Failed SLA Stage per Month" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search +msgid "Failed Ticket" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis +msgid "First Assignment Date" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__assign_date +msgid "First assignment date" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__fold +msgid "Folded in Kanban" +msgstr "مطوي في عرض كانبان" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "" +"Follow this team to automatically track the events associated to tickets of " +"this team." +msgstr "تابع هذا الفريق لتتمكن من تتبع الفعاليات المرتبطة بتذاكرهم تلقائيًا." + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_follower_ids +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_follower_ids +msgid "Followers" +msgstr "المتابعون" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_channel_ids +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_channel_ids +msgid "Followers (Channels)" +msgstr "المتابعون (القنوات)" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_partner_ids +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_partner_ids +msgid "Followers (Partners)" +msgstr "المتابعون (الشركاء)" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_type_icon +msgid "Font awesome icon e.g. fa-tasks" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search +msgid "Future Activities" +msgstr "الأنشطة المستقبلية" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Generate credit notes from tickets" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Get in touch with your website visitors" +msgstr "تواصل مع زوار موقعك" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Grant coupons from tickets" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_ticket__kanban_state__done +msgid "Green" +msgstr "أخضر" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__legend_done +msgid "Green Kanban Label" +msgstr "بطاقة عنوان كانبان خضراء" + +#. module: odex25_helpdesk +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_ticket__kanban_state__normal +msgid "Grey" +msgstr "رمادي" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__legend_normal +msgid "Grey Kanban Label" +msgstr "بطاقة عنوان كانبان رمادية" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis_closed +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search +msgid "Group By" +msgstr "تجميع حسب" + +#. module: odex25_helpdesk +#: model:mail.activity.type,name:odex25_helpdesk.mail_act_odex25_helpdesk_handle +msgid "Handle Ticket" +msgstr "التعامل مع التذكرة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.team_rating_progress_data +msgid "Happy" +msgstr "سعيد" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "Happy Rating" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__has_external_mail_server +msgid "Has External Mail Server" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__sla_reached_late +msgid "Has SLA reached late" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_website_helpdesk_forum +msgid "Help Center" +msgstr "مركز المساعدة" + +#. module: odex25_helpdesk +#: model:ir.ui.menu,name:odex25_helpdesk.menu_odex25_helpdesk_root +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.digest_digest_view_form +msgid "Helpdesk" +msgstr "مكتب المساعدة" + +#. module: odex25_helpdesk +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_team_dashboard_action_main +msgid "Helpdesk Overview" +msgstr "نظرة عامة على مكتب المساعدة" + +#. module: odex25_helpdesk +#: model:ir.model,name:odex25_helpdesk.model_odex25_helpdesk_sla +msgid "Helpdesk SLA Policies" +msgstr "سياسات اتفاق مستوى الخدمة لمكتب المساعدة" + +#. module: odex25_helpdesk +#: model:ir.model,name:odex25_helpdesk.model_odex25_helpdesk_stage +msgid "Helpdesk Stage" +msgstr "مرحلة مكتب المساعدة" + +#. module: odex25_helpdesk +#: model:ir.model,name:odex25_helpdesk.model_odex25_helpdesk_tag +msgid "Helpdesk Tags" +msgstr "وسوم مكتب المساعدة" + +#. module: odex25_helpdesk +#: model:ir.model,name:odex25_helpdesk.model_odex25_helpdesk_team +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__name +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__team_id +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_tree +msgid "Helpdesk Team" +msgstr "فريق مكتب المساعدة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Helpdesk Team..." +msgstr "فريق مكتب المساعدة..." + +#. module: odex25_helpdesk +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_team_action +#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_team_menu +msgid "Helpdesk Teams" +msgstr "فرق مكتب المساعدة" + +#. module: odex25_helpdesk +#: model:ir.model,name:odex25_helpdesk.model_odex25_helpdesk_ticket +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_form +msgid "Helpdesk Ticket" +msgstr "تذكرة مكتب المساعدة" + +#. module: odex25_helpdesk +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_analysis_action +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_graph_analysis +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_pivot_analysis +msgid "Helpdesk Ticket Analysis" +msgstr "تحليل تذكرة مكتب المساعدة" + +#. module: odex25_helpdesk +#: model:ir.model,name:odex25_helpdesk.model_odex25_helpdesk_ticket_type +msgid "Helpdesk Ticket Type" +msgstr "نوع تذكرة مكتب المساعدة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_graph_main +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_pivot_main +msgid "Helpdesk Tickets" +msgstr "تذاكر مكتب المساعدة" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "High Priority (" +msgstr "أولوية عالية (" + +#. module: odex25_helpdesk +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla__priority__2 +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla_report_analysis__priority__2 +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_ticket__priority__2 +msgid "High priority" +msgstr "أولوية عالية" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__time_hours +msgid "Hours" +msgstr "الساعات" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla__time_hours +msgid "Hours to reach given stage based on ticket creation date" +msgstr "عدد الساعات المتبقية للوصول لمرحلة معينة حسب تاريخ إنشاء التذكرة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "How to assign newly created tickets to the right person" +msgstr "كيفية إسناد الرسائل المُنشأة حديثًا للشخص الصحيح" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_digest_digest__id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_tag__id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket_type__id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_partner__id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_users__id +msgid "ID" +msgstr "المُعرف" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__alias_parent_thread_id +msgid "" +"ID of the parent record holding the alias (example: project holding the task" +" creation alias)" +msgstr "" +"مُعرف السجل الأصلي الذي يحتوي اللقب (مثال: 'المشروع' يحمل لقب إنشاء المهمة)" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_exception_icon +msgid "Icon" +msgstr "الأيقونة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_exception_icon +msgid "Icon to indicate an exception activity." +msgstr "الأيقونة للإشارة إلى استثناء النشاط" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__message_needaction +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__message_unread +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__message_needaction +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__message_unread +msgid "If checked, new messages require your attention." +msgstr "إذا كان محددًا، فهناك رسائل جديدة تحتاج لرؤيتها." + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__message_has_error +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__message_has_sms_error +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__message_has_error +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__message_has_sms_error +msgid "If checked, some messages have a delivery error." +msgstr "إذا كان محددًا، فقد حدث خطأ في تسليم بعض الرسائل." + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__closed_by_partner +msgid "" +"If checked, this means the ticket was closed through the customer portal by " +"the customer." +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__alias_bounced_content +msgid "" +"If set, this content will automatically be sent out to unauthorized users " +"instead of the default message." +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_kanban +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_kanban +msgid "In #{kanban_getcolorname(record.color.raw_value)}" +msgstr "في #{kanban_getcolorname(record.color.raw_value)}" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__use_website_helpdesk_livechat +msgid "" +"In Channel: You can create a new ticket by typing /Helpdesk [ticket title]. " +"You can search ticket by typing /odex25_helpdesk_search [Keyword1],[Keyword2],." +msgstr "" +"في القناة: يمكنك إنشاء تذكرة جديدة بكتابة /Helpdesk [عنوان التذكرة]. كما " +"يمكنك البحث عن تذكرة عبر كتابة /odex25_helpdesk_search [الكلمة الدلالية1]،[الكلمة " +"الدلالية2]." + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/models/odex25_helpdesk.py:0 +#: model:odex25_helpdesk.stage,legend_normal:odex25_helpdesk.stage_cancelled +#: model:odex25_helpdesk.stage,legend_normal:odex25_helpdesk.stage_done +#: model:odex25_helpdesk.stage,legend_normal:odex25_helpdesk.stage_in_progress +#: model:odex25_helpdesk.stage,legend_normal:odex25_helpdesk.stage_new +#: model:odex25_helpdesk.stage,legend_normal:odex25_helpdesk.stage_solved +#: model:odex25_helpdesk.stage,name:odex25_helpdesk.stage_in_progress +#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_1 +#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_10 +#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_11 +#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_12 +#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_13 +#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_14 +#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_15 +#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_16 +#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_17 +#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_18 +#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_19 +#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_2 +#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_3 +#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_4 +#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_5 +#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_6 +#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_7 +#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_8 +#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_9 +#, python-format +msgid "In Progress" +msgstr "قيد التنفيذ" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Incoming emails create tickets" +msgstr "الرسائل الواردة تنشئ تذاكر" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "" +"Individuals to whom the tickets will be automatically assigned. Keep empty " +"for everyone to be part of the team." +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_is_follower +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_is_follower +msgid "Is Follower" +msgstr "متابع" + +#. module: odex25_helpdesk +#: model:odex25_helpdesk.ticket.type,name:odex25_helpdesk.type_incident +msgid "Issue" +msgstr "المشكلة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__legend_blocked +msgid "Kanban Blocked Explanation" +msgstr "تفسير حالة متوقف في واجهة كانبان" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__legend_normal +msgid "Kanban Ongoing Explanation" +msgstr "تفسير حالة قيد التقدم في واجهة كانبان" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__kanban_state +msgid "Kanban State" +msgstr "حالة كانبان" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__legend_done +msgid "Kanban Valid Explanation" +msgstr "تفسير حالة صالح في واجهة كانبان" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_digest_digest__kpi_odex25_helpdesk_tickets_closed_value +msgid "Kpi Helpdesk Tickets Closed Value" +msgstr "قيمة مؤشر أداء تذاكر الدعم المقفلة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.team_rating_data +msgid "Last 3 months" +msgstr "آخر 3 أشهر" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.team_rating_data +msgid "Last 30 days" +msgstr "آخر 30 يوم" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.team_rating_data +msgid "Last 7 days" +msgstr "آخر 7 أيام" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_digest_digest____last_update +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla____last_update +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis____last_update +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status____last_update +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage____last_update +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_tag____last_update +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team____last_update +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket____last_update +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket_type____last_update +#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_partner____last_update +#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_users____last_update +msgid "Last Modified on" +msgstr "آخر تعديل في" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__date_last_stage_update +#, python-format +msgid "Last Stage Update" +msgstr "آخر تحديث للمرحلة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__write_uid +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__write_uid +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__write_uid +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_tag__write_uid +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__write_uid +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__write_uid +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket_type__write_uid +msgid "Last Updated by" +msgstr "آخر تحديث بواسطة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__write_date +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__write_date +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__write_date +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_tag__write_date +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__write_date +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__write_date +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket_type__write_date +msgid "Last Updated on" +msgstr "آخر تحديث في" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#, python-format +msgid "Last message is from customer" +msgstr "" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#, python-format +msgid "Last message is from support" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search +msgid "Late Activities" +msgstr "الأنشطة المتأخرة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.team_rating_data +msgid "Latest Feedbacks" +msgstr "أحدث الملاحظات" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0 +#, python-format +msgid "Let's create your first ticket." +msgstr "فلننشئ تذكرتك الأولى." + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__email_cc +msgid "List of cc from incoming emails." +msgstr "قائمة cc من رسائل البريد الإلكتروني الواردة." + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_website_helpdesk_livechat +msgid "Live chat" +msgstr "دردشة حية" + +#. module: odex25_helpdesk +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla__priority__1 +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla_report_analysis__priority__1 +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_ticket__priority__1 +msgid "Low priority" +msgstr "أولوية منخفضة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_main_attachment_id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_main_attachment_id +msgid "Main Attachment" +msgstr "المرفق الرئيسي" + +#. module: odex25_helpdesk +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_sla_action +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_sla_action_main +msgid "" +"Make sure tickets are handled in a timely manner by using SLA Policies.
" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_team__assign_method__manual +msgid "Manually" +msgstr "يدويًا" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__medium_id +msgid "Medium" +msgstr "وسط" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_has_error +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_has_error +msgid "Message Delivery error" +msgstr "خطأ في تسليم الرسائل" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup +msgid "Message and communication history" +msgstr "رسالة وتاريخ الاتصالات" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_ids +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_ids +msgid "Messages" +msgstr "الرسائل" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__priority +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__priority +msgid "Minimum Priority" +msgstr "الأولوية الأدنى" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla__stage_id +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla_status__sla_stage_id +msgid "Minimum stage a ticket needs to reach in order to satisfy this SLA." +msgstr "أدنى مرحلة يجب أن تصل لها التذكرة لتحقيق اتفاق مستوى الخدمة." + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__time_minutes +msgid "Minutes" +msgstr "الدقائق" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla__time_minutes +msgid "Minutes to reach given stage based on ticket creation date" +msgstr "" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "My Failed Tickets" +msgstr "" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "My High Priority Failed Tickets" +msgstr "" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "My High Priority Tickets" +msgstr "" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "My High Priority Tickets Analysis" +msgstr "" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "My Open Tickets" +msgstr "" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "My Open Tickets Analysis" +msgstr "" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "My Performance" +msgstr "أدائي" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search +msgid "My Ticket" +msgstr "" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_ticket_menu_my +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis_closed +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search +#, python-format +msgid "My Tickets" +msgstr "تذاكري" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "My Urgent Failed Tickets" +msgstr "" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "My Urgent Tickets" +msgstr "" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "My Urgent Tickets Analysis" +msgstr "" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/models/odex25_helpdesk.py:0 +#: code:addons/odex25_helpdesk/models/odex25_helpdesk.py:0 +#: model:odex25_helpdesk.stage,name:odex25_helpdesk.stage_new +#, python-format +msgid "New" +msgstr "جديد" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#, python-format +msgid "Newest" +msgstr "الأحدث" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_date_deadline +msgid "Next Activity Deadline" +msgstr "الموعد النهائي للنشاط التالي" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_summary +msgid "Next Activity Summary" +msgstr "ملخص النشاط التالي" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_type_id +msgid "Next Activity Type" +msgstr "نوع النشاط التالي" + +#. module: odex25_helpdesk +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_sla_action +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_sla_action_main +msgid "No SLA policies found. Let's create one!" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_action +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_7days_analysis +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_close_analysis +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_success +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_team_performance +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_analysis_action +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_team_analysis_action +msgid "No data yet !" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_7dayssuccess +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_dashboard +msgid "No data yet!" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_stage_action +msgid "No stages found. Let's create one!" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_tag_action +msgid "No tags found. Let's create one!" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_team_action +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_team_dashboard_action_main +msgid "No teams found" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_my_ticket_action_no_create +msgid "No tickets found" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_main_my +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_main_tree +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_team +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_unassigned +msgid "No tickets found. Let's create one!" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_type_action +msgid "No types found. Let's create one!" +msgstr "" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#, python-format +msgid "None" +msgstr "لا شيء" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_needaction_counter +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_needaction_counter +msgid "Number of Actions" +msgstr "عدد الإجراءات" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__attachment_number +msgid "Number of Attachments" +msgstr "عدد المرفقات" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__partner_ticket_count +msgid "Number of closed tickets from the same partner" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_has_error_counter +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_has_error_counter +msgid "Number of errors" +msgstr "عدد الاخطاء" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__message_needaction_counter +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__message_needaction_counter +msgid "Number of messages which requires an action" +msgstr "عدد الرسائل التي تتطلب إجراء" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__message_has_error_counter +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "عدد الرسائل الحادث بها خطأ في التسليم" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__message_unread_counter +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__message_unread_counter +msgid "Number of unread messages" +msgstr "عدد الرسائل الجديدة" + +#. module: odex25_helpdesk +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla_report_analysis__sla_status__ongoing +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla_status__status__ongoing +msgid "Ongoing" +msgstr "جاري" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla__ticket_type_id +msgid "" +"Only apply the SLA to a specific ticket type. If left empty it will apply to" +" all types." +msgstr "" +"تطبيق اتفاق مستوى الخدمة على نوع تذاكر معين فقط. إذا تُركت هذه الخانة فارغة " +"سيتم تطبيقه على كافة الأنواع." + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla__tag_ids +msgid "" +"Only apply the SLA to tickets with specific tags. If left empty it will " +"apply to all tags." +msgstr "" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search +#, python-format +msgid "Open" +msgstr "فتح" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis +msgid "Open Tickets" +msgstr "فتح تذاكر" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__ticket_open_hours +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__open_hours +msgid "Open Time (hours)" +msgstr "وقت الفتح (بالساعات)" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__alias_force_thread_id +msgid "" +"Optional ID of a thread (record) to which all incoming messages will be " +"attached, even if they did not reply to it. If set, this will disable the " +"creation of new records completely." +msgstr "" +"معرف اختياري لمناقشة (سجل) سيتم إرفاق كافة الرسائل الواردة به، حتى لو لم " +"يردوا عليه. إذا تم تعيين قيمة له، سيعطل هذا إنشاء السجلات الجديدة بالكامل." + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.team_rating_page +msgid "Our Customer Satisfaction" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.portal_my_home_menu_odex25_helpdesk +msgid "Our Ratings" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_stage__legend_blocked +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__legend_blocked +msgid "" +"Override the default value displayed for the blocked state for kanban " +"selection, when the task or issue is in that stage." +msgstr "" +"تجاوز القيمة الافتراضية المعروضة لحالة \"محظورة\" باختيارات كانبان، عندما " +"تكون المهمة أو المشكلة في تلك المرحلة." + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_stage__legend_done +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__legend_done +msgid "" +"Override the default value displayed for the done state for kanban " +"selection, when the task or issue is in that stage." +msgstr "" +"تجاوز القيمة الافتراضية المعروضة لحالة \"منتهية\" باختيارات كانبان، عندما " +"تكون المهمة أو المشكلة في تلك المرحلة." + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_stage__legend_normal +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__legend_normal +msgid "" +"Override the default value displayed for the normal state for kanban " +"selection, when the task or issue is in that stage." +msgstr "" +"تجاوز القيمة الافتراضية المعروضة لحالة \"عادية\" باختيارات كانبان، عندما " +"تكون المهمة أو المشكلة في تلك المرحلة." + +#. module: odex25_helpdesk +#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_menu_team_dashboard +msgid "Overview" +msgstr "نظرة عامة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_user_id +msgid "Owner" +msgstr "المالك" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_parent_model_id +msgid "Parent Model" +msgstr "الكائن الأصل" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_parent_thread_id +msgid "Parent Record Thread ID" +msgstr "معرف مناقشة السجل الأصلي" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__alias_parent_model_id +msgid "" +"Parent model holding the alias. The model holding the alias reference is not" +" necessarily the model given by alias_model_id (example: project " +"(parent_model) and task (model))" +msgstr "" +"الكائن الأب الذي يحتفظ بلقب البريد الإلكتروني. الكائن الذي يحتفظ بالإشارة " +"إلى لقب البريد الإلكتروني لا يشترط بالضرورة أن يكون هو الكائن المحدد في " +"الحقل alias_model_id (مثال: المشروع (الكائن الأب) والمهمة (الكائن))" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__rating_percentage_satisfaction +msgid "Percentage of happy ratings" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Performance" +msgstr "الأداء" + +#. module: odex25_helpdesk +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_team_performance +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_graph_analysis +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_pivot_analysis +msgid "Performance Analysis" +msgstr "تحليل الأداء" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/js/odex25_helpdesk_dashboard.js:0 +#, python-format +msgid "Please enter an integer value" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__alias_contact +msgid "" +"Policy to post a message on the document using the mailgateway.\n" +"- everyone: everyone can post\n" +"- partners: only authenticated partners\n" +"- followers: only followers of the related document or members of following channels\n" +msgstr "" +"سياسة لنشر رسالة على المستند باستخدام بوابة البريد الإلكتروني.\n" +"- الجميع: يمكن للجميع النشر\n" +"- الشركاء: الشركاء المعتمدون فقط\n" +"- المتابعون: فقط متابعو الوثيقة ذات الصلة أو أعضاء القنوات التالية.\n" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__access_url +msgid "Portal Access URL" +msgstr "رابط الوصول لبوابة العملاء" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__priority +#: model:ir.model.fields,field_description:odex25_helpdesk.field_helpdesk_service__priority +msgid "Priority" +msgstr "الأولوية" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Productivity & Visibility" +msgstr "الإنتاجية والرؤية" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Publish this team's ratings on your website" +msgstr "نشر تقييمات هذا الفريق على موقعك" + +#. module: odex25_helpdesk +#: model:odex25_helpdesk.ticket.type,name:odex25_helpdesk.type_question +msgid "Question" +msgstr "السؤال" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Question and answer section on your website" +msgstr "قسم الأسئلة والأجوبة على موقعك" + +#. module: odex25_helpdesk +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_team__assign_method__randomly +msgid "Random" +msgstr "عشوائي" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__rating_ids +msgid "Rating" +msgstr "التقييم" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__rating_avg +msgid "Rating Average" +msgstr "متوسط التقييم" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__rating_last_feedback +msgid "Rating Last Feedback" +msgstr "آخر ملاحظات التقييم" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__rating_last_image +msgid "Rating Last Image" +msgstr "آخر صورة للتقييم" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__rating_last_value +msgid "Rating Last Value" +msgstr "آخر قيمة للتقييم" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__rating_percentage_satisfaction +msgid "Rating Satisfaction" +msgstr "نسبة الرضا" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__rating_count +msgid "Rating count" +msgstr "عدد التقييمات" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__rating_ids +msgid "Ratings" +msgstr "تقييمات" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_rating +msgid "Ratings on tickets" +msgstr "التقييم على التذاكر" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form +msgid "Reach In" +msgstr "الوصول في" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form +msgid "Reach Stage" +msgstr "الوصول للمرحلة" + +#. module: odex25_helpdesk +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla_report_analysis__sla_status__reached +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla_status__status__reached +msgid "Reached" +msgstr "وصل" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__reached_datetime +msgid "Reached Date" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla__target_type__stage +msgid "Reaching Stage" +msgstr "" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/models/odex25_helpdesk.py:0 +#: model:odex25_helpdesk.stage,legend_done:odex25_helpdesk.stage_cancelled +#: model:odex25_helpdesk.stage,legend_done:odex25_helpdesk.stage_done +#: model:odex25_helpdesk.stage,legend_done:odex25_helpdesk.stage_in_progress +#: model:odex25_helpdesk.stage,legend_done:odex25_helpdesk.stage_new +#: model:odex25_helpdesk.stage,legend_done:odex25_helpdesk.stage_solved +#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_1 +#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_10 +#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_11 +#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_12 +#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_13 +#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_14 +#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_15 +#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_16 +#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_17 +#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_18 +#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_19 +#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_2 +#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_3 +#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_4 +#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_5 +#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_6 +#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_7 +#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_8 +#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_9 +#, python-format +msgid "Ready" +msgstr "جاهز" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__rating_last_feedback +msgid "Reason of the rating" +msgstr "سبب التقييم" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_force_thread_id +msgid "Record Thread ID" +msgstr "معرف مناقشة السجل" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Record timesheets on your tickets" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_ticket__kanban_state__blocked +msgid "Red" +msgstr "أحمر" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__legend_blocked +msgid "Red Kanban Label" +msgstr "بطاقة عنوان كانبان حمراء" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.portal_odex25_helpdesk_ticket +msgid "Ref" +msgstr "رقم الإشارة" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#, python-format +msgid "Reference" +msgstr "رقم الإشارة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_credit_notes +msgid "Refunds" +msgstr "المرتجعات" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__use_odex25_helpdesk_sale_timesheet +msgid "Reinvoice the time spent on ticket through tasks." +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Reinvoice time to your customer through tasks" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Repair broken products" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_product_repairs +msgid "Repairs" +msgstr "تعديلات" + +#. module: odex25_helpdesk +#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_ticket_report_menu_main +msgid "Reporting" +msgstr "التقارير" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_user_id +msgid "Responsible User" +msgstr "المستخدم المسؤول" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_kanban +msgid "Restore" +msgstr "استعادة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_product_returns +msgid "Returns" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__sla_id +msgid "SLA" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__sla_deadline +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__sla_deadline +msgid "SLA Deadline" +msgstr "" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis_closed +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search +#, python-format +msgid "SLA Failed" +msgstr "حدث فشل باتفاق مستوى الخدمة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_kanban +msgid "SLA Issues" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_sla_action +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_sla_action_main +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_sla +#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_sla_menu_main +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_tree +msgid "SLA Policies" +msgstr "سياسات اتفاق مستوى الخدمة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form +msgid "SLA Policy" +msgstr "سياسة اتفاق مستوى الخدمة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__description +msgid "SLA Policy Description" +msgstr "وصف سياسة اتفاق مستوى الخدمة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__name +msgid "SLA Policy Name" +msgstr "اسم سياسة اتفاق مستوى الخدمة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__sla_reached_datetime +msgid "SLA Reached Date" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__sla_stage_id +msgid "SLA Stage" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__sla_status_ids +msgid "SLA Status" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_action +#: model:ir.model,name:odex25_helpdesk.model_odex25_helpdesk_sla_report_analysis +#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_ticket_report_menu_sla_analysis +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_graph +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_pivot +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search +msgid "SLA Status Analysis" +msgstr "تحليل حالة اتفاقية مستوى الخدمة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search +msgid "SLA Status Deadline" +msgstr "الموعد النهائي لحالة SLA" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__sla_status_failed +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search +msgid "SLA Status Failed" +msgstr "فشل حالة SLA" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis +msgid "SLA Success" +msgstr "نجاح SLA" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis +msgid "SLA in Progress" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__sla_ids +msgid "SLAs" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_has_sms_error +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_has_sms_error +msgid "SMS Delivery error" +msgstr "خطأ في تسليم الرسائل القصيرة" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#, python-format +msgid "Salesperson" +msgstr "مسئول المبيعات" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "Sample" +msgstr "عينة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Save this page and refresh to activate the feature" +msgstr "احفظ هذه الصفحة وقم بتحديثها لتفعيل الخاصية" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Save this page and refresh to activate the feature." +msgstr "احفظ هذه الصفحة وقم بتحديثها لتفعيل الخاصية." + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0 +#, python-format +msgid "Save this ticket and the modifications you've made to it." +msgstr "حفظ هذه التذكرة والتعديلات التي قمت بها عليها." + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#, python-format +msgid "Search (in Content)" +msgstr "البحث (في المحتوى)" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_search +msgid "Search SLA Policies" +msgstr "البحث في سياسات اتفاق مستوى الخدمة" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#, python-format +msgid "Search in All" +msgstr "البحث في الكل" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#, python-format +msgid "Search in Customer" +msgstr "البحث في العملاء" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#, python-format +msgid "Search in Messages" +msgstr "البحث في الرسائل" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#, python-format +msgid "Search in Reference" +msgstr "" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#, python-format +msgid "Search in Stage" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__access_token +msgid "Security Token" +msgstr "رمز الحماية" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_kanban +msgid "See Customer Satisfaction" +msgstr "رؤية نسبة رضا العميل" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Self-Service" +msgstr "خدمة ذاتية" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Sell & Track Hours" +msgstr "ساعات البيع والتتبع" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Send emails to" +msgstr "إرسال رسائل بريد إلكتروني إلى" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Send emails to:" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__sequence +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__sequence +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket_type__sequence +msgid "Sequence" +msgstr "المسلسل" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Set the calendar used to compute SLA target" +msgstr "إعداد التقويم المُستخدم لحساب اتفاق مستوى الخدمة المستهدف" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Set up your Service Level Agreements to track performance" +msgstr "ضبط اتفاق مستوى الخدمة الخاص بك لمتابعة الأداء" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_kanban +msgid "Settings" +msgstr "الإعدادات" + +#. module: odex25_helpdesk +#: model:ir.actions.server,name:odex25_helpdesk.model_odex25_helpdesk_ticket_action_share +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_kanban +msgid "Share" +msgstr "مشاركة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Share presentation and videos, and organize into courses" +msgstr "مشاركة العروض والفيديوهات، وتنظيمهم في دورات" + +#. module: odex25_helpdesk +#: model:res.groups,name:odex25_helpdesk.group_use_sla +msgid "Show SLA Policies" +msgstr "عرض سياسات اتفاق مستوى الخدمة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search +msgid "Show all records which has next action date is before today" +msgstr "عرض كافة السجلات المُعين لها تاريخ إجراء تالي يسبق تاريخ اليوم الجاري" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__sla_id +msgid "Sla" +msgstr "" + +#. module: odex25_helpdesk +#: model:odex25_helpdesk.stage,name:odex25_helpdesk.stage_solved +msgid "Solved" +msgstr "تم حلّها" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__source_id +msgid "Source" +msgstr "المصدر" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_stage__team_ids +msgid "" +"Specific team that uses this stage. Other teams will not be able to see or " +"use this stage." +msgstr "" +"الفريق المحدد الذي يستخدم هذه المرحلة. لن تتمكن الفرق الأخرى من رؤية أو " +"استخدام هذه المرحلة." + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__stage_id +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_stage_view_tree +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.portal_odex25_helpdesk_ticket +#, python-format +msgid "Stage" +msgstr "المرحلة" + +#. module: odex25_helpdesk +#: model:mail.message.subtype,description:odex25_helpdesk.mt_ticket_stage +#: model:mail.message.subtype,name:odex25_helpdesk.mt_ticket_stage +msgid "Stage Changed" +msgstr "تم تغيير المرحلة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_stage_view_form +msgid "Stage Description and Tooltips" +msgstr "وصف وتلميحات المرحلة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__name +msgid "Stage Name" +msgstr "اسم المرحلة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_stage_view_form +msgid "Stage Search" +msgstr "البحث في المراحل" + +#. module: odex25_helpdesk +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_stage_action +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__stage_ids +#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_stage_menu +msgid "Stages" +msgstr "المراحل" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__stage_ids +msgid "" +"Stages the team will use. This team's tickets will only be able to be in " +"these stages." +msgstr "" +"المراحل التي سيستخدمها الفريق. سيُسمح بوضع تذاكر هذا الفريق في هذه المراحل " +"فقط." + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__sla_status +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__status +msgid "Status" +msgstr "الحالة" + +#. module: odex25_helpdesk +#: model:ir.filters,name:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_filter_status_per_deadline +msgid "Status Per Deadline" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__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: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__name +#, python-format +msgid "Subject" +msgstr "العنوان" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_form +msgid "Subject..." +msgstr "الموضوع..." + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "Submit tickets with an online form" +msgstr "تقديم التذاكر بنموذج أونلاين" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "Success Rate" +msgstr "نسبة النجاح" + +#. module: odex25_helpdesk +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_7dayssuccess +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_success +msgid "Success Rate Analysis" +msgstr "تحليل نسبة النجاح" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__sla_success +msgid "Success SLA Policy" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tag_view_tree +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_type_view_tree +msgid "Tag" +msgstr "الوسم" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_tag__name +msgid "Tag Name" +msgstr "اسم الوسم" + +#. module: odex25_helpdesk +#: model:ir.model.constraint,message:odex25_helpdesk.constraint_odex25_helpdesk_tag_name_uniq +msgid "Tag name already exists !" +msgstr "اسم الوسم موجود بالفعل!" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__tag_ids +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__tag_ids +msgid "Tags" +msgstr "الوسوم" + +#. module: odex25_helpdesk +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_tag_action +msgid "Tags are perfect to organize your tickets." +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form +msgid "Target" +msgstr "الهدف" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_users__odex25_helpdesk_target_rating +msgid "Target Customer Rating" +msgstr "التقييم المستهدف من العميل" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__stage_id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__sla_stage_id +msgid "Target Stage" +msgstr "المرحلة المستهدفة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_users__odex25_helpdesk_target_success +msgid "Target Success Rate" +msgstr "معدل تحقيق الهدف" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_users__odex25_helpdesk_target_closed +msgid "Target Tickets to Close" +msgstr "التذاكر المطلوب إقفالها" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__target_type +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__target_type +msgid "Target Type" +msgstr "نوع الهدف" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_stage_view_form +msgid "Task in progress. Click to block or set as done." +msgstr "المهمة قيد التنفيذ. اضغط لحظرها أو تعيينها كمكتملة." + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_stage_view_form +msgid "Task is blocked. Click to unblock or set as done." +msgstr "المهمة محظورة. اضغط لإلغاء حظرها أو تعيينها كمكتملة." + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__team_id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__team_id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__team_ids +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis_closed +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search +msgid "Team" +msgstr "فريق" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__member_ids +msgid "Team Members" +msgstr "أعضاء الفريق" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__visibility_member_ids +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "" +"Team Members to whom this team will be visible. Keep empty for everyone to " +"see this team." +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_search +msgid "Team Search" +msgstr "فريق البحث" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__visibility_member_ids +msgid "Team Visibility" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_team_action +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_team_dashboard_action_main +msgid "" +"Teams regroup tickets for people sharing the same expertise or from the same" +" area." +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.actions.act_window,name:odex25_helpdesk.email_template_action_odex25_helpdesk +msgid "Templates" +msgstr "قوالب" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla__exclude_stage_ids +msgid "" +"The amount of time the ticket spends in this stage will not be taken into " +"account when evaluating the status of the SLA Policy." +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__sla_deadline +msgid "The closest deadline of all SLA applied on this ticket" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__alias_model_id +msgid "" +"The model (Odoo Document Kind) to which this alias corresponds. Any incoming" +" email that does not reply to an existing record will cause the creation of " +"a new record of this model (e.g. a Project Task)" +msgstr "" +"الكائن (مستندات أودو) الذي يقترن معه هذا اللقب. أي رسالة واردة لا ترد على " +"سجل موجود ستقوم بإنشاء سجل جديد من نفس نوع هذا الكائن (مثلًا: مهمة مشروع)" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__alias_name +msgid "" +"The name of the email alias, e.g. 'jobs' if you want to catch emails for " +"" +msgstr "" +"اسم لقب البريد الإلكتروني، مثلًا: 'وظائف' إذا كنت جمع الرسائل المرسلة " +"لـ" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__alias_user_id +msgid "" +"The owner of records created upon receiving emails on this alias. If this " +"field is not set the system will attempt to find the right owner based on " +"the sender (From) address, or will use the Administrator account if no " +"system user is found for that address." +msgstr "" +"مالك السجلات المنشأة عند استلام رسائل بريد إلكتروني على هذا اللقب. إذا لم " +"يتم تعيين قيمة لهذا الحقل، سيحاول النظام معرفة المالك الصحيح حسب عنوان " +"البريد الإلكتروني للمرسل، أو سيستخدم حساب المشرف إذا لم يجد حسابًا مرتبطًا " +"بعنوان البريد الإلكتروني." + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#, python-format +msgid "The team does not allow ticket closing through portal" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.portal_odex25_helpdesk_ticket +msgid "There are currently no Ticket for your account." +msgstr "لا توجد حاليًا تذاكر لحسابك." + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.team_rating_page +msgid "There are no ratings yet." +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__assign_hours +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__close_hours +msgid "This duration is based on the working calendar of the team" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__open_hours +msgid "This duration is not based on the working calendar of the team" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__campaign_id +msgid "" +"This is a name that helps you keep track of your different campaign efforts," +" e.g. Fall_Drive, Christmas_Special" +msgstr "" +"يساعدك هذا الاسم علي تتبع جهود حملتك المختلفة، مثلًا: fall_drive " +"،christmas_special" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__medium_id +msgid "This is the method of delivery, e.g. Postcard, Email, or Banner Ad" +msgstr "" +"هذه هي طريقة التسليم، مثلًا: بطاقة بريدية، أو البريد الإلكتروني أو لافتة " +"إعلانية" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__source_id +msgid "" +"This is the source of the link, e.g. Search Engine, another domain, or name " +"of email list" +msgstr "" +"هذا هو مصدر الرابط، مثلًا: محرك بحث، أو نطاق آخر، أو اسم في قائمة البريد " +"الإلكتروني" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__use_odex25_helpdesk_timesheet +msgid "This required to have project module installed." +msgstr "يتطلب هذا تثبيت موديول المشروع." + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_stage__fold +msgid "" +"This stage is folded in the kanban view when there are no records in that " +"stage to display." +msgstr "" +"يتم طي هذه المرحلة عند استخدام طريقة كانبان للعرض عندما لا توجد سجلات يمكن " +"عرضها في هذه المرحلة." + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_stage_view_form +msgid "This step is done. Click to block or set in progress." +msgstr "هذه الخطوة مكتملة. اضغط لحظرها أو تعيينها قيد التنفيذ." + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "Three stars, maximum score" +msgstr "ثلاثة نجوم، أقصى درجة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__ticket_id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__ticket_id +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_activity +msgid "Ticket" +msgstr "التذكرة" + +#. module: odex25_helpdesk +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_dashboard +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_team_analysis_action +msgid "Ticket Analysis" +msgstr "تحليل التذكرة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__ticket_closed +msgid "Ticket Closed" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__create_date +msgid "Ticket Create Date" +msgstr "" + +#. module: odex25_helpdesk +#: model:mail.message.subtype,name:odex25_helpdesk.mt_team_ticket_new +#: model:mail.message.subtype,name:odex25_helpdesk.mt_ticket_new +msgid "Ticket Created" +msgstr "تم إنشاء التذكرة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search +msgid "Ticket Creation Date" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__ticket_deadline +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search +msgid "Ticket Deadline" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__ticket_failed +msgid "Ticket Failed" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search +msgid "Ticket ID" +msgstr "مُعرف التذكرة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_tree +msgid "Ticket Name" +msgstr "" + +#. module: odex25_helpdesk +#: model:mail.message.subtype,name:odex25_helpdesk.mt_team_ticket_rated +#: model:mail.message.subtype,name:odex25_helpdesk.mt_ticket_rated +msgid "Ticket Rated" +msgstr "تم تقييم التذكرة" + +#. module: odex25_helpdesk +#: model:ir.model,name:odex25_helpdesk.model_odex25_helpdesk_sla_status +msgid "Ticket SLA Status" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__ticket_stage_id +msgid "Ticket Stage" +msgstr "" + +#. module: odex25_helpdesk +#: model:mail.message.subtype,name:odex25_helpdesk.mt_team_ticket_stage +msgid "Ticket Stage Changed" +msgstr "تم تغيير مرحلة التذكرة" + +#. module: odex25_helpdesk +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_tag_action +#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_tag_menu +msgid "Ticket Tags" +msgstr "وسوم التذكرة" + +#. module: odex25_helpdesk +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_type_action +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__ticket_type_id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__ticket_type_id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__ticket_type_id +#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_ticket_type_menu +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search +msgid "Ticket Type" +msgstr "نوع التذكرة" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#, python-format +msgid "Ticket closed by the customer" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__allow_portal_ticket_closing +msgid "Ticket closing" +msgstr "إغلاق التذكرة" + +#. module: odex25_helpdesk +#: model:mail.message.subtype,description:odex25_helpdesk.mt_ticket_new +msgid "Ticket created" +msgstr "تم إنشاء التذكرة" + +#. module: odex25_helpdesk +#: model:mail.message.subtype,description:odex25_helpdesk.mt_ticket_rated +msgid "Ticket rated" +msgstr "تم تقييم التذكرة" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#: model:ir.actions.act_window,name:odex25_helpdesk.action_upcoming_sla_fail_all_tickets +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_my_ticket_action_no_create +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_main_my +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_main_tree +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_sla +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_team +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_unassigned +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__ticket_ids +#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_partner__ticket_count +#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_users__ticket_count +#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_ticket_menu_main +#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_ticket_report_menu +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_kanban +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_cohort +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_tree +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.portal_odex25_helpdesk_ticket +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.portal_my_home_odex25_helpdesk_ticket +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.portal_my_home_menu_odex25_helpdesk +#, python-format +msgid "Tickets" +msgstr "تذاكر" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_digest_digest__kpi_odex25_helpdesk_tickets_closed +msgid "Tickets Closed" +msgstr "تم قفل التذكرة" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "Tickets Closed Avg 7 Days" +msgstr "" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "Tickets Closed Today" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis_closed +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search +msgid "Tickets Search" +msgstr "البحث في التذاكر" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_stage__is_close +msgid "" +"Tickets in this stage are considered as done. This is used notably when " +"computing SLAs and KPIs on tickets." +msgstr "" +"تُعتبر التذاكر في هذه المرحلة منتهية. يُستخدم هذا بصورة ملحوظة عند احتساب " +"سياسات اتفاق مستوى الخدمة وقيم المؤشر الرئيسي للتذاكر." + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla__priority +msgid "Tickets under this priority will not be taken into account." +msgstr "لن توضع التذاكر ذات هذه الأولوية في الحساب." + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_odex25_helpdesk_sale_timesheet +msgid "Time Reinvoicing" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__ticket_close_hours +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__close_hours +msgid "Time to close (hours)" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__ticket_assignation_hours +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__assign_hours +msgid "Time to first assignment (hours)" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_odex25_helpdesk_timesheet +msgid "Timesheet on Ticket" +msgstr "سجل النشاط على التذكرة" + +#. module: odex25_helpdesk +#: model:digest.tip,name:odex25_helpdesk.digest_tip_odex25_helpdesk_0 +msgid "Tip: Create tickets from incoming emails" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_main_my +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_main_tree +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_team +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_unassigned +msgid "" +"To get things done, use activities and status on tickets.
\n" +" Chat in real time or by email to collaborate efficiently." +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_my_ticket_action_no_create +msgid "" +"To get things done, use activities and status on tickets.
\n" +" Chat in real time or by email to collaborate efficiently." +msgstr "" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "Today" +msgstr "اليوم" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search +msgid "Today Activities" +msgstr "نشاطات اليوم" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "Today Happy Rating" +msgstr "" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "Today's Success Rate" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_twitter +msgid "Twitter" +msgstr "تويتر" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "Two stars, with a maximum of three" +msgstr "نجمتان، بحد أقصى ثلاثة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket_type__name +msgid "Type" +msgstr "النوع" + +#. module: odex25_helpdesk +#: model:ir.model.constraint,message:odex25_helpdesk.constraint_odex25_helpdesk_ticket_type_name_uniq +msgid "Type name already exists !" +msgstr "اسم النوع موجود بالفعل!" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_exception_decoration +msgid "Type of the exception activity on record." +msgstr "نوع النشاط الاستثنائي المسجل." + +#. module: odex25_helpdesk +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_type_action +msgid "Types are perfect to categorize your tickets." +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__portal_rating_url +msgid "URL to Submit an Issue" +msgstr "رابط الإبلاغ عن مشكلة" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/controllers/portal.py:0 +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search +#, python-format +msgid "Unassigned" +msgstr "غير مكلف" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__unassigned_tickets +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_kanban +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis_closed +msgid "Unassigned Tickets" +msgstr "التذاكر غير المسندة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_unread +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_unread +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search +msgid "Unread Messages" +msgstr "الرسائل الجديدة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_unread_counter +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_unread_counter +msgid "Unread Messages Counter" +msgstr "عدد الرسائل الجديدة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search +msgid "Upcoming SLA Fail" +msgstr "فشل مترقب باتفاق مستوى الخدمة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__upcoming_sla_fail_tickets +msgid "Upcoming SLA Fail Tickets" +msgstr "تذاكر الفشل المترقب باتفاق مستوى الخدمة" + +#. module: odex25_helpdesk +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla__priority__3 +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla_report_analysis__priority__3 +#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_ticket__priority__3 +msgid "Urgent" +msgstr "عاجل" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0 +#, python-format +msgid "Urgent (" +msgstr "عاجل (" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__use_coupons +msgid "Use Coupons" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__use_credit_notes +msgid "Use Credit Notes" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__use_product_repairs +msgid "Use Repairs" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__use_product_returns +msgid "Use Returns" +msgstr "" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0 +#, python-format +msgid "Use the breadcrumbs to go back to the Kanban view." +msgstr "استخدم فتات الخبز للعودة لواجهة عرض كانبان." + +#. module: odex25_helpdesk +#: model:res.groups,name:odex25_helpdesk.group_odex25_helpdesk_user +msgid "User" +msgstr "المستخدم" + +#. module: odex25_helpdesk +#: model:ir.model,name:odex25_helpdesk.model_res_users +msgid "Users" +msgstr "المستخدمون" + +#. module: odex25_helpdesk +#: model:odex25_helpdesk.team,name:odex25_helpdesk.odex25_helpdesk_team3 +msgid "VIP Support" +msgstr "" + +#. module: odex25_helpdesk +#. openerp-web +#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0 +#, python-format +msgid "" +"Want to boost your customer satisfaction?
Click Helpdesk to " +"start." +msgstr "" +"أتريد رفع نسبة رضا العميل؟
اضغط على مكتب المساعدة لتبدأ." + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_website_helpdesk_form +msgid "Website Form" +msgstr "نموذج الموقع" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__website_message_ids +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__website_message_ids +msgid "Website Messages" +msgstr "رسائل الموقع" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__website_message_ids +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__website_message_ids +msgid "Website communication history" +msgstr "سجل تواصل الموقع" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "" +"With random assignation, every user gets the same number of tickets. With " +"balanced assignation, tickets are assigned to the user with the least amount" +" of open tickets." +msgstr "" +"من خلال الإسناد العشوائي، ينال كل مستخدم نفس عدد التذاكر. من خلال الإسناد " +"المتزن، يتم إسناد التذاكر للمستخدم صاحب أقل عدد من التذاكر المفتوحة." + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form +msgid "Within" +msgstr "" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__resource_calendar_id +msgid "Working Hours" +msgstr "ساعات العمل" + +#. module: odex25_helpdesk +#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla_status__exceeded_days +msgid "" +"Working days exceeded for reached SLAs compared with deadline. Positive " +"number means the SLA was eached after the deadline." +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_stage_view_form +msgid "" +"You can also add a description to help your coworkers understand the meaning" +" and purpose of the stage." +msgstr "يمكنك أيضًا إضافة وصف لمساعدة زملائك على فهم معنى وغرض المرحلة." + +#. module: odex25_helpdesk +#: model:ir.model.constraint,message:odex25_helpdesk.constraint_res_users_target_closed_not_zero +#: model:ir.model.constraint,message:odex25_helpdesk.constraint_res_users_target_rating_not_zero +#: model:ir.model.constraint,message:odex25_helpdesk.constraint_res_users_target_success_not_zero +msgid "You cannot have negative targets" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.action_upcoming_sla_fail_all_tickets +#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_sla +msgid "You completed all your tickets on time." +msgstr "" + +#. module: odex25_helpdesk +#: code:addons/odex25_helpdesk/models/odex25_helpdesk.py:0 +#, python-format +msgid "You must have team members assigned to change the assignment method." +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form +msgid "days
" +msgstr "أيام
" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form +msgid "e.g. Close urgent tickets within 36 hours" +msgstr "مثلًا: إغلاق التذاكر العاجلة خلال 36 ساعة" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "eLearning" +msgstr "eLearning" + +#. module: odex25_helpdesk +#: model_terms:digest.tip,tip_description:odex25_helpdesk.digest_tip_odex25_helpdesk_0 +msgid "generate tickets in your pipeline." +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form +msgid "hours
" +msgstr "ساعات
" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form +msgid "minutes
" +msgstr "" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "team search" +msgstr "فريق البحث" + +#. module: odex25_helpdesk +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form +msgid "to create tickets" +msgstr "لإنشاء تذاكر" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_helpdesk_service__category_id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__category_id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__category_id +msgid "Category" +msgstr "التصنيف" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_helpdesk_service__name +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__service_id +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__service_id +msgid "Service" +msgstr "الخدمة" + +#. module: odex25_helpdesk +#: model:ir.model,name:odex25_helpdesk.model_hr_employee +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__employee_id +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_form +msgid "Employee" +msgstr "الموظف" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__assistance_user_id +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_form +msgid "Assistance" +msgstr "المساعد" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__work_email +msgid "Work Email" +msgstr "إيميل الموظف" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__work_location +msgid "Work Location" +msgstr "مكان العمل" + + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__department_id +msgid "Administrative Structure" +msgstr "الهيكل الإداري" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__deb_name +msgid "Department Name" +msgstr "القسم" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__project_no +msgid "Project Number" +msgstr "رقم المشروع" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__on_behalf +msgid "On behalf of" +msgstr "نيابة عن" + +#. module: odex25_helpdesk +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_category_action +#: model:ir.model.fields,field_description:odex25_helpdesk.field_service_category__name +#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_menu_service_category +msgid "Service Category" +msgstr "تصنيف الخدمة" + + +#. module: odex25_helpdesk +#: model:ir.actions.act_window,name:odex25_helpdesk.helpdesk_service_action +#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_service_action +#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_menu_service +msgid "Helpdesk Service" +msgstr "الخدمة" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__is_internal_team +msgid "Internal Team" +msgstr "فريق داخلي" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__is_vip_team +msgid "VIP Team" +msgstr "VIP فريق" + +#. module: odex25_helpdesk +#: model:res.groups,name:odex25_helpdesk.group_odex25_helpdesk_assignment +msgid "Assignment User" +msgstr "مستخدم التعيين" + +#. module: odex25_helpdesk +#: model:res.groups,name:odex25_helpdesk.group_odex25_helpdesk_on_behalf +msgid "On Behalf User" +msgstr "نيابة عن المستخدم" + +#. module: odex25_helpdesk +#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__phone_no +msgid "Phone Number" +msgstr "رقم الهاتف" + diff --git a/odex25_helpdesk/odex25_helpdesk/migrations/10.0.1.2/pre-nonulls.py b/odex25_helpdesk/odex25_helpdesk/migrations/10.0.1.2/pre-nonulls.py new file mode 100644 index 000000000..d68aa3c73 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/migrations/10.0.1.2/pre-nonulls.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- + +def migrate(cr, version): + cr.execute(""" + UPDATE odex25_helpdesk_sla + SET time_days = COALESCE(time_days, 0), + time_hours = COALESCE(time_hours, 0), + time_minutes = COALESCE(time_minutes, 0) + """) diff --git a/odex25_helpdesk/odex25_helpdesk/models/__init__.py b/odex25_helpdesk/odex25_helpdesk/models/__init__.py new file mode 100644 index 000000000..f7b7cf4e3 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/models/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +from . import digest +from . import odex25_helpdesk +from . import odex25_helpdesk_ticket +from . import res_users +from . import res_partner +from . import res_setting diff --git a/odex25_helpdesk/odex25_helpdesk/models/digest.py b/odex25_helpdesk/odex25_helpdesk/models/digest.py new file mode 100644 index 000000000..2a08cfdaf --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/models/digest.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +from odoo import fields, models, _ +from odoo.exceptions import AccessError + + +class Digest(models.Model): + _inherit = 'digest.digest' + + kpi_odex25_helpdesk_tickets_closed = fields.Boolean('Tickets Closed') + kpi_odex25_helpdesk_tickets_closed_value = fields.Integer(compute='_compute_kpi_odex25_helpdesk_tickets_closed_value') + + def _compute_kpi_odex25_helpdesk_tickets_closed_value(self): + if not self.env.user.has_group('odex25_helpdesk.group_odex25_helpdesk_user'): + raise AccessError(_("Do not have access, skip this data for user's digest email")) + for record in self: + start, end, company = record._get_kpi_compute_parameters() + closed_ticket = self.env['odex25_helpdesk.ticket'].search_count([ + ('close_date', '>=', start), + ('close_date', '<', end), + ('company_id', '=', company.id) + ]) + record.kpi_odex25_helpdesk_tickets_closed_value = closed_ticket + + def _compute_kpis_actions(self, company, user): + res = super(Digest, self)._compute_kpis_actions(company, user) + res['kpi_odex25_helpdesk_tickets_closed'] = 'odex25_helpdesk.odex25_helpdesk_team_dashboard_action_main&menu_id=%s' % self.env.ref('odex25_helpdesk.menu_odex25_helpdesk_root').id + return res diff --git a/odex25_helpdesk/odex25_helpdesk/models/odex25_helpdesk.py b/odex25_helpdesk/odex25_helpdesk/models/odex25_helpdesk.py new file mode 100644 index 000000000..f1015aa26 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/models/odex25_helpdesk.py @@ -0,0 +1,534 @@ +# -*- coding: utf-8 -*- + +import ast +import datetime + +from dateutil import relativedelta +from odoo import api, fields, models, _ +from odoo.addons.odex25_helpdesk.models.odex25_helpdesk_ticket import TICKET_PRIORITY +from odoo.addons.http_routing.models.ir_http import slug +from odoo.exceptions import UserError, ValidationError +from odoo.osv import expression + + +class odex25_helpdeskTeam(models.Model): + _name = "odex25_helpdesk.team" + _inherit = ['mail.alias.mixin', 'mail.thread', 'rating.parent.mixin'] + _description = "Helpdesk Team" + _order = 'sequence,name' + _rating_satisfaction_days = False # takes all existing ratings + + _sql_constraints = [('not_portal_show_rating_if_not_use_rating', + 'check (portal_show_rating = FALSE OR use_rating = TRUE)', + 'Cannot show ratings in portal if not using them'), ] + + def _default_stage_ids(self): + default_stage = self.env['odex25_helpdesk.stage'].search([('name', '=', _('New'))], limit=1) + if default_stage: + return [(4, default_stage.id)] + return [(0, 0, {'name': _('New'), 'sequence': 0, 'template_id': self.env.ref('odex25_helpdesk.new_ticket_request_email_template', raise_if_not_found=False) or None})] + + def _default_domain_member_ids(self): + return [('groups_id', 'in', self.env.ref('odex25_helpdesk.group_odex25_helpdesk_user').id)] + + name = fields.Char('Helpdesk Team', required=True, translate=True) + description = fields.Text('About Team', translate=True) + active = fields.Boolean(default=True) + company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.company) + sequence = fields.Integer("Sequence", default=10) + color = fields.Integer('Color Index', default=1) + stage_ids = fields.Many2many( + 'odex25_helpdesk.stage', relation='team_stage_rel', string='Stages', + default=_default_stage_ids, + help="Stages the team will use. This team's tickets will only be able to be in these stages.") + assign_method = fields.Selection([ + ('manual', 'Manually'), + ('randomly', 'Random'), + ('balanced', 'Balanced')], string='Assignment Method', default='manual', + compute='_compute_assign_method', store=True, readonly=False, required=True, + help='Automatic assignment method for new tickets:\n' + '\tManually: manual\n' + '\tRandomly: randomly but everyone gets the same amount\n' + '\tBalanced: to the person with the least amount of open tickets') + member_ids = fields.Many2many('res.users', string='Team Members', domain=lambda self: self._default_domain_member_ids()) + visibility_member_ids = fields.Many2many('res.users', 'odex25_helpdesk_visibility_team', string='Team Visibility', domain=lambda self: self._default_domain_member_ids(), + help="Team Members to whom this team will be visible. Keep empty for everyone to see this team.") + ticket_ids = fields.One2many('odex25_helpdesk.ticket', 'team_id', string='Tickets') + + use_alias = fields.Boolean('Email alias', default=True) + has_external_mail_server = fields.Boolean(compute='_compute_has_external_mail_server') + allow_portal_ticket_closing = fields.Boolean('Ticket closing', help="Allow customers to close their tickets") + use_website_helpdesk_form = fields.Boolean('Website Form') + use_website_helpdesk_livechat = fields.Boolean('Live chat', + help="In Channel: You can create a new ticket by typing /Helpdesk [ticket title]. You can search ticket by typing /odex25_helpdesk_search [Keyword1],[Keyword2],.") + use_website_helpdesk_forum = fields.Boolean('Help Center') + use_website_helpdesk_slides = fields.Boolean('Enable eLearning') + use_odex25_helpdesk_timesheet = fields.Boolean('Timesheet on Ticket', help="This required to have project module installed.") + use_odex25_helpdesk_sale_timesheet = fields.Boolean( + 'Time Reinvoicing', compute='_compute_use_odex25_helpdesk_sale_timesheet', store=True, + readonly=False, help="Reinvoice the time spent on ticket through tasks.") + use_credit_notes = fields.Boolean('Refunds') + use_coupons = fields.Boolean('Coupons') + use_product_returns = fields.Boolean('Returns') + use_product_repairs = fields.Boolean('Repairs') + use_twitter = fields.Boolean('Twitter') + use_api = fields.Boolean('API') + use_rating = fields.Boolean('Ratings on tickets') + portal_show_rating = fields.Boolean( + 'Display Rating on Customer Portal', compute='_compute_portal_show_rating', store=True, + readonly=False) + portal_rating_url = fields.Char('URL to Submit an Issue', readonly=True, compute='_compute_portal_rating_url') + use_sla = fields.Boolean('SLA Policies') + upcoming_sla_fail_tickets = fields.Integer(string='Upcoming SLA Fail Tickets', compute='_compute_upcoming_sla_fail_tickets') + unassigned_tickets = fields.Integer(string='Unassigned Tickets', compute='_compute_unassigned_tickets') + resource_calendar_id = fields.Many2one('resource.calendar', 'Working Hours', + default=lambda self: self.env.company.resource_calendar_id, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]") + + is_internal_team = fields.Boolean('Internal Team', default=False) + is_vip_team = fields.Boolean('VIP Team', default=False) + + @api.depends('name', 'portal_show_rating') + def _compute_portal_rating_url(self): + base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + for team in self: + if team.name and team.portal_show_rating and team.id: + team.portal_rating_url = '%s/odex25_helpdesk/rating/%s' % (base_url, slug(team)) + else: + team.portal_rating_url = False + + def _compute_has_external_mail_server(self): + self.has_external_mail_server = self.env['ir.config_parameter'].sudo().get_param('base_setup.default_external_email_server') + + def _compute_upcoming_sla_fail_tickets(self): + ticket_data = self.env['odex25_helpdesk.ticket'].read_group([ + ('team_id', 'in', self.ids), + ('sla_deadline', '!=', False), + ('sla_deadline', '<=', fields.Datetime.to_string((datetime.date.today() + relativedelta.relativedelta(days=1)))), + ], ['team_id'], ['team_id']) + mapped_data = dict((data['team_id'][0], data['team_id_count']) for data in ticket_data) + for team in self: + team.upcoming_sla_fail_tickets = mapped_data.get(team.id, 0) + + def _compute_unassigned_tickets(self): + ticket_data = self.env['odex25_helpdesk.ticket'].read_group([('user_id', '=', False), ('team_id', 'in', self.ids), ('stage_id.is_close', '!=', True)], ['team_id'], ['team_id']) + mapped_data = dict((data['team_id'][0], data['team_id_count']) for data in ticket_data) + for team in self: + team.unassigned_tickets = mapped_data.get(team.id, 0) + + @api.depends('use_rating') + def _compute_portal_show_rating(self): + without_rating = self.filtered(lambda t: not t.use_rating) + without_rating.update({'portal_show_rating': False}) + + @api.depends('member_ids', 'visibility_member_ids') + def _compute_assign_method(self): + with_manual = self.filtered(lambda t: not t.member_ids and not t.visibility_member_ids) + with_manual.update({'assign_method': 'manual'}) + + @api.onchange('use_alias', 'name') + def _onchange_use_alias(self): + if not self.use_alias: + self.alias_name = False + + @api.depends('use_odex25_helpdesk_timesheet') + def _compute_use_odex25_helpdesk_sale_timesheet(self): + without_timesheet = self.filtered(lambda t: not t.use_odex25_helpdesk_timesheet) + without_timesheet.update({'use_odex25_helpdesk_sale_timesheet': False}) + + @api.constrains('assign_method', 'member_ids', 'visibility_member_ids') + def _check_member_assignation(self): + if not self.member_ids and not self.visibility_member_ids and self.assign_method != 'manual': + raise ValidationError(_("You must have team members assigned to change the assignment method.")) + + # ------------------------------------------------------------ + # ORM overrides + # ------------------------------------------------------------ + + @api.model + def create(self, vals): + team = super(odex25_helpdeskTeam, self.with_context(mail_create_nosubscribe=True)).create(vals) + team.sudo()._check_sla_group() + team.sudo()._check_modules_to_install() + # If you plan to add something after this, use a new environment. The one above is no longer valid after the modules install. + return team + + def write(self, vals): + result = super(odex25_helpdeskTeam, self).write(vals) + if 'active' in vals: + self.with_context(active_test=False).mapped('ticket_ids').write({'active': vals['active']}) + self.sudo()._check_sla_group() + self.sudo()._check_modules_to_install() + # If you plan to add something after this, use a new environment. The one above is no longer valid after the modules install. + return result + + def unlink(self): + stages = self.mapped('stage_ids').filtered(lambda stage: stage.team_ids <= self) # remove stages that only belong to team in self + stages.unlink() + return super(odex25_helpdeskTeam, self).unlink() + + def _check_sla_group(self): + for team in self: + if team.use_sla and not self.user_has_groups('odex25_helpdesk.group_use_sla'): + self.env.ref('odex25_helpdesk.group_odex25_helpdesk_user').write({'implied_ids': [(4, self.env.ref('odex25_helpdesk.group_use_sla').id)]}) + if team.use_sla: + self.env['odex25_helpdesk.sla'].with_context(active_test=False).search([('team_id', '=', team.id), ('active', '=', False)]).write({'active': True}) + else: + self.env['odex25_helpdesk.sla'].search([('team_id', '=', team.id)]).write({'active': False}) + if not self.search_count([('use_sla', '=', True)]): + self.env.ref('odex25_helpdesk.group_odex25_helpdesk_user').write({'implied_ids': [(3, self.env.ref('odex25_helpdesk.group_use_sla').id)]}) + self.env.ref('odex25_helpdesk.group_use_sla').write({'users': [(5, 0, 0)]}) + + def _check_modules_to_install(self): + # mapping of field names to module names + FIELD_MODULE = { + 'use_website_helpdesk_form': 'odex25_website_helpdesk_form', + 'use_website_helpdesk_livechat': 'odex25_website_helpdesk_livechat', + 'use_website_helpdesk_forum': 'odex25_website_helpdesk_forum', + 'use_website_helpdesk_slides': 'odex25_website_helpdesk_slides', + 'use_odex25_helpdesk_timesheet': 'odex25_helpdesk_timesheet', + 'use_odex25_helpdesk_sale_timesheet': 'odex25_helpdesk_sale_timesheet', + 'use_credit_notes': 'odex25_helpdesk_account', + 'use_product_returns': 'odex25_helpdesk_stock', + 'use_product_repairs': 'odex25_helpdesk_repair', + 'use_coupons': 'odex25_helpdesk_sale_coupon', + } + + # determine the modules to be installed + expected = [ + mname + for fname, mname in FIELD_MODULE.items() + if any(team[fname] for team in self) + ] + modules = self.env['ir.module.module'] + if expected: + STATES = ('installed', 'to install', 'to upgrade') + modules = modules.search([('name', 'in', expected)]) + modules = modules.filtered(lambda module: module.state not in STATES) + + # other stuff + for team in self: + if team.use_rating: + for stage in team.stage_ids: + if stage.is_close and not stage.fold: + stage.template_id = self.env.ref('odex25_helpdesk.rating_ticket_request_email_template', raise_if_not_found= False) + + if modules: + modules.button_immediate_install() + + # just in case we want to do something if we install a module. (like a refresh ...) + return bool(modules) + + # ------------------------------------------------------------ + # Mail Alias Mixin + # ------------------------------------------------------------ + + def _alias_get_creation_values(self): + values = super(odex25_helpdeskTeam, self)._alias_get_creation_values() + values['alias_model_id'] = self.env['ir.model']._get('odex25_helpdesk.ticket').id + if self.id: + values['alias_defaults'] = defaults = ast.literal_eval(self.alias_defaults or "{}") + defaults['team_id'] = self.id + return values + + # ------------------------------------------------------------ + # Business Methods + # ------------------------------------------------------------ + + @api.model + def retrieve_dashboard(self): + domain = [('user_id', '=', self.env.uid)] + group_fields = ['priority', 'create_date', 'stage_id', 'close_hours'] + list_fields = ['priority', 'create_date', 'stage_id', 'close_hours'] + #TODO: remove SLA calculations if user_uses_sla is false. + user_uses_sla = self.user_has_groups('odex25_helpdesk.group_use_sla') and\ + bool(self.env['odex25_helpdesk.team'].search([('use_sla', '=', True), '|', ('member_ids', 'in', self._uid), ('member_ids', '=', False)])) + + if user_uses_sla: + group_fields.insert(1, 'sla_deadline:year') + group_fields.insert(2, 'sla_deadline:hour') + group_fields.insert(3, 'sla_reached_late') + list_fields.insert(1, 'sla_deadline') + list_fields.insert(2, 'sla_reached_late') + + odex25_helpdeskTicket = self.env['odex25_helpdesk.ticket'] + tickets = odex25_helpdeskTicket.search_read(expression.AND([domain, [('stage_id.is_close', '=', False)]]), ['sla_deadline', 'open_hours', 'sla_reached_late', 'priority']) + + result = { + 'odex25_helpdesk_target_closed': self.env.user.odex25_helpdesk_target_closed, + 'odex25_helpdesk_target_rating': self.env.user.odex25_helpdesk_target_rating, + 'odex25_helpdesk_target_success': self.env.user.odex25_helpdesk_target_success, + 'today': {'count': 0, 'rating': 0, 'success': 0}, + '7days': {'count': 0, 'rating': 0, 'success': 0}, + 'my_all': {'count': 0, 'hours': 0, 'failed': 0}, + 'my_high': {'count': 0, 'hours': 0, 'failed': 0}, + 'my_urgent': {'count': 0, 'hours': 0, 'failed': 0}, + 'show_demo': not bool(odex25_helpdeskTicket.search([], limit=1)), + 'rating_enable': False, + 'success_rate_enable': user_uses_sla + } + + def _is_sla_failed(data): + deadline = data.get('sla_deadline') + sla_deadline = fields.Datetime.now() > deadline if deadline else False + return sla_deadline or data.get('sla_reached_late') + + def add_to(ticket, key="my_all"): + result[key]['count'] += 1 + result[key]['hours'] += ticket['open_hours'] + if _is_sla_failed(ticket): + result[key]['failed'] += 1 + + for ticket in tickets: + add_to(ticket, 'my_all') + if ticket['priority'] == '2': + add_to(ticket, 'my_high') + if ticket['priority'] == '3': + add_to(ticket, 'my_urgent') + + dt = fields.Date.today() + tickets = odex25_helpdeskTicket.read_group(domain + [('stage_id.is_close', '=', True), ('close_date', '>=', dt)], list_fields, group_fields, lazy=False) + for ticket in tickets: + result['today']['count'] += ticket['__count'] + if not _is_sla_failed(ticket): + result['today']['success'] += ticket['__count'] + + dt = fields.Datetime.to_string((datetime.date.today() - relativedelta.relativedelta(days=6))) + tickets = odex25_helpdeskTicket.read_group(domain + [('stage_id.is_close', '=', True), ('close_date', '>=', dt)], list_fields, group_fields, lazy=False) + for ticket in tickets: + result['7days']['count'] += ticket['__count'] + if not _is_sla_failed(ticket): + result['7days']['success'] += ticket['__count'] + + result['today']['success'] = (result['today']['success'] * 100) / (result['today']['count'] or 1) + result['7days']['success'] = (result['7days']['success'] * 100) / (result['7days']['count'] or 1) + result['my_all']['hours'] = round(result['my_all']['hours'] / (result['my_all']['count'] or 1), 2) + result['my_high']['hours'] = round(result['my_high']['hours'] / (result['my_high']['count'] or 1), 2) + result['my_urgent']['hours'] = round(result['my_urgent']['hours'] / (result['my_urgent']['count'] or 1), 2) + + if self.env['odex25_helpdesk.team'].search([('use_rating', '=', True), '|', ('member_ids', 'in', self._uid), ('member_ids', '=', False)]): + result['rating_enable'] = True + # rating of today + domain = [('user_id', '=', self.env.uid)] + dt = fields.Date.today() + tickets = self.env['odex25_helpdesk.ticket'].search(domain + [('stage_id.is_close', '=', True), ('close_date', '>=', dt)]) + activity = tickets.rating_get_grades() + total_rating = self._compute_activity_avg(activity) + total_activity_values = sum(activity.values()) + team_satisfaction = round((total_rating / total_activity_values if total_activity_values else 0), 2) * 5 + if team_satisfaction: + result['today']['rating'] = team_satisfaction + + # rating of last 7 days (6 days + today) + dt = fields.Datetime.to_string((datetime.date.today() - relativedelta.relativedelta(days=6))) + tickets = self.env['odex25_helpdesk.ticket'].search(domain + [('stage_id.is_close', '=', True), ('close_date', '>=', dt)]) + activity = tickets.rating_get_grades() + total_rating = self._compute_activity_avg(activity) + total_activity_values = sum(activity.values()) + team_satisfaction_7days = round((total_rating / total_activity_values if total_activity_values else 0), 2) * 5 + if team_satisfaction_7days: + result['7days']['rating'] = team_satisfaction_7days + return result + + def _action_view_rating(self, period=False, only_my_closed=False): + """ return the action to see all the rating about the tickets of the Team + :param period: either 'today' or 'seven_days' to include (or not) the tickets closed in this period + :param only_my_closed: True will include only the ticket of the current user in a closed stage + """ + domain = [('team_id', 'in', self.ids)] + + if period == 'seven_days': + domain += [('close_date', '>=', fields.Datetime.to_string((datetime.date.today() - relativedelta.relativedelta(days=6))))] + elif period == 'today': + domain += [('close_date', '>=', fields.Datetime.to_string(datetime.date.today()))] + + if only_my_closed: + domain += [('user_id', '=', self._uid), ('stage_id.is_close', '=', True)] + + ticket_ids = self.env['odex25_helpdesk.ticket'].search(domain).ids + action = self.env["ir.actions.actions"]._for_xml_id("rating.rating_rating_view") + action['domain'] = [('res_id', 'in', ticket_ids), ('rating', '!=', -1), ('res_model', '=', 'odex25_helpdesk.ticket'), ('consumed', '=', True)] + action['help'] = '

No data yet !

Create tickets to get statistics.

' + return action + + def action_view_ticket(self): + action = self.env["ir.actions.actions"]._for_xml_id("odex25_helpdesk.odex25_helpdesk_ticket_action_team") + action['display_name'] = self.name + return action + + @api.model + def action_view_rating_today(self): + # call this method of on click "Customer Rating" button on dashbord for today rating of teams tickets + return self.search(['|', ('member_ids', 'in', self._uid), ('member_ids', '=', False)])._action_view_rating(period='today', only_my_closed=True) + + @api.model + def action_view_rating_7days(self): + # call this method of on click "Customer Rating" button on dashbord for last 7days rating of teams tickets + return self.search(['|', ('member_ids', 'in', self._uid), ('member_ids', '=', False)])._action_view_rating(period='seven_days', only_my_closed=True) + + def action_view_all_rating(self): + """ return the action to see all the rating about the all sort of activity of the team (tickets) """ + return self._action_view_rating() + + @api.model + def _compute_activity_avg(self, activity): + # compute average base on all rating value + # like: 5 great, 3 okey, 1 bad + # great = 5, okey = 3, bad = 0 + # (5*5) + (2*3) + (1*0) = 60 / 8 (nuber of activity for rating) + great = activity['great'] * 5.00 + okey = activity['okay'] * 3.00 + bad = activity['bad'] * 0.00 + return great + okey + bad + + def _determine_user_to_assign(self): + """ Get a dict with the user (per team) that should be assign to the nearly created ticket according to the team policy + :returns a mapping of team identifier with the "to assign" user (maybe an empty record). + :rtype : dict (key=team_id, value=record of res.users) + """ + result = dict.fromkeys(self.ids, self.env['res.users']) + for team in self: + member_ids = sorted(team.member_ids.ids) if team.member_ids else sorted(team.visibility_member_ids.ids) + if member_ids: + if team.assign_method == 'randomly': # randomly means new tickets get uniformly distributed + last_assigned_user = self.env['odex25_helpdesk.ticket'].search([('team_id', '=', team.id)], order='create_date desc, id desc', limit=1).user_id + index = 0 + if last_assigned_user and last_assigned_user.id in member_ids: + previous_index = member_ids.index(last_assigned_user.id) + index = (previous_index + 1) % len(member_ids) + result[team.id] = self.env['res.users'].browse(member_ids[index]) + elif team.assign_method == 'balanced': # find the member with the least open ticket + ticket_count_data = self.env['odex25_helpdesk.ticket'].read_group([('stage_id.is_close', '=', False), ('user_id', 'in', member_ids), ('team_id', '=', team.id)], ['user_id'], ['user_id']) + open_ticket_per_user_map = dict.fromkeys(member_ids, 0) # dict: user_id -> open ticket count + open_ticket_per_user_map.update((item['user_id'][0], item['user_id_count']) for item in ticket_count_data) + result[team.id] = self.env['res.users'].browse(min(open_ticket_per_user_map, key=open_ticket_per_user_map.get)) + return result + + def _determine_stage(self): + """ Get a dict with the stage (per team) that should be set as first to a created ticket + :returns a mapping of team identifier with the stage (maybe an empty record). + :rtype : dict (key=team_id, value=record of odex25_helpdesk.stage) + """ + result = dict.fromkeys(self.ids, self.env['odex25_helpdesk.stage']) + for team in self: + result[team.id] = self.env['odex25_helpdesk.stage'].search([('team_ids', 'in', team.id)], order='sequence', limit=1) + return result + + def _get_closing_stage(self): + """ + Return the first closing kanban stage or the last stage of the pipe if none + """ + closed_stage = self.stage_ids.filtered(lambda stage: stage.is_close) + if not closed_stage: + closed_stage = self.stage_ids[-1] + return closed_stage + + + +class odex25_helpdeskStage(models.Model): + _name = 'odex25_helpdesk.stage' + _description = 'Helpdesk Stage' + _order = 'sequence, id' + + def _default_team_ids(self): + team_id = self.env.context.get('default_team_id') + if team_id: + return [(4, team_id, 0)] + + name = fields.Char('Stage Name', required=True, translate=True) + description = fields.Text(translate=True) + sequence = fields.Integer('Sequence', default=10) + is_close = fields.Boolean( + 'Closing Stage', + help='Tickets in this stage are considered as done. This is used notably when ' + 'computing SLAs and KPIs on tickets.') + fold = fields.Boolean( + 'Folded in Kanban', + help='This stage is folded in the kanban view when there are no records in that stage to display.') + team_ids = fields.Many2many( + 'odex25_helpdesk.team', relation='team_stage_rel', string='Team', + default=_default_team_ids, + help='Specific team that uses this stage. Other teams will not be able to see or use this stage.') + template_id = fields.Many2one( + 'mail.template', 'Email Template', + domain="[('model', '=', 'odex25_helpdesk.ticket')]", + help="Automated email sent to the ticket's customer when the ticket reaches this stage.") + legend_blocked = fields.Char( + 'Red Kanban Label', default=lambda s: _('Blocked'), translate=True, required=True, + help='Override the default value displayed for the blocked state for kanban selection, when the task or issue is in that stage.') + legend_done = fields.Char( + 'Green Kanban Label', default=lambda s: _('Ready'), translate=True, required=True, + help='Override the default value displayed for the done state for kanban selection, when the task or issue is in that stage.') + legend_normal = fields.Char( + 'Grey Kanban Label', default=lambda s: _('In Progress'), translate=True, required=True, + help='Override the default value displayed for the normal state for kanban selection, when the task or issue is in that stage.') + + def unlink(self): + stages = self + default_team_id = self.env.context.get('default_team_id') + if default_team_id: + shared_stages = self.filtered(lambda x: len(x.team_ids) > 1 and default_team_id in x.team_ids.ids) + tickets = self.env['odex25_helpdesk.ticket'].with_context(active_test=False).search([('team_id', '=', default_team_id), ('stage_id', 'in', self.ids)]) + if shared_stages and not tickets: + shared_stages.write({'team_ids': [(3, default_team_id)]}) + stages = self.filtered(lambda x: x not in shared_stages) + return super(odex25_helpdeskStage, stages).unlink() + + +class odex25_helpdeskSLA(models.Model): + _name = "odex25_helpdesk.sla" + _order = "name" + _description = "Helpdesk SLA Policies" + + name = fields.Char('SLA Policy Name', required=True, index=True) + description = fields.Text('SLA Policy Description') + active = fields.Boolean('Active', default=True) + team_id = fields.Many2one('odex25_helpdesk.team', 'Team', required=True) + target_type = fields.Selection([('stage', 'Reaching Stage'), ('assigning', 'Assigning Ticket')], default='stage', required=True) + ticket_type_id = fields.Many2one( + 'odex25_helpdesk.ticket.type', "Ticket Type", + help="Only apply the SLA to a specific ticket type. If left empty it will apply to all types.") + tag_ids = fields.Many2many( + 'odex25_helpdesk.tag', string='Tags', + help="Only apply the SLA to tickets with specific tags. If left empty it will apply to all tags.") + stage_id = fields.Many2one( + 'odex25_helpdesk.stage', 'Target Stage', + help='Minimum stage a ticket needs to reach in order to satisfy this SLA.') + exclude_stage_ids = fields.Many2many( + 'odex25_helpdesk.stage', string='Exclude Stages', + compute='_compute_exclude_stage_ids', store=True, readonly=False, copy=True, + domain="[('id', '!=', stage_id.id)]", + help='The amount of time the ticket spends in this stage will not be taken into account when evaluating the status of the SLA Policy.') + priority = fields.Selection( + TICKET_PRIORITY, string='Minimum Priority', + default='0', required=True, + help='Tickets under this priority will not be taken into account.') + company_id = fields.Many2one('res.company', 'Company', related='team_id.company_id', readonly=True, store=True) + time_days = fields.Integer( + 'Days', default=0, required=True, + help="Days to reach given stage based on ticket creation date") + time_hours = fields.Integer( + 'Hours', default=0, inverse='_inverse_time_hours', required=True, + help="Hours to reach given stage based on ticket creation date") + time_minutes = fields.Integer( + 'Minutes', default=0, inverse='_inverse_time_minutes', required=True, + help="Minutes to reach given stage based on ticket creation date") + category_id = fields.Many2one('service.category') + service_id = fields.Many2one('helpdesk.service') + + @api.depends('target_type') + def _compute_exclude_stage_ids(self): + self.update({'exclude_stage_ids': False}) + + def _inverse_time_hours(self): + for sla in self: + sla.time_hours = max(0, sla.time_hours) + if sla.time_hours >= 24: + sla.time_days += sla.time_hours / 24 + sla.time_hours = sla.time_hours % 24 + + def _inverse_time_minutes(self): + for sla in self: + sla.time_minutes = max(0, sla.time_minutes) + if sla.time_minutes >= 60: + sla.time_hours += sla.time_minutes / 60 + sla.time_minutes = sla.time_minutes % 60 diff --git a/odex25_helpdesk/odex25_helpdesk/models/odex25_helpdesk_ticket.py b/odex25_helpdesk/odex25_helpdesk/models/odex25_helpdesk_ticket.py new file mode 100644 index 000000000..3ee17259a --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/models/odex25_helpdesk_ticket.py @@ -0,0 +1,970 @@ +# -*- coding: utf-8 -*- + +from dateutil.relativedelta import relativedelta +from datetime import timedelta +from random import randint + +from odoo import api, fields, models, tools, _ +from odoo.osv import expression +from odoo.exceptions import AccessError + +TICKET_PRIORITY = [ + ('0', 'All'), + ('1', 'Low priority'), + ('2', 'High priority'), + ('3', 'Urgent'), +] + + +class odex25_helpdeskTag(models.Model): + _name = 'odex25_helpdesk.tag' + _description = 'Helpdesk Tags' + _order = 'name' + + def _get_default_color(self): + return randint(1, 11) + + name = fields.Char('Tag Name', required=True) + color = fields.Integer('Color', default=_get_default_color) + + _sql_constraints = [ + ('name_uniq', 'unique (name)', "Tag name already exists !"), + ] + + +class odex25_helpdeskTicketType(models.Model): + _name = 'odex25_helpdesk.ticket.type' + _description = 'Helpdesk Ticket Type' + _order = 'sequence' + + name = fields.Char('Type', required=True, translate=True) + sequence = fields.Integer(default=10) + + _sql_constraints = [ + ('name_uniq', 'unique (name)', "Type name already exists !"), + ] + + +class odex25_helpdeskSLAStatus(models.Model): + _name = 'odex25_helpdesk.sla.status' + _description = "Ticket SLA Status" + _table = 'odex25_helpdesk_sla_status' + _order = 'deadline ASC, sla_stage_id' + _rec_name = 'sla_id' + + ticket_id = fields.Many2one('odex25_helpdesk.ticket', string='Ticket', required=True, ondelete='cascade', + index=True) + sla_id = fields.Many2one('odex25_helpdesk.sla', required=True, ondelete='cascade') + sla_stage_id = fields.Many2one('odex25_helpdesk.stage', related='sla_id.stage_id', + store=True) # need to be stored for the search in `_sla_reach` + target_type = fields.Selection(related='sla_id.target_type', store=True) + deadline = fields.Datetime("Deadline", compute='_compute_deadline', compute_sudo=True, store=True) + reached_datetime = fields.Datetime("Reached Date", + help="Datetime at which the SLA stage was reached for the first time") + status = fields.Selection([('failed', 'Failed'), ('reached', 'Reached'), ('ongoing', 'Ongoing')], string="Status", + compute='_compute_status', compute_sudo=True, search='_search_status') + color = fields.Integer("Color Index", compute='_compute_color') + exceeded_days = fields.Float("Excedeed Working Days", compute='_compute_exceeded_days', compute_sudo=True, + store=True, + help="Working days exceeded for reached SLAs compared with deadline. Positive number means the SLA was eached after the deadline.") + + @api.depends('ticket_id.create_date', 'sla_id', 'ticket_id.stage_id') + def _compute_deadline(self): + for status in self: + if (status.deadline and status.reached_datetime) or ( + status.deadline and status.target_type == 'stage' and not status.sla_id.exclude_stage_ids) or ( + status.status == 'failed'): + continue + if status.target_type == 'assigning' and status.sla_stage_id == status.ticket_id.stage_id: + deadline = fields.Datetime.now() + elif status.target_type == 'assigning' and status.sla_stage_id: + status.deadline = False + continue + else: + deadline = status.ticket_id.create_date + working_calendar = status.ticket_id.team_id.resource_calendar_id + if not working_calendar: + # Normally, having a working_calendar is mandatory + status.deadline = deadline + continue + + if status.target_type == 'stage' and status.sla_id.exclude_stage_ids: + if status.ticket_id.stage_id in status.sla_id.exclude_stage_ids: + # We are in the freezed time stage: No deadline + status.deadline = False + continue + + time_days = status.sla_id.time_days + if time_days and ( + status.sla_id.target_type == 'stage' or status.sla_id.target_type == 'assigning' and not status.sla_id.stage_id): + deadline = working_calendar.plan_days(time_days + 1, deadline, compute_leaves=True) + # We should also depend on ticket creation time, otherwise for 1 day SLA, all tickets + # created on monday will have their deadline filled with tuesday 8:00 + create_dt = status.ticket_id.create_date + deadline = deadline.replace(hour=create_dt.hour, minute=create_dt.minute, second=create_dt.second, + microsecond=create_dt.microsecond) + elif time_days and status.target_type == 'assigning' and status.sla_stage_id == status.ticket_id.stage_id: + deadline = working_calendar.plan_days(time_days + 1, deadline, compute_leaves=True) + reached_stage_dt = fields.Datetime.now() + deadline = deadline.replace(hour=reached_stage_dt.hour, minute=reached_stage_dt.minute, + second=reached_stage_dt.second, microsecond=reached_stage_dt.microsecond) + + sla_hours = status.sla_id.time_hours + (status.sla_id.time_minutes / 60) + + if status.target_type == 'stage' and status.sla_id.exclude_stage_ids: + sla_hours += status._get_freezed_hours(working_calendar) + + # Except if ticket creation time is later than the end time of the working day + deadline_for_working_cal = working_calendar.plan_hours(0, deadline) + if deadline_for_working_cal and deadline.day < deadline_for_working_cal.day: + deadline = deadline.replace(hour=0, minute=0, second=0, microsecond=0) + # We should execute the function plan_hours in any case because, in a 1 day SLA environment, + # if I create a ticket knowing that I'm not working the day after at the same time, ticket + # deadline will be set at time I don't work (ticket creation time might not be in working calendar). + status.deadline = working_calendar.plan_hours(sla_hours, deadline, compute_leaves=True) + + @api.depends('deadline', 'reached_datetime') + def _compute_status(self): + """ Note: this computed field depending on 'now()' is stored, but refreshed by a cron """ + for status in self: + if status.reached_datetime and status.deadline: # if reached_datetime, SLA is finished: either failed or succeeded + status.status = 'reached' if status.reached_datetime < status.deadline else 'failed' + else: # if not finished, deadline should be compared to now() + status.status = 'ongoing' if not status.deadline or status.deadline > fields.Datetime.now() else 'failed' + + @api.model + def _search_status(self, operator, value): + """ Supported operators: '=', 'in' and their negative form. """ + # constants + datetime_now = fields.Datetime.now() + positive_domain = { + 'failed': ['|', '&', ('reached_datetime', '=', True), ('deadline', '<=', 'reached_datetime'), '&', + ('reached_datetime', '=', False), ('deadline', '<=', fields.Datetime.to_string(datetime_now))], + 'reached': ['&', ('reached_datetime', '=', True), ('reached_datetime', '<', 'deadline')], + 'ongoing': ['&', ('reached_datetime', '=', False), + ('deadline', '<=', fields.Datetime.to_string(datetime_now))] + } + # in/not in case: we treat value as a list of selection item + if not isinstance(value, list): + value = [value] + # transform domains + if operator in expression.NEGATIVE_TERM_OPERATORS: + # "('status', 'not in', [A, B])" tranformed into "('status', '=', C) OR ('status', '=', D)" + domains_to_keep = [dom for key, dom in positive_domain if key not in value] + return expression.OR(domains_to_keep) + else: + return expression.OR(positive_domain[value_item] for value_item in value) + + @api.depends('status') + def _compute_color(self): + for status in self: + if status.status == 'failed': + status.color = 1 + elif status.status == 'reached': + status.color = 10 + else: + status.color = 0 + + @api.depends('deadline', 'reached_datetime') + def _compute_exceeded_days(self): + for status in self: + if status.reached_datetime and status.deadline and status.ticket_id.team_id.resource_calendar_id: + if status.reached_datetime <= status.deadline: + start_dt = status.reached_datetime + end_dt = status.deadline + factor = -1 + else: + start_dt = status.deadline + end_dt = status.reached_datetime + factor = 1 + duration_data = status.ticket_id.team_id.resource_calendar_id.get_work_duration_data(start_dt, end_dt, + compute_leaves=True) + status.exceeded_days = duration_data['days'] * factor + else: + status.exceeded_days = False + + def _get_freezed_hours(self, working_calendar): + self.ensure_one() + hours_freezed = 0 + + field_stage = self.env['ir.model.fields']._get(self.ticket_id._name, "stage_id") + freeze_stages = self.sla_id.exclude_stage_ids.ids + tracking_lines = self.ticket_id.message_ids.tracking_value_ids.filtered( + lambda tv: tv.field == field_stage).sorted(key="create_date") + + if not tracking_lines: + return 0 + + old_time = self.ticket_id.create_date + for tracking_line in tracking_lines: + if tracking_line.old_value_integer in freeze_stages: + # We must use get_work_hours_count to compute real waiting hours (as the deadline computation is also based on calendar) + hours_freezed += working_calendar.get_work_hours_count(old_time, tracking_line.create_date) + old_time = tracking_line.create_date + if tracking_lines[-1].new_value_integer in freeze_stages: + # the last tracking line is not yet created + hours_freezed += working_calendar.get_work_hours_count(old_time, fields.Datetime.now()) + return hours_freezed + + +class odex25_helpdeskTicket(models.Model): + _name = 'odex25_helpdesk.ticket' + _description = 'Helpdesk Ticket' + _order = 'priority desc, id desc' + _inherit = ['portal.mixin', 'mail.thread.cc', 'utm.mixin', 'rating.mixin', 'mail.activity.mixin'] + + @api.model + def default_get(self, fields): + result = super(odex25_helpdeskTicket, self).default_get(fields) + if result.get('team_id') and fields: + team = self.env['odex25_helpdesk.team'].browse(result['team_id']) + if 'user_id' in fields and 'user_id' not in result: # if no user given, deduce it from the team + result['user_id'] = team._determine_user_to_assign()[team.id].id + if 'stage_id' in fields and 'stage_id' not in result: # if no stage given, deduce it from the team + result['stage_id'] = team._determine_stage()[team.id].id + return result + + @api.model + def _read_group_stage_ids(self, stages, domain, order): + # write the domain + # - ('id', 'in', stages.ids): add columns that should be present + # - OR ('team_ids', '=', team_id) if team_id: add team columns + search_domain = [('id', 'in', stages.ids)] + if self.env.context.get('default_team_id'): + search_domain = ['|', ('team_ids', 'in', self.env.context['default_team_id'])] + search_domain + + return stages.search(search_domain, order=order) + + name = fields.Char(string='Subject', required=True, index=True) + team_id = fields.Many2one('odex25_helpdesk.team', string='Helpdesk Team', index=True) + description = fields.Text(required=True, tracking=True) + active = fields.Boolean(default=True) + ticket_type_id = fields.Many2one('odex25_helpdesk.ticket.type', string="Ticket Type") + tag_ids = fields.Many2many('odex25_helpdesk.tag', string='Tags') + company_id = fields.Many2one(related='team_id.company_id', string='Company', store=True, readonly=True) + color = fields.Integer(string='Color Index') + kanban_state = fields.Selection([ + ('normal', 'Grey'), + ('done', 'Green'), + ('blocked', 'Red')], string='Kanban State', + default='normal', required=True) + kanban_state_label = fields.Char(compute='_compute_kanban_state_label', string='Column Status', tracking=True) + legend_blocked = fields.Char(related='stage_id.legend_blocked', string='Kanban Blocked Explanation', readonly=True, + related_sudo=False) + legend_done = fields.Char(related='stage_id.legend_done', string='Kanban Valid Explanation', readonly=True, + related_sudo=False) + legend_normal = fields.Char(related='stage_id.legend_normal', string='Kanban Ongoing Explanation', readonly=True, + related_sudo=False) + domain_user_ids = fields.Many2many('res.users', compute='_compute_domain_user_ids') + user_id = fields.Many2one( + 'res.users', string='Assigned to', compute='_compute_user_and_stage_ids', store=True, + readonly=False, tracking=True, + domain=lambda self: [('groups_id', 'in', self.env.ref('odex25_helpdesk.group_odex25_helpdesk_user').id)]) + partner_id = fields.Many2one('res.partner', string='Customer') + partner_ticket_count = fields.Integer('Number of closed tickets from the same partner', + compute='_compute_partner_ticket_count') + attachment_number = fields.Integer(compute='_compute_attachment_number', string="Number of Attachments") + is_self_assigned = fields.Boolean("Am I assigned", compute='_compute_is_self_assigned') + # Used to submit tickets from a contact form + partner_name = fields.Char(string='Customer Name', compute='_compute_partner_info', store=True, readonly=False) + partner_email = fields.Char(string='Customer Email', compute='_compute_partner_info', store=True, readonly=False) + closed_by_partner = fields.Boolean('Closed by Partner', readonly=True, + help="If checked, this means the ticket was closed through the customer portal by the customer.") + # Used in message_get_default_recipients, so if no partner is created, email is sent anyway + email = fields.Char(related='partner_email', string='Email on Customer', readonly=False) + # priority = fields.Selection(related="service_id.priority", string='Priority', default='0') + priority = fields.Selection(TICKET_PRIORITY, string='Priority', default='0') + stage_id = fields.Many2one( + 'odex25_helpdesk.stage', string='Stage', compute='_compute_user_and_stage_ids', store=True, + readonly=False, ondelete='restrict', tracking=True, group_expand='_read_group_stage_ids', + copy=False, index=True, domain="[('team_ids', '=', team_id)]") + date_last_stage_update = fields.Datetime("Last Stage Update", copy=False, readonly=True) + # next 4 fields are computed in write (or create) + assign_date = fields.Datetime("First assignment date") + assign_hours = fields.Integer("Time to first assignment (hours)", compute='_compute_assign_hours', store=True, + help="This duration is based on the working calendar of the team") + close_date = fields.Datetime("Close date", copy=False) + close_hours = fields.Integer("Time to close (hours)", compute='_compute_close_hours', store=True, + help="This duration is based on the working calendar of the team") + open_hours = fields.Integer("Open Time (hours)", compute='_compute_open_hours', search='_search_open_hours', + help="This duration is not based on the working calendar of the team") + # SLA relative + sla_ids = fields.Many2many('odex25_helpdesk.sla', 'odex25_helpdesk_sla_status', 'ticket_id', 'sla_id', + string="SLAs", copy=False) + sla_status_ids = fields.One2many('odex25_helpdesk.sla.status', 'ticket_id', string="SLA Status") + sla_reached_late = fields.Boolean("Has SLA reached late", compute='_compute_sla_reached_late', compute_sudo=True, + store=True) + sla_deadline = fields.Datetime("SLA Deadline", compute='_compute_sla_deadline', compute_sudo=True, store=True, + help="The closest deadline of all SLA applied on this ticket") + sla_fail = fields.Boolean("Failed SLA Policy", compute='_compute_sla_fail', search='_search_sla_fail') + sla_success = fields.Boolean("Success SLA Policy", compute='_compute_sla_success', search='_search_sla_success') + + use_credit_notes = fields.Boolean(related='team_id.use_credit_notes', string='Use Credit Notes') + use_coupons = fields.Boolean(related='team_id.use_coupons', string='Use Coupons') + use_product_returns = fields.Boolean(related='team_id.use_product_returns', string='Use Returns') + use_product_repairs = fields.Boolean(related='team_id.use_product_repairs', string='Use Repairs') + + # customer portal: include comment and incoming emails in communication history + website_message_ids = fields.One2many( + domain=lambda self: [('model', '=', self._name), ('message_type', 'in', ['email', 'comment'])]) + + category_id = fields.Many2one('service.category') + service_id = fields.Many2one('helpdesk.service') + assistance_user_id = fields.Many2one('res.users', string="Assistance", domain=lambda self: [("groups_id", "=", + self.env.ref("odex25_helpdesk.group_odex25_helpdesk_user").id)]) + # work_email = fields.Char(related='employee_id.work_email', string="Work Email") + department_id = fields.Many2one('hr.department', string="Administrative Structure") + phone_no = fields.Char(string="Phone Number") + on_behalf = fields.Many2one('hr.employee', string="On behalf of") + emp_req = fields.Boolean(default=False) + schedule_date = fields.Datetime("Scheduled Date") + + @api.onchange('service_id') + def _onchange_invoice_date(self): + if self.service_id: + self.priority = self.service_id.priority + + @api.onchange('partner_id') + def onchange_partner_id(self): + """ + get the partner of the user + """ + user = self.env['res.users'].search([('partner_id', '=', self.partner_id.id)], limit=1) + employee_id = self.env['hr.employee'].search([('user_id', '=', user.id)], limit=1) + self.department_id = employee_id.department_id + + def activity_update(self): + for ticket in self.filtered(lambda request: request.schedule_date): + ticket.activity_schedule('odex25_helpdesk.mail_act_odex25_helpdesk_assistance', + fields.Datetime.from_string(ticket.schedule_date).date(), + note=ticket.name, user_id=ticket.assistance_user_id.id or self.env.uid) + + @api.depends('stage_id', 'kanban_state') + def _compute_kanban_state_label(self): + for task in self: + if task.kanban_state == 'normal': + task.kanban_state_label = task.legend_normal + elif task.kanban_state == 'blocked': + task.kanban_state_label = task.legend_blocked + else: + task.kanban_state_label = task.legend_done + + @api.depends('team_id') + def _compute_domain_user_ids(self): + for task in self: + if task.team_id and task.team_id.visibility_member_ids: + odex25_helpdesk_manager = self.env['res.users'].search( + [('groups_id', 'in', self.env.ref('odex25_helpdesk.group_odex25_helpdesk_manager').id)]) + task.domain_user_ids = [(6, 0, (odex25_helpdesk_manager + task.team_id.visibility_member_ids).ids)] + else: + odex25_helpdesk_users = self.env['res.users'].search( + [('groups_id', 'in', self.env.ref('odex25_helpdesk.group_odex25_helpdesk_user').id)]).ids + task.domain_user_ids = [(6, 0, odex25_helpdesk_users)] + + def _compute_access_url(self): + super(odex25_helpdeskTicket, self)._compute_access_url() + for ticket in self: + ticket.access_url = '/my/ticket/%s' % ticket.id + + def _compute_attachment_number(self): + read_group_res = self.env['ir.attachment'].read_group( + [('res_model', '=', 'odex25_helpdesk.ticket'), ('res_id', 'in', self.ids)], + ['res_id'], ['res_id']) + attach_data = {res['res_id']: res['res_id_count'] for res in read_group_res} + for record in self: + record.attachment_number = attach_data.get(record.id, 0) + + @api.depends('sla_status_ids.deadline', 'sla_status_ids.reached_datetime') + def _compute_sla_reached_late(self): + """ Required to do it in SQL since we need to compare 2 columns value """ + mapping = {} + if self.ids: + self.env.cr.execute(""" + SELECT ticket_id, COUNT(id) AS reached_late_count + FROM odex25_helpdesk_sla_status + WHERE ticket_id IN %s AND deadline < reached_datetime + GROUP BY ticket_id + """, (tuple(self.ids),)) + mapping = dict(self.env.cr.fetchall()) + + for ticket in self: + ticket.sla_reached_late = mapping.get(ticket.id, 0) > 0 + + @api.depends('sla_status_ids.deadline', 'sla_status_ids.reached_datetime') + def _compute_sla_deadline(self): + """ Keep the deadline for the last stage (closed one), so a closed ticket can have a status failed. + Note: a ticket in a closed stage will probably have no deadline + """ + for ticket in self: + deadline = False + status_not_reached = ticket.sla_status_ids.filtered( + lambda status: not status.reached_datetime and status.deadline) + ticket.sla_deadline = min(status_not_reached.mapped('deadline')) if status_not_reached else deadline + + @api.depends('sla_deadline', 'sla_reached_late') + def _compute_sla_fail(self): + now = fields.Datetime.now() + for ticket in self: + if ticket.sla_deadline: + ticket.sla_fail = (ticket.sla_deadline < now) or ticket.sla_reached_late + else: + ticket.sla_fail = ticket.sla_reached_late + + @api.model + def _search_sla_fail(self, operator, value): + datetime_now = fields.Datetime.now() + if (value and operator in expression.NEGATIVE_TERM_OPERATORS) or ( + not value and operator not in expression.NEGATIVE_TERM_OPERATORS): # is not failed + return ['&', ('sla_reached_late', '=', False), '|', ('sla_deadline', '=', False), + ('sla_deadline', '>=', datetime_now)] + return ['|', ('sla_reached_late', '=', True), ('sla_deadline', '<', datetime_now)] # is failed + + @api.depends('sla_deadline', 'sla_reached_late') + def _compute_sla_success(self): + now = fields.Datetime.now() + for ticket in self: + ticket.sla_success = (ticket.sla_deadline and ticket.sla_deadline > now) + + @api.model + def _search_sla_success(self, operator, value): + datetime_now = fields.Datetime.now() + if (value and operator in expression.NEGATIVE_TERM_OPERATORS) or ( + not value and operator not in expression.NEGATIVE_TERM_OPERATORS): # is failed + return [[('sla_status_ids.reached_datetime', '>', datetime_now), ('sla_reached_late', '!=', False)]] + return [('sla_status_ids.reached_datetime', '<', datetime_now), ('sla_reached_late', '=', False)] # is success + + @api.depends('user_id') + def _compute_is_self_assigned(self): + for ticket in self: + ticket.is_self_assigned = self.env.user == ticket.user_id + + @api.depends('team_id') + def _compute_user_and_stage_ids(self): + for ticket in self.filtered(lambda ticket: ticket.team_id): + if not ticket.user_id: + ticket.user_id = ticket.team_id._determine_user_to_assign()[ticket.team_id.id] + if not ticket.stage_id or ticket.stage_id not in ticket.team_id.stage_ids: + ticket.stage_id = ticket.team_id._determine_stage()[ticket.team_id.id] + + @api.depends('partner_id') + def _compute_partner_info(self): + for ticket in self: + if ticket.partner_id: + ticket.partner_name = ticket.partner_id.name + ticket.partner_email = ticket.partner_id.email + + @api.depends('partner_id') + def _compute_partner_ticket_count(self): + data = self.env['odex25_helpdesk.ticket'].read_group([ + ('partner_id', 'in', self.mapped('partner_id').ids), + ('stage_id.is_close', '=', False) + ], ['partner_id'], ['partner_id'], lazy=False) + ticket_per_partner_map = dict((item['partner_id'][0], item['__count']) for item in data) + for ticket in self: + ticket.partner_ticket_count = ticket_per_partner_map.get(ticket.partner_id.id, 0) + + @api.depends('assign_date') + def _compute_assign_hours(self): + for ticket in self: + create_date = fields.Datetime.from_string(ticket.create_date) + if create_date and ticket.assign_date and ticket.team_id.resource_calendar_id: + duration_data = ticket.team_id.resource_calendar_id.get_work_duration_data(create_date, + fields.Datetime.from_string( + ticket.assign_date), + compute_leaves=True) + ticket.assign_hours = duration_data['hours'] + else: + ticket.assign_hours = False + + @api.depends('create_date', 'close_date') + def _compute_close_hours(self): + for ticket in self: + create_date = fields.Datetime.from_string(ticket.create_date) + if create_date and ticket.close_date: + duration_data = ticket.team_id.resource_calendar_id.get_work_duration_data(create_date, + fields.Datetime.from_string( + ticket.close_date), + compute_leaves=True) + ticket.close_hours = duration_data['hours'] + else: + ticket.close_hours = False + + @api.depends('close_hours') + def _compute_open_hours(self): + for ticket in self: + if ticket.create_date: # fix from https://github.com/odoo/enterprise/commit/928fbd1a16e9837190e9c172fa50828fae2a44f7 + if ticket.close_date: + time_difference = ticket.close_date - fields.Datetime.from_string(ticket.create_date) + else: + time_difference = fields.Datetime.now() - fields.Datetime.from_string(ticket.create_date) + ticket.open_hours = (time_difference.seconds) / 3600 + time_difference.days * 24 + else: + ticket.open_hours = 0 + + @api.model + def _search_open_hours(self, operator, value): + dt = fields.Datetime.now() - relativedelta.relativedelta(hours=value) + + d1, d2 = False, False + if operator in ['<', '<=', '>', '>=']: + d1 = ['&', ('close_date', '=', False), ('create_date', expression.TERM_OPERATORS_NEGATION[operator], dt)] + d2 = ['&', ('close_date', '!=', False), ('close_hours', operator, value)] + elif operator in ['=', '!=']: + subdomain = ['&', ('create_date', '>=', dt.replace(minute=0, second=0, microsecond=0)), + ('create_date', '<=', dt.replace(minute=59, second=59, microsecond=99))] + if operator in expression.NEGATIVE_TERM_OPERATORS: + subdomain = expression.distribute_not(subdomain) + d1 = expression.AND([[('close_date', '=', False)], subdomain]) + d2 = ['&', ('close_date', '!=', False), ('close_hours', operator, value)] + return expression.OR([d1, d2]) + + # ------------------------------------------------------------ + # ORM overrides + # ------------------------------------------------------------ + + def name_get(self): + result = [] + for ticket in self: + result.append((ticket.id, "%s (#%d)" % (ticket.name, ticket._origin.id))) + return result + + @api.model + def create_action(self, action_ref, title, search_view_ref): + action = self.env["ir.actions.actions"]._for_xml_id(action_ref) + if title: + action['display_name'] = title + if search_view_ref: + action['search_view_id'] = self.env.ref(search_view_ref).read()[0] + action['views'] = [(False, view) for view in action['view_mode'].split(",")] + + return {'action': action} + + @api.model_create_multi + def create(self, list_value): + now = fields.Datetime.now() + # determine user_id and stage_id if not given. Done in batch. + teams = self.env['odex25_helpdesk.team'].browse([vals['team_id'] for vals in list_value if vals.get('team_id')]) + team_default_map = dict.fromkeys(teams.ids, dict()) + for team in teams: + team_default_map[team.id] = { + 'stage_id': team._determine_stage()[team.id].id, + 'user_id': team._determine_user_to_assign()[team.id].id + } + + # Manually create a partner now since 'generate_recipients' doesn't keep the name. This is + # to avoid intrusive changes in the 'mail' module + for vals in list_value: + partner_id = vals.get('partner_id', False) + partner_name = vals.get('partner_name', False) + partner_email = vals.get('partner_email', False) + if partner_name and partner_email and not partner_id: + try: + vals['partner_id'] = self.env['res.partner'].find_or_create( + tools.formataddr((partner_name, partner_email)) + ).id + except UnicodeEncodeError: + # 'formataddr' doesn't support non-ascii characters in email. Therefore, we fall + # back on a simple partner creation. + vals['partner_id'] = self.env['res.partner'].create({ + 'name': partner_name, + 'email': partner_email, + }).id + + # determine partner email for ticket with partner but no email given + partners = self.env['res.partner'].browse([vals['partner_id'] for vals in list_value if + 'partner_id' in vals and vals.get( + 'partner_id') and 'partner_email' not in vals]) + partner_email_map = {partner.id: partner.email for partner in partners} + partner_name_map = {partner.id: partner.name for partner in partners} + + for vals in list_value: + if vals.get('team_id'): + team_default = team_default_map[vals['team_id']] + if 'stage_id' not in vals: + vals['stage_id'] = team_default['stage_id'] + # Note: this will break the randomly distributed user assignment. Indeed, it will be too difficult to + # equally assigned user when creating ticket in batch, as it requires to search after the last assigned + # after every ticket creation, which is not very performant. We decided to not cover this user case. + if 'user_id' not in vals: + vals['user_id'] = team_default['user_id'] + if vals.get( + 'user_id'): # if a user is finally assigned, force ticket assign_date and reset assign_hours + vals['assign_date'] = fields.Datetime.now() + vals['assign_hours'] = 0 + + # set partner email if in map of not given + if vals.get('partner_id') in partner_email_map: + vals['partner_email'] = partner_email_map.get(vals['partner_id']) + # set partner name if in map of not given + if vals.get('partner_id') in partner_name_map: + vals['partner_name'] = partner_name_map.get(vals['partner_id']) + + if vals.get('stage_id'): + vals['date_last_stage_update'] = now + + # context: no_log, because subtype already handle this + tickets = super(odex25_helpdeskTicket, self).create(list_value) + tickets.activity_update() + tickets.emp_req = True + + # make customer follower + for ticket in tickets: + if ticket.partner_id: + ticket.message_subscribe(partner_ids=ticket.partner_id.ids) + + ticket._portal_ensure_token() + + # apply SLA + tickets.sudo()._sla_apply() + + return tickets + + def write(self, vals): + # we set the assignation date (assign_date) to now for tickets that are being assigned for the first time + # same thing for the closing date + assigned_tickets = closed_tickets = self.browse() + if vals.get('user_id'): + assigned_tickets = self.filtered(lambda ticket: not ticket.assign_date) + + if vals.get('stage_id'): + if self.env['odex25_helpdesk.stage'].browse(vals.get('stage_id')).is_close: + closed_tickets = self.filtered(lambda ticket: not ticket.close_date) + else: # auto reset the 'closed_by_partner' flag + vals['closed_by_partner'] = False + + now = fields.Datetime.now() + + # update last stage date when changing stage + if 'stage_id' in vals: + vals['date_last_stage_update'] = now + + res = super(odex25_helpdeskTicket, self - assigned_tickets - closed_tickets).write(vals) + res &= super(odex25_helpdeskTicket, assigned_tickets - closed_tickets).write(dict(vals, **{ + 'assign_date': now, + })) + res &= super(odex25_helpdeskTicket, closed_tickets - assigned_tickets).write(dict(vals, **{ + 'close_date': now, + })) + res &= super(odex25_helpdeskTicket, assigned_tickets & closed_tickets).write(dict(vals, **{ + 'assign_date': now, + 'close_date': now, + })) + + if vals.get('partner_id'): + self.message_subscribe([vals['partner_id']]) + if vals.get('assistance_user_id'): + # need to change description of activity also so unlink old and create new activity + self.activity_unlink(['odex25_helpdesk.mail_act_odex25_helpdesk_assistance']) + self.activity_update() + + # SLA business + sla_triggers = self._sla_reset_trigger() + if any(field_name in sla_triggers for field_name in vals.keys()): + self.sudo()._sla_apply(keep_reached=True) + if 'stage_id' in vals: + self.sudo()._sla_reach(vals['stage_id']) + + if 'stage_id' in vals or 'user_id' in vals: + self.filtered(lambda ticket: ticket.user_id).sudo()._sla_assigning_reach() + + return res + + # ------------------------------------------------------------ + # Actions and Business methods + # ------------------------------------------------------------ + + @api.model + def _sla_reset_trigger(self): + """ Get the list of field for which we have to reset the SLAs (regenerate) """ + return ['team_id', 'priority', 'service_id', 'tag_ids'] + + def _sla_apply(self, keep_reached=False): + """ Apply SLA to current tickets: erase the current SLAs, then find and link the new SLAs to each ticket. + Note: transferring ticket to a team "not using SLA" (but with SLAs defined), SLA status of the ticket will be + erased but nothing will be recreated. + :returns recordset of new odex25_helpdesk.sla.status applied on current tickets + """ + # get SLA to apply + sla_per_tickets = self._sla_find() + + # generate values of new sla status + sla_status_value_list = [] + for tickets, slas in sla_per_tickets.items(): + sla_status_value_list += tickets._sla_generate_status_values(slas, keep_reached=keep_reached) + + sla_status_to_remove = self.mapped('sla_status_ids') + if keep_reached: # keep only the reached one to avoid losing reached_date info + sla_status_to_remove = sla_status_to_remove.filtered(lambda status: not status.reached_datetime) + + # if we are going to recreate many sla.status, then add norecompute to avoid 2 recomputation (unlink + recreate). Here, + # `norecompute` will not trigger recomputation. It will be done on the create multi (if value list is not empty). + if sla_status_value_list: + sla_status_to_remove.with_context(norecompute=True) + + # unlink status and create the new ones in 2 operations (recomputation optimized) + sla_status_to_remove.unlink() + return self.env['odex25_helpdesk.sla.status'].create(sla_status_value_list) + + def _sla_find(self): + """ Find the SLA to apply on the current tickets + :returns a map with the tickets linked to the SLA to apply on them + :rtype : dict {: } + """ + tickets_map = {} + sla_domain_map = {} + + def _generate_key(ticket): + """ Return a tuple identifying the combinaison of field determining the SLA to apply on the ticket """ + fields_list = self._sla_reset_trigger() + key = list() + for field_name in fields_list: + if ticket._fields[field_name].type == 'many2one': + key.append(ticket[field_name].id) + else: + key.append(ticket[field_name]) + return tuple(key) + + for ticket in self: + if ticket.team_id.use_sla: # limit to the team using SLA + key = _generate_key(ticket) + # group the ticket per key + tickets_map.setdefault(key, self.env['odex25_helpdesk.ticket']) + tickets_map[key] |= ticket + # group the SLA to apply, by key + if key not in sla_domain_map: + sla_domain_map[key] = [ + ('team_id', '=', ticket.team_id.id), ('priority', '<=', ticket.priority), + '|', + '&', ('stage_id.sequence', '>=', ticket.stage_id.sequence), ('target_type', '=', 'stage'), + ('target_type', '=', 'assigning'), + '|', ('service_id', '=', ticket.service_id.id), ('service_id', '=', False)] + + result = {} + for key, tickets in tickets_map.items(): # only one search per ticket group + domain = sla_domain_map[key] + slas = self.env['odex25_helpdesk.sla'].search(domain) + result[tickets] = slas.filtered(lambda s: s.tag_ids <= tickets.tag_ids) # SLA to apply on ticket subset + return result + + def _sla_generate_status_values(self, slas, keep_reached=False): + """ Return the list of values for given SLA to be applied on current ticket """ + status_to_keep = dict.fromkeys(self.ids, list()) + + # generate the map of status to keep by ticket only if requested + if keep_reached: + for ticket in self: + for status in ticket.sla_status_ids: + if status.reached_datetime: + status_to_keep[ticket.id].append(status.sla_id.id) + + # create the list of value, and maybe exclude the existing ones + result = [] + for ticket in self: + for sla in slas: + if not (keep_reached and sla.id in status_to_keep[ticket.id]): + if sla.target_type == 'stage' and ticket.stage_id == sla.stage_id: + # in case of SLA of type stage and on first stage + reached_datetime = fields.Datetime.now() + elif sla.target_type == 'assigning' and ( + not sla.stage_id or ticket.stage_id == sla.stage_id) and ticket.user_id: + # in case of SLA of type assigning and ticket is already assigned + reached_datetime = fields.Datetime.now() + else: + reached_datetime = False + result.append({ + 'ticket_id': ticket.id, + 'sla_id': sla.id, + 'reached_datetime': reached_datetime + }) + + return result + + def _sla_assigning_reach(self): + """ Flag the SLA status of current ticket for the given stage_id as reached, and even the unreached SLA applied + on stage having a sequence lower than the given one. + """ + self.env['odex25_helpdesk.sla.status'].search([ + ('ticket_id', 'in', self.ids), + ('reached_datetime', '=', False), + ('deadline', '!=', False), + ('target_type', '=', 'assigning') + ]).write({'reached_datetime': fields.Datetime.now()}) + + def _sla_reach(self, stage_id): + """ Flag the SLA status of current ticket for the given stage_id as reached, and even the unreached SLA applied + on stage having a sequence lower than the given one. + """ + stage = self.env['odex25_helpdesk.stage'].browse(stage_id) + stages = self.env['odex25_helpdesk.stage'].search([('sequence', '<=', stage.sequence), ( + 'team_ids', 'in', self.mapped('team_id').ids)]) # take previous stages + self.env['odex25_helpdesk.sla.status'].search([ + ('ticket_id', 'in', self.ids), + ('sla_stage_id', 'in', stages.ids), + ('reached_datetime', '=', False), + ('target_type', '=', 'stage') + ]).write({'reached_datetime': fields.Datetime.now()}) + + # For all SLA of type assigning, we compute deadline if they are not succeded (is succeded = has a reach_datetime) + # and if they are linked to a specific stage. + self.env['odex25_helpdesk.sla.status'].search([ + ('ticket_id', 'in', self.ids), + ('sla_stage_id', '!=', False), + ('reached_datetime', '=', False), + ('target_type', '=', 'assigning') + ])._compute_deadline() + + def assign_ticket_to_self(self): + self.ensure_one() + self.user_id = self.env.user + + def open_customer_tickets(self): + return { + 'type': 'ir.actions.act_window', + 'name': _('Customer Tickets'), + 'res_model': 'odex25_helpdesk.ticket', + 'view_mode': 'kanban,tree,form,pivot,graph', + 'context': {'search_default_is_open': True, 'search_default_partner_id': self.partner_id.id} + } + + def action_get_attachment_tree_view(self): + attachment_action = self.env.ref('base.action_attachment') + action = attachment_action.read()[0] + action['domain'] = str(['&', ('res_model', '=', self._name), ('res_id', 'in', self.ids)]) + return action + + # ------------------------------------------------------------ + # Messaging API + # ------------------------------------------------------------ + + # DVE FIXME: if partner gets created when sending the message it should be set as partner_id of the ticket. + def _message_get_suggested_recipients(self): + recipients = super(odex25_helpdeskTicket, self)._message_get_suggested_recipients() + try: + for ticket in self: + if ticket.partner_id and ticket.partner_id.email: + ticket._message_add_suggested_recipient(recipients, partner=ticket.partner_id, reason=_('Customer')) + elif ticket.partner_email: + ticket._message_add_suggested_recipient(recipients, email=ticket.partner_email, + reason=_('Customer Email')) + except AccessError: # no read access rights -> just ignore suggested recipients because this implies modifying followers + pass + return recipients + + def _ticket_email_split(self, msg): + email_list = tools.email_split((msg.get('to') or '') + ',' + (msg.get('cc') or '')) + # check left-part is not already an alias + return [ + x for x in email_list + if x.split('@')[0] not in self.mapped('team_id.alias_name') + ] + + @api.model + def message_new(self, msg, custom_values=None): + values = dict(custom_values or {}, partner_email=msg.get('from'), partner_id=msg.get('author_id')) + ticket = super(odex25_helpdeskTicket, self.with_context(mail_notify_author=True)).message_new(msg, + custom_values=values) + partner_ids = [x.id for x in + self.env['mail.thread']._mail_find_partner_from_emails(self._ticket_email_split(msg), + records=ticket) if x] + customer_ids = [p.id for p in self.env['mail.thread']._mail_find_partner_from_emails( + tools.email_split(values['partner_email']), records=ticket) if p] + partner_ids += customer_ids + if customer_ids and not values.get('partner_id'): + ticket.partner_id = customer_ids[0] + if partner_ids: + ticket.message_subscribe(partner_ids) + return ticket + + def message_update(self, msg, update_vals=None): + partner_ids = [x.id for x in + self.env['mail.thread']._mail_find_partner_from_emails(self._ticket_email_split(msg), + records=self) if x] + if partner_ids: + self.message_subscribe(partner_ids) + return super(odex25_helpdeskTicket, self).message_update(msg, update_vals=update_vals) + + def _message_post_after_hook(self, message, msg_vals): + if self.partner_email and self.partner_id and not self.partner_id.email: + self.partner_id.email = self.partner_email + + if self.partner_email and not self.partner_id: + # we consider that posting a message with a specified recipient (not a follower, a specific one) + # on a document without customer means that it was created through the chatter using + # suggested recipients. This heuristic allows to avoid ugly hacks in JS. + new_partner = message.partner_ids.filtered(lambda partner: partner.email == self.partner_email) + if new_partner: + self.search([ + ('partner_id', '=', False), + ('partner_email', '=', new_partner.email), + ('stage_id.fold', '=', False)]).write({'partner_id': new_partner.id}) + return super(odex25_helpdeskTicket, self)._message_post_after_hook(message, msg_vals) + + def _track_template(self, changes): + res = super(odex25_helpdeskTicket, self)._track_template(changes) + ticket = self[0] + if 'stage_id' in changes and ticket.stage_id.template_id: + res['stage_id'] = (ticket.stage_id.template_id, { + 'auto_delete_message': True, + 'subtype_id': self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'), + 'email_layout_xmlid': 'mail.mail_notification_light' + } + ) + return res + + def _creation_subtype(self): + return self.env.ref('odex25_helpdesk.mt_ticket_new') + + def _track_subtype(self, init_values): + self.ensure_one() + if 'stage_id' in init_values: + return self.env.ref('odex25_helpdesk.mt_ticket_stage') + return super(odex25_helpdeskTicket, self)._track_subtype(init_values) + + def _notify_get_groups(self, msg_vals=None): + """ Handle Helpdesk users and managers recipients that can assign + tickets directly from notification emails. Also give access button + to portal and portal customers. If they are notified they should + probably have access to the document. """ + groups = super(odex25_helpdeskTicket, self)._notify_get_groups() + msg_vals = msg_vals or {} + + self.ensure_one() + for group_name, group_method, group_data in groups: + if group_name != 'customer': + group_data['has_button_access'] = True + + if self.user_id: + return groups + + take_action = self._notify_get_action_link('assign', **msg_vals) + odex25_helpdesk_actions = [{'url': take_action, 'title': _('Assign to me')}] + odex25_helpdesk_user_group_id = self.env.ref('odex25_helpdesk.group_odex25_helpdesk_user').id + new_groups = [( + 'group_odex25_helpdesk_user', + lambda pdata: pdata['type'] == 'user' and odex25_helpdesk_user_group_id in pdata['groups'], + {'actions': odex25_helpdesk_actions} + )] + return new_groups + groups + + def _notify_get_reply_to(self, default=None, records=None, company=None, doc_names=None): + """ Override to set alias of tickets to their team if any. """ + aliases = self.mapped('team_id').sudo()._notify_get_reply_to(default=default, records=None, company=company, + doc_names=None) + res = {ticket.id: aliases.get(ticket.team_id.id) for ticket in self} + leftover = self.filtered(lambda rec: not rec.team_id) + if leftover: + res.update(super(odex25_helpdeskTicket, leftover)._notify_get_reply_to(default=default, records=None, + company=company, + doc_names=doc_names)) + return res + + # ------------------------------------------------------------ + # Rating Mixin + # ------------------------------------------------------------ + + def rating_apply(self, rate, token=None, feedback=None, subtype_xmlid=None): + return super(odex25_helpdeskTicket, self).rating_apply(rate, token=token, feedback=feedback, + subtype_xmlid="odex25_helpdesk.mt_ticket_rated") + + def _rating_get_parent_field_name(self): + return 'team_id' diff --git a/odex25_helpdesk/odex25_helpdesk/models/res_partner.py b/odex25_helpdesk/odex25_helpdesk/models/res_partner.py new file mode 100644 index 000000000..da829178f --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/models/res_partner.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +from odoo import api, fields, models + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + ticket_count = fields.Integer("Tickets", compute='_compute_ticket_count') + + def _compute_ticket_count(self): + # retrieve all children partners and prefetch 'parent_id' on them + all_partners = self.with_context(active_test=False).search([('id', 'child_of', self.ids)]) + all_partners.read(['parent_id']) + + # group tickets by partner, and account for each partner in self + groups = self.env['odex25_helpdesk.ticket'].read_group( + [('partner_id', 'in', all_partners.ids)], + fields=['partner_id'], groupby=['partner_id'], + ) + self.ticket_count = 0 + for group in groups: + partner = self.browse(group['partner_id'][0]) + while partner: + if partner in self: + partner.ticket_count += group['partner_id_count'] + partner = partner.parent_id + + def action_open_odex25_helpdesk_ticket(self): + action = self.env["ir.actions.actions"]._for_xml_id("odex25_helpdesk.odex25_helpdesk_ticket_action_main_tree") + action['context'] = {} + action['domain'] = [('partner_id', 'child_of', self.ids)] + return action diff --git a/odex25_helpdesk/odex25_helpdesk/models/res_setting.py b/odex25_helpdesk/odex25_helpdesk/models/res_setting.py new file mode 100644 index 000000000..0e27c1138 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/models/res_setting.py @@ -0,0 +1,40 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import ValidationError, UserError + +class odex25_helpdeskSLA(models.Model): + _inherit = "odex25_helpdesk.sla" + + category_id = fields.Many2one('service.category') + service_id = fields.Many2one('helpdesk.service') + + + +class ServiceCategory(models.Model): + _name = 'service.category' + _description = 'Service Category' + + name = fields.Char('Service Category', required=True) + + @api.constrains('name') + def unique_service_category_constrains(self): + if self.name: + parties_party = self.env['service.category'].search([('name', '=', self.name), ('id', '!=', self.id)]) + if parties_party: + raise ValidationError(_('The Service Category Must Be Unique')) + + +class HelpdeskService(models.Model): + _name = 'helpdesk.service' + _description = 'Helpdesk Service' + + name = fields.Char('Service', required=True) + category_id = fields.Many2one('service.category') + priority = fields.Selection([('0', 'All'), ('1', 'Low priority'), ('2', 'High priority'), ('3', 'Urgent')], + string='Priority', default='0') + + @api.constrains('name') + def unique_helpdesk_service_constrains(self): + if self.name: + parties_party = self.env['helpdesk.service'].search([('name', '=', self.name), ('id', '!=', self.id)]) + if parties_party: + raise ValidationError(_('The Helpdesk Service Must Be Unique')) diff --git a/odex25_helpdesk/odex25_helpdesk/models/res_users.py b/odex25_helpdesk/odex25_helpdesk/models/res_users.py new file mode 100644 index 000000000..41e366df9 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/models/res_users.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +from odoo import fields, models, api + + +class ResUsers(models.Model): + _inherit = 'res.users' + + odex25_helpdesk_target_closed = fields.Float(string='Target Tickets to Close', default=1) + odex25_helpdesk_target_rating = fields.Float(string='Target Customer Rating', default=100) + odex25_helpdesk_target_success = fields.Float(string='Target Success Rate', default=100) + + _sql_constraints = [ + ('target_closed_not_zero', 'CHECK(odex25_helpdesk_target_closed > 0)', 'You cannot have negative targets'), + ('target_rating_not_zero', 'CHECK(odex25_helpdesk_target_rating > 0)', 'You cannot have negative targets'), + ('target_success_not_zero', 'CHECK(odex25_helpdesk_target_success > 0)', 'You cannot have negative targets'), + ] + + def __init__(self, pool, cr): + """ Override of __init__ to add access rights. + Access rights are disabled by default, but allowed + on some specific fields defined in self.SELF_{READ/WRITE}ABLE_FIELDS. + """ + init_res = super(ResUsers, self).__init__(pool, cr) + odex25_helpdesk_fields = [ + 'odex25_helpdesk_target_closed', + 'odex25_helpdesk_target_rating', + 'odex25_helpdesk_target_success', + ] + # duplicate list to avoid modifying the original reference + type(self).SELF_WRITEABLE_FIELDS = list(self.SELF_WRITEABLE_FIELDS) + type(self).SELF_WRITEABLE_FIELDS.extend(odex25_helpdesk_fields) + # duplicate list to avoid modifying the original reference + type(self).SELF_READABLE_FIELDS = list(self.SELF_READABLE_FIELDS) + type(self).SELF_READABLE_FIELDS.extend(odex25_helpdesk_fields) + return init_res + + +class GroupsView(models.Model): + _inherit = 'res.groups' + + def get_application_groups(self, domain): + group_helpdesk_user = self.env.ref('odex25_helpdesk.group_odex25_helpdesk_user', raise_if_not_found=False) + group_helpdesk_admin = self.env.ref('odex25_helpdesk.group_odex25_helpdesk_manager', raise_if_not_found=False) + if group_helpdesk_admin and group_helpdesk_user: + return super().get_application_groups( + domain + [('id', 'not in', (group_helpdesk_user.id, group_helpdesk_admin.id))]) + return super(GroupsView, self).get_application_groups(domain) diff --git a/odex25_helpdesk/odex25_helpdesk/report/__init__.py b/odex25_helpdesk/odex25_helpdesk/report/__init__.py new file mode 100644 index 000000000..32d83819d --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/report/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import odex25_helpdesk_sla_report_analysis diff --git a/odex25_helpdesk/odex25_helpdesk/report/odex25_helpdesk_sla_report_analysis.py b/odex25_helpdesk/odex25_helpdesk/report/odex25_helpdesk_sla_report_analysis.py new file mode 100644 index 000000000..e0b30f9c4 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/report/odex25_helpdesk_sla_report_analysis.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- + +from odoo import api, fields, models, tools +from odoo.addons.odex25_helpdesk.models.odex25_helpdesk_ticket import TICKET_PRIORITY + + +class odex25_helpdeskSLAReport(models.Model): + _name = 'odex25_helpdesk.sla.report.analysis' + _description = "SLA Status Analysis" + _auto = False + _order = 'create_date DESC' + + ticket_id = fields.Many2one('odex25_helpdesk.ticket', string='Ticket', readonly=True) + create_date = fields.Date("Ticket Create Date", readonly=True) + priority = fields.Selection(TICKET_PRIORITY, string='Minimum Priority', readonly=True) + user_id = fields.Many2one('res.users', string="Assigned To", readonly=True) + partner_id = fields.Many2one('res.partner', string="Customer", readonly=True) + ticket_type_id = fields.Many2one('odex25_helpdesk.ticket.type', string="Ticket Type", readonly=True) + ticket_stage_id = fields.Many2one('odex25_helpdesk.stage', string="Ticket Stage", readonly=True) + ticket_deadline = fields.Datetime("Ticket Deadline", readonly=True) + ticket_failed = fields.Boolean("Ticket Failed", group_operator="bool_or", readonly=True) + ticket_closed = fields.Boolean("Ticket Closed", readonly=True) + ticket_close_hours = fields.Integer("Time to close (hours)", group_operator="avg", readonly=True) + ticket_open_hours = fields.Integer("Open Time (hours)", group_operator="avg", readonly=True) + ticket_assignation_hours = fields.Integer("Time to first assignment (hours)", group_operator="avg", readonly=True) + + sla_id = fields.Many2one('odex25_helpdesk.sla', string="SLA", readonly=True) + sla_stage_id = fields.Many2one('odex25_helpdesk.stage', string="SLA Stage", readonly=True) + sla_deadline = fields.Datetime("SLA Deadline", group_operator='min', readonly=True) + sla_reached_datetime = fields.Datetime("SLA Reached Date", group_operator='min', readonly=True) + sla_status = fields.Selection([('failed', 'Failed'), ('reached', 'Reached'), ('ongoing', 'Ongoing')], string="Status", readonly=True) + sla_status_failed = fields.Boolean("SLA Status Failed", group_operator='bool_or', readonly=True) + sla_exceeded_days = fields.Integer("Day to reach SLA", group_operator='avg', readonly=True, help="Day to reach the stage of the SLA, without taking the working calendar into account") + + team_id = fields.Many2one('odex25_helpdesk.team', string='Team', readonly=True) + company_id = fields.Many2one('res.company', string='Company', readonly=True) + + def init(self): + tools.drop_view_if_exists(self.env.cr, 'odex25_helpdesk_sla_report_analysis') + self.env.cr.execute(""" + CREATE VIEW odex25_helpdesk_sla_report_analysis AS ( + SELECT + ST.id as id, + T.create_date AS create_date, + T.id AS ticket_id, + T.team_id, + T.stage_id AS ticket_stage_id, + T.ticket_type_id, + T.user_id, + T.partner_id, + T.company_id, + T.priority AS priority, + T.sla_reached_late OR T.sla_deadline < NOW() AT TIME ZONE 'UTC' AS ticket_failed, + T.sla_deadline AS ticket_deadline, + T.close_hours AS ticket_close_hours, + EXTRACT(HOUR FROM (COALESCE(T.assign_date, NOW()) - T.create_date)) AS ticket_open_hours, + T.assign_hours AS ticket_assignation_hours, + STA.is_close AS ticket_closed, + ST.sla_id, + SLA.stage_id AS sla_stage_id, + ST.deadline AS sla_deadline, + ST.reached_datetime AS sla_reached_datetime, + ST.exceeded_days AS sla_exceeded_days, + CASE + WHEN ST.reached_datetime IS NOT NULL AND ST.reached_datetime < ST.deadline THEN 'reached' + WHEN ST.reached_datetime IS NOT NULL AND ST.reached_datetime >= ST.deadline THEN 'failed' + WHEN ST.reached_datetime IS NULL AND ST.deadline > NOW() THEN 'ongoing' + ELSE 'failed' + END AS sla_status, + ST.reached_datetime >= ST.deadline OR (ST.reached_datetime IS NULL AND ST.deadline < NOW() AT TIME ZONE 'UTC') AS sla_status_failed + FROM odex25_helpdesk_ticket T + LEFT JOIN odex25_helpdesk_stage STA ON (T.stage_id = STA.id) + LEFT JOIN odex25_helpdesk_sla_status ST ON (T.id = ST.ticket_id) + LEFT JOIN odex25_helpdesk_sla SLA ON (ST.sla_id = SLA.id) + ) + """) diff --git a/odex25_helpdesk/odex25_helpdesk/report/odex25_helpdesk_sla_report_analysis_views.xml b/odex25_helpdesk/odex25_helpdesk/report/odex25_helpdesk_sla_report_analysis_views.xml new file mode 100644 index 000000000..fc226ad56 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/report/odex25_helpdesk_sla_report_analysis_views.xml @@ -0,0 +1,100 @@ + + + + + odex25_helpdesk.sla.report.analysis.pivot + odex25_helpdesk.sla.report.analysis + + + + + + + + + + + + odex25_helpdesk.sla.report.analysis.graph + odex25_helpdesk.sla.report.analysis + + + + + + + + + + odex25_helpdesk.sla.report.analysis.search + odex25_helpdesk.sla.report.analysis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SLA Status Analysis + odex25_helpdesk.sla.report.analysis + pivot,graph + + {'search_default_last_7days': 1} + +

+ No data yet ! +

+ Create tickets to get statistics. +

+
+
+ + + + pivot + + + + + + + graph + + + + + + +
diff --git a/odex25_helpdesk/odex25_helpdesk/security/ir.model.access.csv b/odex25_helpdesk/odex25_helpdesk/security/ir.model.access.csv new file mode 100644 index 000000000..cb34efd9c --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/security/ir.model.access.csv @@ -0,0 +1,21 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_odex25_helpdesk_tag,odex25_helpdesk.tag,model_odex25_helpdesk_tag,odex25_helpdesk.group_odex25_helpdesk_user,1,1,1,1 +access_odex25_helpdesk_sla,odex25_helpdesk.sla,model_odex25_helpdesk_sla,odex25_helpdesk.group_odex25_helpdesk_user,1,0,0,0 +access_odex25_helpdesk_sla_status,odex25_helpdesk.sla.status,model_odex25_helpdesk_sla_status,odex25_helpdesk.group_odex25_helpdesk_user,1,0,0,0 +access_odex25_helpdesk_sla_manager,odex25_helpdesk.sla.manager,model_odex25_helpdesk_sla,odex25_helpdesk.group_odex25_helpdesk_manager,1,1,1,1 +access_odex25_helpdesk_stage,odex25_helpdesk.stage,model_odex25_helpdesk_stage,odex25_helpdesk.group_odex25_helpdesk_user,1,0,0,0 +access_odex25_helpdesk_stage_manager,odex25_helpdesk.stage.manager,model_odex25_helpdesk_stage,odex25_helpdesk.group_odex25_helpdesk_manager,1,1,1,1 +access_odex25_helpdesk_stage_portal,odex25_helpdesk.stage.portal,odex25_helpdesk.model_odex25_helpdesk_stage,base.group_portal,1,0,0,0 +access_odex25_helpdesk_ticket_portal,odex25_helpdesk.ticket.portal,odex25_helpdesk.model_odex25_helpdesk_ticket,base.group_portal,1,0,0,0 +access_odex25_helpdesk_ticket,odex25_helpdesk.ticket,model_odex25_helpdesk_ticket,odex25_helpdesk.group_odex25_helpdesk_user,1,1,1,1 +access_odex25_helpdesk_team_public,odex25_helpdesk.team,model_odex25_helpdesk_team,odex25_helpdesk.group_odex25_helpdesk_user,1,0,0,0 +access_odex25_helpdesk_team_no_group,odex25_helpdesk.team,model_odex25_helpdesk_team,,1,0,0,0 +access_odex25_helpdesk_team_portal,odex25_helpdesk.team.portal,odex25_helpdesk.model_odex25_helpdesk_team,base.group_portal,1,0,0,0 +access_odex25_helpdesk_team_manager,odex25_helpdesk.team.manager,model_odex25_helpdesk_team,odex25_helpdesk.group_odex25_helpdesk_manager,1,1,1,1 +access_odex25_helpdesk_ticket_type_user,odex25_helpdesk.ticket.type.user,model_odex25_helpdesk_ticket_type,odex25_helpdesk.group_odex25_helpdesk_user,1,0,0,0 +access_odex25_helpdesk_ticket_type_manager,odex25_helpdesk.ticket.type.manager,model_odex25_helpdesk_ticket_type,odex25_helpdesk.group_odex25_helpdesk_manager,1,1,1,1 +access_odex25_helpdesk_sla_report_analysis,access_odex25_helpdesk_sla_report_analysis,model_odex25_helpdesk_sla_report_analysis,odex25_helpdesk.group_odex25_helpdesk_manager,1,0,0,0 +access_mail_activity_type_odex25_helpdesk_manager,mail.activity.type.odex25_helpdesk.manager,mail.model_mail_activity_type,odex25_helpdesk.group_odex25_helpdesk_manager,1,1,1,1 + +access_zfp_helpdesk_service_category,zfp_helpdesk.zfp_helpdesk,odex25_helpdesk.model_service_category,odex25_helpdesk.group_odex25_helpdesk_user,1,1,1,1 +access_zfp_helpdesk_service,zfp_helpdesk.zfp_helpdesk,odex25_helpdesk.model_helpdesk_service,odex25_helpdesk.group_odex25_helpdesk_user,1,1,1,1 \ No newline at end of file diff --git a/odex25_helpdesk/odex25_helpdesk/security/odex25_helpdesk_security.xml b/odex25_helpdesk/odex25_helpdesk/security/odex25_helpdesk_security.xml new file mode 100644 index 000000000..704dbfbf8 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/security/odex25_helpdesk_security.xml @@ -0,0 +1,120 @@ + + + + User + + + + + Administrator + + + + + + + + Assignment User + + + + + + On Behalf User + + + + + + Show SLA Policies + + + + + + + + + + + Helpdesk Administrator + + [(1,'=',1)] + + + + + + + Helpdesk User + + ['|', ('visibility_member_ids','in', user.id), ('visibility_member_ids','=', + False)] + + + + + Helpdesk Ticket User + + ['|', ('team_id.visibility_member_ids','in', user.id), + ('team_id.visibility_member_ids','=', False)] + + + + + + Helpdesk Ticket Managers + + [(1,'=',1)] + + + + + Project: tickets multi-company + + ['|', + ('company_id', '=', False), + ('company_id', 'in', company_ids) + ] + + + + Project: teams multi-company + + ['|', + ('company_id', '=', False), + ('company_id', 'in', company_ids), + ] + + + + Project: multi-company + + ['|', + ('company_id', '=', False), + ('company_id', 'in', company_ids), + ] + + + + Tickets: portal users: portal or following + + [ + '|', + ('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]), + ('message_partner_ids', 'in', [user.partner_id.id]) + ] + + + + + + Helpdesk SLA Report: multi-company + + ['|', ('company_id', '=', False), ('company_id', 'child_of', + [user.company_id.id])] + + + + + diff --git a/odex25_helpdesk/odex25_helpdesk/static/description/icon.png b/odex25_helpdesk/odex25_helpdesk/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9712983824028168b605b14a5a242e5a90c8008b GIT binary patch literal 11811 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 BO%(tD literal 0 HcmV?d00001 diff --git a/odex25_helpdesk/odex25_helpdesk/static/description/icon.svg b/odex25_helpdesk/odex25_helpdesk/static/description/icon.svg new file mode 100644 index 000000000..361bc6ad3 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/static/description/icon.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/odex25_helpdesk/odex25_helpdesk/static/src/css/portal_odex25_helpdesk.css b/odex25_helpdesk/odex25_helpdesk/static/src/css/portal_odex25_helpdesk.css new file mode 100644 index 000000000..c07ea6c24 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/static/src/css/portal_odex25_helpdesk.css @@ -0,0 +1,11 @@ +.oe_website_rating_team .oe_rate{ + clear: both; +} +.oe_website_rating_team .oe_rate img { + margin-right: 6px; +} + +.oe_website_rating_team .thumbnail{ + height: 355px; +} + diff --git a/odex25_helpdesk/odex25_helpdesk/static/src/img/menu-navigation.gif b/odex25_helpdesk/odex25_helpdesk/static/src/img/menu-navigation.gif new file mode 100644 index 0000000000000000000000000000000000000000..9c786bfd6d9f9e190fedd185c2c1707419e545f8 GIT binary patch literal 32619 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', {type: "text", name: target_name}); + if (target_value) { + $input.attr('value', target_value); + } + $input.on('keyup input', function (e) { + if (e.which === $.ui.keyCode.ENTER) { + self._notifyTargetChange(target_name, $input.val()); + } + }); + $input.on('blur', function () { + self._notifyTargetChange(target_name, $input.val()); + }); + $input.replaceAll($target) + .focus() + .select(); + }, +}); + +var odex25_helpdeskDashboardModel = KanbanModel.extend({ + /** + * @override + */ + init: function () { + this.dashboardValues = {}; + this._super.apply(this, arguments); + }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @override + */ + __get: function (localID) { + var result = this._super.apply(this, arguments); + if (_.isObject(result)) { + result.dashboardValues = this.dashboardValues[localID]; + } + return result; + }, + /** + * @œverride + * @returns {Promise} + */ + __load: function () { + return this._loadDashboard(this._super.apply(this, arguments)); + }, + /** + * @œverride + * @returns {Promise} + */ + __reload: function () { + return this._loadDashboard(this._super.apply(this, arguments)); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + * @param {Promise} super_def a promise that resolves with a dataPoint id + * @returns {Promise -> string} resolves to the dataPoint id + */ + _loadDashboard: function (super_def) { + var self = this; + var dashboard_def = this._rpc({ + model: 'odex25_helpdesk.team', + method: 'retrieve_dashboard', + }); + return Promise.all([super_def, dashboard_def]).then(function(results) { + var id = results[0]; + var dashboardValues = results[1]; + self.dashboardValues[id] = dashboardValues; + return id; + }); + }, +}); + +var odex25_helpdeskDashboardController = KanbanController.extend({ + custom_events: _.extend({}, KanbanController.prototype.custom_events, { + dashboard_open_action: '_onDashboardOpenAction', + dashboard_edit_target: '_onDashboardEditTarget', + }), + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param {OdooEvent} e + */ + _onDashboardEditTarget: function (e) { + var target_name = e.data.target_name; + var target_value = e.data.target_value; + if (isNaN(target_value)) { + this.do_warn(false, _t("Please enter an integer value")); + } else { + var values = {}; + values[target_name] = parseInt(target_value); + this._rpc({ + model: 'res.users', + method: 'write', + args: [[session.uid], values], + }) + .then(this.reload.bind(this)); + } + }, + /** + * @private + * @param {OdooEvent} e + */ + _onDashboardOpenAction: function (e) { + var self = this; + var action_name = e.data.action_name; + if (_.contains(['action_view_rating_today', 'action_view_rating_7days'], action_name)) { + return this._rpc({model: this.modelName, method: action_name}) + .then(function (data) { + if (data) { + return self.do_action(data); + } + }); + } + return this.do_action(action_name); + }, +}); + +var odex25_helpdeskDashboardView = KanbanView.extend({ + config: _.extend({}, KanbanView.prototype.config, { + Model: odex25_helpdeskDashboardModel, + Renderer: odex25_helpdeskDashboardRenderer, + Controller: odex25_helpdeskDashboardController, + }), + display_name: _lt('Dashboard'), + icon: 'fa-dashboard', + searchview_hidden: true, +}); + +view_registry.add('odex25_helpdesk_dashboard', odex25_helpdeskDashboardView); + +return { + Model: odex25_helpdeskDashboardModel, + Renderer: odex25_helpdeskDashboardRenderer, + Controller: odex25_helpdeskDashboardController, +}; + +}); diff --git a/odex25_helpdesk/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js b/odex25_helpdesk/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js new file mode 100644 index 000000000..cd01e62c4 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js @@ -0,0 +1,75 @@ +odoo.define('odex25_helpdesk.tour', function(require) { +"use strict"; + +var core = require('web.core'); +var tour = require('web_tour.tour'); + +var _t = core._t; + +tour.register('odex25_helpdesk_tour', { + url: "/web", +}, [{ + trigger: '.o_app[data-menu-xmlid="odex25_helpdesk.menu_odex25_helpdesk_root"]', + content: _t('Want to boost your customer satisfaction?
Click Helpdesk to start.'), + position: 'bottom', +}, { + trigger: '.oe_kanban_action_button', + extra_trigger: '.o_kanban_primary_left', + content: _t('Click here to view this team\'s tickets.'), + position: 'bottom', + width: 200, +}, { + trigger: '.o-kanban-button-new', + extra_trigger: '.o_kanban_odex25_helpdesk_ticket', + content: _t('Let\'s create your first ticket.'), + position: 'bottom', + width: 200, +}, { + trigger: 'input.field_name', + extra_trigger: '.o_form_editable', + content: _t('Enter a subject or title for this ticket.
(e.g. Problem with installation, Wrong order, Can\'t understand bill, etc.)'), + position: 'right', +}, { + trigger: '.o_field_widget.field_partner_id', + extra_trigger: '.o_form_editable', + content: _t('Enter the customer. Feel free to create it on the fly.'), + position: 'top', +}, { + trigger: '.o_field_widget.field_user_id', + extra_trigger: '.o_form_editable', + content: _t('Assign the ticket to someone.'), + position: 'right', +}, { + trigger: '.o_form_button_save', + content: _t('Save this ticket and the modifications you\'ve made to it.'), + position: 'bottom', +}, { + trigger: '.o_back_button', + extra_trigger: '.o_form_view.o_form_readonly', + content: _t('Use the breadcrumbs to go back to the Kanban view.'), + position: 'bottom', +}, { + trigger: '.o_kanban_record', + content: _t('Click these cards to open their form view, or drag & drop them through the different stages of this team.'), + position: 'right', + run: "drag_and_drop .o_kanban_group:eq(2) ", +}, { + trigger: '.o_priority', + extra_trigger: '.o_kanban_record', + content: _t('Stars mark the ticket priority. You can change it directly from here!'), + position: 'bottom', + run: "drag_and_drop .o_kanban_group:eq(2) ", +}, { + trigger: ".o_column_quick_create .o_quick_create_folded", + content: _t('Add columns to configure stages for your tickets.
e.g. Awaiting Customer Feedback, Customer Followup, ...'), + position: 'right', +} +// TODO: Restore this step +// , { +// trigger: '.dropdown-toggle[data-menu-xmlid="odex25_helpdesk.odex25_helpdesk_menu_config"]', +// content: _t('Click here and select "Helpdesk Teams" for further configuration.'), +// position: 'bottom', +// } +]); + +}); diff --git a/odex25_helpdesk/odex25_helpdesk/static/src/scss/odex25_helpdesk.scss b/odex25_helpdesk/odex25_helpdesk/static/src/scss/odex25_helpdesk.scss new file mode 100644 index 000000000..412f0177a --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/static/src/scss/odex25_helpdesk.scss @@ -0,0 +1,153 @@ +.o_kanban_view.o_kanban_dashboard.o_odex25_helpdesk_kanban { + $sale-table-spacing: 10px; + $odex25_helpdesk-record-width: 420px; + + .o_kanban_group { + width: $odex25_helpdesk-record-width + 2*$o-kanban-group-padding; + } + .o_kanban_record { + width: $odex25_helpdesk-record-width; + min-height: 250px; + } + + .o_odex25_helpdesk_dashboard { + @include o-position-sticky($top: 0px) + border-bottom: 1px solid #CED4DA; + background-color: $o-view-background-color; + z-index: 10; + + .o_welcome_message { + width: 100%; + @include o-position-absolute($left: 0, $top: 0); + display: flex; + justify-content: center; + + .o_welcome_image { + padding: 20px; + } + .o_welcome_content > a { + color: white; + display: inline-block; + } + } + + .o_dashboard_action { + cursor: pointer; + } + + .ribbon { + &::before, &::after { + display: none; + } + + span { + background-color: $o-brand-odoo; + padding: 5px; + font-size: medium; + z-index: unset; + height:auto; + } + } + + .ribbon-top-right { + margin-top: -$o-kanban-dashboard-vpadding; + + span { + left: 0px; + right: 30px; + } + } + + > .o_demo { + opacity: 0.07; + } + + > div { + display: inline-block; + vertical-align: top; + @include media-breakpoint-up(md) { + width: 50%; + } + + > table { + margin-bottom: 0; + table-layout: fixed; + border-spacing: $sale-table-spacing 0px; + border-collapse: separate; + > tbody > tr > td { + vertical-align: middle; + text-align: center; + border-top: 1px solid $o-view-background-color; + width: 25%; + + height: 33px; + + span { + display: inline; + } + + a:hover { + text-decoration: none; + } + + &.o_demo{ + cursor: default; + a { + cursor: default; + } + } + + &.o_main { + background-color: $o-brand-primary; + &:hover { + background-color: darken($o-brand-primary, 10%); + } + a { + color: white; + } + &.o_demo{ + &:hover { + background-color: $o-brand-primary; + } + } + } + &.o_warning { + background-color: orange; + &:hover { + background-color: darken(orange, 10%); + } + a { + color: white; + } + &.o_demo{ + &:hover { + background-color: orange; + } + } + } + &.o_secondary { + background-color: $o-brand-lightsecondary; + &:hover { + background-color: darken($o-brand-lightsecondary, 10%); + } + a { + color: black; + } + &.o_demo{ + &:hover { + background-color: $o-brand-lightsecondary; + } + } + } + &.o_highlight, .o_highlight { + font-size: 20px; + } + &.o_text { + text-align: left; + } + } + + } + } + } +} diff --git a/odex25_helpdesk/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml b/odex25_helpdesk/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml new file mode 100644 index 000000000..7278c53b1 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml @@ -0,0 +1,202 @@ + + + + +
+
+ + + +
+ Sample +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
My Tickets
+
+ +
+ Tickets +
+
+ +
+ High Priority () +
+
+ +
+ Urgent () +
+
Avg Open Hours + + + + + + + + + +
SLA Failed + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + +
+
My Performance
+ Today +
+ + + +
+ Closed Tickets +
+
+ + + % +
+ Happy Rating +
+
+ + + + % +
+ Success Rate +
+
Avg 7 days + + + + + + % + + + + % + +
Daily Target + + + + + + Click to set + + + + + + % + + + Click to set + + + + + + % + + + Click to set + + +
+
+
+
+
diff --git a/odex25_helpdesk/odex25_helpdesk/static/tests/odex25_helpdesk_dashboard_tests.js b/odex25_helpdesk/odex25_helpdesk/static/tests/odex25_helpdesk_dashboard_tests.js new file mode 100644 index 000000000..5eb8ba7f4 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/static/tests/odex25_helpdesk_dashboard_tests.js @@ -0,0 +1,150 @@ +odoo.define('odex25_helpdesk.dashboard_tests', function (require) { +"use strict"; + +var testUtils = require('web.test_utils'); +var view_registry = require('web.view_registry'); + +var createView = testUtils.createView; + +QUnit.module('Views', {}, function () { + +QUnit.module('Helpdesk Dashboard', { + beforeEach: function() { + this.data = { + partner: { + fields: { + foo: {string: "Foo", type: "char"}, + }, + records: [ + {id: 1, foo: "yop"}, + {id: 2, foo: "blip"}, + {id: 3, foo: "gnap"}, + {id: 4, foo: "blip"}, + ] + }, + }; + this.dashboard_data = { + '7days': {count: 0, rating: 0, success: 0}, + odex25_helpdesk_target_closed: 12, + odex25_helpdesk_target_rating: 0, + odex25_helpdesk_target_success: 0, + my_all: {count: 0, hours: 0, failed: 0}, + my_high: {count: 0, hours: 0, failed: 0}, + my_urgent: {count: 0, hours: 0, failed: 0}, + rating_enable: false, + show_demo: false, + success_rate_enable: false, + today: {count: 0, rating: 0, success: 0}, + }; + } +}); + +QUnit.test('dashboard basic rendering', async function(assert) { + assert.expect(4); + + var dashboard_data = this.dashboard_data; + var kanban = await createView({ + View: view_registry.get('odex25_helpdesk_dashboard'), + model: 'partner', + data: this.data, + arch: '' + + '' + + '
' + + '
' + + '
', + mockRPC: function(route, args) { + if (args.method === 'retrieve_dashboard') { + assert.ok(true, "should call /retrieve_dashboard"); + return Promise.resolve(dashboard_data); + } + return this._super(route, args); + }, + }); + + assert.containsOnce(kanban, 'div.o_odex25_helpdesk_dashboard', + "should render the dashboard"); + assert.strictEqual(kanban.$('.o_target_to_set').text().trim(), '12', + "should have written correct target"); + assert.hasAttrValue(kanban.$('.o_target_to_set'), 'value', '12', + "target's value is 12"); + kanban.destroy(); +}); + +QUnit.test('edit the target', async function(assert) { + assert.expect(6); + + var dashboard_data = this.dashboard_data; + dashboard_data.odex25_helpdesk_target_closed = 0; + var kanban = await createView({ + View: view_registry.get('odex25_helpdesk_dashboard'), + model: 'partner', + data: this.data, + arch: '' + + '' + + '
' + + '
' + + '
', + mockRPC: function(route, args) { + if (args.method === 'retrieve_dashboard') { + // should be called twice: for the first rendering, and after the target update + assert.ok(true, "should call /retrieve_dashboard"); + return Promise.resolve(dashboard_data); + } + if (args.model === 'res.users' && args.method === 'write') { + assert.ok(true, "should modify odex25_helpdesk_target_closed"); + dashboard_data.odex25_helpdesk_target_closed = args.args[1]['odex25_helpdesk_target_closed']; + return Promise.resolve(); + } + return this._super(route, args); + }, + }); + + assert.strictEqual(kanban.$('.o_target_to_set').text().trim(), "Click to set", + "should have correct target"); + assert.ok(!kanban.$('.o_target_to_set').attr('value'), "should have no target"); + + // edit the target + await testUtils.dom.click(kanban.$('.o_target_to_set')); + await testUtils.fields.editAndTrigger(kanban.$('.o_odex25_helpdesk_dashboard input'), + 1200, [$.Event('keyup', {which: $.ui.keyCode.ENTER})]); // set the target + + assert.strictEqual(kanban.$('.o_target_to_set').text().trim(), "1200", + "should have correct target"); + kanban.destroy(); +}); + +QUnit.test('dashboard rendering with empty many2one', async function(assert) { + assert.expect(2); + + // add an empty many2one + this.data.partner.fields.partner_id = {string: "Partner", type: 'many2one', relation: 'partner'}; + this.data.partner.records[0].partner_id = false; + + var dashboard_data = this.dashboard_data; + var kanban = await createView({ + View: view_registry.get('odex25_helpdesk_dashboard'), + model: 'partner', + data: this.data, + arch: '' + + '' + + '' + + '
' + + '
' + + '
', + mockRPC: function(route, args) { + if (args.method === 'retrieve_dashboard') { + assert.ok(true, "should call /retrieve_dashboard"); + return Promise.resolve(dashboard_data); + } + return this._super(route, args); + }, + }); + + assert.containsOnce(kanban, 'div.o_odex25_helpdesk_dashboard', + "should render the dashboard"); + kanban.destroy(); +}); + +}); + +}); diff --git a/odex25_helpdesk/odex25_helpdesk/tests/__init__.py b/odex25_helpdesk/odex25_helpdesk/tests/__init__.py new file mode 100644 index 000000000..f91800691 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/tests/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +from . import test_odex25_helpdesk_flow +from . import test_odex25_helpdesk_sla +from . import test_ui +from . import test_doc_links \ No newline at end of file diff --git a/odex25_helpdesk/odex25_helpdesk/tests/common.py b/odex25_helpdesk/odex25_helpdesk/tests/common.py new file mode 100644 index 000000000..9a04c62a7 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/tests/common.py @@ -0,0 +1,170 @@ +# -*- coding: utf-8 -*- +from contextlib import contextmanager + +from odoo import fields +from odoo.tests.common import SavepointCase +from odoo import fields + + +class odex25_helpdeskCommon(SavepointCase): + + @classmethod + def setUpClass(cls): + super(odex25_helpdeskCommon, cls).setUpClass() + cls.env.user.tz = 'Europe/Brussels' + cls.env['resource.calendar'].search([]).write({'tz': 'Europe/Brussels'}) + + # we create a Helpdesk user and a manager + Users = cls.env['res.users'].with_context(tracking_disable=True) + cls.main_company_id = cls.env.ref('base.main_company').id + cls.odex25_helpdesk_manager = Users.create({ + 'company_id': cls.main_company_id, + 'name': 'Helpdesk Manager', + 'login': 'hm', + 'email': 'hm@example.com', + 'groups_id': [(6, 0, [cls.env.ref('odex25_helpdesk.group_odex25_helpdesk_manager').id])], + 'tz': 'Europe/Brussels', + }) + cls.odex25_helpdesk_user = Users.create({ + 'company_id': cls.main_company_id, + 'name': 'Helpdesk User', + 'login': 'hu', + 'email': 'hu@example.com', + 'groups_id': [(6, 0, [cls.env.ref('odex25_helpdesk.group_odex25_helpdesk_user').id])], + 'tz': 'Europe/Brussels', + }) + # the manager defines a team for our tests (the .sudo() at the end is to avoid potential uid problems) + cls.test_team = cls.env['odex25_helpdesk.team'].with_user(cls.odex25_helpdesk_manager).create({'name': 'Test Team'}).sudo() + # He then defines its stages + stage_as_manager = cls.env['odex25_helpdesk.stage'].with_user(cls.odex25_helpdesk_manager) + cls.stage_new = stage_as_manager.create({ + 'name': 'New', + 'sequence': 10, + 'team_ids': [(4, cls.test_team.id, 0)], + 'is_close': False, + }) + cls.stage_progress = stage_as_manager.create({ + 'name': 'In Progress', + 'sequence': 20, + 'team_ids': [(4, cls.test_team.id, 0)], + 'is_close': False, + }) + cls.stage_done = stage_as_manager.create({ + 'name': 'Done', + 'sequence': 30, + 'team_ids': [(4, cls.test_team.id, 0)], + 'is_close': True, + }) + cls.stage_cancel = stage_as_manager.create({ + 'name': 'Cancelled', + 'sequence': 40, + 'team_ids': [(4, cls.test_team.id, 0)], + 'is_close': True, + }) + + # He also creates a ticket types for Question and Issue + cls.type_question = cls.env['odex25_helpdesk.ticket.type'].with_user(cls.odex25_helpdesk_manager).create({ + 'name': 'Question_test', + }).sudo() + cls.type_issue = cls.env['odex25_helpdesk.ticket.type'].with_user(cls.odex25_helpdesk_manager).create({ + 'name': 'Issue_test', + }).sudo() + + @classmethod + def setUpSLATeam(cls): + """ Generate Team, some stage and SLAs for the team """ + # create team and stages + cls.team_with_sla = cls.env['odex25_helpdesk.team'].create({ + 'name': 'Team with SLAs', + 'use_sla': True + }) + + Stage = cls.env['odex25_helpdesk.stage'] + cls.team_sla_stage_new = Stage.create({ + 'name': 'New', + 'sequence': 10, + 'team_ids': [(4, cls.team_with_sla.id, 0)], + 'is_close': False, + }) + cls.team_sla_stage_progress = Stage.create({ + 'name': 'In Progress', + 'sequence': 20, + 'team_ids': [(4, cls.team_with_sla.id, 0)], + 'is_close': False, + }) + cls.team_sla_stage_done = Stage.create({ + 'name': 'Done', + 'sequence': 30, + 'team_ids': [(4, cls.team_with_sla.id, 0)], + 'is_close': True, + }) + cls.team_sla_stage_cancel = Stage.create({ + 'name': 'Cancelled', + 'sequence': 40, + 'team_ids': [(4, cls.team_with_sla.id, 0)], + 'is_close': True, + }) + + # create SLAs + SLA = cls.env['odex25_helpdesk.sla'] + cls.sla_1_progress = SLA.create({ + 'name': "2 days to be in progress", + 'stage_id': cls.team_sla_stage_progress.id, + 'time_days': 2, + 'team_id': cls.team_with_sla.id, + }) + cls.sla_2_done = SLA.create({ + 'name': "7 days to be in progress", + 'stage_id': cls.team_sla_stage_done.id, + 'time_days': 7, + 'team_id': cls.team_with_sla.id, + 'priority': '0', + }) + cls.sla_3_done_prior = SLA.create({ + 'name': "5 days to be in done for 3 stars ticket", + 'stage_id': cls.team_sla_stage_done.id, + 'time_days': 5, + 'team_id': cls.team_with_sla.id, + 'priority': '3', + }) + + def _utils_set_create_date(self, records, date_str): + """ This method is a hack in order to be able to define/redefine the create_date + of the any recordset. This is done in SQL because ORM does not allow to write + onto the create_date field. + :param records: recordset of any odoo models + """ + query = """ + UPDATE %s + SET create_date = %%s + WHERE id IN %%s + """ % (records._table,) + self.env.cr.execute(query, (date_str, tuple(records.ids))) + + records.invalidate_cache() + + if records._name == 'odex25_helpdesk.ticket': + field = self.env['odex25_helpdesk.sla.status']._fields['deadline'] + self.env.add_to_compute(field, records.sla_status_ids) + records.recompute() + + @contextmanager + def _ticket_patch_now(self, datetime_str): + datetime_now_old = getattr(fields.Datetime, 'now') + datetime_today_old = getattr(fields.Datetime, 'today') + + def new_now(): + return fields.Datetime.from_string(datetime_str) + + def new_today(): + return fields.Datetime.from_string(datetime_str).replace(hour=0, minute=0, second=0) + + try: + setattr(fields.Datetime, 'now', new_now) + setattr(fields.Datetime, 'today', new_today) + + yield + finally: + # back + setattr(fields.Datetime, 'now', datetime_now_old) + setattr(fields.Datetime, 'today', datetime_today_old) diff --git a/odex25_helpdesk/odex25_helpdesk/tests/test_doc_links.py b/odex25_helpdesk/odex25_helpdesk/tests/test_doc_links.py new file mode 100644 index 000000000..81961bf26 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/tests/test_doc_links.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- + +from odoo.tests.common import HttpCase, tagged +import re + + +@tagged('-standard', 'external', 'post_install', '-at_install') # nightly is not a real tag +class TestDocLinks(HttpCase): + """ + Parse the 'odex25_helpdesk.team' view to extract all documentation links and + check that every links are still valid. + """ + + def setUp(self): + """ + Set-up the test environment + """ + super(TestDocLinks, self).setUp() + self.re = re.compile(" +To: odex25_helpdesk_team_0@aqualung.com +Content-Type: multipart/alternative; boundary="000000000000a47519057e029630" + +--000000000000a47519057e029630 +Content-Type: text/plain; charset="UTF-8" + + +--000000000000a47519057e029630 +Content-Type: text/html; charset="UTF-8" +Content-Transfer-Encoding: quoted-printable + +
A good message
+ +--000000000000a47519057e029630-- +""" + + new_message1 = """MIME-Version: 1.0 +Date: Thu, 27 Dec 2018 16:27:45 +0100 +Message-ID: blablabla1 +Subject: Helpdesk team 1 in company 1 +From: B client +To: odex25_helpdesk_team_1@aqualung.com +Content-Type: multipart/alternative; boundary="000000000000a47519057e029630" + +--000000000000a47519057e029630 +Content-Type: text/plain; charset="UTF-8" + + +--000000000000a47519057e029630 +Content-Type: text/html; charset="UTF-8" +Content-Transfer-Encoding: quoted-printable + +
A good message bis
+ +--000000000000a47519057e029630-- +""" + partners_exist = Partner.search([('email', 'in', ['client_a@someprovider.com', 'client_b@someprovider.com'])]) + self.assertFalse(partners_exist) + + odex25_helpdesk_ticket0_id = self.env['mail.thread'].message_process('odex25_helpdesk.ticket', new_message0) + odex25_helpdesk_ticket1_id = self.env['mail.thread'].message_process('odex25_helpdesk.ticket', new_message1) + + odex25_helpdesk_ticket0 = self.env['odex25_helpdesk.ticket'].browse(odex25_helpdesk_ticket0_id) + odex25_helpdesk_ticket1 = self.env['odex25_helpdesk.ticket'].browse(odex25_helpdesk_ticket1_id) + + self.assertEqual(odex25_helpdesk_ticket0.team_id, odex25_helpdesk_team0) + self.assertEqual(odex25_helpdesk_ticket1.team_id, odex25_helpdesk_team1) + + self.assertEqual(odex25_helpdesk_ticket0.company_id, company0) + self.assertEqual(odex25_helpdesk_ticket1.company_id, company1) + + partner0 = Partner.search([('email', '=', 'client_a@someprovider.com')]) + partner1 = Partner.search([('email', '=', 'client_b@someprovider.com')]) + self.assertTrue(partner0) + self.assertTrue(partner1) + + self.assertEqual(partner0.company_id, company0) + self.assertEqual(partner1.company_id, company1) + + self.assertEqual(odex25_helpdesk_ticket0.partner_id, partner0) + self.assertEqual(odex25_helpdesk_ticket1.partner_id, partner1) + + self.assertTrue(partner0 in odex25_helpdesk_ticket0.message_follower_ids.mapped('partner_id')) + self.assertTrue(partner1 in odex25_helpdesk_ticket1.message_follower_ids.mapped('partner_id')) + + def test_team_assignation_balanced(self): + #We create an sla policy with minimum priority set as '2' + self.test_team.use_sla = True + sla = self.env['odex25_helpdesk.sla'].create({ + 'name': 'test sla policy', + 'team_id': self.test_team.id, + 'stage_id': self.stage_progress.id, + 'priority': '2', + 'time_days': 0, + 'time_hours': 1 + }) + + #We create a ticket with priority less than what's on the sla policy + ticket_1 = self.env['odex25_helpdesk.ticket'].create({ + 'name': 'test ', + 'team_id': self.test_team.id, + 'priority': '1' + }) + + #We create a ticket with priority equal to what's on the sla policy + ticket_2 = self.env['odex25_helpdesk.ticket'].create({ + 'name': 'test sla ticket', + 'team_id': self.test_team.id, + 'priority': '2' + }) + + #We create a ticket with priority greater than what's on the sla policy + ticket_3 = self.env['odex25_helpdesk.ticket'].create({ + 'name': 'test sla ticket', + 'team_id': self.test_team.id, + 'priority': '3' + }) + #We confirm that the sla policy has been applied successfully on the ticket. + #sla policy must not be applied + self.assertTrue(sla not in ticket_1.sla_status_ids.mapped('sla_id')) + #sla policy must be applied + self.assertTrue(sla in ticket_2.sla_status_ids.mapped('sla_id')) + self.assertTrue(sla in ticket_3.sla_status_ids.mapped('sla_id')) diff --git a/odex25_helpdesk/odex25_helpdesk/tests/test_odex25_helpdesk_sla.py b/odex25_helpdesk/odex25_helpdesk/tests/test_odex25_helpdesk_sla.py new file mode 100644 index 000000000..6bb5f1b06 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/tests/test_odex25_helpdesk_sla.py @@ -0,0 +1,353 @@ +# -*- coding: utf-8 -*- +from contextlib import contextmanager +from unittest.mock import patch +from dateutil.relativedelta import relativedelta +from datetime import datetime + +from odoo import fields +from odoo.tests.common import SavepointCase + +NOW = datetime(2018, 10, 10, 9, 18) +NOW2 = datetime(2019, 1, 8, 9, 0) + + +class odex25_helpdeskSLA(SavepointCase): + + @classmethod + def setUpClass(cls): + super(odex25_helpdeskSLA, cls).setUpClass() + + # we create a Helpdesk user and a manager + Users = cls.env['res.users'].with_context(tracking_disable=True) + cls.main_company_id = cls.env.ref('base.main_company').id + cls.odex25_helpdesk_manager = Users.create({ + 'company_id': cls.main_company_id, + 'name': 'Helpdesk Manager', + 'login': 'hm', + 'email': 'hm@example.com', + 'groups_id': [(6, 0, [cls.env.ref('odex25_helpdesk.group_odex25_helpdesk_manager').id])] + }) + cls.odex25_helpdesk_user = Users.create({ + 'company_id': cls.main_company_id, + 'name': 'Helpdesk User', + 'login': 'hu', + 'email': 'hu@example.com', + 'groups_id': [(6, 0, [cls.env.ref('odex25_helpdesk.group_odex25_helpdesk_user').id])] + }) + # the manager defines a team for our tests (the .sudo() at the end is to avoid potential uid problems) + cls.test_team = cls.env['odex25_helpdesk.team'].with_user(cls.odex25_helpdesk_manager).create({ + 'name': 'Test Team', + 'use_sla': True + }).sudo() + # He then defines its stages + stage_as_manager = cls.env['odex25_helpdesk.stage'].with_user(cls.odex25_helpdesk_manager) + cls.stage_new = stage_as_manager.create({ + 'name': 'New', + 'sequence': 10, + 'team_ids': [(4, cls.test_team.id, 0)], + 'is_close': False, + }) + cls.stage_progress = stage_as_manager.create({ + 'name': 'In Progress', + 'sequence': 20, + 'team_ids': [(4, cls.test_team.id, 0)], + 'is_close': False, + }) + cls.stage_wait = stage_as_manager.create({ + 'name': 'Waiting', + 'sequence': 25, + 'team_ids': [(4, cls.test_team.id, 0)], + 'is_close': False, + }) + cls.stage_done = stage_as_manager.create({ + 'name': 'Done', + 'sequence': 30, + 'team_ids': [(4, cls.test_team.id, 0)], + 'is_close': True, + }) + cls.stage_cancel = stage_as_manager.create({ + 'name': 'Cancelled', + 'sequence': 40, + 'team_ids': [(4, cls.test_team.id, 0)], + 'is_close': True, + }) + + cls.tag_vip = cls.env['odex25_helpdesk.tag'].with_user(cls.odex25_helpdesk_manager).create({'name': 'VIP'}) + cls.tag_urgent = cls.env['odex25_helpdesk.tag'].with_user(cls.odex25_helpdesk_manager).create({'name': 'Urgent'}) + cls.tag_freeze = cls.env['odex25_helpdesk.tag'].with_user(cls.odex25_helpdesk_manager).create({'name': 'Freeze'}) + + cls.sla = cls.env['odex25_helpdesk.sla'].create({ + 'name': 'SLA', + 'team_id': cls.test_team.id, + 'time_days': 1, + 'time_hours': 24, + 'stage_id': cls.stage_progress.id, + }) + cls.sla_2 = cls.env['odex25_helpdesk.sla'].create({ + 'name': 'SLA done stage with freeze time', + 'team_id': cls.test_team.id, + 'time_days': 1, + 'time_hours': 2, + 'time_minutes': 2, + 'tag_ids': [(4, cls.tag_freeze.id)], + 'exclude_stage_ids': cls.stage_wait.ids, + 'stage_id': cls.stage_done.id, + }) + cls.sla_assigning_1 = cls.env['odex25_helpdesk.sla'].create({ + 'name': 'SLA assigning no stage', + 'team_id': cls.test_team.id, + 'time_hours': 1, + 'target_type': 'assigning' + }) + + cls.sla_assigning_2 = cls.env['odex25_helpdesk.sla'].create({ + 'name': 'SLA assigning new stage', + 'team_id': cls.test_team.id, + 'time_hours': 1, + 'stage_id': cls.stage_new.id, + 'target_type': 'assigning' + }) + + cls.sla_assigning_3 = cls.env['odex25_helpdesk.sla'].create({ + 'name': 'SLA assigning progress stage', + 'team_id': cls.test_team.id, + 'time_hours': 1, + 'stage_id': cls.stage_progress.id, + 'target_type': 'assigning' + }) + + cls.sla_assigning_4 = cls.env['odex25_helpdesk.sla'].create({ + 'name': 'SLA assigning done stage', + 'team_id': cls.test_team.id, + 'time_hours': 1, + 'stage_id': cls.stage_done.id, + 'target_type': 'assigning' + }) + + # He also creates a ticket types for Question and Issue + cls.type_question = cls.env['odex25_helpdesk.ticket.type'].with_user(cls.odex25_helpdesk_manager).create({ + 'name': 'Question_test', + }).sudo() + cls.type_issue = cls.env['odex25_helpdesk.ticket.type'].with_user(cls.odex25_helpdesk_manager).create({ + 'name': 'Issue_test', + }).sudo() + + def _utils_set_create_date(self, records, date_str, ticket_to_update=False): + """ This method is a hack in order to be able to define/redefine the create_date + of the any recordset. This is done in SQL because ORM does not allow to write + onto the create_date field. + :param records: recordset of any odoo models + """ + query = """ + UPDATE %s + SET create_date = %%s + WHERE id IN %%s + """ % (records._table,) + self.env.cr.execute(query, (date_str, tuple(records.ids))) + + records.invalidate_cache() + + if ticket_to_update: + ticket_to_update.sla_status_ids._compute_deadline() + + @contextmanager + def _ticket_patch_now(self, datetime_str): + datetime_now_old = getattr(fields.Datetime, 'now') + datetime_today_old = getattr(fields.Datetime, 'today') + + def new_now(): + return fields.Datetime.from_string(datetime_str) + + def new_today(): + return fields.Datetime.from_string(datetime_str).replace(hour=0, minute=0, second=0) + + try: + setattr(fields.Datetime, 'now', new_now) + setattr(fields.Datetime, 'today', new_today) + + yield + finally: + # back + setattr(fields.Datetime, 'now', datetime_now_old) + setattr(fields.Datetime, 'today', datetime_today_old) + + def create_ticket(self, *arg, **kwargs): + default_values = { + 'name': "Help me", + 'team_id': self.test_team.id, + 'tag_ids': [(4, self.tag_urgent.id)], + 'stage_id': self.stage_new.id, + } + if 'tag_ids' in kwargs: + # from recordset to ORM command + kwargs['tag_ids'] = [(6, False, [tag.id for tag in kwargs['tag_ids']])] + values = dict(default_values, **kwargs) + return self.env['odex25_helpdesk.ticket'].create(values) + + def test_sla_no_tag(self): + """ SLA without tag should apply to all tickets """ + self.sla.tag_ids = [(5,)] + ticket = self.create_ticket(tag_ids=self.tag_urgent) + self.assertEqual(ticket.sla_status_ids.filtered(lambda sla: sla.target_type != 'assigning').sla_id, self.sla, "SLA should have been applied") + + def test_sla_single_tag(self): + self.sla.tag_ids = [(4, self.tag_urgent.id)] + ticket = self.create_ticket(tag_ids=self.tag_urgent) + self.assertEqual(ticket.sla_status_ids.filtered(lambda sla: sla.target_type != 'assigning').sla_id, self.sla, "SLA should have been applied") + + def test_sla_multiple_tags(self): + self.sla.tag_ids = [(6, False, (self.tag_urgent | self.tag_vip).ids)] + ticket = self.create_ticket(tag_ids=self.tag_urgent) + self.assertFalse(ticket.sla_status_ids.filtered(lambda sla: sla.target_type != 'assigning'), "SLA should not have been applied yet") + ticket.tag_ids = [(4, self.tag_vip.id)] + self.assertEqual(ticket.sla_status_ids.filtered(lambda sla: sla.target_type != 'assigning').sla_id, self.sla, "SLA should have been applied") + + def test_sla_tag_and_ticket_type(self): + self.sla.tag_ids = [(6, False, self.tag_urgent.ids)] + self.sla.ticket_type_id = self.type_question + ticket = self.create_ticket(tag_ids=self.tag_urgent) + self.assertFalse(ticket.sla_status_ids.filtered(lambda sla: sla.target_type != 'assigning'), "SLA should not have been applied yet") + ticket.ticket_type_id = self.type_question + self.assertEqual(ticket.sla_status_ids.filtered(lambda sla: sla.target_type != 'assigning').sla_id, self.sla, "SLA should have been applied") + + def test_sla_remove_tag(self): + self.sla.tag_ids = [(6, False, (self.tag_urgent | self.tag_vip).ids)] + ticket = self.create_ticket(tag_ids=self.tag_urgent | self.tag_vip) + self.assertEqual(ticket.sla_status_ids.filtered(lambda sla: sla.target_type != 'assigning').sla_id, self.sla, "SLA should have been applied") + ticket.tag_ids = [(5,)] # Remove all tags + self.assertFalse(ticket.sla_status_ids.filtered(lambda sla: sla.target_type != 'assigning'), "SLA should no longer apply") + + @patch.object(fields.Datetime, 'now', lambda: NOW2) + def test_sla_waiting(self): + ticket = self.create_ticket(tag_ids=self.tag_freeze) + self._utils_set_create_date(ticket, '2019-01-08 9:00:00', ticket) + status = ticket.sla_status_ids.filtered(lambda sla: sla.sla_id.id == self.sla_2.id) + self.assertEqual(status.deadline, datetime(2019, 1, 9, 12, 2, 0), 'No waiting time, deadline = creation date + 1 day + 2 hours + 2 minutes') + + ticket.write({'stage_id': self.stage_progress.id}) + initial_values = {ticket.id: {'stage_id': self.stage_new}} + ticket.message_track(['stage_id'], initial_values) + self._utils_set_create_date(ticket.message_ids.tracking_value_ids, '2019-01-08 11:09:50', ticket) + self.assertEqual(status.deadline, datetime(2019, 1, 9, 12, 2, 0), 'No waiting time, deadline = creation date + 1 day + 2 hours + 2 minutes') + + # We are in waiting stage, they are no more deadline. + ticket.write({'stage_id': self.stage_wait.id}) + initial_values = {ticket.id: {'stage_id': self.stage_progress}} + ticket.message_track(['stage_id'], initial_values) + self._utils_set_create_date(ticket.message_ids.tracking_value_ids[0], '2019-01-08 12:15:00', ticket) + self.assertFalse(status.deadline, 'In waiting stage: no more deadline') + + # We have a response of our customer, the ticket switch to in progress stage (outside working hours) + ticket.write({'stage_id': self.stage_progress.id}) + initial_values = {ticket.id: {'stage_id': self.stage_wait}} + ticket.message_track(['stage_id'], initial_values) + self._utils_set_create_date(ticket.message_ids.tracking_value_ids[0], '2019-01-12 10:35:58', ticket) + # waiting time = 3 full working days 9 - 10 - 11 January (12 doesn't count as it's Saturday) + # + (8 January) 12:15:00 -> 16:00:00 (end of working day) 3,75 hours + # Old deadline = '2019-01-09 12:02:00' + # New: '2019-01-09 12:02:00' + 3 days (waiting) + 2 days (weekend) + 3.75 hours (waiting) = '2019-01-14 15:47:00' + self.assertEqual(status.deadline, datetime(2019, 1, 14, 15, 47), 'We have waiting time: deadline = old_deadline + 3 full working days (waiting) + 3.75 hours (waiting) + 2 days (weekend)') + + ticket.write({'stage_id': self.stage_wait.id}) + initial_values = {ticket.id: {'stage_id': self.stage_progress}} + ticket.message_track(['stage_id'], initial_values) + self._utils_set_create_date(ticket.message_ids.tracking_value_ids[0], '2019-01-14 15:30:00', ticket) + self.assertFalse(status.deadline, 'In waiting stage: no more deadline') + + # We need to patch now with a new value as it will be used to compute freezed time. + with patch.object(fields.Datetime, 'now', lambda: datetime(2019, 1, 16, 15, 0)): + ticket.write({'stage_id': self.stage_done.id}) + initial_values = {ticket.id: {'stage_id': self.stage_wait}} + ticket.message_track(['stage_id'], initial_values) + self._utils_set_create_date(ticket.message_ids.tracking_value_ids[0], '2019-01-16 15:00:00', ticket) + self.assertEqual(status.deadline, datetime(2019, 1, 16, 15, 17), 'We have waiting time: deadline = old_deadline + 7.5 hours (waiting)') + + def test_sla_assigning(self): + ticket = self.create_ticket() + + status_1 = ticket.sla_status_ids.filtered(lambda sla: sla.sla_id.id == self.sla_assigning_1.id) + status_2 = ticket.sla_status_ids.filtered(lambda sla: sla.sla_id.id == self.sla_assigning_2.id) + status_3 = ticket.sla_status_ids.filtered(lambda sla: sla.sla_id.id == self.sla_assigning_3.id) + status_4 = ticket.sla_status_ids.filtered(lambda sla: sla.sla_id.id == self.sla_assigning_4.id) + + self.assertFalse(status_1.reached_datetime, "SLA status 1: reached not his target") + self.assertFalse(status_2.reached_datetime, "SLA status 2: reached not his target") + self.assertFalse(status_3.reached_datetime, "SLA status 3: reached not his target") + self.assertFalse(status_4.reached_datetime, "SLA status 4: reached not his target") + self.assertTrue(status_1.deadline, "SLA status 1: has deadline") + self.assertTrue(status_2.deadline, "SLA status 2: has deadline") + self.assertFalse(status_3.deadline, "SLA status 3: hasn't deadline") + self.assertFalse(status_4.deadline, "SLA status 4: hasn't deadline") + + ticket.write({'user_id': self.odex25_helpdesk_user.id}) + + self.assertTrue(status_1.reached_datetime, "SLA status 1: reached his target") + self.assertTrue(status_2.reached_datetime, "SLA status 2: reached his target") + self.assertFalse(status_3.reached_datetime, "SLA status 3: reached not his target") + self.assertTrue(status_1.deadline, "SLA status 1: has deadline") + self.assertTrue(status_2.deadline, "SLA status 2: has deadline") + self.assertFalse(status_3.deadline, "SLA status 3: hasn't deadline") + + ticket.write({'stage_id': self.stage_progress.id}) + + self.assertTrue(status_1.reached_datetime, "SLA status 1: reached his target") + self.assertTrue(status_2.reached_datetime, "SLA status 2: reached his target") + self.assertTrue(status_3.reached_datetime, "SLA status 3: reached his target") + self.assertTrue(status_1.deadline, "SLA status 1: has deadline") + self.assertTrue(status_2.deadline, "SLA status 2: has deadline") + self.assertTrue(status_3.deadline, "SLA status 3: has deadline") + + ticket.write({'user_id': False}) + + self.assertTrue(status_1.reached_datetime, "SLA status 1: reached his target") + self.assertTrue(status_2.reached_datetime, "SLA status 2: reached his target") + self.assertTrue(status_3.reached_datetime, "SLA status 3: reached his target") + self.assertFalse(status_4.reached_datetime, "SLA status 4: reached not his target") + self.assertTrue(status_1.deadline, "SLA status 1: has deadline") + self.assertTrue(status_2.deadline, "SLA status 2: has deadline") + self.assertTrue(status_3.deadline, "SLA status 3: has deadline") + self.assertFalse(status_4.deadline, "SLA status 4: hasn't deadline") + + ticket.write({'stage_id': self.stage_done.id}) + + self.assertTrue(status_3.reached_datetime, "SLA status 3: reached his target") + self.assertFalse(status_4.reached_datetime, "SLA status 4: reached not his target") + self.assertTrue(status_3.deadline, "SLA status 3: has deadline") + self.assertTrue(status_4.deadline, "SLA status 4: has deadline") + + ticket.write({'user_id': self.odex25_helpdesk_user.id}) + + self.assertTrue(status_4.reached_datetime, "SLA status 4: reached his target") + + def test_sla_assigning_skip_step(self): + ticket = self.create_ticket() + + status_1 = ticket.sla_status_ids.filtered(lambda sla: sla.sla_id.id == self.sla_assigning_1.id) + status_2 = ticket.sla_status_ids.filtered(lambda sla: sla.sla_id.id == self.sla_assigning_2.id) + + self.assertTrue(status_2.deadline, "SLA status 2: has deadline") + + ticket.write({'stage_id': self.stage_progress.id}) + + self.assertFalse(status_2.deadline, "SLA status 2: has no more deadline") + self.assertFalse(status_2.reached_datetime, "SLA status 2: reached not his target") + + ticket.write({'user_id': self.odex25_helpdesk_user.id}) + + self.assertTrue(status_1.reached_datetime, "SLA status 1: reached his target") + + @patch.object(fields.Date, 'today', lambda: NOW.date()) + @patch.object(fields.Datetime, 'today', lambda: NOW.replace(hour=0, minute=0, second=0)) + @patch.object(fields.Datetime, 'now', lambda: NOW) + def test_failed_tickets(self): + self.sla.time_days = 0 + self.sla.time_hours = 3 + # Failed ticket + failed_ticket = self.create_ticket(user_id=self.env.user.id, create_date=NOW - relativedelta(hours=3, minutes=2)) + + # Not failed ticket + ticket = self.create_ticket(user_id=self.env.user.id, create_date=NOW - relativedelta(hours=2, minutes=2)) + + data = self.env['odex25_helpdesk.team'].retrieve_dashboard() + self.assertEqual(data['my_all']['count'], 2, "There should be 2 tickets") + self.assertEqual(data['my_all']['failed'], 1, "There should be 1 failed ticket") diff --git a/odex25_helpdesk/odex25_helpdesk/tests/test_ui.py b/odex25_helpdesk/odex25_helpdesk/tests/test_ui.py new file mode 100644 index 000000000..418d53f86 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/tests/test_ui.py @@ -0,0 +1,10 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. +# -*- coding: utf-8 -*- + +import odoo.tests + + +@odoo.tests.tagged('post_install', '-at_install') +class TestUi(odoo.tests.HttpCase): + def test_ui(self): + self.start_tour("/web", 'odex25_helpdesk_tour', login="admin") diff --git a/odex25_helpdesk/odex25_helpdesk/views/assets.xml b/odex25_helpdesk/odex25_helpdesk/views/assets.xml new file mode 100644 index 000000000..fecff3d02 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/views/assets.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/odex25_helpdesk/odex25_helpdesk/views/digest_views.xml b/odex25_helpdesk/odex25_helpdesk/views/digest_views.xml new file mode 100644 index 000000000..3c3688fbf --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/views/digest_views.xml @@ -0,0 +1,15 @@ + + + + digest.digest.view.form.inherit.sale.order + digest.digest + + + + + + + + + + diff --git a/odex25_helpdesk/odex25_helpdesk/views/mail_activity_views.xml b/odex25_helpdesk/odex25_helpdesk/views/mail_activity_views.xml new file mode 100644 index 000000000..9b93d44b4 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/views/mail_activity_views.xml @@ -0,0 +1,23 @@ + + + + + + Ticket Assistance + Your Assistance is Required + fa-ticket + + + + + Activity Types + mail.activity.type + tree,form + ['|', ('res_model_id', '=', False), ('res_model_id.model', '=', 'odex25_helpdesk.ticket')] + {'default_res_model': 'odex25_helpdesk.ticket'} + + + \ No newline at end of file diff --git a/odex25_helpdesk/odex25_helpdesk/views/odex25_helpdesk_portal_templates.xml b/odex25_helpdesk/odex25_helpdesk/views/odex25_helpdesk_portal_templates.xml new file mode 100644 index 000000000..9df5b3c52 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/views/odex25_helpdesk_portal_templates.xml @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + + + + + + diff --git a/odex25_helpdesk/odex25_helpdesk/views/odex25_helpdesk_team_views.xml b/odex25_helpdesk/odex25_helpdesk/views/odex25_helpdesk_team_views.xml new file mode 100644 index 000000000..ce547f42e --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/views/odex25_helpdesk_team_views.xml @@ -0,0 +1,635 @@ + + + + + + odex25_helpdesk.team.tree + odex25_helpdesk.team + + + + + + + + + + + + SLA Policies + odex25_helpdesk.sla + tree,form + {'default_team_id': active_id, 'search_default_team_id': active_id} + +

+ No SLA policies found. Let's create one! +

+

+ Make sure tickets are handled in a timely manner by using SLA Policies. +
+

+
+
+ + + Templates + mail.template + tree,form + [('team_id', '=', active_id)] + {'default_team_id': active_id} + +

+ Create a new template +

+
+
+ + + Helpdesk Teams + odex25_helpdesk.team + tree,form + +

+ No teams found +

+

+ Teams regroup tickets for people sharing the same expertise or from the same area. +

+
+
+ + + odex25_helpdesk.team.form + odex25_helpdesk.team + +
+ +
+ +
+
+ + + +

Productivity & Visibility

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Channels

+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+

Sell & Track Hours

+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+

Performance

+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+

Self-Service

+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + odex25_helpdesk.team.search + odex25_helpdesk.team + + + + + + + + + + odex25_helpdesk.team.dashboard + odex25_helpdesk.team + 200 + + + + + + + + + + + + + + + +
+ +
+
+
+ +
+ + &nbsp; + + +
+
+ + + +
+
+ + +
+
+
+
+
+
+ + + Helpdesk Overview + odex25_helpdesk.team + kanban,form + {} + + +

+ No teams found +

+

+ Teams regroup tickets for people sharing the same expertise or from the same area. +

+
+
+ + + + diff --git a/odex25_helpdesk/odex25_helpdesk/views/odex25_helpdesk_views.xml b/odex25_helpdesk/odex25_helpdesk/views/odex25_helpdesk_views.xml new file mode 100644 index 000000000..83bd24b53 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/views/odex25_helpdesk_views.xml @@ -0,0 +1,1036 @@ + + + + + + + + + + odex25_helpdesk.tags.tree + odex25_helpdesk.tag + + + + + + + + + + Ticket Tags + odex25_helpdesk.tag + tree,form + +

+ No tags found. Let's create one! +

+

+ Tags are perfect to organize your tickets. +

+
+
+ + + + + + + + odex25_helpdesk.ticket.type.tree + odex25_helpdesk.ticket.type + + + + + + + + + + Ticket Type + odex25_helpdesk.ticket.type + tree,form + +

+ No types found. Let's create one! +

+

+ Types are perfect to categorize your tickets. +

+
+
+ + + + + + + odex25_helpdesk.stages.tree + odex25_helpdesk.stage + + + + + + + + + + + odex25_helpdesk.stage.form + odex25_helpdesk.stage + +
+ + + + + + + + + + + + + + +

+ At each stage employees can block or make task/issue ready for next stage. + You can define here labels that will be displayed for the state instead + of the default labels. +

+
+
+
+
+
+ + + Stages + odex25_helpdesk.stage + tree,form + +

+ No stages found. Let's create one! +

+

+ Adapt your pipeline to your workflow and easily track the progress of your tickets. +

+
+
+ + + + + + odex25_helpdesk.sla.tree + odex25_helpdesk.sla + + + + + + + + + + + + odex25_helpdesk.sla.search + odex25_helpdesk.sla + + + + + + + + + + + odex25_helpdesk.sla.form + odex25_helpdesk.sla + +
+ + +
+
+ + + + + + + + + + + + + +
+
+ +
+
+
+ + days +
+ + hours +
+ + minutes +
+
+ +
+
+ +
+
+
+
+ + + SLA Policies + odex25_helpdesk.sla + tree,form + +

+ No SLA policies found. Let's create one! +

+

+ Make sure tickets are handled in a timely manner by using SLA Policies. +
+

+
+
+ + + + + + odex25_helpdesk.ticket.activity + odex25_helpdesk.ticket + + + + +
+ +
+ + +
+
+
+
+
+
+ + + odex25_helpdesk.ticket.graph + odex25_helpdesk.ticket + + + + + + + + + + odex25_helpdesk.ticket.pivot + odex25_helpdesk.ticket + + + + + + + + + + + odex25_helpdesk.ticket.graph.analysis + odex25_helpdesk.ticket + + + + + + + + + + + odex25_helpdesk.ticket.pivot.analysis + odex25_helpdesk.ticket + + + + + + + + + + + odex25_helpdesk.ticket.search + odex25_helpdesk.ticket + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + odex25_helpdesk.ticket.search + odex25_helpdesk.ticket + + + + + + + + + + + + + + + + + + + + + + + + + + odex25_helpdesk.ticket.tree + odex25_helpdesk.ticket + + + + + + + + + + + + + + + + + + odex25_helpdesk.ticket.form.quick_create + odex25_helpdesk.ticket + 1000 + +
+ + + + + + +
+ +
+
+
+
+
+ + + odex25_helpdesk.ticket.kanban + odex25_helpdesk.ticket + 10 + + + + + + + + + + + + + + + + + + +
+ + +
+
+ +
+
+ + + (#) + +
+
+ +
+
+ + + + + +
+
+
+ +
+
+ + + +
+
+
+
+
+
+
+
+
+
+ + + + odex25_helpdesk.ticket.form + odex25_helpdesk.ticket + +
+
+ +
+ + + + + +
+ +
+ + +
+

+ +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + + Tickets + odex25_helpdesk.ticket + list,kanban,form,activity + + {'search_default_my_ticket': True, 'search_default_is_open': True} + +

+ No tickets found. Let's create one! +

+

To get things done, use activities and status on tickets. +
+ Chat in real time or by email to collaborate efficiently. +

+
+
+ + + Tickets + odex25_helpdesk.ticket + list,kanban,form,activity + + {'search_default_is_open': True} + +

+ No tickets found. Let's create one! +

+

To get things done, use activities and status on tickets. +
+ Chat in real time or by email to collaborate efficiently. +

+
+
+ + + Tickets + odex25_helpdesk.ticket + kanban,list,form,pivot,graph + + {'search_default_upcoming_sla_fail': True} + +

+ Congratulations! +

+

You completed all your tickets on time. +

+
+
+ + + + Tickets + odex25_helpdesk.ticket + list,kanban,form,activity + + {'create': False, 'search_default_is_open': True, 'search_default_my_ticket': True} + + +

+ No tickets found +

+

To get things done, use activities and status on tickets. +
+ Chat in real time or by email to collaborate efficiently. +

+
+
+ + + Tickets + odex25_helpdesk.ticket + list,kanban,form,activity + [] + {'create': False, 'search_default_is_open': True, 'search_default_my_ticket': True, + 'search_default_sla_failed': True} + + + +

+ Congratulations! +

+

You completed all your tickets on time. +

+
+
+ + + + Closed Tickets Analysis + odex25_helpdesk.ticket + pivot,graph + + + {'search_default_my_ticket': True} + + +

+ No data yet ! +

+

+ Create tickets to get statistics. +

+
+
+ + + Closed Tickets Analysis + odex25_helpdesk.ticket + pivot,graph + + + + {'search_default_my_ticket': True, 'search_default_close': True} + +

+ No data yet ! +

+

+ Create tickets to get statistics. +

+
+
+ + + + Tickets + odex25_helpdesk.ticket + kanban,list,form,activity + [('team_id', '=', active_id)] + {'default_team_id': active_id} + + +

+ No tickets found. Let's create one! +

+

To get things done, use activities and status on tickets. +
+ Chat in real time or by email to collaborate efficiently. +

+
+
+ + + Tickets + odex25_helpdesk.ticket + kanban,tree,form + {'search_default_team_id': active_ids, 'search_default_archive': True} + + +

+ Click create a new ticket. +

+
+
+ + + Tickets + odex25_helpdesk.ticket + kanban,tree,form + {'search_default_team_id': active_id, 'search_default_sla_failed': True} + + +

+ Click create a new ticket. +

+
+
+ + + Tickets + odex25_helpdesk.ticket + kanban,list,form,activity + {'search_default_team_id': active_id, 'search_default_unassigned': True} + + +

+ No tickets found. Let's create one! +

+

To get things done, use activities and status on tickets. +
+ Chat in real time or by email to collaborate efficiently. +

+
+
+ + + odex25_helpdesk.ticket.search + odex25_helpdesk.ticket + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Helpdesk Ticket Analysis + odex25_helpdesk.ticket + pivot,graph + + + {'search_default_group_by_create_date': 1} + +

+ No data yet ! +

+

+ Create tickets to get statistics. +

+
+
+ + + + + Ticket Analysis + odex25_helpdesk.ticket + pivot,graph + + + {'search_default_team_id': active_ids, 'search_default_is_open': True} + +

+ No data yet ! +

+

+ Create tickets to get statistics. +

+
+
+ + + + odex25_helpdesk.ticket.pivot + odex25_helpdesk.ticket + + + + + + + + + + + + odex25_helpdesk.ticket.graph + odex25_helpdesk.ticket + + + + + + + + + + + Performance Analysis + odex25_helpdesk.ticket + pivot,graph + + + + {'search_default_team_id': active_ids, 'pivot_measures': ['close_hours', '__count__']} + + +

+ No data yet ! +

+

+ Create tickets to get statistics. +

+
+
+ + + Success Rate Analysis + odex25_helpdesk.ticket + pivot,graph + + + + {'search_default_is_close': True, 'search_default_my_ticket': True, + 'search_default_not_sla_failed': True,} + + +

+ No data yet ! +

+

+ Create tickets to get statistics. +

+
+
+ + + Success Rate Analysis + odex25_helpdesk.ticket + pivot,graph + + + + {'search_default_is_close': True, 'search_default_my_ticket': True, + 'search_default_not_sla_failed': True,} + + +

+ No data yet! +

+

+ Create tickets to get statistics. +

+
+
+ + + Ticket Analysis + odex25_helpdesk.ticket + pivot,graph + + [('stage_id.is_close', '=', False)] + {'search_default_my_ticket': True, 'pivot_measures': ['close_hours', '__count__']} + + +

+ No data yet! +

+

+ Create tickets to get statistics. +

+
+
+ + + + + + +
diff --git a/odex25_helpdesk/odex25_helpdesk/views/res_partner_views.xml b/odex25_helpdesk/odex25_helpdesk/views/res_partner_views.xml new file mode 100644 index 000000000..05ac5449f --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/views/res_partner_views.xml @@ -0,0 +1,23 @@ + + + + + res.partner.form.inherit.odex25_helpdesk + res.partner + + + + +
+ +
+
+
+ +
diff --git a/odex25_helpdesk/odex25_helpdesk/views/zfp_config_setting.xml b/odex25_helpdesk/odex25_helpdesk/views/zfp_config_setting.xml new file mode 100644 index 000000000..4b300bf69 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk/views/zfp_config_setting.xml @@ -0,0 +1,115 @@ + + + + + service.category.form + service.category + +
+ + + + + +
+
+
+ + + service.category.tree + service.category + + + + + + + + + + Service Category + service.category + tree,form + +

+ No Service Category found. Let's create one! +

+
+
+ + + + + + + + + + + + + + + + + + + helpdesk.service.form + helpdesk.service + +
+ + + + + + + + + + + + +
+
+
+ + + helpdesk.service.tree + helpdesk.service + + + + + + + + + + Helpdesk Service + helpdesk.service + + + + Helpdesk Service + helpdesk.service + tree,form + +

+ No Service found. Let's create one! +

+
+
+ + + + +
diff --git a/odex25_helpdesk/odex25_helpdesk_assignation_method/__init__.py b/odex25_helpdesk/odex25_helpdesk_assignation_method/__init__.py new file mode 100644 index 000000000..dc5e6b693 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_assignation_method/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/odex25_helpdesk/odex25_helpdesk_assignation_method/__manifest__.py b/odex25_helpdesk/odex25_helpdesk_assignation_method/__manifest__.py new file mode 100644 index 000000000..fcb67ec65 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_assignation_method/__manifest__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'EXP Helpdesk Assignation methods', + 'summary': 'Ticket Assignation methods for team members considering ticket types', + 'author': "Expert Co Ltd", + 'website': "http://www.ex.com", + 'category': 'Odex25-Helpdesk/Odex25-Helpdesk', + 'depends': ['odex25_helpdesk_security'], + 'description': """ + Ticket Assignation methods for team members considering ticket types + """, + 'auto_install': True, + 'data': [ + 'security/ir.model.access.csv', + 'views/helpdesk_views.xml', + ], + 'license': '', +} diff --git a/odex25_helpdesk/odex25_helpdesk_assignation_method/i18n/ar_001.po b/odex25_helpdesk/odex25_helpdesk_assignation_method/i18n/ar_001.po new file mode 100644 index 000000000..7a5bf325c --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_assignation_method/i18n/ar_001.po @@ -0,0 +1,132 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * odex25_helpdesk_assignation_method +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-09-21 11:37+0000\n" +"PO-Revision-Date: 2022-09-21 11:37+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: odex25_helpdesk_assignation_method +#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member__create_uid +msgid "Created by" +msgstr "" + +#. module: odex25_helpdesk_assignation_method +#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member__create_date +msgid "Created on" +msgstr "" + +#. module: odex25_helpdesk_assignation_method +#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member__display_name +#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_odex25_helpdesk_team__display_name +#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_odex25_helpdesk_ticket__display_name +msgid "Display Name" +msgstr "الاسم المعروض" + +#. module: odex25_helpdesk_assignation_method +#: model:ir.model,name:odex25_helpdesk_assignation_method.model_odex25_helpdesk_team +msgid "Helpdesk Team" +msgstr "فريق مكتب المساعدة" + +#. module: odex25_helpdesk_assignation_method +#: model:ir.model,name:odex25_helpdesk_assignation_method.model_odex25_helpdesk_ticket +msgid "Helpdesk Ticket" +msgstr "تذكرة مكتب المساعدة" + +#. module: odex25_helpdesk_assignation_method +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_assignation_method.helpdesk_team_view_form_assignation_method +msgid "How to assign newly created tickets to the right person" +msgstr "كيفية تعيين التذاكر التي تم إنشاؤها حديثًا إلى الشخص المناسب" + +#. module: odex25_helpdesk_assignation_method +#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member__id +#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_odex25_helpdesk_team__id +#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_odex25_helpdesk_ticket__id +msgid "ID" +msgstr "المُعرف" + +#. module: odex25_helpdesk_assignation_method +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_assignation_method.helpdesk_team_view_form_assignation_method +msgid "Keep empty for everyone to see this team" +msgstr "ابق فارغًا حتى يرى الجميع هذا الفريق" + +#. module: odex25_helpdesk_assignation_method +#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member____last_update +#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_odex25_helpdesk_team____last_update +#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_odex25_helpdesk_ticket____last_update +msgid "Last Modified on" +msgstr "آخر تعديل في" + +#. module: odex25_helpdesk_assignation_method +#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: odex25_helpdesk_assignation_method +#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member__write_date +msgid "Last Updated on" +msgstr "" + +#. module: odex25_helpdesk_assignation_method +#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member__member_id +msgid "Member" +msgstr "العضو" + +#. module: odex25_helpdesk_assignation_method +#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_odex25_helpdesk_team__members_ids +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_assignation_method.helpdesk_team_view_form_assignation_method +msgid "Members" +msgstr "الأعضاء" + +#. module: odex25_helpdesk_assignation_method +#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member__team_id +msgid "Team" +msgstr "فريق" + +#. module: odex25_helpdesk_assignation_method +#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_odex25_helpdesk_team__team_leader_id +msgid "Team Leader" +msgstr "قائد الفريق" + +#. module: odex25_helpdesk_assignation_method +#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_odex25_helpdesk_team__member_ids +msgid "Team Members" +msgstr "أعضاء الفريق" + +#. module: odex25_helpdesk_assignation_method +#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member__ticket_type_ids +msgid "Ticket Type" +msgstr "نوع التذكرة" + +#. module: odex25_helpdesk_assignation_method +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_assignation_method.helpdesk_team_view_form_assignation_method +msgid "" +"With random assignation, every user gets the same number of tickets. With " +"balanced assignation, tickets are assigned to the user with the least amount" +" of open tickets." +msgstr "مع التخصيص العشوائي ، يحصل كل مستخدم على نفس عدد التذاكر. مع التوزيع المتوازن ، يتم تخصيص التذاكر للمستخدم بأقل عدد من التذاكر المفتوحة" + +#. module: odex25_helpdesk_assignation_method +#: code:addons/odex25_helpdesk_assignation_method/models/helpdesk_team.py:0 +#, python-format +msgid "You must have team members assigned to change the assignation method." +msgstr "يجب أن يكون لديك أعضاء فريق معينين لتغيير طريقة التعيين" + +#. module: odex25_helpdesk_assignation_method +#: model:ir.model,name:odex25_helpdesk_assignation_method.model_helpdesk_team_member +msgid "helpdesk.team.member" +msgstr "" + +#. module: odex25_helpdesk_assignation_method +#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member__service_id +msgid "Service" +msgstr "الخدمة" diff --git a/odex25_helpdesk/odex25_helpdesk_assignation_method/models/__init__.py b/odex25_helpdesk/odex25_helpdesk_assignation_method/models/__init__.py new file mode 100644 index 000000000..283c2fd2f --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_assignation_method/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import helpdesk_team \ No newline at end of file diff --git a/odex25_helpdesk/odex25_helpdesk_assignation_method/models/helpdesk_team.py b/odex25_helpdesk/odex25_helpdesk_assignation_method/models/helpdesk_team.py new file mode 100644 index 000000000..cb9f6ea11 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_assignation_method/models/helpdesk_team.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models, _ +from odoo.exceptions import UserError, ValidationError + + +class HelpdeskTicket(models.Model): + _inherit = "odex25_helpdesk.ticket" + + team_leader_id = fields.Many2one(related='team_id.team_leader_id') + + @api.onchange('service_id') + def get_user_and_assign_it(self): + for rec in self: + result = rec._onchange_ticket_type_values(rec.team_id,rec.service_id) + rec.user_id = result['user_id'] + + def _onchange_ticket_type_values(self, team, ticket_type=None): + return { + 'user_id': team.get_new_user(ticket_type), + } + + + @api.model + def create(self, vals): + res = super(HelpdeskTicket, self).create(vals) + result = self._onchange_ticket_type_values(res.team_id,res.service_id) + res.user_id = result['user_id'] + return res + +class HelpdeskTeam(models.Model): + _inherit = "odex25_helpdesk.team" + + team_leader_id = fields.Many2one('res.users',domain=lambda self: [('groups_id', 'in', self.env.ref('odex25_helpdesk.group_odex25_helpdesk_manager').id)]) + members_ids = fields.One2many('helpdesk.team.member','team_id') + member_ids = fields.Many2many('res.users', string='Team Members', domain=lambda self: [('groups_id', 'in', self.env.ref('odex25_helpdesk.group_odex25_helpdesk_user').id)]) + + @api.onchange('members_ids') + def _onchange_members_ids(self): + """ + update the member_ids based on members_ids + """ + for team in self: + for rec in team.member_ids: + rec = False + members = [member.member_id.id for member in self.members_ids] + addmember = [(6,0,members)] + team.update({ + 'member_ids': addmember + }) + + @api.constrains('assign_method', 'members_ids') + def _check_members_assignation(self): + if not self.members_ids and self.assign_method != 'manual': + raise ValidationError(_("You must have team members assigned to change the assignation method.")) + + def get_new_user(self, ticket_type=None): + for rec in self: + rec.ensure_one() + new_user = self.env['res.users'] + members_ids = [] + if ticket_type: + for member in rec.members_ids: + if ticket_type in member.service_id: + members_ids.append(member.member_id.id) + members_ids = sorted(members_ids) + elif len(members_ids) == 0: + members_ids = sorted([member.member_id.id for member in rec.members_ids]) + if members_ids: + if rec.assign_method == 'randomly': + # randomly means new ticketss get uniformly distributed + previous_assigned_user = self.env['odex25_helpdesk.ticket'].search([('team_id', '=', rec.id)], order='create_date desc', limit=1).user_id + # handle the case where the previous_assigned_user has left the team (or there is none). + if previous_assigned_user and previous_assigned_user.id in members_ids: + previous_index = members_ids.index(previous_assigned_user.id) + new_user = new_user.browse(members_ids[(previous_index + 1) % len(members_ids)]) + else: + new_user = new_user.browse(members_ids[0]) + elif rec.assign_method == 'balanced': + read_group_res = self.env['odex25_helpdesk.ticket'].read_group([('stage_id.is_close', '=', False), ('user_id', 'in', members_ids)], ['user_id'], ['user_id']) + # add all the members in case a member has no more open tickets (and thus doesn't appear in the previous read_group) + count_dict = dict((m_id, 0) for m_id in members_ids) + count_dict.update((data['user_id'][0], data['user_id_count']) for data in read_group_res) + new_user = new_user.browse(min(count_dict, key=count_dict.get)) + return new_user + + +class HelpdeskTeamMemebers(models.Model): + _name = "helpdesk.team.member" + + team_id = fields.Many2one('odex25_helpdesk.team') + member_id = fields.Many2one('res.users',domain=lambda self: [('groups_id', 'in', self.env.ref('odex25_helpdesk.group_odex25_helpdesk_user').id)]) + # ticket_type_ids = fields.Many2many('odex25_helpdesk.ticket.type') + service_id = fields.Many2many('helpdesk.service') + +# +# +# class InheritUser(models.Model): +# _inherit = 'res.users' +# +# @api.model +# def name_search(self, name='', args=None, operator='ilike', limit=100): +# if self._context.get('members', []): +# member_id = self.env['odex25_helpdesk.team'].new('members_ids',self._context.get('members', [])) +# args.append(('id', 'not in', +# [isinstance(d['member_id'], tuple) and d['member_id'][0] or d['member_id'] +# for d in member_id])) +# return super(InheritUser, self).name_search(name, args=args, operator=operator, limit=limit) \ No newline at end of file diff --git a/odex25_helpdesk/odex25_helpdesk_assignation_method/security/ir.model.access.csv b/odex25_helpdesk/odex25_helpdesk_assignation_method/security/ir.model.access.csv new file mode 100644 index 000000000..82181a5a2 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_assignation_method/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_helpdesk_team_member,helpdesk.team.member,odex25_helpdesk_assignation_method.model_helpdesk_team_member,,1,1,1,1 \ No newline at end of file diff --git a/odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/icon.png b/odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a37b5967b6ad9299b27c32e2a7ff37da643852e3 GIT binary patch literal 4615 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 literal 0 HcmV?d00001 diff --git a/odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/index.html b/odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/index.html new file mode 100644 index 000000000..4bd6cf0ca --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/index.html @@ -0,0 +1,83 @@ +
+
+

+ ONE OF ODEX MODULES

+
+ ODEX system is over than 200+ modules developed by love of Expert Company, based on ODOO system +
+ .to effectively suite's Saudi and Arabic market needs.It is the first Arabic open source ERP and all-in-one + solution +
+
+
+ +
+
+

+ Contact Us +

+
+
+
+
+ +
+
+ + + +
+

+ + exposa + +

+
+
+
+ + + +
+

+ + exposa + +

+
+ +
+
+
+
+ +
+
diff --git a/odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/internet.png b/odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/internet.png new file mode 100644 index 0000000000000000000000000000000000000000..a89809bfa97bf56afbd7baf34a67cf9df9e1ecaa GIT binary patch literal 35772 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 II;Vst0JKPgn*aa+ literal 0 HcmV?d00001 diff --git a/odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/linkedin.png b/odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/linkedin.png new file mode 100644 index 0000000000000000000000000000000000000000..a0fe09caabfdd5887e7dc238af577690e1ef246b GIT binary patch literal 12914 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 LA?vcejOhOZ?O@z} literal 0 HcmV?d00001 diff --git a/odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/twitter.png b/odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/twitter.png new file mode 100644 index 0000000000000000000000000000000000000000..983f325a3a866fc4ccee54083eb936853f93e920 GIT binary patch literal 20535 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 H$n5_Df`3J3 literal 0 HcmV?d00001 diff --git a/odex25_helpdesk/odex25_helpdesk_assignation_method/views/helpdesk_views.xml b/odex25_helpdesk/odex25_helpdesk_assignation_method/views/helpdesk_views.xml new file mode 100644 index 000000000..0af842550 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_assignation_method/views/helpdesk_views.xml @@ -0,0 +1,50 @@ + + + + helpdesk.team.form.inherit.assignation.method + odex25_helpdesk.team + + + +
+
+
+
+
+
+
+
+
+
+
+ + + + + + + +
+
+
+
+
diff --git a/odex25_helpdesk/odex25_helpdesk_reopen/__init__.py b/odex25_helpdesk/odex25_helpdesk_reopen/__init__.py new file mode 100644 index 000000000..dc5e6b693 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_reopen/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/odex25_helpdesk/odex25_helpdesk_reopen/__manifest__.py b/odex25_helpdesk/odex25_helpdesk_reopen/__manifest__.py new file mode 100644 index 000000000..744d8eefb --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_reopen/__manifest__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'EXP Helpdesk Reopen', + 'summary': 'adding reopen feature to helpdesk', + 'author': "Expert Co Ltd", + 'website': "http://www.ex.com", + 'category': 'Odex25-Helpdesk/Odex25-Helpdesk', + 'depends': ['odex25_helpdesk'], + 'description': """ + ODEX system is over than 200+ modules developed by love of Expert Company, based on ODOO system + .to effectively suite's Saudi and Arabic market needs.It is the first Arabic open source ERP and all-in-one solution + """, + 'auto_install': True, + 'data': [ + 'views/helpdesk_views.xml', + 'views/cron_repair.xml', + ], + 'license': '', +} diff --git a/odex25_helpdesk/odex25_helpdesk_reopen/i18n/ar_001.po b/odex25_helpdesk/odex25_helpdesk_reopen/i18n/ar_001.po new file mode 100644 index 000000000..43d298ce8 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_reopen/i18n/ar_001.po @@ -0,0 +1,102 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * odex25_helpdesk_reopen +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-08-20 13:31+0000\n" +"PO-Revision-Date: 2023-08-20 13:31+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: odex25_helpdesk_reopen +#: model:ir.model.fields,field_description:odex25_helpdesk_reopen.field_odex25_helpdesk_stage__is_auto_close +msgid "Auto Close Kanban Stage" +msgstr "مرحلة الإغلاق التلقائي" + +#. module: odex25_helpdesk_reopen +#: model:ir.model.fields,field_description:odex25_helpdesk_reopen.field_odex25_helpdesk_stage__close_time +msgid "Close Time" +msgstr "زمن الإقفال" + +#. module: odex25_helpdesk_reopen +#: model:ir.model.fields,field_description:odex25_helpdesk_reopen.field_odex25_helpdesk_stage__display_name +#: model:ir.model.fields,field_description:odex25_helpdesk_reopen.field_odex25_helpdesk_ticket__display_name +msgid "Display Name" +msgstr "الاسم المعروض" + +#. module: odex25_helpdesk_reopen +#: model:ir.actions.server,name:odex25_helpdesk_reopen.ir_cron_automatic_done_state_ir_actions_server +#: model:ir.cron,cron_name:odex25_helpdesk_reopen.ir_cron_automatic_done_state +#: model:ir.cron,name:odex25_helpdesk_reopen.ir_cron_automatic_done_state +msgid "Help Desk Cancel ;Automatic Done State" +msgstr "إلغاء مكتب المساعدة ؛ حالة تم تلقائيًا" + +#. module: odex25_helpdesk_reopen +#: model:ir.model,name:odex25_helpdesk_reopen.model_odex25_helpdesk_stage +msgid "Helpdesk Stage" +msgstr "مرحلة مكتب المساعدة" + +#. module: odex25_helpdesk_reopen +#: model:ir.model,name:odex25_helpdesk_reopen.model_odex25_helpdesk_ticket +msgid "Helpdesk Ticket" +msgstr "تذكرة مكتب المساعدة" + +#. module: odex25_helpdesk_reopen +#: model:ir.model.fields,field_description:odex25_helpdesk_reopen.field_odex25_helpdesk_stage__id +#: model:ir.model.fields,field_description:odex25_helpdesk_reopen.field_odex25_helpdesk_ticket__id +msgid "ID" +msgstr "المُعرف" + +#. module: odex25_helpdesk_reopen +#: model:ir.model.fields,field_description:odex25_helpdesk_reopen.field_odex25_helpdesk_stage____last_update +#: model:ir.model.fields,field_description:odex25_helpdesk_reopen.field_odex25_helpdesk_ticket____last_update +msgid "Last Modified on" +msgstr "آخر تعديل في" + +#. module: odex25_helpdesk_reopen +#: model:ir.model.fields,field_description:odex25_helpdesk_reopen.field_odex25_helpdesk_stage__is_reopen +msgid "Re-open Kanban Stage" +msgstr "أعد فتح مرحلة كانبان" + +#. module: odex25_helpdesk_reopen +#: model:ir.model.fields,field_description:odex25_helpdesk_reopen.field_odex25_helpdesk_stage__reopen_time +msgid "Reopen Time" +msgstr "وقت إعادة الفتح" + +#. module: odex25_helpdesk_reopen +#: model:ir.model.fields,help:odex25_helpdesk_reopen.field_odex25_helpdesk_stage__is_auto_close +msgid "" +"in this stage the ticket will be closed again if the customer not send email" +msgstr "في هذه المرحلة ، سيتم إغلاق التذكرة مرة أخرى إذا لم يرسل العميل بريدًا إلكترونيًا" + +#. module: odex25_helpdesk_reopen +#: model:ir.model.fields,help:odex25_helpdesk_reopen.field_odex25_helpdesk_stage__is_reopen +msgid "" +"in this stage the ticket will be opened again if the customerreplied to your" +" close stage email" +msgstr "" +"في هذه المرحلة ، سيتم فتح التذكرة مرة أخرى إذا قام العميل بالرد على البريد " +"الإلكتروني الخاص بك" + +#. module: odex25_helpdesk_reopen +#: model:ir.model.fields,help:odex25_helpdesk_reopen.field_odex25_helpdesk_stage__close_time +msgid "the time that make a ticket close task" +msgstr "الوقت الذي يجعل مهمة مغلقة" + +#. module: odex25_helpdesk_reopen +#: model:ir.model.fields,help:odex25_helpdesk_reopen.field_odex25_helpdesk_stage__reopen_time +msgid "the time that make a ticket reopen task" +msgstr "الوقت الذي يجعل مهمة إعادة فتح تذكرة" + + +#. module: odex25_helpdesk_reopen +#: model:ir.model.fields,field_description:odex25_helpdesk_reopen.field_odex25_helpdesk_stage__is_done +msgid "Close Ticket" +msgstr "إغلاق التذكرة" \ No newline at end of file diff --git a/odex25_helpdesk/odex25_helpdesk_reopen/models/__init__.py b/odex25_helpdesk/odex25_helpdesk_reopen/models/__init__.py new file mode 100644 index 000000000..678daff6c --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_reopen/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import helpdesk \ No newline at end of file diff --git a/odex25_helpdesk/odex25_helpdesk_reopen/models/helpdesk.py b/odex25_helpdesk/odex25_helpdesk_reopen/models/helpdesk.py new file mode 100644 index 000000000..2d170c9f8 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_reopen/models/helpdesk.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import datetime +from odoo import api, fields, models, tools, _ +from dateutil.relativedelta import relativedelta +import logging + +_logger = logging.getLogger(__name__) + + +class HelpdeskStage(models.Model): + _inherit = 'odex25_helpdesk.stage' + + is_reopen = fields.Boolean('Re-open Kanban Stage', help="in this stage the ticket will be opened again if the " + "customer""replied to your close stage email") + reopen_time = fields.Float(string="Reopen Time", help="the time that make a ticket reopen task") + is_auto_close = fields.Boolean('Auto Close Kanban Stage',help="in this stage the ticket will be closed again if " + "the customer not send email") + close_time = fields.Float(string="Close Time", help="the time that make a ticket close task") + is_done = fields.Boolean(string="Close Ticket") + + +class HelpdeskTicket(models.Model): + _inherit = 'odex25_helpdesk.ticket' + + def _message_post_after_hook(self, message, msg_vals): + if self.partner_email and self.partner_id and not self.partner_id.email: + self.partner_id.email = self.partner_email + + if self.partner_email and not self.partner_id: + # we consider that posting a message with a specified recipient (not a follower, a specific one) + # on a document without customer means that it was created through the chatter using + # suggested recipients. This heuristic allows to avoid ugly hacks in JS. + new_partner = message.partner_ids.filtered(lambda partner: partner.email == self.partner_email) + if new_partner: + self.search([ + ('partner_id', '=', False), + ('partner_email', '=', new_partner.email), + ('stage_id.fold', '=', False)]).write({'partner_id': new_partner.id}) + if message.author_id: + if message.author_id.email == self.partner_email and self.stage_id.is_close: + previous = self.env['mail.message'].search([ + ('res_id', '=', self.id), + ('subject', '!=', ''), + ('model', '=', 'odex25_helpdesk.ticket'), + ('author_id', '!=', self.partner_id.id), + ]) + if len(previous) != 0 and previous[0]: + previous = previous[0] + for stage in self.team_id.stage_ids: + if stage.is_reopen: + difference = fields.Datetime.from_string(message.date) - fields.Datetime.from_string( + previous.date) + difference = str(difference).split(':') + hour, minute = divmod(stage.reopen_time, 1) + minute *= 60 + result = '{}:{}'.format(int(hour), int(minute)) + result = result.split(':') + if int(difference[0]) < int(result[0]): + self.stage_id = stage.id + break + elif int(difference[1]) <= int(result[1]): + self.stage_id = stage.id + + return super(HelpdeskTicket, self)._message_post_after_hook(message, msg_vals) + + @api.model + def automatic_close_state(self): + ticket = self.env['odex25_helpdesk.ticket'].search([]) + is_done = self.env['odex25_helpdesk.stage'].search([('is_done', '=', True)], limit=1) + for rec in ticket: + if rec.stage_id.is_auto_close: + close_time = rec.stage_id.close_time + hour, minute = divmod(close_time, 1) + minute *= 60 + to_str = fields.Datetime.to_string( + fields.Datetime.from_string(fields.Datetime.now()) - relativedelta(hours=hour, minutes=int(minute))) + if not rec.stage_id.is_close: + msg_res = rec.message_ids.filtered(lambda x: str(x.date) < to_str) + if len(rec.message_ids) == len(msg_res): + rec.stage_id = is_done.id diff --git a/odex25_helpdesk/odex25_helpdesk_reopen/views/cron_repair.xml b/odex25_helpdesk/odex25_helpdesk_reopen/views/cron_repair.xml new file mode 100644 index 000000000..42b1a6b32 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_reopen/views/cron_repair.xml @@ -0,0 +1,13 @@ + + + + Help Desk Cancel ;Automatic Done State + + code + model.automatic_close_state() + 3 + minutes + -1 + + + diff --git a/odex25_helpdesk/odex25_helpdesk_reopen/views/helpdesk_views.xml b/odex25_helpdesk/odex25_helpdesk_reopen/views/helpdesk_views.xml new file mode 100644 index 000000000..87137d409 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_reopen/views/helpdesk_views.xml @@ -0,0 +1,20 @@ + + + + helpdesk.stage.reopen.form + odex25_helpdesk.stage + + + + + + + + + + + + diff --git a/odex25_helpdesk/odex25_helpdesk_sale/__init__.py b/odex25_helpdesk/odex25_helpdesk_sale/__init__.py new file mode 100644 index 000000000..cde864bae --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_sale/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models diff --git a/odex25_helpdesk/odex25_helpdesk_sale/__manifest__.py b/odex25_helpdesk/odex25_helpdesk_sale/__manifest__.py new file mode 100644 index 000000000..e81f02b3d --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_sale/__manifest__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +{ + 'name': 'Helpdesk After Sales', + 'summary': 'Project, Tasks, After Sales', + 'author': "Expert Co Ltd", + 'website': "http://www.ex.com", + 'category': 'Odex25-Helpdesk/Odex25-Helpdesk', + 'depends': ['odex25_helpdesk', 'sale_management'], + 'auto_install': True, + 'description': """ +Manage the after sale of the products from helpdesk tickets. + """, + 'data': [ + 'views/odex25_helpdesk_views.xml', + ], + 'demo': ['data/odex25_helpdesk_sale_demo.xml'], +} diff --git a/odex25_helpdesk/odex25_helpdesk_sale/data/odex25_helpdesk_sale_demo.xml b/odex25_helpdesk/odex25_helpdesk_sale/data/odex25_helpdesk_sale_demo.xml new file mode 100644 index 000000000..4f496f622 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_sale/data/odex25_helpdesk_sale_demo.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + 1 + 3645.00 + + + + + + + + + + + + 1 + 14.00 + + + + + + + + + + + + 1 + 12.50 + + + + + + + + + + + + + + diff --git a/odex25_helpdesk/odex25_helpdesk_sale/i18n/ar.po b/odex25_helpdesk/odex25_helpdesk_sale/i18n/ar.po new file mode 100644 index 000000000..b93406335 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_sale/i18n/ar.po @@ -0,0 +1,59 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * odex25_helpdesk_sale +# +# Translators: +# Mustafa Rawi , 2020 +# Osama Ahmaro , 2020 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server saas~13.5+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-01 07:39+0000\n" +"PO-Revision-Date: 2020-09-07 08:20+0000\n" +"Last-Translator: Osama Ahmaro , 2020\n" +"Language-Team: Arabic (https://www.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: odex25_helpdesk_sale +#: model:ir.model.fields,field_description:odex25_helpdesk_sale.field_odex25_helpdesk_ticket__commercial_partner_id +msgid "Commercial Entity" +msgstr "الكيان التجاري" + +#. module: odex25_helpdesk_sale +#: model:ir.model.fields,field_description:odex25_helpdesk_sale.field_odex25_helpdesk_ticket__display_name +msgid "Display Name" +msgstr "الاسم المعروض" + +#. module: odex25_helpdesk_sale +#: model:ir.model,name:odex25_helpdesk_sale.model_odex25_helpdesk_ticket +msgid "Helpdesk Ticket" +msgstr "تذكرة مكتب المساعدة" + +#. module: odex25_helpdesk_sale +#: model:ir.model.fields,field_description:odex25_helpdesk_sale.field_odex25_helpdesk_ticket__id +msgid "ID" +msgstr "المُعرف" + +#. module: odex25_helpdesk_sale +#: model:ir.model.fields,field_description:odex25_helpdesk_sale.field_odex25_helpdesk_ticket____last_update +msgid "Last Modified on" +msgstr "آخر تعديل في" + +#. module: odex25_helpdesk_sale +#: model:ir.model.fields,field_description:odex25_helpdesk_sale.field_odex25_helpdesk_ticket__sale_order_id +msgid "Ref. Sales Order" +msgstr "" + +#. module: odex25_helpdesk_sale +#: model:ir.model.fields,help:odex25_helpdesk_sale.field_odex25_helpdesk_ticket__sale_order_id +msgid "" +"Reference of the Sales Order to which this ticket refers. Setting this " +"information aims at easing your After Sales process and only serves " +"indicative purposes." +msgstr "" diff --git a/odex25_helpdesk/odex25_helpdesk_sale/models/__init__.py b/odex25_helpdesk/odex25_helpdesk_sale/models/__init__.py new file mode 100644 index 000000000..27eb465e7 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_sale/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import odex25_helpdesk diff --git a/odex25_helpdesk/odex25_helpdesk_sale/models/odex25_helpdesk.py b/odex25_helpdesk/odex25_helpdesk_sale/models/odex25_helpdesk.py new file mode 100644 index 000000000..66e965b0b --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_sale/models/odex25_helpdesk.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- + +from odoo import api, fields, models + + +class odex25_helpdeskTicket(models.Model): + _inherit = 'odex25_helpdesk.ticket' + + commercial_partner_id = fields.Many2one(related='partner_id.commercial_partner_id') + sale_order_id = fields.Many2one('sale.order', string='Ref. Sales Order', + domain="""[ + '|', (not commercial_partner_id, '=', 1), ('partner_id', 'child_of', commercial_partner_id or []), + ('company_id', '=', company_id)]""", + groups="sales_team.group_sale_salesman,account.group_account_invoice", + help="Reference of the Sales Order to which this ticket refers. Setting this information aims at easing your After Sales process and only serves indicative purposes.") + + def copy(self, default=None): + if not self.env.user.has_group('sales_team.group_sale_salesman') and not self.env.user.has_group('account.group_account_invoice'): + if default is None: + default = {'sale_order_id': False} + else: + default.update({'sale_order_id': False}) + return super(odex25_helpdeskTicket, self).copy(default=default) diff --git a/odex25_helpdesk/odex25_helpdesk_sale/static/description/icon.png b/odex25_helpdesk/odex25_helpdesk_sale/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4141f52daab6a780510b5f4e3dd762511add78fa GIT binary patch literal 32929 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 OgtWK + + + odex25_helpdesk.ticket.form.inherit.invoicing + odex25_helpdesk.ticket + + + + + + + + {'always_reload': True} + {'res_partner_search_mode': 'customer'} + + + + + + odex25_helpdesk.ticket.form.quick_create + odex25_helpdesk.ticket + + + + {'always_reload': True} + {'res_partner_search_mode': 'customer'} + + + + + + odex25_helpdesk.ticket.form.inherit.invoicing + odex25_helpdesk.ticket + + + + {"no_create": True} + 0 + + + + + + diff --git a/odex25_helpdesk/odex25_helpdesk_security/__init__.py b/odex25_helpdesk/odex25_helpdesk_security/__init__.py new file mode 100644 index 000000000..b75bfd8cf --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_security/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models +from . import controllers \ No newline at end of file diff --git a/odex25_helpdesk/odex25_helpdesk_security/__manifest__.py b/odex25_helpdesk/odex25_helpdesk_security/__manifest__.py new file mode 100644 index 000000000..29efa4c63 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_security/__manifest__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'EXP Helpdesk Security', + 'summary': 'Ticket Security', + 'author': "Expert Co Ltd", + 'website': "http://www.ex.com", + 'category': 'Odex25-Helpdesk/Odex25-Helpdesk', + 'depends': ['odex25_helpdesk', 'odex25_helpdesk_reopen'], + 'description': """ + Ticket Security + """, + 'auto_install': True, + 'data': [ + # 'data/helpdesk_data.xml', + 'security/helpdesk_security.xml', + 'security/ir.model.access.csv', + 'views/view.xml', + ], + 'qweb': [ + "static/src/xml/template.xml", + ], + 'license': '', +} diff --git a/odex25_helpdesk/odex25_helpdesk_security/controllers/__init__.py b/odex25_helpdesk/odex25_helpdesk_security/controllers/__init__.py new file mode 100644 index 000000000..72f4562d7 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_security/controllers/__init__.py @@ -0,0 +1 @@ +from . import controller \ No newline at end of file diff --git a/odex25_helpdesk/odex25_helpdesk_security/controllers/controller.py b/odex25_helpdesk/odex25_helpdesk_security/controllers/controller.py new file mode 100644 index 000000000..1606225ee --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_security/controllers/controller.py @@ -0,0 +1,33 @@ +from odoo import http +from odoo.http import request +from odoo.tools.translate import _ +from odoo.addons.mail.controllers.main import MailController +from odoo.osv.expression import OR +from odoo.tools import consteq, pycompat + +class MailController(MailController): + + @http.route('/mail/view', type='http', auth='none') + def mail_action_view(self, model=None, res_id=None, message_id=None, access_token=None, **kwargs): + user = request.session.uid + if message_id: + try: + message = request.env['mail.message'].sudo().browse(int(message_id)).exists() + except: + message = request.env['mail.message'] + if message: + model, res_id = message.model, message.res_id + else: + # either a wrong message_id, either someone trying ids -> just go to messaging + return self._redirect_to_messaging() + elif res_id and isinstance(res_id, str): + res_id = int(res_id) + + if request.session.uid: + user = request.env['res.users'].search([('id','=',request.session.uid)]) + if not user.has_group('odex25_helpdesk_security.group_helpdesk_normal_manager') and not user.has_group('odex25_helpdesk_security.group_helpdesk_normal_user'): + return False + else: + return self._redirect_to_record(model, res_id, access_token) + return self._redirect_to_record(model, res_id, access_token) + \ No newline at end of file diff --git a/odex25_helpdesk/odex25_helpdesk_security/i18n/ar_001.po b/odex25_helpdesk/odex25_helpdesk_security/i18n/ar_001.po new file mode 100644 index 000000000..30ff4ad31 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_security/i18n/ar_001.po @@ -0,0 +1,163 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * odex25_helpdesk_security +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-08-29 07:21+0000\n" +"PO-Revision-Date: 2023-08-29 07:21+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: odex25_helpdesk_security +#: model:ir.actions.act_window,name:odex25_helpdesk_security.helpdesk_manager_ticket_action_main_tree +#: model:ir.actions.act_window,name:odex25_helpdesk_security.helpdesk_ticket_normal_user_action_main_tree +#: model:ir.ui.menu,name:odex25_helpdesk_security.helpdesk_ticket_normal_user_menu_main +#: model:ir.ui.menu,name:odex25_helpdesk_security.odex25_helpdesk_ticket_action_main_my_tree +msgid "All Tickets" +msgstr "كل التذاكر" + +#. module: odex25_helpdesk_security +#: code:addons/odex25_helpdesk_security/models/odex25_helpdesk_ticket.py:0 +#, python-format +msgid "" +"Can't create ticket in team , only team leader can create ticket in this " +"channel" +msgstr "" +"لا يمكن إنشاء تذكرة في الفريق ، يمكن لقائد الفريق فقط إنشاء تذكرة في هذه " +"القناة" + +#. module: odex25_helpdesk_security +#: code:addons/odex25_helpdesk_security/models/odex25_helpdesk_ticket.py:0 +#, python-format +msgid "" +"Can't create ticket in team without leader and members except for helpdesk " +"manager" +msgstr "" +"لا يمكن إنشاء تذكرة في الفريق بدون قائد وأعضاء باستثناء مدير مكتب المساعدة" + +#. module: odex25_helpdesk_security +#: model_terms:ir.actions.act_window,help:odex25_helpdesk_security.action_upcoming_sla_fail_all_tickets_normal_user +#: model_terms:ir.actions.act_window,help:odex25_helpdesk_security.helpdesk_ticket_action_team_user +#: model_terms:ir.actions.act_window,help:odex25_helpdesk_security.helpdesk_ticket_action_unassigned_normal_user +msgid "Click create a new ticket." +msgstr "انقر فوق إنشاء تذكرة جديدة" + +#. module: odex25_helpdesk_security +#: model:ir.actions.act_window,name:odex25_helpdesk_security.helpdesk_team_dashboard_normal_user_action_main +#: model:ir.ui.menu,name:odex25_helpdesk_security.helpdesk_menu_team_normal_user_dashboard +msgid "Dashboard" +msgstr "لوحة عرض" + +#. module: odex25_helpdesk_security +#: model:ir.model.fields,field_description:odex25_helpdesk_security.field_odex25_helpdesk_stage__display_name +#: model:ir.model.fields,field_description:odex25_helpdesk_security.field_odex25_helpdesk_ticket__display_name +msgid "Display Name" +msgstr "الاسم المعروض" + +#. module: odex25_helpdesk_security +#: model:ir.ui.menu,name:odex25_helpdesk_security.menu_helpdesk_normal_user_root +msgid "Helpdesk" +msgstr "مكتب المساعدة" + +#. module: odex25_helpdesk_security +#: model:res.groups,name:odex25_helpdesk_security.group_helpdesk_normal_manager +msgid "Helpdesk Manager" +msgstr "مدير الدعم الفني" + +#. module: odex25_helpdesk_security +#: model:ir.model,name:odex25_helpdesk_security.model_odex25_helpdesk_stage +msgid "Helpdesk Stage" +msgstr "مرحلة مكتب المساعدة" + +#. module: odex25_helpdesk_security +#: model:ir.model,name:odex25_helpdesk_security.model_odex25_helpdesk_ticket +msgid "Helpdesk Ticket" +msgstr "تذكرة مكتب المساعدة" + +#. module: odex25_helpdesk_security +#: model:ir.model.fields,field_description:odex25_helpdesk_security.field_odex25_helpdesk_stage__id +#: model:ir.model.fields,field_description:odex25_helpdesk_security.field_odex25_helpdesk_ticket__id +msgid "ID" +msgstr "المُعرف" + +#. module: odex25_helpdesk_security +#: model:ir.model.fields,field_description:odex25_helpdesk_security.field_odex25_helpdesk_stage____last_update +#: model:ir.model.fields,field_description:odex25_helpdesk_security.field_odex25_helpdesk_ticket____last_update +msgid "Last Modified on" +msgstr "آخر تعديل في" + +#. module: odex25_helpdesk_security +#: model_terms:ir.actions.act_window,help:odex25_helpdesk_security.helpdesk_manager_ticket_action_main_tree +#: model_terms:ir.actions.act_window,help:odex25_helpdesk_security.helpdesk_ticket_normal_user_action_main_tree +msgid "No tickets to display." +msgstr "لا تذاكر لعرضها." + +#. module: odex25_helpdesk_security +#: model:ir.ui.menu,name:odex25_helpdesk_security.ticket_report_menu_main +msgid "Reporting" +msgstr "التقارير" + +#. module: odex25_helpdesk_security +#: model:ir.ui.menu,name:odex25_helpdesk_security.menu_sla_analysis +msgid "SLA Status Analysis" +msgstr "تحليل حالة اتفاقية مستوى الخدمة" + +#. module: odex25_helpdesk_security +#: model:res.groups,name:odex25_helpdesk_security.group_helpdesk_normal_user_manager +msgid "Team Manager" +msgstr "قائد فريق" + +#. module: odex25_helpdesk_security +#: model:res.groups,name:odex25_helpdesk_security.group_helpdesk_normal_user +msgid "Team Member" +msgstr "عضو فريق" + +#. module: odex25_helpdesk_security +#: model:ir.actions.act_window,name:odex25_helpdesk_security.action_upcoming_sla_fail_all_tickets_normal_user +#: model:ir.actions.act_window,name:odex25_helpdesk_security.helpdesk_ticket_action_team_user +#: model:ir.actions.act_window,name:odex25_helpdesk_security.helpdesk_ticket_action_unassigned_normal_user +#: model:ir.ui.menu,name:odex25_helpdesk_security.helpdesk_ticket_report_menu +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_security.helpdesk_team_normal_user_view_kanban +msgid "Tickets" +msgstr "تذاكر" + +#. module: odex25_helpdesk_security +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_security.helpdesk_team_normal_user_view_kanban +msgid "Tickets to Review" +msgstr "تذاكر للمراجعة" + +#. module: odex25_helpdesk_security +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_security.helpdesk_team_normal_user_view_kanban +msgid "Unassigned Tickets" +msgstr "التذاكر غير المخصصة" + +#. module: odex25_helpdesk_security +#: code:addons/odex25_helpdesk_security/models/odex25_helpdesk_ticket.py:0 +#: code:addons/odex25_helpdesk_security/models/odex25_helpdesk_ticket.py:0 +#, python-format +msgid "" +"You don't have permission to delete this record, please contact Helpdesk " +"Manager" +msgstr "ليس لديك إذن بحذف هذا السجل ، يرجى الاتصال بمدير مكتب المساعدة" + +#. module: odex25_helpdesk_security +#: code:addons/odex25_helpdesk_security/models/odex25_helpdesk_ticket.py:0 +#, python-format +msgid "" +"You're not allowed to create ticket in this team, please select a team that " +"you are a member in." +msgstr "" +"غير مسموح لك بإنشاء تذكرة في هذا الفريق ، يرجى تحديد الفريق الذي أنت عضو " +"فيه." + +#. module: odex25_helpdesk_security +#: model_terms:ir.actions.act_window,help:odex25_helpdesk_security.helpdesk_team_dashboard_normal_user_action_main +msgid "Your teams will appear here." +msgstr "ستظهر فرقك هنا." \ No newline at end of file diff --git a/odex25_helpdesk/odex25_helpdesk_security/models/__init__.py b/odex25_helpdesk/odex25_helpdesk_security/models/__init__.py new file mode 100644 index 000000000..52d1688ec --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_security/models/__init__.py @@ -0,0 +1 @@ +from . import odex25_helpdesk_ticket \ No newline at end of file diff --git a/odex25_helpdesk/odex25_helpdesk_security/models/odex25_helpdesk_ticket.py b/odex25_helpdesk/odex25_helpdesk_security/models/odex25_helpdesk_ticket.py new file mode 100644 index 000000000..8759db247 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_security/models/odex25_helpdesk_ticket.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +from odoo import api, fields, models, tools, _ +from odoo.exceptions import ValidationError + + +class HelpdeskTicket(models.Model): + _inherit = 'odex25_helpdesk.ticket' + + @api.model + def create(self, vals): + """ + prevent creating ticket for other teams for normal users + """ + if 'team_id' in vals: + team = self.env['odex25_helpdesk.team'].browse(vals['team_id']) + if not team.team_leader_id and not team.member_ids and not self.env.user.has_group('odex25_helpdesk_security.group_helpdesk_normal_manager'): + raise ValidationError( + _("Can't create ticket in team without leader and members except for helpdesk manager")) + + if team.team_leader_id and not team.member_ids and self.env.user.id != team.team_leader_id.id: + raise ValidationError( + _("Can't create ticket in team , only team leader can create ticket in this channel")) + + if self.env.user.has_group('odex25_helpdesk_security.group_helpdesk_normal_user'): + if 'team_id' in vals and vals['team_id']: + team = self.env['odex25_helpdesk.team'].search([('id','=',vals['team_id'])]) + members = [member.id for member in team.member_ids] + members.append(team.team_leader_id.id) + if self.env.user.id not in members: + raise ValidationError(_("You're not allowed to create ticket in this team, please select a team that you are a member in.")) + res = super(HelpdeskTicket,self).create(vals) + return res + + def unlink(self): + """ + prevent deleting for normal users + """ + if self.env.user.has_group('odex25_helpdesk_security.group_helpdesk_normal_user'): + raise ValidationError(_("You don't have permission to delete this record, please contact Helpdesk Manager")) + return super(HelpdeskTicket,self).unlink() + + +class HelpdeskStage(models.Model): + _inherit = 'odex25_helpdesk.stage' + + def unlink(self): + """ + prevent deleting for normal users + """ + if self.env.user.has_group('odex25_helpdesk_security.group_helpdesk_normal_user'): + raise ValidationError(_("You don't have permission to delete this record, please contact Helpdesk Manager")) + return super(HelpdeskStage,self).unlink() + + # @api.multi + # def write(self,vals): + # """ + # prevent writing for normal users + # """ + # if self.env.user.has_group('odex25_helpdesk_security.group_helpdesk_normal_user'): + # raise ValidationError(_("You don't have permission to this record, please contact Helpdesk Manager")) + # return super(HelpdeskStage,self).unlink() \ No newline at end of file diff --git a/odex25_helpdesk/odex25_helpdesk_security/security/helpdesk_security.xml b/odex25_helpdesk/odex25_helpdesk_security/security/helpdesk_security.xml new file mode 100644 index 000000000..5c576ddbc --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_security/security/helpdesk_security.xml @@ -0,0 +1,31 @@ + + + + + + + Team Member + + + + + + Team Manager + + + + + + Helpdesk Manager + + + + + + + + + + + diff --git a/odex25_helpdesk/odex25_helpdesk_security/security/ir.model.access.csv b/odex25_helpdesk/odex25_helpdesk_security/security/ir.model.access.csv new file mode 100644 index 000000000..4ab9b533f --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_security/security/ir.model.access.csv @@ -0,0 +1,5 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_helpdesk_user_ir_config_parameter,helpdesk.ir.config.parameter,base.model_ir_config_parameter,odex25_helpdesk.group_odex25_helpdesk_user,1,1,1,0 +access_helpdesk_stage,helpdesk.stage,odex25_helpdesk.model_odex25_helpdesk_stage,odex25_helpdesk.group_odex25_helpdesk_user,1,1,1,1 +access_helpdesk_tags,helpdesk.tag,odex25_helpdesk.model_odex25_helpdesk_tag,odex25_helpdesk.group_odex25_helpdesk_manager,1,1,1,1 +access_helpdesk_sla_report_analysis,sla_report_analysis,odex25_helpdesk.model_odex25_helpdesk_sla_report_analysis,odex25_helpdesk_security.group_helpdesk_normal_user_manager,1,1,1,1 \ No newline at end of file diff --git a/odex25_helpdesk/odex25_helpdesk_security/static/src/xml/template.xml b/odex25_helpdesk/odex25_helpdesk_security/static/src/xml/template.xml new file mode 100644 index 000000000..f8edc3622 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_security/static/src/xml/template.xml @@ -0,0 +1,30 @@ + + diff --git a/odex25_helpdesk/odex25_helpdesk_security/views/view.xml b/odex25_helpdesk/odex25_helpdesk_security/views/view.xml new file mode 100644 index 000000000..ed542930c --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_security/views/view.xml @@ -0,0 +1,340 @@ + + + + + + + All Tickets + odex25_helpdesk.ticket + tree,kanban,form + + {'search_default_my_ticket': True} + +

+ No tickets to display. +

+
+
+ + + [('user_id','=',uid)] + + + [('user_id','=',uid)] + + + [('user_id','=',uid)] + + + + + + [('user_id','=',uid),('stage_id.is_close', '=', False)] + + + + [('stage_id.is_close', '=', False),('user_id','=',uid),('priority', '=', '2')] + + + + [('user_id','=',uid),('priority', '!=', False)] + + + + + + [('user_id','=',uid),('close_date', '>=', (datetime.datetime.today() - + datetime.timedelta(hours=15)).strftime('%Y-%m-%d %H:%M:%S'))] + + + + [('user_id','=',uid),('close_date', '>=', (datetime.datetime.today() - + datetime.timedelta(hours=15)).strftime('%Y-%m-%d %H:%M:%S'))] + + + + + [('user_id','=',uid),('close_date', '>=', (datetime.datetime.today() - + datetime.timedelta(days=7)).strftime('%Y-%m-%d %H:%M:%S'))] + + + + [('user_id','=',uid),('close_date', '>=', (datetime.datetime.today() - + datetime.timedelta(days=7)).strftime('%Y-%m-%d %H:%M:%S'))] + + + + + + ['|',('team_id.member_ids' , 'in' , uid),('team_id.member_ids' , '=' ,False)] + + + ['|',('team_id.member_ids' , 'in' , uid),('team_id.member_ids' , '=' ,False)] + + + + ['|',('team_id.member_ids' , 'in' , uid),('team_id.member_ids' , '=' ,False)] + + + ['|',('team_id.member_ids' , 'in' , uid),('team_id.member_ids' , '=' ,False)] + + + + + + Tickets + odex25_helpdesk.ticket + kanban,tree,form,pivot,graph + + {'search_default_upcoming_sla_fail': True} + [('user_id','=',uid)] + +

+ Click create a new ticket. +

+
+
+ + + Tickets + odex25_helpdesk.ticket + kanban,tree,form + {'search_default_team_id': active_id, 'search_default_unassigned': True} + [('user_id','=',uid)] + + +

+ Click create a new ticket. +

+
+
+ + + Dashboard + odex25_helpdesk.team + kanban,form + {} + ['|',('member_ids','in',uid),'|',('team_leader_id','=',uid),'&',('team_leader_id','=',False),('member_ids','=',False)] + + +

+ Your teams will appear here. +

+
+
+ + + + + + All Tickets + odex25_helpdesk.ticket + tree,kanban,form + + {'search_default_my_ticket': True} + ['|',('user_id','=',uid),('team_leader_id','=',uid)] + +

+ No tickets to display. +

+
+
+ + + + + + + + + + + + + + + + + + + + Tickets + odex25_helpdesk.ticket + kanban,tree,form + {'search_default_my_ticket': True} + ['|',('user_id','=',uid),('team_leader_id','=',uid),('team_id', '=', active_id)] + + +

+ Click create a new ticket. +

+
+
+ + + helpdesk.team.dashboard + odex25_helpdesk.team + + + + odex25_helpdesk_security.group_helpdesk_normal_manager + + + odex25_helpdesk_security.group_helpdesk_normal_manager + + + odex25_helpdesk_security.group_helpdesk_normal_manager + + + odex25_helpdesk_security.group_helpdesk_normal_manager + + + odex25_helpdesk_security.group_helpdesk_normal_manager + + + + + Tickets to Review + + + + + + Unassigned Tickets + + + + + + + + odex25_helpdesk_security.group_helpdesk_normal_user_manager,odex25_helpdesk_security.group_helpdesk_normal_manager + + + + odex25_helpdesk_security.group_helpdesk_normal_manager + + + + + + helpdesk.ticket.kanban + odex25_helpdesk.ticket + + + + odex25_helpdesk_security.group_helpdesk_normal_manager + + + + + + + + + + + + + + + + + + + + + + + + + + helpdesk.stage.reopen.form + odex25_helpdesk.stage + + + + + 1 + + + 1 + + + 1 + {'no_open' : True} + + + 1 + {'no_open' : True} + + + 1 + + + 1 + + + + + + + odex25_helpdesk.stage.reopen.form + odex25_helpdesk.stage + + + + + 1 + + + 1 + + + + + + + + +
diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/__init__.py b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/__init__.py new file mode 100644 index 000000000..dc5e6b693 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/__manifest__.py b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/__manifest__.py new file mode 100644 index 000000000..bc2047c66 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/__manifest__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'EXP Helpdesk SLA Escalation Reminder', + 'summary': 'Reminder in SLA Policy to reminde the team leader of the task depending on configuration', + 'author': "Expert Co Ltd", + 'website': "http://www.ex.com", + 'category': 'Odex25-Helpdesk/Odex25-Helpdesk', + 'depends': ['odex25_helpdesk'], + 'description': """ + Reminder in SLA Policy to reminde the team leader of the task depending on configuration + """, + 'auto_install': True, + 'data': [ + 'security/ir.model.access.csv', + 'data/reminder_cron.xml', + 'data/reminder_templates.xml', + 'views/helpdesk_sla_views.xml', + ], + 'license': '', +} diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/data/reminder_cron.xml b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/data/reminder_cron.xml new file mode 100644 index 000000000..b497c4475 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/data/reminder_cron.xml @@ -0,0 +1,13 @@ + + + + Helpdesk: reminder and escalation SLAs + + code + model.reminder_and_escalation() + 30 + minutes + -1 + + + \ No newline at end of file diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/data/reminder_templates.xml b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/data/reminder_templates.xml new file mode 100644 index 000000000..420ada2b6 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/data/reminder_templates.xml @@ -0,0 +1,63 @@ + + + + Ticket Reminder + + Ticket Reminder + + + + + +
+
+

Reminder!

+

Dear ${ctx['mail_to'|safe]},

+ your ticket + + % if object.access_token: + ${object.name|safe}#${object.id|safe} + + % endif + + is about to seek the deadline + in ${ctx['time_untill']|safe}. Please follow up. +
+

+ Kind regards,
+ ${object.team_id.name or 'Helpdesk'} Team. +

+
+
+
+ + Ticket Escalation + + Ticket Escalation + + + + + +
+
+

Escalation!

+

Dear ${ctx['mail_to'|safe]},

+ The ticket + + % if object.access_token: + ${object.name|safe}#${object.id|safe} + + % endif + + assigned to ${object.user_id.name} + exceeded the deadline by ${ctx['time_untill']|safe}. +
+

+ Kind regards,
+ ${object.team_id.name or 'Helpdesk'} Team. +

+
+
+
+
\ No newline at end of file diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/i18n/ar_001.po b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/i18n/ar_001.po new file mode 100644 index 000000000..cf8490f65 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/i18n/ar_001.po @@ -0,0 +1,282 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * odex25_helpdesk_sla_escalation_reminder +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-09-21 11:48+0000\n" +"PO-Revision-Date: 2022-09-21 11:48+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: odex25_helpdesk_sla_escalation_reminder +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#, python-format +msgid " Hours and " +msgstr "" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#, python-format +msgid " Minutes" +msgstr "" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:mail.template,body_html:odex25_helpdesk_sla_escalation_reminder.ticket_sla_escalation +msgid "" +"\n" +"
\n" +"
\n" +"

Escalation!

\n" +"

Dear ${ctx['mail_to'|safe]},

\n" +" The ticket \n" +" \n" +" % if object.access_token:\n" +" ${object.name|safe}#${object.id|safe}\n" +" \n" +" % endif\n" +" \n" +" assigned to ${object.user_id.name} \n" +" exceeded the deadline by ${ctx['time_untill']|safe}.\n" +"
\n" +"

\n" +" Kind regards,
\n" +" ${object.team_id.name or 'Helpdesk'} Team.\n" +"

\n" +"
\n" +" " +msgstr "" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:mail.template,body_html:odex25_helpdesk_sla_escalation_reminder.ticket_sla_reminder +msgid "" +"\n" +"
\n" +"
\n" +"

Reminder!

\n" +"

عزيزي ${ctx['mail_to'|safe]},

\n" +" تذكرتك \n" +" \n" +" % if object.access_token:\n" +" ${object.name|safe}#${object.id|safe}\n" +" \n" +" % endif\n" +" \n" +" على وشك البحث عن الموعد النهائي\n" +" in ${ctx['time_untill']|safe}.أرجو المتابعة\n" +"
\n" +"

\n" +" أطيب التحيات
\n" +" ${object.team_id.name or 'الدعم الفني'} فريق.\n" +"

\n" +"
\n" +" " +msgstr "" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model.fields.selection,name:odex25_helpdesk_sla_escalation_reminder.selection__helpdesk_sla_policy__type__after +msgid "After" +msgstr "بعد" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model.fields.selection,name:odex25_helpdesk_sla_escalation_reminder.selection__helpdesk_sla_policy__type__before +msgid "Before" +msgstr "قبل" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__create_uid +msgid "Created by" +msgstr "" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__create_date +msgid "Created on" +msgstr "" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#, python-format +msgid "Days, " +msgstr "الأيام" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__display_name +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_odex25_helpdesk_sla__display_name +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_odex25_helpdesk_ticket__display_name +msgid "Display Name" +msgstr "الاسم المعروض" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_odex25_helpdesk_sla__escalation_ids +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_sla_escalation_reminder.helpdesk_sla_view_form_reminder_policy +msgid "Escalation" +msgstr "التصعيد" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model,name:odex25_helpdesk_sla_escalation_reminder.model_odex25_helpdesk_sla +msgid "Helpdesk SLA Policies" +msgstr "سياسات اتفاق مستوى الخدمة لمكتب المساعدة" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model,name:odex25_helpdesk_sla_escalation_reminder.model_odex25_helpdesk_ticket +msgid "Helpdesk Ticket" +msgstr "تذكرة مكتب المساعدة" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.actions.server,name:odex25_helpdesk_sla_escalation_reminder.helpdesk_sla_reminder_ir_actions_server +#: model:ir.cron,cron_name:odex25_helpdesk_sla_escalation_reminder.helpdesk_sla_reminder +#: model:ir.cron,name:odex25_helpdesk_sla_escalation_reminder.helpdesk_sla_reminder +msgid "Helpdesk: reminder and escalation SLAs" +msgstr "مكتب المساعدة: تذكير وتصعيد اتفاقيات مستوى الخدمة" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#, python-format +msgid "Hours Can't be more than working hours, Kindly! try again" +msgstr "لا يمكن أن تكون الساعات أكثر من ساعات العمل ، يرجى! حاول مرة أخرى" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__id +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_odex25_helpdesk_sla__id +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_odex25_helpdesk_ticket__id +msgid "ID" +msgstr "المُعرف" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#, python-format +msgid "Kindly, make sure that time is not less than or equal zero" +msgstr "يرجى التأكد من أن الوقت ليس أقل من أو يساوي الصفر" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#, python-format +msgid "Kindly, make sure that time is not less than zero" +msgstr "يرجى التأكد من أن الوقت لا يقل عن الصفر" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy____last_update +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_odex25_helpdesk_sla____last_update +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_odex25_helpdesk_ticket____last_update +msgid "Last Modified on" +msgstr "آخر تعديل في" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__write_date +msgid "Last Updated on" +msgstr "" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0 +#, python-format +msgid "Madam, Sir" +msgstr "سيدتي ، سيدي" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_odex25_helpdesk_sla__reminder_ids +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_sla_escalation_reminder.helpdesk_sla_view_form_reminder_policy +msgid "Reminder" +msgstr "تذكير" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_sla_escalation_reminder.helpdesk_sla_view_form_reminder_policy +msgid "Reminder After-Days" +msgstr "" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_sla_escalation_reminder.helpdesk_sla_view_form_reminder_policy +msgid "Reminder After-Hours" +msgstr "تذكير بعد الأيام" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_sla_escalation_reminder.helpdesk_sla_view_form_reminder_policy +msgid "Reminder Before-Days" +msgstr "تذكير قبل الأيام" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_sla_escalation_reminder.helpdesk_sla_view_form_reminder_policy +msgid "Reminder Before-Hours" +msgstr "تذكير قبل ساعات" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__reminder_days +msgid "Reminder Days" +msgstr "أيام التذكير" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__reminder_hours +msgid "Reminder Hours" +msgstr "ساعات التذكير" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_odex25_helpdesk_ticket__reminders_ids +msgid "Reminders" +msgstr "تذكير" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__sla_escalation_id +msgid "Sla Escalation" +msgstr "تصعيد اتفاقية مستوى الخدمة (SLA)" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__sla_reminder_id +msgid "Sla Reminder" +msgstr "تذكير اتفاقية مستوى الخدمة (SLA)" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__team_id +msgid "Team" +msgstr "الفريق" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:mail.template,subject:odex25_helpdesk_sla_escalation_reminder.ticket_sla_escalation +msgid "Ticket Escalation" +msgstr "تصعيد التذاكر" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:mail.template,subject:odex25_helpdesk_sla_escalation_reminder.ticket_sla_reminder +msgid "Ticket Reminder" +msgstr "تذكير التذكرة" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__type +msgid "Type" +msgstr "النوع" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__user_id +msgid "User" +msgstr "المستخدم" + +#. module: odex25_helpdesk_sla_escalation_reminder +#: model:ir.model,name:odex25_helpdesk_sla_escalation_reminder.model_helpdesk_sla_policy +msgid "helpdesk.sla.policy" +msgstr "" diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/models/__init__.py b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/models/__init__.py new file mode 100644 index 000000000..a73b21d8e --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import helpdesk_reminder_policy \ No newline at end of file diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py new file mode 100644 index 000000000..081250132 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py @@ -0,0 +1,204 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models, _ +from odoo.exceptions import UserError, ValidationError +import datetime +import logging +_logger = logging.getLogger(__name__) + + +class HelpdeskTicket(models.Model): + _inherit = "odex25_helpdesk.ticket" + + reminders_ids = fields.Many2many('helpdesk.sla.policy') + # escalations_ids = fields.Many2many('helpdesk.sla.policy') + + +class HelpdeskSLA(models.Model): + _inherit = "odex25_helpdesk.sla" + + reminder_ids = fields.One2many('helpdesk.sla.policy','sla_reminder_id') + escalation_ids = fields.One2many('helpdesk.sla.policy','sla_escalation_id') + + @api.constrains('time_hours') + def prevent_hours_morethan_workinghours(self): + working_calendar = self.env.user.company_id.resource_calendar_id + for rec in self: + if working_calendar.is_full_day: + if rec.time_hours > working_calendar.working_hours: + raise ValidationError(_("Hours Can't be more than working hours, Kindly! try again")) + else: + if rec.time_hours > working_calendar.shift_one_working_hours or rec.time_hours > working_calendar.shift_two_working_hours: + raise ValidationError(_("Hours Can't be more than working hours, Kindly! try again")) + + @api.model + def reminder_and_escalation(self): + """ + send reminder and escalation according to sla policy + """ + policies = self.env['odex25_helpdesk.sla'].search([]) + for sla in policies: + if sla.reminder_ids: + for reminder in sla.reminder_ids: + tickets = self.env['odex25_helpdesk.ticket'].search([('sla_id','=',sla.id),('reminders_ids','not in',reminder.id),('deadline','!=',None)]) + _logger.error('length of tickets %s',len(tickets)) + for ticket in tickets: + difference = fields.Datetime.from_string(ticket.deadline) - datetime.datetime.now() + difference = str(difference).split(',') + hour, minute = divmod(reminder.reminder_hours, 1) + minute *= 60 + result = '{}:{}'.format(int(hour), int(minute)) + result = result.split(':') + if len(difference) == 2: + days = int(difference[0].split(' ')[0]) + hours = int(difference[1].split(':')[0]) + minutes = int(difference[1].split(':')[1]) + else: + days = 0 + hours = int(difference[0].split(':')[0]) + minutes = int(difference[0].split(':')[1]) + if days <= reminder.reminder_days: + if hours < int(result[0]): + if ticket.sla_id.stage_id.id != ticket.stage_id.id: + template = self.env.ref('helpdesk_sla_escalation_reminder.ticket_sla_reminder') + template.email_to = ticket.user_id.partner_id.email + times = str(days) + _('Days, ') + str(hours) + _(' Hours and ') + str(minutes) + _(' Minutes') + if ticket.user_id: + to = ticket.user_id.name + else: + to = _("Madam, Sir") + template.sudo().with_context({ + 'time_untill':times, + 'mail_to': to, + 'lang':self.env.user.lang, + }).send_mail(ticket.id, force_send=True, raise_exception=False) + ticket.write({'reminders_ids': [(4, reminder.id, 0)]}) + elif hours == int(result[0]): + if minutes <= int(result[1]): + if ticket.sla_id.stage_id.id != ticket.stage_id.id: + template = self.env.ref('helpdesk_sla_escalation_reminder.ticket_sla_reminder') + template.email_to = ticket.user_id.partner_id.email + times = str(days) + _('Days, ') + str(hours) + _(' Hours and ') + str(minutes) + _(' Minutes') + if ticket.user_id: + to = ticket.user_id.name + else: + to = _("Madam, Sir") + template.sudo().with_context({ + 'time_untill':times, + 'mail_to': to, + 'lang':self.env.user.lang, + }).send_mail(ticket.id, force_send=True, raise_exception=False) + ticket.write({'reminders_ids': [(4, reminder.id, 0)]}) + elif days <= -1: + if ticket.sla_id.stage_id.id != ticket.stage_id.id: + template = self.env.ref('helpdesk_sla_escalation_reminder.ticket_sla_reminder') + template.email_to = ticket.user_id.partner_id.email + times = str(days) + _('Days, ') + str(hours) + _(' Hours and ') + str(minutes) + _(' Minutes') + if ticket.user_id: + to = ticket.user_id.name + else: + to = _("Madam, Sir") + template.sudo().with_context({ + 'time_untill':times, + 'mail_to': to, + 'lang':self.env.user.lang, + }).send_mail(ticket.id, force_send=True, raise_exception=False) + ticket.write({'reminders_ids': [(4, reminder.id, 0)]}) + else: + continue + if sla.escalation_ids: + for escalation in sla.escalation_ids: + tickets = self.env['odex25_helpdesk.ticket'].search([('sla_id','=',sla.id),('reminders_ids','not in',escalation.id),('deadline','!=',None)]) + _logger.error('length of tickets %s',len(tickets)) + for ticket in tickets: + difference = datetime.datetime.now() - fields.Datetime.from_string(ticket.deadline) + difference = str(difference).split(',') + hour, minute = divmod(escalation.reminder_hours, 1) + minute *= 60 + result = '{}:{}'.format(int(hour), int(minute)) + result = result.split(':') + if len(difference) == 2: + days = int(difference[0].split(' ')[0]) + hours = int(difference[1].split(':')[0]) + minutes = int(difference[1].split(':')[1]) + else: + days = 0 + hours = int(difference[0].split(':')[0]) + minutes = int(difference[0].split(':')[1]) + if days >= escalation.reminder_days: + if hours > int(result[0]): + if ticket.sla_id.stage_id.id != ticket.stage_id.id: + template = self.env.ref('odex25_helpdesk_sla_escalation_reminder.ticket_sla_escalation') + template.email_to = escalation.user_id.partner_id.email + times = str(days) + _('Days, ') + str(hours) + _(' Hours and ') + str(minutes) + _(' Minutes') + if escalation.user_id: + to = escalation.user_id.name + else: + to = _("Madam, Sir") + template.sudo().with_context({ + 'time_untill':times, + 'mail_to': to, + 'lang':self.env.user.lang, + }).send_mail(ticket.id, force_send=True, raise_exception=False) + ticket.write({'reminders_ids': [(4, escalation.id, 0)]}) + elif hours == int(result[0]): + if minutes >= int(result[1]): + if ticket.sla_id.stage_id.id != ticket.stage_id.id: + template = self.env.ref('helpdesk_sla_escalation_reminder.ticket_sla_escalation') + template.email_to = escalation.user_id.partner_id.email + times = str(days) + _('Days, ') + str(hours) + _(' Hours and ') + str(minutes) + _(' Minutes') + if escalation.user_id: + to = escalation.user_id.name + else: + to = _("Madam, Sir") + template.sudo().with_context({ + 'time_untill':times, + 'mail_to': to, + 'lang':self.env.user.lang, + }).send_mail(ticket.id, force_send=True, raise_exception=False) + ticket.write({'reminders_ids': [(4, escalation.id, 0)]}) + else: + continue + + +class HelpdeskSLAReminderPolicy(models.Model): + _name = 'helpdesk.sla.policy' + + sla_reminder_id = fields.Many2one('odex25_helpdesk.sla') + sla_escalation_id = fields.Many2one('odex25_helpdesk.sla') + team_id = fields.Many2one(related="sla_reminder_id.team_id",store=True) + + type = fields.Selection([ + ('after','After'), + ('before','Before'), + ]) + reminder_hours = fields.Float() + reminder_days = fields.Integer() + user_id = fields.Many2one('res.users') + + @api.constrains('reminder_hours','reminder_days') + def _prevent_zero(self): + """ + Prevent the time to be zero if the days are zero + """ + if self.reminder_days < 0.0 or self.reminder_hours < 0.0: + raise ValidationError(_("Kindly, make sure that time is not less than zero")) + if self.reminder_days == 0.0 and self.reminder_hours <= 0.0: + raise ValidationError(_("Kindly, make sure that time is not less than or equal zero")) + + @api.onchange('team_id') + def _domain_user_to_sla(self): + """ + return domain in users + """ + users = [] + if self.sla_reminder_id: + users = self.sla_reminder_id.team_id.members_ids.mapped('member_id').ids + users.append(self.sla_reminder_id.team_id.team_leader_id.id) + if self.sla_escalation_id: + users = self.sla_escalation_id.team_id.members_ids.mapped('member_id').ids + users.append(self.sla_escalation_id.team_id.team_leader_id.id) + return{ + 'domain':{'user_id':[('id','in',users)]} + } \ No newline at end of file diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/security/ir.model.access.csv b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/security/ir.model.access.csv new file mode 100644 index 000000000..eb5c019fd --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_helpdesk_sla_policy,odex25_helpdesk_sla_escalation_reminder.sla.policy,odex25_helpdesk_sla_escalation_reminder.model_helpdesk_sla_policy,,1,1,1,1 \ No newline at end of file diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/icon.png b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a37b5967b6ad9299b27c32e2a7ff37da643852e3 GIT binary patch literal 4615 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 literal 0 HcmV?d00001 diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/index.html b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/index.html new file mode 100644 index 000000000..4bd6cf0ca --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/index.html @@ -0,0 +1,83 @@ +
+
+

+ ONE OF ODEX MODULES

+
+ ODEX system is over than 200+ modules developed by love of Expert Company, based on ODOO system +
+ .to effectively suite's Saudi and Arabic market needs.It is the first Arabic open source ERP and all-in-one + solution +
+
+
+ +
+
+

+ Contact Us +

+
+
+
+
+ +
+
+ + + +
+

+ + exposa + +

+
+
+
+ + + +
+

+ + exposa + +

+
+ +
+
+
+
+ +
+
diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/internet.png b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/internet.png new file mode 100644 index 0000000000000000000000000000000000000000..a89809bfa97bf56afbd7baf34a67cf9df9e1ecaa GIT binary patch literal 35772 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 II;Vst0JKPgn*aa+ literal 0 HcmV?d00001 diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/linkedin.png b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/linkedin.png new file mode 100644 index 0000000000000000000000000000000000000000..a0fe09caabfdd5887e7dc238af577690e1ef246b GIT binary patch literal 12914 zcmd^mcT`i`*6&UsNEadsQiXtp8Xyq5iXfpXMNkkSH0cSw6D&wnictiq>O};libw!~ ztwuQ>1yriiA|fb}CWavO?Rd&}zc=nz#{2U=$6&D8YnI=dbFR7PnrlftVs}`Ge=EprH{H6QdrZtsWNcuYuLq*VoY0($LaUgDBJ@;zA?6 zV%0(;6#tTN$Tz|}Jb)A#5Ed%WmFPtbi;6T>fRO$%g%Hx;VnZYTG!tYnjaV;|23B2@ zJEdQQKHh)Rk)pzbe<}Cz*6Vk-s3zo0)MPqc6Y5B_Px%EGA;_Kk@h;@{05|R?yPa(p1yLt7&7Mu-XP%ng%$W zil(-Krsf}{wqZU2esTYj6l$W(T{SIr>@T>swg!aI zh)AzcZ(qV8V+F`Q>Hz^h21IW^KYd?cyqd3;kB*v-ua37G9hJx!vY zkC&I%U()Qu1E4kO75vXqxu|>~i8?xZT3)`|UTQ?5mk$?^mYTjU4swRCw=d4e7f-RauTXzqDAj5xaOb1JO-g^bM&tkX+OM&HN??DHLs^mg_;)&n{`fng`-VcI!=ZFN z(S6qx090iOhYmQ!7S4>s&`&zw=vbJu-F5Db+d*{tmRseBgVOP$TeoGuL5gp)cq>`- z#Ztxz`NE?9wQC3epv1)kPDkQ%O%&dhT3%7uin@A4%yKe|GXe9S?bM&DZs3^sCz*Cf z$QDjao(${Kzq&q@P}ABEPV9Aj=_6e$UdvZ2tI6le=UUB9xTGW-wtapJNkFrTonI7t zIa&>616xAsqK<{F$M>ux@GktUv`bW`l}>jmXL@x5DX`hl)=*S6s`wT5%l>gdQ#N)- z?T&dr&&c1$Wtv6sV)({!G6!adR8}9Knw|~|V`8?Clwc~7>r+{&5tNdxuw7J9>U1z- ztq}igU4cA#YU+s`vwvyt}hR?Mt> zrW@YC9y2O5ZI>Y#%m<8XrHJ!BunU2y!rZUL6<$q!*1Q~HJ7-Vp6;z~p&+G!1z;*EK z$adBerImkPk*TF{LuW*kvA*oSWVe7X*0WG4Gxee0IY;1?O?&vYZD+!o2`% zYyGHW!NK8fx$1_O10|d_rHT}_!>{+Q^2-!{c_al~$0+|?x1LJES!E)=74rq*S|QEa zLV#nc0OiimPs9w8l9RqFXc#kcEFw7n)?|TL|LnuR` z2gaw+N=LsRMa3N=dBHrznM22iD!St&1MRF`?|)q<(BfYK^Cvw&^Q3rt!I<0P7xg#I zn@VD@VH-+d9Od!|(>VD4dm9)+Yk>at2lB&bV4jBYVdvYUlM0Q7=DJ{+R!62Z^SR*> zBg24Ob2KCXyYlK>t0K$dde+Y_rRu^+)}ikn_vAvkFmwUw&FNFI>`=YeoN1eB&g9-ooGnmApK&P#7$C(uew{DChg(Wx`3Gn9;U z#HJYEw22Kebqt;h8-QzI%2fz~*{Lrb%et&jYLku6D^ZLoMO4J*3Gs|oI7x=Im!wWQ zZ2rlLwsGv;rv})~_ZEN~lyp`R)&oPrTVc#dCNGF)KT;r=6XG97i+O6R2_(|!dMVsJcd05stB*Bv3KRmXaU2Cg;91yB4+MCCk;KlT`E5@r(&b6!DYTEex=o32 zQWNN%%3$1C?4jfY&MGyGq#ucp)+`%6h9L-e1LdQ8n2TU1LvS-0cb#5D7MM@VPq>0n z1A!w|ebnbsnJA0?VQXhypn7*G*^;*NDR87nCbfW4H62=tx`e(e-F=IrjJI+)r3E;B z!ZY>2<^0w1ArD3$g&L)&K)9m~OyI+S7szvnQKUI3Aoj=qBek9?v#RqD3*G?TSfPh# zR^EVR5R!zFePe*lAM}e|Y?iuGN|ONI8HmUFkJ$dWsg&M|RPN(ydkSQCT>(3$LvLTG zvizo4OB~6tbl|gqvTn&BYB_ks_8}A#Xu1GhDpo9ZdmilZA)7X-!>Ye&U%|6HYS`_p zWG8Y6G3?w3C5~(Dq)tFHQAIRswU6!5Cg?8gen?e2F@iN<(4d7SlM_5eJaZ1MM@@%- zq96g6p+PX0$TRW{oYt;t?pC<317B9EwHERo<4crV7M(vm{y~xI|3d`1RJJ0sJ_!to z3ZMwIgHSj{0BKX7oKoi7MF-J?Tk$u3@TR1zY}o;yQw|kM zv<tqx(}ZzFxqWE_H_Q@~|vs_I%3D*tRnZAIQeFk|c$n zH3u&v+s=E#9}J&C`y-% z)yDd_cGNfPjk7G0E#eV;qcE-zO?j@JwTobm1tR^h%=y>eiTul3`fMLbRS!|mXV1RE zzt{&RX!qxqj!OUriyofr%j_&(ldE;f>Kt^0xa{xv7g)xpR9Y!g5SWo7R36EF)kyiI z)!#iMN)&JZdSkjBg_m~>A?(Y9-Ar1A*SvCC)nh1zwa$uw$rCL}WT&!bnv*nQ|Eh48 z6NhiS|DYa=qxV*6q9vmaHa}_8pj5F3MOmukf2?FS(!}tc>Co=2?%@N)Zv@I+tEk3^ zKL_3vbGAkR4y(d-XHT`qNyeJaA58nO>bO36h}Ihefr_(F%kN3G_bDIR#)04UDQ_{V z4T91?#fuG7-J8;~cQ)B*(6*-w<%Pil5>Np=YWG>rIf zfF{2CSkL0Uix}KFm?x=1MBQ4sdf*Z?*t_))A(hz>VyksoiT{vGC)=)Yz`uoRvub!O zc2~a-atIr}`WMdJ_|I)`OUF*#9H=az7vER0a>#`ym9RZlf!tXoHo#Isfc?~<(Hwg5 zA8zRl8VD@eSD`IwBDV7*MoGK(0DjbZatIGEjHq1i1Z|u_5WtA*Z{g`il(=^Gf7GPH)Z1L*~8n zg+BfcMwRd-< zT_ggo4=-u=76Q_=TcJIwFzMo3vvD34wIJwfW+P}yj#h67%^tY#SH9ZwXFM;h zX_|F@PVI)`d0P!a_|=gZ1JZ#%*1`USINmCa^KfO_rToYLD8 zz=y>>Btlv<0{TceYeB1z4C>wpEGhnT^|j6U-ZPZNpT!5w@){yKeD1CDGSmKAw?(#X zGyY=-k(YZfeRc{_B%bl8p6jFDn6`2tLn|{u62I4b{cQSfM10>)u!izktFOI;zNta5 z0)aO-dQvZF_itiI!7o;t9$$CW49K|bod}EF>zzc{cM*0|yFT@*b{5%a9v44VA7@Nb z5`~AyA@OS5VY?b>pZtj-FzH6e@(!l|L`z}u@1r1Y;`q0zzRc%`H!*wnBKA*t04DBF z@Cm@C^-4&jqXZ(62Y+dBc+cG+iky$E8SSDslzeMaBUB`&aNuXqpBlw3@U$L3`nX~u zCGextcwjnf!`gv}qRb`<<~cvX|ARU;y$O6f{v`ISq`lF-Sn>I^i!avp#VvVdmySyU z%^c173jPkaOL;p;+O<3SlA`aUb64d6;j-oiep#f+$+xcDL@nz@VO z^Nq4bGvK3kMlmUJv7{f+#Ujlh~3zkv#IirdRo!L=ik zV!5AF5bpfLhhn;4*~{xHe}D~U&?ipiK1+2Bl0HSt-}7}L-syMZ`S_i9N?xT)$ErSj z@DUrH$2ZJ3EHunx#AnpA!q#V0t}CVbru%bt!&tB&mk(@a7jK(u44T$ECkO<%o4IG2 z-kG&XKfCHzI6&Kp?ySBi$!N&ktw;IK6*X z0sTfy1jrMm_s+}@u?jT)dd&c`N2NVYi56{XOYnKSaXu8m;k+Oa_Zk_^(^c7W1yw-! z7Nc1;X@aS+L{Sp`&{@366|)vQzmPqHn76q_hl3>WJ)bwRaImZY@X*sDhVX?=w+K0! zlMZ4(ZUZq$#V@M87fF4|XoJ&AcS-{HBKYP-z~?y5(F;4$N6;de*>!7LpdV1)k7i7O z-S0-?*IU0t6P=&daPfT=M^UC*k%?ll=(=;H_IP2u?BKO6Fj^@J3)OYHMCG-F)b`$C z>%ewE7sy0-R5pqe-aVB7rg0*88P}4*!=A@lX9S1Xpc_;e@CMDA({zxC{n|FX!iav1 zdV#59n4wHdnrrWr9NF2FFwKXe_{HVZgWGiH`SRE4!@Ekw7f$gP z3JjloOQ)4y76e*9#W5@U1&D2+Fc=M5fbk&RD-$acy9FxBGx1+GxBGv$b{N9~KdO0C z=Ndg0s}3Mjqf!?@TGc>!)n+EjZ|nVqPUMD_4L*^A|1xG~)UH98jzCd1zLQwaj$T7q*9%bMa9C5O@$Q+q^m(oEoZSW9zxUqV8oFKDT071c= zKMJOS%HWHt%}`5eW`x8*e5+sfSjpjYsblDt3!@gnbT+9h*+~LHDRszD(yjf_%>GdL zQKKy99Z)wub%-fbv%)@{u0*flsfH&XKF3p`2w_mVvE|!f{0v;xIn40nahbt(Hau30 zF1>8d!nGDZ>J1VEzN#H?f9EFYS~=+v|3sUy-raF}_V{a{){52O-r1`2)`-c+JenUj z|9;D0Bl|@Aq(`_b)ny}D1MR}I!HX~0!u#%^B=Al`=<$x&9pt@lISmmhsAS>9^OM1N z9lks(_vDiekmTnfpgx!)`mtI)2;haym6?jHoRuVe6L}!BAnDFuNI;&EY>2vJvGZ4P zTVEaO_iX7w6=hD0H5at(0dEtQhP|$?xPS%sf)_MjN3kTCmP|wOOnuZKZ>(J1(d(?j z$3*3M$MHpa+oVTtYiUOil!l}&*G#YFuJp9*96USSt&q#x#rvo{b~u^sJigDWYsm1v zsMpMq@&1CeXsLM|Gt&Li%>m5%rXo6=6OC-Uz`N=GNI~80SRBga9B+Z}L~{-MliB9v>jK>PXcq)&KV?Q8?3}QHx|WzZH85j8i`km8?^HY&xCwce>s>Lq3Kvak--h6kfa8U-H#A(H1@@*95UFB z-~bnxP1+OdF7{<_M-_)~ZDo8(SB)DyigkbIS{bk~H7bv#P%$W3gLmc`e!_^b{w zdx5pV6ge0gEst7GWbAFm;umW{PkW5u#>*dc-W_boR5u9?5GX9b4 z0D z4W3GGVEcd{j{0&H1NA!1vHHjZ=q0F<|5 zQdoUqR$~u8k2gzMdg>F!>B(AG@yyLFbef;sj|*C8{9X3W!{;bxQ;SY_^_(1n55U6| zC#}o91Oc|;Syx!q*tO)%u)ADlL*|3#0?P?q;@%-SSV~n>Zm*x{tw|fA!pB{V&zybN z-*{Xj9NIF-M{k|ucOrEL*JHLKtfti3)eAUTQJge zre8;Ze2i62!Oj<_+HkY1KnXWBF2Q6xG3m@dDy9SAFA< z!~&Mn;u@m<>uzY3@x4Vo)H`V@&I@$b!xzCHtkTRip<$jBh1o?pTlWo@c>Ap?{K?sY zHI=~@Mi0I$loQ)N((-5TGS1cAXN8smiN*|0E9WS1PDwh!!!S8126E4{H$j zoi*+1AeGb6>U9Yv-5gHZ7;Ffh)vL``u(^@;Y72(8+{N$f=^HR$9Oc8`q-eiL}zg-`q5Lft~v zH|_AH)c94NQiX6nnJ}ndMK4&se{I8du6jig$=1`f3uwIteZb#&%I;b>o$g6(ypjUH zaWBe%bZ`>ewneo#?lQDfz;HVl#pl?Qa9$urxw2vsUmSZ|F}H#8Osiib6XHIS4E5j1 zV3=6HWBqHysKD?Q@?~!d4jR449c?4zHv#Q%lY*O>`60@64Pn(vXLA9Qs}<4Ec7c}m z9a*O|M=}DWEr;PpHe1lk1DDxjh~VtHWGpni{j(IqPje*C^lMmrb967l4zaP@aYmGp zh!DC8nZwZJ&^LjxmyR%wnBnS3&K*VE$OXy;?Y>c|3x(`8c2#t8zczNy?IB3(bnhzCEPUlr9i(0)Wi(^%f z$Y=qUw0qwP-z&HL+z`aA7IVjQhEgN6dPlaqnwpKYO?p#kD{n15Z9R=WB|X3P2T9iq zYb2++)_88Oi|aIFEOm4r8wvKxR8)U|*94X%SLd4Z0g8=Y)^=Xq-Cb}!1>uz%#EZz| zb7UgVvjhr@@`}QHVXW-Lto-&%9~8+W>(q?<2FI(LPybr;-pm>3@M!y?Etb9qSq(DS zXVT1MrUO&V{j27AM}i#SVTODXgdUjTmUgW~XX3})v+3IF`)q?-N9~8T@H_Blw;)3! zhMQEV#!AY&t6Stylt=n}M;v-)Y%2F|Zmm=F>f_zy$v_Arlg)la8r2@Gl3|zC_?9brOJf zL>a80Z^&xsI#QTd7TCDzT4-UGAiI1tt+~ao{5{k#pyd-on2E#&`oO@jb>nv44RN{@ z{v4fl>q(M^nJr>J_tQ14yK{LDNvhVZv$gbr!u-kc?G`oe&&!{wfko7yPZ>p7evZUrD@S8-o|^ucXpx&SX}m|s>7A*8~M#r0XI{AZ3BhojTV0Mv0682iX4PY z5q=_v-p*Pde3Eo|3;a%tGP%{`q|H4oE~hGDjowso z7UGu^Bp(?gK)tUeIa^!EC)4t!AP4Ai2bZ;K&7tmb-ce8_b)bL5(zXmCvb(zJz<7L; zFSNU{oHm?8nm{{JHG5I1#Wm4bvApLh^_{th7oxcrcf-)sB);i~f+XdI@`+10j>Cna z^lrtR26X>4cRqWo5Nk0RpG=W!JL#3|_{V2W4f9=FQW89QiTdviHZl^?B>uYlo&lkV z(c;?C6MR8z87MJ2S)l!EtX+&|Bf#Te04NR)tFvD3nl(Fh*r>!5I@YZVJk_WDyD8iq zZgW3&xcSbgLlv z*(wIeTDpf-G1x0mI&|O<0!r?ku51DuDY(=uc$Tx~WFN!NBq4i49+$@l)`@5IZL1g? z%AsJXK}$jqylrhr)#Fd_jcEHFiRpPbkA_9+;a4UikKLcdI_?cRP|#s%`txlAfP!+J zj(Zr-QnhE{9B?(2r6LiuryJ1$7+%?&ezu3ny3ui()>N8e?*c_v7&ogA_-hT6nAtE^ z0rGsv#aEOJs%>WY2mV2&MG4#QP+QOgCUFkR-=xPmv61SzQWliQLJAhL{=2 z=01A`CZxaTw0a8Hnv%#bu$eDiTL^woFVq!^f@jd@&rR-?>+0DF<9~E{>DG#T-BEud zY`5n>Y#ItC&YN#q9!?Y_ZH3%D;wl^KR`$UZXe+3Q4(*1oiZ$|t<24;elyCeEtTzMk zTZCWK?mEFxja5Y5{-eTkC=0)PBikwlVDtQ-JQR}6P5mK4Y+pk?Jd-e>wu7nKTKyguaHGHWq&YdF`aNQZ=UolC91ji}}i6|Js*F-w_6S&;vou zy(we8B^UU;A*)sQ@)km;rMQvH47T8(2r{gpzOtqf3J|&k!ECWms&;vY+)v3x4@ljh z7jxPE9gk8WYyNwk4gY^VA@0Gr4Klj2g->Z9tK=)AS?bN$LPA-!B(&@FWZl^aKRO~* zz9qT47Z(Pzq&dk$9WDY7x8(aD9wS3N{%S%r#4EK;DQy(7CKF7yKy3%88@oTX_@1Yr z>AXuZv@rfrA$<=)-3&t7?bMAU$i_jj}Acki?e8hI! zTWy21&*A_$DE0wiy!9H?sopMY~J(u*V<@UWvI>d z8o2};MXXhf9$R_p&eZWVV?N%wN(W*QB!l8RcjkflP|p}6W^4Xf{lm55sMDU`uT@mI z5O!L+*SK6pJ_4d){m2&sLw4P<#j$oAKj(AgD&)6sK>{_%isp11#_fCbX4zv)iwH9Zs6r!qjK32qG>6cF5$k2snliIKWExp4#zSz2{gI zxVR`#a@C9Sa|t;Mbiqf3Y@R+pgCXRH5j>Na#*Dh}A`?lsXWc2>dnxcrL8F5<%&Qo7 z<8em&&qkoqWLHGHV~5J65jTuiBjaxw*Q_ zlh?D;z<`G73l;EGC6On|b|_)icz5UA-0g=mYek`}LCw~&eU3Z=%B}XW#eE@%X}y@k zVw_JA?+ZT4xM$sr;xoMZacRJ5k>Y)7T|fXuQAw3^?$5+jP}?k5a;8rI)78H{&MCG( z5X=uQn@^w4vZaNM9L@D;r4}a&jIwUo?wXVmbm>_Q%D+oBKfm$fDI7$D2VzzB)2yz4 z0$Ti^p0J&(**giF<1Kcd*FzP|{1jlehet!sd1wn2QI3C%+bPMeuNs;e&ytZp{ZDYO z{PhXqTnO9?dT6cQ${rpNdv<*iy4$Bq)}Lr*2oY%cHH;w;5v7;?CId_c3!fx=9u+V# zI$S9n>;1G0L+IqVrsZcTSF)$hHB4H+KUEY8MDzFwv(08Imw#-7nqFRKAgb0~9n`T- zzMzq7wf%{ESMR@i4rVf5Z|-VnnfRnieDT?3&*Fn@OwQzELEK%X%Dx{P_jI%J<~h2a z7KSM|ui{Gl5psy7r%`ZBMahVDG2&bIiG~ZTDb?1oU zZa6F2JHsonp+=m(5o?MPMa^(6VmD&bE@Jgnh%UKo(;m7rC?ay#^X?Nj{t#)j=e2yLv4)=mE8jU#a-M3T|LAj{DXkeAdrfB zM39S{uSY1itB0p|fGQ8ZwUdY2+g+8%M$SmmC`i-8%Udrh*ux^q*wQV^*GWxzOvyu5P7DBql2eqE zcSX6${tKE(us0x+E`I+J>Lph007NNS4_QTNB_%NwND_b$RKEHk+M65 zNiaa0j6Q`a9L~u7zO|M33N<&i-i?)WLjDK%RcRT|&h@OF6P2$jKleX5U6=73Y8{%3 z-k2D!$Im^pdtn3@rK$!EKn*Dj$4&*XHrT1bxDE;x7&)jTwWuPu+dBw?w7xk8X$7%? z$eW}SzVDT_Ro;oJG=;!5rGPTP|M9qTgtU|i0Z*SH-Ap* z{sHMOOS$TC6^0XYJ6g84?L#Cb%KC;eXrRxVWQOWtE(fFwKx-g}n0IEHv;U_!&b4{Wm3i za$4_@S4RDcDveCOB!EeGGx4;qGY>ww;S;Ub8{c2Rb2jzM=Mx}y_zxn(tR07GS7F&z zUhjE6EfnMB)})NUKQIA{B1?F0-n3c@G5|zR2aV6} z-gvV}nrJ7a1eWZ#3AB`FYScX!+B#TyFH`=@O*IsWUoTOz{GE2s&O7WG!Ng!im83pu z{0e(|z}M9G6MrTIH0axe0s*|n&2D@whxOX?>T3-WsJG;u=dLFTn!uj*Xm70u%y9T8nME8(om`- z1=u&(H?EB{PY>o=k>mSn9X|1vE#>&1w-4L5a5W$c?axMT>j6XUu{fva}?^v$Q{^<4VdDXECV1^yLj2#vAJQ_U8h<{pM+?|Z`bVJo#%H7^e@Fo!@|I04CnB`)) zKcaeET%gHv3%OgL%gq2uk$>bXNtX%6XiFP&WcRPRG671 z$Q+?=HEGk#t9C4A4--Je(Bky{)pbPN!W8?e2i*d!{*hK8`)T_-%lal-V zJ)IIGFNs#z`!CLUgA`f@3C0U2f8G3kKa#A(7Nxmto>@u_uIP1;K17KU98;U2l}3QoZ_o;`8Oz({GE0T-FA^ydk}=$&BAii6NOp|r{3IR z{sc$tx0Iwx3=C9<8Fw#Zazh}G+S@U9+ePcRv!aVXRzr$xE%JuzP3!5GM|@Zj3Ofc`-93)EkZt9A9)TDBElNT78wt9hNo ze4O(G)ip1{j8I0BY~GENxq-nYwVWVB-xV15^>se>D!3wvztCxBkb_J;{igqUi1`A2 zXm6Zm*}D^f3UqUjzlq``Zb z;eVbY*=6e6{ydoL_mPW+kR@Bro(LBtIO0F3Z`WLD>A{yZQWs7aglcu{qt zX90!Mfsl3??@W$Ox3xa;yb}yCH&S8Ayj487c4^KW$MWRWhRnMjJ&#wb!M^U ze5NwR(r8SAe6#X4EdQeUBlfc(O9XX5<&hR@}7}PT$XHA8d0xUi0Qx@ zVVSUz<6l3=`DKH}LBx(9hE-(Ao?tVoA}sCB_<3zZAl6xowqSa8L)O$X%W-B%Vq zJEdar2beQ-@^)c{UE`(tF25e6xJ45rEa~{B{&hABdTiuiAD>w+I$FI2D8`AGBta@s ziX3EyS@=+4|6Tmv?ZJ!Ye;5M$xliWfgbPLIe~)KZKL{yGwetp0$*=u_sYiQa zUIZ7Vrq4)MfJa!;e8T)tO91Qmoi@SdVEg=?ly@hiIM6X8U;XL{7geaeK?QJL`fMAv z6T`4wq<8%^f%31IE2oOtZ?Bf0$>7GepJT6;pM~^KxmTQRRcEe4Ka*qSu+A7qO!y8! z0Rph>NxA>9>KWat>SOpJF$dcBD5~}7YdFRw*)2??CU({TJkPTsPJCPF)M~g1JA_Md zkpW~E>zA2~4jE3~<~fEnj>eQ6efOPS2369P^g}48&pbl*pdLmL=xcDS6Q$qk*jUS}nZ$ z%qi~ya3NY(^Ig0hel<~LI-z0FG3K4qoZl&qXgo3~`J9;#9s8vyVJfs55@CR?woHr~ zFweg-@z7bPgX*FbSK28r$!q^IHX&H;w0Hdex%f$0=gc$?uPocE)aVCyK)1-}O#p{t z{dR@8h^O=H{IQpJTr|=iNwy%4)H2>lmV}&fG$Z0xH%1PS?#4Uci5h*1R9_vUJ_JLM zdtUI+a%awyv(F=ypYM7-Jnc*q?yV9~FY7!J#zo&2I-b+I1&d%m4555C7`zO_t_`l? zkh9|-Z^wa$v|of?U)1y}k0#l(dB#@QeJH1R9*X6y(8pE>D1vLF>WG3!TDJK6s2AuP z2HFI+ZX^HH9bNebjd^QK5=)w<$`V9XYlbwecTfTbzvBwx8x|6rGhMI^y*HpeaZzQMXKg79FUHM4;3p(`>^vkYt4<1J*&2VyAr%-C17GdI0P8 z)0&~~1x%m*bYNgz2Cn40h6X_mGD$M2d9Zbh6%Y0;T|P3o=gOHJrSTq|X_XBOtJG;I zXa#O=7q8b*u?tP;h`lu(OQ|4XspTivp*Gab!S0NGaJ1D#B$gO_`?#F8t`n+G(p43% z4kjtvZU=)2@+3o7lH3@FfI%Z8xF4;loRV8yfjO%jal}D1}=ZXd=phS1}&!Ppu zdU=kRa_J0$RIQ3IzUot0yT=6VlunMl@7NCur*AfRZr2KsPxw2vW7E?YGZ^g=hB|9< z=x{dHk)`(owXMJsGs>j^WNTH8R>pbk<#AG;QWS25dpi^E-s2o1c`}>ziNmZP_dIL_sWcN7$ts2gf9{Bx)@{An+m)B|5NIC}f0=Ogke8lQn7Z0RMBz$mg z%kuPRq7waHr@W>-NNZ(dP<0v#A})|GVsE0cH%DVLXrD%d^Ni9bc2j`ZEbV|UJ(_Ai z`~9&%WO_)+egAVZPv&=Sq)l< z_MvY!91tut4N6+?k@ ziFIgn8A|=NEO-XO#m7F@x1owm>~cFN`X)qphPnv|2v2^-?u*D*k7DK{M2hncJHkaE{U8|#UGrim8(alVSsU|cZo30JpB?JKPZ^N4)-C9c06Si`&KI7a_yxGgnlV(Ei!$ji z-)YlSrSv=Q+?2Goo4r!-G^kB`xG&zW6cC3pZy4oG#kmD>67e2w*nIk5z&9&OkRdvaO&k)hhuD6uSqx*SI&IS7f(4QE00| z&F!dd)qh5yjPh1JYK~6m^&a{GZTcQ>ZCu&AvcdbDDhiM8$UU!7E<0O~YNFZ- zDSMZVXr{EE*#i&3l$Wok``m^+8XCZYkj-rZ=B`5OS2^H!i-Ov&q+^}1>w+x8@kNm) z+@4(EUViqkRiKS#s$s5YSMkY~59rXc*4mAq_s&svsS_i6uZ7yk3ktoe10iQA{%JUY;-#KIB&tfo5iRL)0sRhGqRdBehncAO4{ z!&&dXf(=$wa^6Mql*?r|3og9w5Mb`$l)MytwBvX0sHhPk^b{mVK*=B<%IQp$+NIX#z9ekW$G)7r_&i6O>PgvrT{G2|-P}{b63e&mz6cq=&}A?rcX&W~ z(PCMfRRC%j60PX^+JEP_f1y`_?(o-&zvN+?n*Oe-Yu9&WKB(Z6>BnQRW63@Gd(+Kk z`&w19GQhT^gCLSFRpoV0E%|)p`_iy|$vwiyuP!>UVf5kO!xe+rTI%-u3RcZSUpx8w zLdLGK_UqV@wtK#c3VdD7Q=}rELc9>P;8h^#;UO}5H?Wv!(@4_Tn(;<)#ZD~8S-iOJ zarM!9$bwjCz#>E6tjyKX4cv*Dvc=GJtNIZ)sfv|nSEPfeQX`NuqCrFv!G;YMHe;b_!wDT1Mp)OCBPsO zp%Eu1fN`l38OI}3radCSqkdZ7$n32j-ofktIXhFi?BvvwD&>H}-uoLhAEz&rsm!D* z_V860@0x;HRX65{F3oMJNzI3hB&|+ZrrBwwi?t)?z#DnJpV!A zmyV2c86-)l)Wk;S>hV&go|VwM+hw=?PWf-B*YB9FPPVMVKq$?y?{R~XoO|w^cuy`}7VE9FS<^3D1Cb`z0{4nQ)yexO zOkbmU!uYKshpd2=UxO8M{)qE_pb_&dLT8tXsT)1Ng$e#*UtLsT88p!*@Z?pL5r`F2 zZpxd@%{()r}@TpnNbf;!a6Q^3t1F5jx zBTp{uBTb%Xh~1aQ+dJkU)5a;GFAY>ybg|N2zvokaPs7{>l7@P0)h0E*CTIs2=~hJP zgU(YZ(+0D5RMs3TO_UD=SOv0_Ffnuwk9ekyBZLU=vnQd+Jp^Gy#|pw@VP;w7q2%5p zP$<#)y}r{HSQp!sHWunv)#hzqV$#K%g`SM*og5$2PVC1O1{wDE^y6q>+AD#Xo2QbF z9B~Q4zUM{*J3Ka4CLok9%Pge^fm2)IM>_-kTR&xx$Tj9y>fO_1&hT0J>^{kH{-dY& z-R$q=p%ZD->S@EGm}?lEwDC12MBOti?Zr&({^3TI1)yD>XysJm?(1}CkT@Fpw`q(d z{05T7m3&i{`}!(#Zv<~QZbwLdWLB+c|7e;OLn``Ed^@=o7L`y zpa&BYK>3gpFfU{=%dT?!Jr5s9br@V`IizLq$Xfi2E`6zKX-R?n8`SalNeH6tHM$ke z`+8x`aDo<{%{58Wby_>8#iUP9(2AmYp}2f1wdDjH+4q#toO2cOQ?U_S#&wF(>hr;T zWLi`Z-`e&;0HRvQGM;U(RN63$7L8lZTOav+kzNvE^=NUhMz8q%>TL4Q$5^#PwF_y3 zcchZe@ltEuBDfg!N;a?lrfpXZg0xK*jC&i~b7cX9q#JF3)(8k$gE((W>IkQn>8J=f zJm^2`3qV}D?!)r)&=`zn^35Vo-Q{1`iM#q7zSE&`Ptq|Jma9OV%xdg`EPQ$$-lOMn zqP29I(o|FZ!@Ny?mAh4q)@lM)aHZPV@HO)9K&pB8L$MGDb&U6CP{Zz1p8mJGA&*z~ z_V2_?nd8X)9(z@X45!Cg2M9UkbBsjiH_qO}sUCkRiMVdQ-M`$I2hwt$i6B1yg{SS@ zH+a`@#2~5oQBi1DC(+nfj``3t{_HBDM5Q{EIfH4nH^| zbSdGg*W0)_heewz`jZRo7NmEs)=~k*-Fz?6e&>{_im%{t6mzXW<#4hfLjfc#TQ%W{ zsztx-cTTKoS7X3k=)^B4F6t`I=Mfen=8#8g*S08fdXEdP(Q6=ldGxOuW%D+jcA`hd2Co;yGzgQ3 znK1)cchyZ6TD0TH$JSh^YdWI;EWd0y&cHJp=0@*9guFBAr=dL|ajSs-?FfCRh!ZfhpXqFAA*YzA{6h$2ZD0#<;Kk#tAHV%E5r>ECi1Jwc{P(4{^8 zvR9s&S8FW4!%wPr?lf- zv23C86?y<^*hcUJCh(p*^<(lZ(xQw?qPnIl`u5E_od*xho{OO=D>#oCT{+LyA#CL< zBC&^_zN0WV@)wF!1N3mj;{aLFCmti8V{YV#ilL)Ea}^w=O|N*4PnB8=#d`YvNGO+| zyG8rdD@=EJz1OT{HNcEtT5gzXc9q?1@jzL58BbfyEHpQe5-eEH8`VC!wgYps9L%hM zcph7ypKy#Y)V2%kN)Y?-4Ej#M5ouK;))rb1%%H+z3&w_sFus~TCJ_LvrTO&kI%7tq{&)3eE8PM}U;wi4!9GGka+P+O% zD-1-M3o$#xr3ViBL_2z*6`k3aon4TR{1ERBbTLw)2SJ zPguzhP6(s+EQ;4Jy(l?^sQV;tq*F2m?P;cDa^J#C-X`;y5S8VsR4>S9CPa%)GGX$l z_e#GwyM`?g;(vnv3d)>c1%hFn@rz<Wo;bOAc%sx}KU5$0y=p8!26c`}|v#r?G0^A{X;cFIeO_C}u! z|4g~^wv?_=fw;RKUN!o}D@6umVP0_m}bUgmltVE4ToH})dhXqh510Qtuv=BHm$ABooq+6<7ypPRIkgZ{>bxy)G{d>O1ULS z**Azz_!-~cPw~&Z9KN|zbah10Y61TsOJjF(+pxmK2sOpv`50uF7GQJCG%R$F&`Td< z`+53}J{-G>m4NpUUd@@Ew%1B0t2oTT71`dWkT-oX7Xa;)oqSL*QQ3U2a=$OJtWT~w zrZN*G3+B4U+*=%_OE~z{$KTR+mu%OsP++-4hz?DG@icv!aYc+t{(aZmLx5OxbYlW# za^`-}fox=2Q?snWx{pCG@2pNVzPLK+$zq{{+m(W@aOAzX8mmpno;6 z-VSrC&IKlI0jv1zimdPs6&n%%fE5_Vn}4*YLwjRR%nW9)h#9d30GST&G_B_OiAq5V zXF#_NJPhcr4krVL{8EM2vpS~cooHE=a3mGAk4;MJiNUeb6^1Sk?D>a@AN#Y3wbNID zv?Gx(D`B{&UIo(YPY+-Nw2>S!h;$#BPM#=YiLp8GbUx@$D&>MU`y319pY$jaU~_He z1ryH{J~9in<#`pTCHDx`t3{e%MN7Fb=_8d9TCW$#5pkBp^C*8K5WhywMgeC3yndg> z-bL{`ceaN}_o%HIEt*Pi=is+?xdzrQwnY~`P zTXRNbpAYg&<2;Y^>3u%EbPXx@L#{@@vH=3}rUeb}TboUP(^=C?P$NEL_4j6j3?+T_ycDNNex%4U zb=$ss@s0Se22~W>Y`VCT{!i)>-LiKne##5zHUXFH@~c>WoE@8)u;S3f=?EPl_jQHv zO}1;>LbWPE0SHl%1o|0J)iq|`EDb@po{RJXPRotq`)&GFeb0K8LU6SqU`q5yc2u!! zkp2Xw$I(o6<25Wf3GSvUz*G@X+)G6p2$a1zsCw3{+sMkF=+84?r=;$nTCbgA8hPEX zQAkV3md)BcoW>DjM{fe6y2e@sK9s1{DO^9B4Y1M=Yd{z7=QqhH5pIqtIzb9=r^y=D7}fYRWQ zxGumhBjGsmIqtY%)y0~4s$3YOAe|wjRO`aqa$CYz9jpmmv?ZuML#7=id23BD3fX`W z93A3_8HmTkl~a=T(p@S)X+5A{Y}@+VnvCfngYXD+sTcXh9qR zycenvDX9r5xVOwZ zp@goXO@BK25Zm;;l_w&!5KV?T8DY~k+l6rw5Am-sn$-(`qw&WNW>3MkH#OC0f|P*7+=h*;l??Xyo)0yHNP{sosAa{ zE9nlv@?rs(uIok`_aMhH1vVSr^X1cHtN}~LJlgnM=Y!3|42L^-%p1BGsH1}`sz!$G zc-%QjaT6R@Z12Hl9CS#a*bd8F%f_K9;rL ze|f_*Z1S&Y9yg0FfFdn8o#zyP-I18s4pH)^MH^wt(cyGySr2wbG>-<@T@_0DJIcgQ z`z*}*s4NDST7e6^BO4S@g3XCw@FznWhLo^%Hw{MfWGAocjqR2}TU_e?8iG*vMbk3a zWS&zCv2sxuN+v9OdXAqp)KbL>#zH{$YG)E#fuTpKR{hJ#J>QALMnvi?(7Zqn_W{F; zC1i?L7aXA!rrC_iUW~L#fR$()=veJ4MEd4e>4kV33-l4-CyJxtl%lPrhzr=#Kru4S zelTTI;dK_^OXorzLpmIXcDx!b;n=L?p4>-#rYzc5sVr?*t9F(n&L{ zJqx|2R_eg;niUsHM5!lhJvju+)}X>hg@!H&Up6`J%+RgNiOgN&G=gRl}Vlt}U~&cX!Ga6F>6*vI`aZs}Tq`eA5e^ z>OD>q-Z@K<#dG8X`mG(sJ(%^Eo$S-P-Kdfi!?MkqNG6VO3JSbbd=cD3BJ55bNL^)h{%Rhzzm!Yn?0H+l9QwuIM<<$Kus=w-uk z5pbGKQDv*MJ{p7o?J5t6SM0K>XLW%iPCrK6$7uWw>&siS!mlJv*lUtX>*PAbi@&<* zVlgiH%251<0&7RnftGYAm;NLdbMJ+Ew>-jgTghEXy*8%o-KL?ev6>;kYx3A*}qt@nZR$ja5BdNY$Hsj;`Ss_BsiC?BnX@jm! zw19WCLxaM@7RCst(SV1G5D|W?s;yi$bqg>e+fDLXrBa>?;}b__eQ#O!8(%BWzgn** z6;0K5XmRFhqPKaLm9Of&6sOMIPBWfQHtZBcx?^4h#gw10C^EJuTxfhSxfgdoPfm>Z z$4pAx1^2*E9sPkWZGzPkB*M?So}slw|0N$6=9p?fZ{aJ))RHM)dGzfm?qKDk z=sm5>fx9r#)NDmw(S&T;Dbf@L`L7l;v71ZdtBwomrEELqN(J%rU2>fB4~X8uuVL~VNHmj%ej)X^X5*|A(J<+3yfM!+UXw4m;Uv+ z8J9`;gZ(moepA?htIR4@X;@=mzu`s)NLAAO85Q4H2p?;pVd9gfFJs}@&+uN6C-$4| zijVG6{&fdr3<}WMsIA|Z@$qS5l2qY#I{Zv95B;s%wS5PA3=0!Dl7%a!?w?6>C1ek* zO&~`*UcY(Bs-HE>y?{jWkFzZ?rkKe|QuFy6lwVsG^toQWE9I?DbODb@X-K@$TGGA{ z_oV4`UA8h>X}lljV0&ve^~onM3x!^_`_E`Qo6SgmR6$+OIunXw4vgQ8|446&sl#XZ zuuR>gZk1Dm$n$K9-bl^GdZ!t8~Xqbhk*~b3T(2oEyX)FEmx(y(6H}`&ZkWLH4va11j;)5@)L`9%7Bb z?{p5}QBoWq4|77*G}~vwKU2jG=l^^R?_JLjpZT2`G{AGn(jMstHN5BZQD{ zJ1i%v;~MLDefHEkW+>ydi`#N|&wq9b*ndcmIJ`(W&hbMSC&uj9gPCW21ZD7#k(@OI z=iQQY#Z<`_B{akE)$ZEAmz>>@Ptq#>M9xdcC0OqUT!TpRa^kn%<*%69Ocao5+e z)3|U%jzs!~uVPElq16IIrrxjH++iG{Rzcas^1TrZs$Lxk_}9>i`UOL;b)?;B=3#oj zC$8YeZnSw(OSyY6Vdd>j=DpZMNkUZ^UHL?^So~s4a-S8K&uZL!Axs3Mtp`GIoWId+ z7SWEN`S$2ov{B^gi;+H~KGpYoBfo=0Iz-Slz#V{gG&`~F?^VI3W8;A+A&LCUrit{; z%n0DF;VNG)s`$*AT4E@(Mjqezb?Lb;{rF<%`xl{hf9|OXS$z+|F&g&w5@Th$R8KD+ zY^Ej6O*2=OT?3Vjy=^URZ%P^)oE;JP=q5CI@G%yT9G&A=BAQU4GS!|hJGftYOkAUt zka_nzJ`~~6sPJU7mT-7_pVewZ6B9e9eRs<-Lzz>%;if+#r~8NI4;GkjzDC8I@(U-N zY5vSjrujjfb!R0@@_xQW*vE#+e8%@&vl7%l#TwerI!hvIth-u+?M8nQL;dm_Z$tr&>Hk+2U0q|7%@GFy?O z{d(jXN{6H94n>lO|JZ#_$ZKa3k`iflk)}Y?km6>bG=irY+PkWntlaQ-UWj}0%IGGs)S!Lv;GAunlp*{#NBM@BCCg&K{7j9kg4Z$hG8)$8b%!(lDACs zU-|v6Rgzus5Z-f`dSL64dmDGvUKZTMb`A#zsXFdIZB8E+%iaOPP|$XHi%xZqmxgc< z5MDRgrB1@7$Se7@Y@5`z!w2l~2B9x}*LFa&=+S9nCLjV@OrS@X6zkf1^o~TenXrLi;X`0RtgR4h))|Z--UVWz>=?Crye7 z7Nt8|uYv)aFdRrjH@ygFSYtAKt~&J-SE^1mebCI@50;R)JrS*IKs%J+{>bqVRPAst7B zX6-_9-gY!4`CILY2Ag=SS6*kM9o3%H5^srkx&E@xOx?0U3{0~}LV{#gD zYOvtCt0u#y%zNSN1yQQGnSwRP|Mecjw zRR2FiH%_p2M+}a5mN2FV9&S3w$k38-(p^re<0-9*Ux#7~8!PI!I*5BsrgX;Odt2WG z1m~!5FNe!GR{l`lwOQx30uI4IO98w?+#P+z<0XwJ&7X&?=qj`AoEL29PV*nzGWEiF z;IIS-e!)3k+@qrz4Z?KUbvP}$(jT1mJIt~?(#^DuVZ^26#l6Mvc)3z2(k_!EBY?Mo zuU4z)_JEeL(`{ZF2IY3HXMt^Q9tUTh_JA z*bBJyLo5~~N>n{$3_PV9qN1gxUc4_Zjg?u2bqU`Gl0As#2V%r3tn`60mM>|;h(GEa zkdr>o<)AfOhd0KV}LLZ41?57@1$ ze9G^}E1zngnFppDr{Ila&I58^)Drphs+l0F`4_Gs!`8O8?zDH@Oh?%02Ivo1Wr#If z73vT8Tgmq|V=J+R8ibZ|>5s1nE~fQOxcdPiZ7I)c#_%H?;)$lOmS^_z0EwiO3rVu& zC!Tnuju?oUknj77;0mQN%uUqTlnNB zm@EC|bZh;@-7+i~@m)nB#mzoJcm%{&K(Wb=r88%(fSmW+D*yV=l^y72r>PO|6Yb4o;)5w!-^sVs3&-&VM`h7(4i>fKsP@W;4 zZ$N>;2`i5{w|7;und=t!nj9hQO}&(Fd#)MDAP^UKg2=<>$Wn3=H{WvvRU~6l(S!E@ zqd=IBlQP9q8V)#tY<6aHrPePb2q#W^_=T}SYtgDwu_jIpf;jXJG=aFg-yod!8pz2f zpuKol^Cvu1o_n1#XsqnBNMacqOiZUg8xgk~Q>-%oI}NDi5Q+IHb$-CVV37&b=HE?f zdfRi#Dm|@!K`rf8r9gq5$HYQt8jtWC7_hHG^{VHx=#Q*+%%}0ipNH{5^d0 z46s4;5;`;l>WVd*%T)96q1BKUpv)_rFwu*vo|Mrbq?BBTTZ8Uisg2>(_24_Zx9Feb z^aHrj4T;Mv`bEn44utba3!kX5XWcAJppB~Ew}nMyyidGn$F;vN;ynMN2=?lHJA6L* zMT4-S9XJe3^vg^fVVl8p$v*5e;PrX3$0KRcVeyW3nleT)kC)q;E=aVN(Z0Lx4v??P zpGV&_(|w)PKAt>+$%}h@#sWKOr@fyl2&o1NMB?IGaA5INzqKvs6N z0Q7u^(&sSP%Czl;sm;f%ctEqwl8D|P@>D6_ai|M+n4_z`aSOU}AVEWqrws+2+$)L5 zT7_}f6pthmRLxdjR~y~q)FN4`a@!e({kX68c$b*7KV12X(L?duQf!V;``Au04xYhj z39K|*Zfis|w<3)u@xHY8(C>+#F zy>ogUH7>_4(fox12Vf)Y#t9 zy5&$$)Wr&Cr>Lg5%SQb+0H}bU3&iOUs9_Jl1pXV%L4~QxL3SN;nt`a0GO>6vvsZO> zTSOvzH6puEiBgN1S+(E1^lqa76vq;f>3QSSu=oo0cw>$<9CM|h-Z8{f$J_eu>yby%$ShS?Me z3bc<1zezq6sP7hL37p4|T^+vIRHE+ID>aXlevbGcU*w!=&zI~!90OtvKlzXq)l;i3g zP1`iEjJV_ADx*sUqXx4T4KI7~p6TA=285h@lRTcG-}H3rtxX8%A>F}N9IF-m;ZJ97 zV^GR!3=lu}Hf`%HG5AemalCG41yC}I}h$-iYm~<=TLSu%SX73{^dq~Uc=uE}6C0?gltr=DT%KSQ8QMC}F zK{(fU;0ltdnsva^;a;|1F)8!!BZ*(M;{o9R)(lnLS}chWgZJhE7*xkh>iUN7V;iVY)v)De|>c;Ji*-q(t&4fwfS`ZFGb>^cFZ8h_;Vzvqc^` zLPXP*-+R>1%r3pC?iVXZPL$NMxNau+kE@%Pp#s~g9+y~;tDDtE|HXpin z@RlY*g$(G9vCl)hVH(kaki{^tI8grU2wlu&F-*&z|4OV()OAAY$P*fj5GDHsE6Ll` z_E7o?>jp}b*kz+lRqCVUsDxW*cm(H`aMr_#08O|BKofgXYuG(JP@hm10`?^Vx~LS` zN}k7lEQz>)Si{t)QanEvfGlU;0+HR-gJE=pMG5Lg@8VRpoiNAn_DMR{9ZZOY$dl}Y z6G*!13l5E1)Ep9w$vOeU0IbM9YpZC5P^lNrXPm7KN1C=Zo9>ic5OvqH0~_X&DBUic zxwlx8(f9P67U<0K$=2x96ag3yeAdyyN&hu?1y<5Qu4D6nQ^)Z2f+)5eD-KYl(w(bB zT~ZTKO@p0Br(?LFi*_1{;0kpkth(9i8x1f)8Ol`RpA-OCe|>)c@c8&*p%^Rl7{t}? zBQVnWpxpc;V1fAGQ_dV@0oL_f(d>K(xOLI=?IbDyBBVWVM9c}ymQzt(aIP58fP3+a zxmn#k*fH#>24M(f4FMXdJc)qn-UM)K`P@&SIC`D(M|{&&WmuPtNL>cND$2-Fbc80_ z2z@p(*Eu020GeFXFJamON@a)_OiQ>V!vLc2uckBtkp((CP;!vp=s$K2&?l`g}1v=&X4bceEdY*4w zG26xBfQyV&53LQ-L#T+*2!Q0}jj%Uy(#3B9ULhVLdIRDoP4di6{I`?jTyihglenvn z7O0M0+Tj+aivoDVx?=464-LYamG18EkkXa6-UQ4*_25I}qdvjTIJhU_c(qAM9|&kewdSy^vXAYc-1IT!@BNjhrv9 z&|qUK#QfA7dLyPj4&F zZT(%0qAjV(XJrt18kyf4`%Wo4eclNhpz-d_Re7oA6rew4g8;3)YG3vhD5NBte%kad zpIMQN88N`Qb9eCF5Kn+egU+$L=q5Lo}L(@$+7<0v1e zQ~cKf|9<#~cS>mo%M*MPzwHs7--L zuhPJTN}_*ofhJAOW5VHCve?(g6yWTH?q-OyD~Wt&j#R^?JeOIUi(skj_;O1@HfLw# zhvD9Vo!5sFUpZHCTwMwP_%%7nK9M|czKE``c;sue-Eg;fD^T0{?i<6Pe>PBs3bJc^ z8%yPf_`xu1O#)Qj`%IP5eR-XqF&+)6RrV>^&1n;m=8OXuX%?HV+pRTCzb(OUhyjrf zi&Lll73Bk}MWd({i0gX{2KSttPg zPAp7bgQ+yF;&yfKh|A|F8KR);L88mYj{aI99UNyIYlDtJ$xni~({$dh2q_BjVa~F; zz?j5Z=U}keM$0H{L^LIery6La^{JIVzApM?N2Sa$U3|djtqFP=vomVg>-Ps(f^cM~(|vKB$!3nsokKq6 zsB(bHk99zCP)0;zevj4AE_togwC)?G`m+AaJAKhSEYt8T(jg%87XRbHF~5p3CJyfr zS~c&|Ag&~D@M!}rW)cGKFTLk#QD)xBhM@{6u;848y}2=ePp=rtf%d+)6ieopTuHRn z?SmUT8^wKk?o#1i$5pu%h<=9kV6pPEZTMrY^fGmiC>hBuDff{F{0sK!k9C?pD&_~i-esXoK6XWwt+e6cL;Y!@0gZuQ?3I->3qIqm;|dCU4BAb_43tP6lY3 zdIywao(wVq4X?FawL<>uU$;1F#e-l{iK8gZ`KKIE*MAI?YJpsU#d(01QnK+M&GX>BZJziQv8L#oeY^H-M`S74`+_vXf`U#DT)8 z$Ea8TuJrKe7i-K zFi0s|B|Dk!rEW|aH)IKu7OpHQvh({q?tT6K)pVY7&U2p6d4JxY_xtnwT(E~pPq5|C zNBi>+*tFNE+s#YVX}H>-+ORr$Yx$$)wChJi?|zZO&wk`Ed#dq2*PEO4goC!YijZy+7c?}O82NvmKPXD{6vMTGOADC<-T6NZG(r(`w^ zyve97I?ir+o5I}(jnR|JHITKP^|5Dr10eYuK9p{gdIZM=p`i%wC%$T%*G@C_p@PzD zRtddz>4cRelpS^sgLnTTU|Z1Az$J?jW@s)}9Wx@(%;MqiSik?!@-O{s`j-3RIf>a@ z^E$5eb(JVw-UcXkGf75srh!P6c!&;rM+Pr;0 z*^MoT>!@X}-}dOs9Qum6!#p%_7+lfeZ0$hR7OiP}rA5JyuR1K7BlB}hGr&&4Flg=M zG>-CItrLgZP&ozD#pdnb&7%{r6{^q2;v-&oU4@~f8vg}+WfuOyLn5r>)|&oF0fMY* z!9^Ku6oT;JMLzg6xsieD?U(}{zf|hpo-h0k*GwRJmzyFbFL!nlu#X_vJ-R9qd7ity zaoO16D_D*A*3R*{LzcO`CLqvcQfc%kI&S@RcNJfv&ecK5Vj!iJpCigTzE7-RssmWe zs=bBL7Xjv}OM=xWWf9ws5fW%Vi@i?l#t0Ca)$7}%QkfMXh>LACTLNR5I(YY;51X*l z>I{*agl4&$B~=h0RCw!_8&hw})=t`gjn>Gtaf{Gkus>0C99+7Rxz&#*beV>vBlQ5Y=;8zKbf`eFX|K(#4t+_ z$zP8%R2a7W+sx2z11nx$`;`udXj&FBDmE@bZqtxJU1iEhnN>W;M4)+ze5J46NHMMJ zi$+jJ`^dVu47+AkexPFA3id+<2=0%-$+1^fM<5-TP_XjM5Q7#1l5jmiF+&5jgDGdR z7grMu`)+V{ar!vb$h1bOcGNMd=lF_V#>g zZ$Qz)+pEo~f^@Mv`?%e=Xb!N74u=#`w+T7PDdVS~B15RAZG#RV5c9R1{%IS3^u7?w zyITuZ@RzfN{v6Pr^_%5?F6n^!>ylih)H7hKFN{{!yF=hGfJfTQ8uBANwA>d6sdE#t ztt%y&5!fmV`EA?n2=}ejktJfYt}k3e&U!cV2dD0nD^eDbx}K5WnJ&)4-UVHS?O2as zYj14X_Ajin70&2*PcUlJ{W_|HCw!XL;sE4sISe`Rs^zr7zGK)(&>0$7!qik<`^w(P zk$rcZ9j*{PJNE1BKk5v9{#_8zw{KK~Hq3DxMTf)Y1`0h9G=f)M^akQ}&G$HAf`kMonGRnrUy z$(!NJ5M#JlG@Nm){W^s8$XEey;59v&K#ECF*Ds2LOFnTQnvlmOu5WN@M01gZu8paU zg9-KoP4Ky2dqVDunUav45`8+f=0PMp?QnOPsH%2_=HO_;U4ZBMl6TTB8ddxwR96)}g9B8UFT@e(4TfOdF z{tVi1>{7e7BCWOQDP+QrO1OB_B+;Wbp|YCq2H_Nrwfppc-~N+4CFxBuxm6I zLaO1cAS(nJtNVEX=5}%lu}5wbTigU!v`Qu6e?iL)d+@YKo_I=u5@8;vigT{{mS2YD z^4D{+PoD$_T4)q+KZMapebwQAc5Mfsj}uS-AkQA)`W=*Jw-uU;CUhZ28&f+DNs-&f zm@w9`913{QVZ-?poDS!Hws?~<&ci16g@JE! z+StvH2@lLO?Zgyg{i2P;bogtV&#Oql3)Qg5 z;}FX|Cul)e3r|uTW_eTNe*lkn_YS6K-cQUF;cg^W)qKEW zZ}x2pe_i>@>^Fl)I|5F*ZFbAgtV3?)%97hk#xr>*kj5yK?XfZwT1{?^EWHjShIU24;g#LMSqS@6 zOt%1nP+QE?jPeGyFR3-sTLkC{V2UEZQH0W`yM=4xn3)U?(@Qz}e)3vPVZhMdfVdAe#mPSCK)VIQ6K!0&E(&?cW`=eY{wW2zxnS#)W4kae zPZthTu7zxM&51NorGNT-3Ysff=v$xsddXz*(;m1WGLi>WKru|PoX#&7tA;3|T0Qzg z0T>uw&RPPXQG-(etC;@iQ_m@S$xMrtH{`p8KSP!YHRg(G8I=Q{0ly9~T{<+IHE}=` zK2RA`r~=Q7Z`QBR0HAUEMm(Lcmr*1y)AfpH2Mp1|_*K!t^=>|ow$}9q0OJCbbPxH@o1quU;psNH9o+!4T_1dl@^`MUNEVOB5mPKzlI&~r$nX*28{B#n_3K;V(tF+vYJg~yTa)7C zHuQ(P4&vQOcW^<133D=P)4M?zpRwuPtI@rW%i&hu2U}w0`8o|Q?}aQmFuMYZk93ln zLAg`)s?oVvf2Io47)sMd&=ThONhhrepcen~8{)QZ4?Fg)eY7!PfE{We1uc9!5F=6z zDP2QxP>dtcJTaF+Td>X*8WVjlxFsdmruLhm*`OY0g#$ek=r$@50Z_#A`Yc70>12jU z%~*YImjXJtq%caFcf>8Wn5TWg`gM5*&|(9M#ra2`E5!^$F1&35nr3myWc9^vZAx*@ zXuqZRUQb>-5c<53+3zCZ3#W9P)DZzp-LzQZQGhPQCQh6GpE3LdX#?5MoIW zEVOaQtnA}?worSE8+`dikahevRFAx^_%e)Dri%k7v3YjYpkna{;gNVCVCguJdgii2 zgQ2JpCg1UUuL$QZ1X0^L(iWEeGVtmk1_j;gF90-bKmVu;+ zEuVu3;Uy3#&yxtHfK;UJ&rfuxeg27H_4cMYy?An&+MrGy;e@~~mytaApTIt~BYgab z<+NHm$wGE1Z|UiMu>^hL)K`8uK<3_F23qTMC)&#jh2}8oXNYI7cW^2>|E%jVFm1>P z>YK@%W{DLfX| zy5twF&}&hm3r0=H(DnJ|1~)a30GGds`3_0YZ*&lFODFGx+3ac7|}Q-^C2e zCsf(phg1zxPgn0tP7zQ1s10q@2W?Am_PI~KDcfPoL|E0^!piB&s`LlONctlj@3Z+^ zLi6IL9zu^Ogi)IQa_nN>xR5a58=PzD0ElwncblsZbclC9XudUn1zv=fa02u{n$YO9 z=l@{3WK-Lt*FGG1e*OYh=Qknt%|bbVz|yJhrVG{jQ!=IY`hlVzz@eSw*&9m1^o zQ20AN=sFIZ#~fFVAp%qT{!{6ZS|GT-7CPdKlm9KWJDTJvJ@ieW=`M-_HWx?dc4a}t zXhZ3fRWdo?NM{9K&!Z~H93WdrnhCk;-}B-3Sk)UV9Sr~oL6WE^Wb z&KdJbtpbMSa&uLPc5K)Ij7dRZp?XvzB+u9Emao51itzH9n3fCN;99z3t~j`($osiW z=Yau07kHW%wgewE~aPDN-R0DK9z=0 zJf+_X^;00LTW=C)u9GJg)y&W#OrxXRP=a(eeDeeQ`8td%j6jQqM922%3M#Sph$%|!Y4>6D99cz8@E!&>FU{%|6^vUVkAGKp z!}8>7M2xPd*EXd*X;>40@KK?+DxzV_W%(1~)GBwLsnTn`h0%yE>pcM?D0yIDS|AsK z;vi*|+uaBi^!$q+LIN$tG@3Yb0)Un=)*~4&Y8yfl7KKyk)jKcXa;@fJX)P=x4S9Ul zc?QoGa&4`>1=pb^G$5RYdy`ut=sM|7yPY2HrVJtLjiu*U+_6u1hBM!hF~SZ#T<|%6 z3E*Z1?GB*y+F-HH#NX}##pc|JXNWuMa*cdHy2a%`AGfC=6VBPZ9s2QDEFK7i{}5>;aS<~_ga}#Tu_WapS8ARjoM0$D=3XCcQ8L|1K}H4L-!FIR zD-OOJ4g5et_k+7nhO{MFD|5*}k#5Mpz&E|1v5BjWq>Ee`8v>E>O`nXBDD)*bn}jAvP?p>wZ`$$i4!nm5hgep!&ClumcjJp^+OVWMrkZMVqaq}Mn zyu56tx2bJv*seOr+9|MeuOX*;_?LL)q+{k6U0>`Fe;7witJE#3<#ZTohtV*ox7}{Pi+yYJ(|= zICHr>SDE{sjwocu-MMbQASQg>7siv!#kS!_1YWMlh87gcQ1>v3nd~NPcpfhDCe(N- z6!p44JL0F`M!i9%7ZJUg@p?l@9}<~tF9<#5g-jK@CW_y%r6&-1#?&< z_1s$6P1^s~BD+&^Fz)eMYS34(V(gNP+1IEc70h-}iYMo1z`bQF5%wa*XJP9LKjJbz z(D73etV83X^sX1D)^B>yPyIBXL4FcJse0z-Uk8@FlFAi6%-F z9>dT6vc{lZy{QX-AHPg25oj>4S{tbm$1vYfE25wOGjbZnkXhX5A`yDteeI8V_y_4s zVqg{>et%(JX7n{)Yd(vL#e9=|wB_TgZ7_)vGTlbxszNresV5W0SK@1PP8H>)z??U| z4CHU_^q8;hUM7}0U@UGEmnB>z4MpME>MUjeuq0bo3b%sOF?xe8Q2$wbs5woSqY>&N~ zQv2NS75b5{`<6!zTHL=C1>>llHLANYx1Im7;~D)lR%NMwcnvf1h4*Goe7`!{82^CH LA?vcejOhOZ?O@z} literal 0 HcmV?d00001 diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/twitter.png b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/twitter.png new file mode 100644 index 0000000000000000000000000000000000000000..983f325a3a866fc4ccee54083eb936853f93e920 GIT binary patch literal 20535 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 H$n5_Df`3J3 literal 0 HcmV?d00001 diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/views/helpdesk_sla_views.xml b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/views/helpdesk_sla_views.xml new file mode 100644 index 000000000..230318467 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/views/helpdesk_sla_views.xml @@ -0,0 +1,35 @@ + + + + odex25_helpdesk.sla.form.inherit.reminder.policy + odex25_helpdesk.sla + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/odex25_helpdesk/odex25_helpdesk_stock/__init__.py b/odex25_helpdesk/odex25_helpdesk_stock/__init__.py new file mode 100644 index 000000000..35e7c9600 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_stock/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import models +from . import wizard diff --git a/odex25_helpdesk/odex25_helpdesk_stock/__manifest__.py b/odex25_helpdesk/odex25_helpdesk_stock/__manifest__.py new file mode 100644 index 000000000..95f24a8a1 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_stock/__manifest__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +{ + 'name': 'Helpdesk Stock', + 'summary': 'Project, Tasks, Stock', + 'author': "Expert Co Ltd", + 'website': "http://www.ex.com", + 'category': 'Odex25-Helpdesk/Odex25-Helpdesk', + 'depends': ['odex25_helpdesk_sale', 'stock'], + 'auto_install': False, + 'description': """ +Manage Product returns from helpdesk tickets + """, + 'data': [ + 'wizard/stock_picking_return_views.xml', + 'views/odex25_helpdesk_views.xml', + ], + 'demo': ['data/odex25_helpdesk_stock_demo.xml'], +} diff --git a/odex25_helpdesk/odex25_helpdesk_stock/data/odex25_helpdesk_stock_demo.xml b/odex25_helpdesk/odex25_helpdesk_stock/data/odex25_helpdesk_stock_demo.xml new file mode 100644 index 000000000..bbc0d88ee --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_stock/data/odex25_helpdesk_stock_demo.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A first stock move + + + + + 1 + + 0.0 + + + + + + + + + + + + + + + + + + + + A second stock move + + + + + 1 + + 0.0 + + + + + + + diff --git a/odex25_helpdesk/odex25_helpdesk_stock/i18n/ar.po b/odex25_helpdesk/odex25_helpdesk_stock/i18n/ar.po new file mode 100644 index 000000000..fd97fe8dd --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_stock/i18n/ar.po @@ -0,0 +1,142 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * odex25_helpdesk_stock +# +# Translators: +# Mustafa Rawi , 2020 +# Martin Trigaux, 2020 +# Osoul , 2020 +# Ghaith Gammar , 2020 +# Osama Ahmaro , 2020 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server saas~13.5+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-09-01 07:39+0000\n" +"PO-Revision-Date: 2020-09-07 08:20+0000\n" +"Last-Translator: Osama Ahmaro , 2020\n" +"Language-Team: Arabic (https://www.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: odex25_helpdesk_stock +#: model:ir.model.fields,field_description:odex25_helpdesk_stock.field_stock_return_picking__partner_id +msgid "Customer" +msgstr "العميل" + +#. module: odex25_helpdesk_stock +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_stock.view_stock_return_picking_form_inherit_odex25_helpdesk_stock +msgid "Delivery to Return" +msgstr "" + +#. module: odex25_helpdesk_stock +#: model:ir.model.fields,field_description:odex25_helpdesk_stock.field_odex25_helpdesk_ticket__display_name +#: model:ir.model.fields,field_description:odex25_helpdesk_stock.field_stock_return_picking__display_name +msgid "Display Name" +msgstr "الاسم المعروض" + +#. module: odex25_helpdesk_stock +#: model:ir.model.fields,help:odex25_helpdesk_stock.field_odex25_helpdesk_ticket__tracking +msgid "Ensure the traceability of a storable product in your warehouse." +msgstr "تأكد من إمكانية تتبع المنتج القابل للتخزين في مخزنك." + +#. module: odex25_helpdesk_stock +#: model:ir.model,name:odex25_helpdesk_stock.model_odex25_helpdesk_ticket +msgid "HelpdeskTicket" +msgstr "تذكرة مكتب المساعدة" + +#. module: odex25_helpdesk_stock +#: model:ir.model.fields,field_description:odex25_helpdesk_stock.field_odex25_helpdesk_ticket__id +#: model:ir.model.fields,field_description:odex25_helpdesk_stock.field_stock_return_picking__id +msgid "ID" +msgstr "المُعرف" + +#. module: odex25_helpdesk_stock +#: model:ir.model.fields,field_description:odex25_helpdesk_stock.field_odex25_helpdesk_ticket____last_update +#: model:ir.model.fields,field_description:odex25_helpdesk_stock.field_stock_return_picking____last_update +msgid "Last Modified on" +msgstr "آخر تعديل في" + +#. module: odex25_helpdesk_stock +#: model:ir.model.fields,field_description:odex25_helpdesk_stock.field_odex25_helpdesk_ticket__lot_id +msgid "Lot/Serial Number" +msgstr "رقم اللوط/المسلسل" + +#. module: odex25_helpdesk_stock +#: model:ir.model.fields,help:odex25_helpdesk_stock.field_odex25_helpdesk_ticket__lot_id +msgid "Lot/Serial number concerned by the ticket" +msgstr "" + +#. module: odex25_helpdesk_stock +#: model:ir.model.fields,field_description:odex25_helpdesk_stock.field_stock_return_picking__picking_id +msgid "Picking" +msgstr "استلام" + +#. module: odex25_helpdesk_stock +#: model:ir.model.fields,field_description:odex25_helpdesk_stock.field_odex25_helpdesk_ticket__product_id +msgid "Product" +msgstr "المنتج" + +#. module: odex25_helpdesk_stock +#: model:ir.model.fields,help:odex25_helpdesk_stock.field_odex25_helpdesk_ticket__product_id +msgid "Product concerned by the ticket" +msgstr "" + +#. module: odex25_helpdesk_stock +#: model:ir.model.fields,help:odex25_helpdesk_stock.field_stock_return_picking__sale_order_id +msgid "" +"Reference of the Sales Order to which this ticket refers. Setting this " +"information aims at easing your After Sales process and only serves " +"indicative purposes." +msgstr "" + +#. module: odex25_helpdesk_stock +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_stock.odex25_helpdesk_ticket_view_form_inherit_stock_user +msgid "Return" +msgstr "الترجيع" + +#. module: odex25_helpdesk_stock +#: code:addons/odex25_helpdesk_stock/models/odex25_helpdesk.py:0 +#: model:ir.model.fields,field_description:odex25_helpdesk_stock.field_odex25_helpdesk_ticket__picking_ids +#, python-format +msgid "Return Orders" +msgstr "" + +#. module: odex25_helpdesk_stock +#: model:ir.model.fields,field_description:odex25_helpdesk_stock.field_odex25_helpdesk_ticket__pickings_count +msgid "Return Orders Count" +msgstr "" + +#. module: odex25_helpdesk_stock +#: model:ir.model,name:odex25_helpdesk_stock.model_stock_return_picking +msgid "Return Picking" +msgstr "استلام الشحنة المعادة" + +#. module: odex25_helpdesk_stock +#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_stock.odex25_helpdesk_ticket_view_form_inherit_odex25_helpdesk_stock +msgid "Returns" +msgstr "" + +#. module: odex25_helpdesk_stock +#: model:ir.model.fields,field_description:odex25_helpdesk_stock.field_stock_return_picking__sale_order_id +msgid "Sales Order" +msgstr "أمر البيع" + +#. module: odex25_helpdesk_stock +#: model:ir.model.fields,field_description:odex25_helpdesk_stock.field_stock_return_picking__suitable_picking_ids +msgid "Suitable Picking" +msgstr "" + +#. module: odex25_helpdesk_stock +#: model:ir.model.fields,field_description:odex25_helpdesk_stock.field_stock_return_picking__ticket_id +msgid "Ticket" +msgstr "التذكرة" + +#. module: odex25_helpdesk_stock +#: model:ir.model.fields,field_description:odex25_helpdesk_stock.field_odex25_helpdesk_ticket__tracking +msgid "Tracking" +msgstr "التتبع" diff --git a/odex25_helpdesk/odex25_helpdesk_stock/models/__init__.py b/odex25_helpdesk/odex25_helpdesk_stock/models/__init__.py new file mode 100644 index 000000000..27eb465e7 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_stock/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import odex25_helpdesk diff --git a/odex25_helpdesk/odex25_helpdesk_stock/models/odex25_helpdesk.py b/odex25_helpdesk/odex25_helpdesk_stock/models/odex25_helpdesk.py new file mode 100644 index 000000000..e3e21f93b --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_stock/models/odex25_helpdesk.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +from odoo import api, fields, models, _ + + +class odex25_helpdeskTicket(models.Model): + _inherit = 'odex25_helpdesk.ticket' + + product_id = fields.Many2one('product.product', string='Product', domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", help="Product concerned by the ticket") + tracking = fields.Selection(related='product_id.tracking') + lot_id = fields.Many2one('stock.production.lot', string='Lot/Serial Number', help="Lot/Serial number concerned by the ticket", domain="[('product_id', '=', product_id)]") + + pickings_count = fields.Integer('Return Orders Count', compute="_compute_pickings_count") + picking_ids = fields.Many2many('stock.picking', string="Return Orders") + + @api.depends('picking_ids') + def _compute_pickings_count(self): + for ticket in self: + ticket.pickings_count = len(ticket.picking_ids) + + def action_view_pickings(self): + self.ensure_one() + action = { + 'type': 'ir.actions.act_window', + 'name': _('Return Orders'), + 'res_model': 'stock.picking', + 'view_mode': 'tree,form', + 'domain': [('id', 'in', self.picking_ids.ids)], + 'context': dict(self._context, create=False, default_company_id=self.company_id.id) + } + if self.pickings_count == 1: + action.update({ + 'view_mode': 'form', + 'res_id': self.picking_ids.id + }) + return action diff --git a/odex25_helpdesk/odex25_helpdesk_stock/static/description/icon.png b/odex25_helpdesk/odex25_helpdesk_stock/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4141f52daab6a780510b5f4e3dd762511add78fa GIT binary patch literal 32929 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 OgtWK + + + odex25_helpdesk.ticket.form.inherit.stock + odex25_helpdesk.ticket + + +
+ +
+ + + + + + + + + +
+ + + odex25_helpdesk.ticket.form.inherit.return.stock.user + odex25_helpdesk.ticket + + + + + + + ml-2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +
+
+
+
+ +
+
+ + + +
+
+
+
+
+
+
+ + + + + + + + + + + +
+
+ + + + + + + +
+
+
+
+
+ + + odex25_helpdesk.ticket.search.inherit.timesheet + odex25_helpdesk.ticket + + + + + + + + + + + odex25_helpdesk.ticket.kanban.timer + odex25_helpdesk.ticket + 5 + + + + + + + + + + + + + + + +
diff --git a/odex25_helpdesk/odex25_helpdesk_timesheet/views/project_views.xml b/odex25_helpdesk/odex25_helpdesk_timesheet/views/project_views.xml new file mode 100644 index 000000000..15d3024a1 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_timesheet/views/project_views.xml @@ -0,0 +1,47 @@ + + + + + Project Tickets + odex25_helpdesk.ticket + kanban,tree,form,pivot,graph + + {'search_default_project_id': active_id, 'default_project_id': active_id} + + + + project.project.tickets.kanban.inherited + project.project + + 24 + + + + + + + + + + + + project.form.inherit.odex25_helpdesk.timesheet + project.project + + +
+ +
+
+ +
+ +
diff --git a/odex25_helpdesk/odex25_helpdesk_timesheet/wizard/__init__.py b/odex25_helpdesk/odex25_helpdesk_timesheet/wizard/__init__.py new file mode 100644 index 000000000..2ba46a4a2 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_timesheet/wizard/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import odex25_helpdesk_ticket_create_timesheet diff --git a/odex25_helpdesk/odex25_helpdesk_timesheet/wizard/odex25_helpdesk_ticket_create_timesheet.py b/odex25_helpdesk/odex25_helpdesk_timesheet/wizard/odex25_helpdesk_ticket_create_timesheet.py new file mode 100644 index 000000000..aff87ba79 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_timesheet/wizard/odex25_helpdesk_ticket_create_timesheet.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- + +from odoo import api, fields, models + + +class odex25_helpdeskTicketCreateTimesheet(models.TransientModel): + _name = 'odex25_helpdesk.ticket.create.timesheet' + _description = "Create Timesheet from ticket" + + _sql_constraints = [('time_positive', 'CHECK(time_spent > 0)', "The timesheet's time must be positive" )] + + time_spent = fields.Float('Time', digits=(16, 2)) + description = fields.Char('Description') + ticket_id = fields.Many2one( + 'odex25_helpdesk.ticket', "Ticket", required=True, + default=lambda self: self.env.context.get('active_id', None), + help="Ticket for which we are creating a sales order", + ) + + def action_generate_timesheet(self): + values = { + 'task_id': self.ticket_id.task_id.id, + 'project_id': self.ticket_id.project_id.id, + 'date': fields.Datetime.now(), + 'name': self.description, + 'user_id': self.env.uid, + 'unit_amount': self.time_spent, + } + + timesheet = self.env['account.analytic.line'].create(values) + + self.ticket_id.write({ + 'timer_start': False, + 'timer_pause': False + }) + self.ticket_id.timesheet_ids = [(4, timesheet.id, None)] + self.ticket_id.user_timer_id.unlink() + return timesheet diff --git a/odex25_helpdesk/odex25_helpdesk_timesheet/wizard/odex25_helpdesk_ticket_create_timesheet_views.xml b/odex25_helpdesk/odex25_helpdesk_timesheet/wizard/odex25_helpdesk_ticket_create_timesheet_views.xml new file mode 100644 index 000000000..c4738a0a7 --- /dev/null +++ b/odex25_helpdesk/odex25_helpdesk_timesheet/wizard/odex25_helpdesk_ticket_create_timesheet_views.xml @@ -0,0 +1,22 @@ + + + + + odex25_helpdesk.ticket.create.timesheet.wizard.form + odex25_helpdesk.ticket.create.timesheet + +
+ + + + + +
+
+
+
+
+ +
diff --git a/odex25_helpdesk/odex25_hr_gantt/__init__.py b/odex25_helpdesk/odex25_hr_gantt/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/odex25_helpdesk/odex25_hr_gantt/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/odex25_helpdesk/odex25_hr_gantt/__manifest__.py b/odex25_helpdesk/odex25_hr_gantt/__manifest__.py new file mode 100644 index 000000000..d91bde236 --- /dev/null +++ b/odex25_helpdesk/odex25_hr_gantt/__manifest__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + +{ + 'name': 'Employees in Gantt', + 'category': 'Hidden', + 'summary': 'Employees in Gantt', + 'version': '1.0', + 'description': """ """, + 'depends': ['hr', 'odex25_web_gantt'], + 'data': ['views/assets.xml'], + 'qweb': ['static/src/xml/odex25_hr_gantt.xml'], + 'auto_install': True, +} diff --git a/odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_renderer.js b/odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_renderer.js new file mode 100644 index 000000000..179f41b8a --- /dev/null +++ b/odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_renderer.js @@ -0,0 +1,14 @@ +odoo.define('odex25_hr_gantt.GanttRenderer', function (require) { + 'use strict'; + + const GanttRenderer = require('odex25_web_gantt.GanttRenderer'); + const HrGanttRow = require('odex25_hr_gantt.GanttRow'); + + const HrGanttRenderer = GanttRenderer.extend({ + config: Object.assign({}, GanttRenderer.prototype.config, { + GanttRow: HrGanttRow + }), + }); + + return HrGanttRenderer; +}); diff --git a/odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_row.js b/odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_row.js new file mode 100644 index 000000000..155acecf8 --- /dev/null +++ b/odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_row.js @@ -0,0 +1,59 @@ +odoo.define('odex25_hr_gantt.GanttRow', function (require) { + 'use strict'; + + const GanttRow = require('odex25_web_gantt.GanttRow'); + const StandaloneM2OAvatarEmployee = require('hr.StandaloneM2OAvatarEmployee'); + + const HrGanttRow = GanttRow.extend({ + template: 'HrGanttView.Row', + + /** + * @override + */ + init(parent, pillsInfo, viewInfo, options) { + this._super(...arguments); + const isGroupedByEmployee = pillsInfo.groupedByField === 'employee_id'; + const isEmptyGroup = pillsInfo.groupId === 'empty'; + this.showEmployeeAvatar = (isGroupedByEmployee && !isEmptyGroup && !!pillsInfo.resId); + }, + + /** + * @override + */ + willStart() { + const defs = [this._super(...arguments)]; + if (this.showEmployeeAvatar) { + defs.push(this._preloadAvatarWidget()); + } + return Promise.all(defs); + }, + + /** + * @override + */ + start() { + if (this.showEmployeeAvatar) { + this.avatarWidget.$el.appendTo(this.$('.o_gantt_row_employee_avatar')); + } + return this._super(...arguments); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Initialize the avatar widget in virtual DOM. + * + * @private + * @returns {Promise} + */ + async _preloadAvatarWidget() { + const employee = [this.resId, this.name]; + this.avatarWidget = new StandaloneM2OAvatarEmployee(this, employee); + return this.avatarWidget.appendTo(document.createDocumentFragment()); + }, + }); + + return HrGanttRow; +}); diff --git a/odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_view.js b/odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_view.js new file mode 100644 index 000000000..a825ef4c7 --- /dev/null +++ b/odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_view.js @@ -0,0 +1,16 @@ +odoo.define('odex25_hr_gantt.GanttView', function (require) { + 'use strict'; + + const viewRegistry = require('web.view_registry'); + const GanttView = require('odex25_web_gantt.GanttView'); + const HrGanttRenderer = require('odex25_hr_gantt.GanttRenderer'); + + const HrGanttView = GanttView.extend({ + config: Object.assign({}, GanttView.prototype.config, { + Renderer: HrGanttRenderer, + }), + }); + + viewRegistry.add('odex25_hr_gantt', HrGanttView); + return HrGanttView; +}); diff --git a/odex25_helpdesk/odex25_hr_gantt/static/src/xml/odex25_hr_gantt.xml b/odex25_helpdesk/odex25_hr_gantt/static/src/xml/odex25_hr_gantt.xml new file mode 100644 index 000000000..6ab662c02 --- /dev/null +++ b/odex25_helpdesk/odex25_hr_gantt/static/src/xml/odex25_hr_gantt.xml @@ -0,0 +1,10 @@ + + + + + +
+ + + + diff --git a/odex25_helpdesk/odex25_hr_gantt/static/tests/odex25_hr_gantt_tests.js b/odex25_helpdesk/odex25_hr_gantt/static/tests/odex25_hr_gantt_tests.js new file mode 100644 index 000000000..034f05694 --- /dev/null +++ b/odex25_helpdesk/odex25_hr_gantt/static/tests/odex25_hr_gantt_tests.js @@ -0,0 +1,123 @@ +odoo.define('odex25_hr_gantt.Tests', function (require) { + 'use strict'; + + const { createView } = require('web.test_utils'); + const HrGanttView = require('odex25_hr_gantt.GanttView'); + + let initialDate = new Date(2018, 11, 20, 8, 0, 0); + initialDate = new Date(initialDate.getTime() - initialDate.getTimezoneOffset() * 60 * 1000); + + QUnit.module('odex25_hr_gantt', {}, function () { + QUnit.module('GanttView', { + beforeEach: function () { + this.data = { + tasks: { + fields: { + name: {string: 'Name', type: 'char'}, + start: {string: 'Start Date', type: 'datetime'}, + stop: {string: 'Stop Date', type: 'datetime'}, + employee_id: {string: "Employee", type: 'many2one', relation: 'hr.employee'}, + foo: {string: "Foo", type: 'char'}, + }, + records: [ + {id: 1, name: 'Task 1', start: '2018-11-30 18:30:00', stop: '2018-12-31 18:29:59', employee_id: 11, foo: 'Foo 1'}, + {id: 2, name: 'Task 2', start: '2018-12-17 11:30:00', stop: '2018-12-22 06:29:59', employee_id: 7, foo: 'Foo 2'}, + {id: 3, name: 'Task 3', start: '2018-12-27 06:30:00', stop: '2019-01-03 06:29:59', employee_id: 23, foo: 'Foo 1'}, + {id: 4, name: 'Task 4', start: '2018-12-19 18:30:00', stop: '2018-12-20 06:29:59', employee_id: 11, foo: 'Foo 3'} + ], + }, + 'hr.employee': { + fields: {}, + records: [ + {id: 11, name: "Mario"}, + {id: 7, name: "Luigi"}, + {id: 23, name: "Yoshi"}, + ], + }, + }; + }, + }); + + QUnit.test('hr gantt view not grouped', async function (assert) { + assert.expect(1); + + const gantt = await createView({ + View: HrGanttView, + model: 'tasks', + data: this.data, + arch: '', + viewOptions: { + initialDate: initialDate, + }, + }); + + assert.containsNone(gantt, '.o_standalone_avatar_employee', + 'should have 0 employee avatars'); + + gantt.destroy(); + }); + + QUnit.test('hr gantt view grouped by employee only', async function (assert) { + assert.expect(1); + + const gantt = await createView({ + View: HrGanttView, + model: 'tasks', + data: this.data, + arch: '', + viewOptions: { + initialDate: initialDate, + }, + groupBy: ['employee_id'], + }); + + assert.containsN(gantt, + '.o_gantt_row .o_gantt_row_sidebar .o_gantt_row_title .o_gantt_row_employee_avatar .o_standalone_avatar_employee', + 3, 'should have 3 employee avatars'); + + gantt.destroy(); + }); + + QUnit.test('hr gantt view grouped by employee > foo', async function (assert) { + assert.expect(1); + + const gantt = await createView({ + View: HrGanttView, + model: 'tasks', + data: this.data, + arch: '', + viewOptions: { + initialDate: initialDate, + }, + groupBy: ['employee_id', 'foo'], + }); + + assert.containsN(gantt, + '.o_gantt_row_group .o_gantt_row_sidebar .o_gantt_row_title .o_gantt_row_employee_avatar .o_standalone_avatar_employee', + 3, 'should have 3 employee avatars'); + + gantt.destroy(); + }); + + QUnit.test('hr gantt view grouped by foo > employee', async function (assert) { + assert.expect(1); + + const gantt = await createView({ + View: HrGanttView, + model: 'tasks', + data: this.data, + arch: '', + viewOptions: { + initialDate: initialDate, + }, + groupBy: ['foo', 'employee_id'], + }); + + assert.containsN(gantt, + '.o_gantt_row_nogroup .o_gantt_row_sidebar .o_gantt_row_title .o_gantt_row_employee_avatar .o_standalone_avatar_employee', + 4, 'should have 4 employee avatars'); + + gantt.destroy(); + }); + }); +}); diff --git a/odex25_helpdesk/odex25_hr_gantt/views/assets.xml b/odex25_helpdesk/odex25_hr_gantt/views/assets.xml new file mode 100644 index 000000000..dfee8558c --- /dev/null +++ b/odex25_helpdesk/odex25_hr_gantt/views/assets.xml @@ -0,0 +1,16 @@ + + + + diff --git a/odex25_helpdesk/odex25_timesheet_grid/views/hr_timesheet_views.xml b/odex25_helpdesk/odex25_timesheet_grid/views/hr_timesheet_views.xml new file mode 100644 index 000000000..21120e554 --- /dev/null +++ b/odex25_helpdesk/odex25_timesheet_grid/views/hr_timesheet_views.xml @@ -0,0 +1,718 @@ + + + + + + + account.analytic.line.tree.hr_timesheet + account.analytic.line + + + + validated + timesheet_tree + 1 + + + + + + + + + + {'readonly': [('id', '!=', False), ('validated', '=', True)]} + + + {'readonly': [('id', '!=', False), ('validated', '=', True)]} + + + {'readonly': [('id', '!=', False), ('validated', '=', True)]} + + + {'readonly': [('id', '!=', False), ('validated', '=', True)]} + + + { + 'readonly': [ + ('id', '!=', False), + '|', + ('validated', '=', True), + ('timer_start', '!=', False) + ]} + + timesheet_uom_timer + + + + + + + account.analytic.line.kanban.odex25_timesheet_grid + account.analytic.line + + + + timesheet_kanban_view + 1 + + + + + + + + + + + timesheet_uom_timer + + + + + + + account.analytic.line.kanban.odex25_timesheet_grid + account.analytic.line + + + + + + + {'readonly': [('id', '!=', False), ('timer_start', '!=', False)]} + + + + + + + account.analytic.line.form + account.analytic.line + 4567 + +
+ + + + + + + + + + + + + + + + + + + +
+
+
+ + + account.analytic.line.grid.project + account.analytic.line + + + + + + + + + + + + + + + + account.analytic.line.grid.project + account.analytic.line + + + + + + + + + + + + + + + account.analytic.line.grid.project.readonly + account.analytic.line + + primary + + + false + false + + + + + + + + + + + account.analytic.line.grid.project.no.section + account.analytic.line + + primary + + + + + + + + + account.analytic.line.grid.employee + account.analytic.line + + + + + + + + + + + + + + + account.analytic.line.grid.employee.readonly + account.analytic.line + + primary + + + false + false + + + + + account.analytic.line.grid.employee.manager + account.analytic.line + + primary + + + 1 + + + + + + true + true + + + + + account.analytic.line.grid.employee.validation + account.analytic.line + + primary + + + + + + + +
diff --git a/odex25_helpdesk/odex_subscription_service/views/res_settings.xml b/odex25_helpdesk/odex_subscription_service/views/res_settings.xml new file mode 100644 index 000000000..b76b769ba --- /dev/null +++ b/odex25_helpdesk/odex_subscription_service/views/res_settings.xml @@ -0,0 +1,37 @@ + + + + + Config Settings for Subscription Services + res.config.settings + + + + +
+ +

Subscription Services

+
+
+
+
+
+
+ Notify Days Before Subscription End +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
\ No newline at end of file diff --git a/odex25_helpdesk/odex_subscription_service/views/subscription_portal_templates.xml b/odex25_helpdesk/odex_subscription_service/views/subscription_portal_templates.xml new file mode 100644 index 000000000..f74abc7f3 --- /dev/null +++ b/odex25_helpdesk/odex_subscription_service/views/subscription_portal_templates.xml @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + diff --git a/odex25_helpdesk/odex_subscription_service/views/subscription_service_views.xml b/odex25_helpdesk/odex_subscription_service/views/subscription_service_views.xml new file mode 100644 index 000000000..81497d6e3 --- /dev/null +++ b/odex25_helpdesk/odex_subscription_service/views/subscription_service_views.xml @@ -0,0 +1,250 @@ + + + + subscription.service.form.view + subscription.service + +
+
+
+ +
+ +
+
+

+ +

+
+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+ + +
+
+
+
+ + + subscription.service.list + subscription.service + + + + + + + + + + + + + + + + subscription.service.search + subscription.service + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + subscription.service.graph + subscription.service + + + + + + + + + + subscription.service.pivot + subscription.service + + + + + + + + + + Subscriptions Services + subscription.service + ir.actions.act_window + tree,form,pivot,graph + {'search_default_user_id':uid, 'user_id': uid,} + +

+ Click to create a new contract. +

+ Use contracts to follow tasks, issues, timesheets or invoicing based on + work done, expenses and/or purchase orders. Odoo will automatically manage + the alerts for the renewal of the contracts to the right salesperson. +

+
+
+ + + + + + + + Subscriptions to Renew + subscription.service + ir.actions.act_window + tree,form,pivot,graph + {'search_default_user_id':uid, 'search_default_pending':1, 'search_default_renew':1} + +

+ Click to define a new contract. +

+ You will find here the contracts to be renewed because the + end date is passed or the working effort is higher than the + maximum authorized one. +

+ Odoo automatically sets contracts to be renewed in a pending + state. After the negociation, the salesman should close or renew + pending contracts. +

+
+
+ + + + + + +