diff --git a/announcement/README.rst b/announcement/README.rst new file mode 100644 index 0000000000..66edf04f9b --- /dev/null +++ b/announcement/README.rst @@ -0,0 +1,137 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +============ +Announcement +============ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:e8e3b291ae957eb17175e8c37b94d1a44110a11cbc32f48d997c96702cbe167c + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--ux-lightgray.png?logo=github + :target: https://github.com/OCA/server-ux/tree/19.0/announcement + :alt: OCA/server-ux +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-ux-19-0/server-ux-19-0-announcement + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/server-ux&target_branch=19.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module adds popup announcements in the backend for targeted +internal users. Those announcements can contain rich format and a user +read log is kept for everyone. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To create new announcements a user should be in the *Announcements +Managers* group. When your user has such permissions, this is the way to +create an announcement: + +1. Go to *Discuss > Announcements* +2. Create a new one and define a title. This title will be shown in the + announcement header. +3. Define the announcement scope: + + - Specific users: manually select which users will see the + announcement. + - User groups: users from the selected groups will be the ones to see + the announcement. + +4. Define the announcement body. You can use rich formatting and event + paste your own html (editor in debug mode). +5. By default, the announcement will be archived. This is to prevent the + announcement to show up before time. +6. Once the announcement is ready, unarchive it going to the *Actions* + menu an choosing the *Unarchive* option. +7. Optionally you can set an announcement date to schedule the + announcement. The announcement won't show up until that date. +8. If the announcement doesn't make sense once a date is passed, you can + set a due date. From that date, the announcement won't be shown to + anyone. + +Usage +===== + +When a user in the scope of active announcements logs in, those will +popup. The user has to mark them as read to continue working. If the +announcement is set during the user session, the announcement will be +eventually prompted in the top bar on the right part. The user click on +the unread announcements icon (a speaker) and the announcements will +popup for the user to check them. + +Users can go *Discuss > Announcements* to check current and past +announcements. Announcement managers can also track which users have +read the announcement. + +Known issues / Roadmap +====================== + +- It could be integrated in Discuss app to review past announcements. +- Log other information like geolocation, IP, browser agent, etc when + marking announcement as read. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Tecnativa + +Contributors +------------ + +- `Tecnativa `__: + + - Pedro M. Baeza + - David Vidal + - Carlos Roca + - David Bañón Gil + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/server-ux `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/announcement/__init__.py b/announcement/__init__.py new file mode 100644 index 0000000000..aee8895e7a --- /dev/null +++ b/announcement/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/announcement/__manifest__.py b/announcement/__manifest__.py new file mode 100644 index 0000000000..e32e0d5984 --- /dev/null +++ b/announcement/__manifest__.py @@ -0,0 +1,30 @@ +# Copyright 2022 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Announcement", + "version": "19.0.1.0.0", + "summary": "Notify internal users about relevant organization stuff", + "author": "Tecnativa, Odoo Community Association (OCA)", + "license": "AGPL-3", + "category": "Server UX", + "website": "https://github.com/OCA/server-ux", + "depends": ["mail"], + "data": [ + "security/announcement_security.xml", + "security/ir.model.access.csv", + "views/announcement_views.xml", + "views/announcement_tag_views.xml", + "wizards/read_announcement_wizard.xml", + ], + "demo": [ + "demo/announcement_tag_demo.xml", + ], + "assets": { + "web.assets_backend": [ + "announcement/static/src/js/announcement_dialog/**/*", + "announcement/static/src/js/announcement_menu/**/*", + "announcement/static/src/js/announcement_service/**/*", + ], + }, +} diff --git a/announcement/demo/announcement_tag_demo.xml b/announcement/demo/announcement_tag_demo.xml new file mode 100644 index 0000000000..0a7da84509 --- /dev/null +++ b/announcement/demo/announcement_tag_demo.xml @@ -0,0 +1,23 @@ + + + + Company information + + + + Employees + + + + Accounting + + + + Sales + + + + Manufacturing + + + diff --git a/announcement/i18n/announcement.pot b/announcement/i18n/announcement.pot new file mode 100644 index 0000000000..5425cb0fd2 --- /dev/null +++ b/announcement/i18n/announcement.pot @@ -0,0 +1,427 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * announcement +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \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: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_management_view_form +msgid "Read(s)" +msgstr "" + +#. module: announcement +#: model:announcement.tag,name:announcement.announcement_tag_3 +msgid "Accounting" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__active +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_search +msgid "Active" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__allowed_user_ids +msgid "Allowed User" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__allowed_users_count +msgid "Allowed Users Count" +msgstr "" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "Announce at" +msgstr "" + +#. module: announcement +#. odoo-javascript +#: code:addons/announcement/static/src/js/announcement_menu/announcement_menu.xml:0 +#: model:ir.model.fields,field_description:announcement.field_announcement_log__announcement_id +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__announcement_id +#: model:ir.module.category,name:announcement.category_announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "Announcement" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__announcement_log_ids +msgid "Announcement Log" +msgstr "" + +#. module: announcement +#: model:ir.actions.act_window,name:announcement.action_announcement_log +msgid "Announcement Logs" +msgstr "" + +#. module: announcement +#: model:res.groups,name:announcement.announcemenent_manager +msgid "Announcement Manager" +msgstr "" + +#. module: announcement +#: model:ir.actions.act_window,name:announcement.announcement_tag_action +#: model:ir.model,name:announcement.model_announcement_tag +#: model:ir.ui.menu,name:announcement.menu_announcement_tag_management +msgid "Announcement Tags" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__announcement_type +msgid "Announcement Type" +msgstr "" + +#. module: announcement +#. odoo-javascript +#: code:addons/announcement/static/src/js/announcement_menu/announcement_menu.xml:0 +#: model:ir.actions.act_window,name:announcement.announcement_action +#: model:ir.ui.menu,name:announcement.menu_announcement_management +msgid "Announcements" +msgstr "" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_search +msgid "Archived" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__attachment_ids +msgid "Attachments" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__color +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__color +msgid "Color" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__company_id +msgid "Company" +msgstr "" + +#. module: announcement +#: model:announcement.tag,name:announcement.announcement_tag_1 +msgid "Company information" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,help:announcement.field_announcement_tag__company_id +msgid "Company related to this tag" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__content +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "Content" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__create_uid +#: model:ir.model.fields,field_description:announcement.field_announcement_log__create_uid +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__create_uid +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__create_uid +msgid "Created by" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__create_date +#: model:ir.model.fields,field_description:announcement.field_announcement_log__create_date +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__create_date +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__create_date +msgid "Created on" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__display_name +#: model:ir.model.fields,field_description:announcement.field_announcement_log__display_name +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__display_name +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__display_name +msgid "Display Name" +msgstr "" + +#. module: announcement +#: model:announcement.tag,name:announcement.announcement_tag_2 +msgid "Employees" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__is_general_announcement +msgid "General Announcement" +msgstr "" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_management_view_form +msgid "Groups" +msgstr "" + +#. module: announcement +#: model:ir.model,name:announcement.model_ir_http +msgid "HTTP Routing" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__id +#: model:ir.model.fields,field_description:announcement.field_announcement_log__id +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__id +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__id +msgid "ID" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__in_date +msgid "In Date" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__write_uid +#: model:ir.model.fields,field_description:announcement.field_announcement_log__write_uid +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__write_uid +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__write_date +#: model:ir.model.fields,field_description:announcement.field_announcement_log__write_date +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__write_date +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__write_date +msgid "Last Updated on" +msgstr "" + +#. module: announcement +#: model:ir.model,name:announcement.model_announcement_log +msgid "Log user reads" +msgstr "" + +#. module: announcement +#: model:announcement.tag,name:announcement.announcement_tag_5 +msgid "Manufacturing" +msgstr "" + +#. module: announcement +#. odoo-javascript +#: code:addons/announcement/static/src/js/announcement_menu/announcement_menu.esm.js:0 +msgid "Mark as read" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__name +msgid "Name" +msgstr "" + +#. module: announcement +#. odoo-javascript +#: code:addons/announcement/static/src/js/announcement_menu/announcement_menu.xml:0 +msgid "No announcements." +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__notification_date +msgid "Notification Date" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__notification_end_date +msgid "Notification End Date" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__notification_expiry_date +msgid "Notification Expiry Date" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__notification_start_date +msgid "Notification Start Date" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__parent_id +msgid "Parent Tag" +msgstr "" + +#. module: announcement +#: model:ir.model.fields.selection,name:announcement.selection__read_announcement_wizard__read_state__read +msgid "Read" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_res_users__read_announcement_ids +msgid "Read Announcement" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__read_announcement_count +msgid "Read Announcement Count" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__date +msgid "Read Date" +msgstr "" + +#. module: announcement +#. odoo-python +#: code:addons/announcement/models/announcement.py:0 +msgid "Read Logs" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__read_state +msgid "Read State" +msgstr "" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_management_view_tree +msgid "Reads" +msgstr "" + +#. module: announcement +#: model:announcement.tag,name:announcement.announcement_tag_4 +msgid "Sales" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__sequence +msgid "Sequence" +msgstr "" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "Set here the content of the announcement." +msgstr "" + +#. module: announcement +#: model:ir.model,name:announcement.model_read_announcement_wizard +msgid "Show altogether users who read and users who didn't" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__specific_user_ids +msgid "Specific User" +msgstr "" + +#. module: announcement +#: model:ir.model.fields.selection,name:announcement.selection__announcement__announcement_type__specific_users +msgid "Specific users" +msgstr "" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_search +msgid "Tag" +msgstr "" + +#. module: announcement +#: model:ir.model.constraint,message:announcement.constraint_announcement_tag_name_uniq +msgid "Tag name already exists!" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__tag_ids +msgid "Tags" +msgstr "" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "Tags..." +msgstr "" + +#. module: announcement +#: model:ir.model.fields,help:announcement.field_announcement__notification_end_date +#: model:ir.model.fields,help:announcement.field_announcement__notification_start_date +msgid "Technical field to display announcements in the calendar view" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,help:announcement.field_announcement__color +msgid "Technical field to display items by color in the calendar" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__name +msgid "Title" +msgstr "" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_management_view_tree +msgid "Total users" +msgstr "" + +#. module: announcement +#: model:ir.model.fields.selection,name:announcement.selection__read_announcement_wizard__read_state__unread +msgid "Unread" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_res_users__unread_announcement_ids +msgid "Unread Announcement" +msgstr "" + +#. module: announcement +#: model:ir.model,name:announcement.model_res_users +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__user_id +msgid "User" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__user_group_ids +msgid "User Group" +msgstr "" + +#. module: announcement +#: model:ir.model,name:announcement.model_announcement +msgid "User announcements" +msgstr "" + +#. module: announcement +#: model:ir.model.fields.selection,name:announcement.selection__announcement__announcement_type__user_group +msgid "User groups" +msgstr "" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_management_view_form +msgid "Users" +msgstr "" + +#. module: announcement +#: model:res.groups,comment:announcement.announcemenent_manager +msgid "Users allowed to manage and configure announcements." +msgstr "" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "Valid up to" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,help:announcement.field_announcement__attachment_ids +msgid "You can attach the copy of your Letter" +msgstr "" + +#. module: announcement +#. odoo-python +#: code:addons/announcement/models/announcement_tag.py:0 +msgid "You cannot create recursive tags." +msgstr "" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "e.g. Announcement description..." +msgstr "" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_calendar +msgid "name" +msgstr "" diff --git a/announcement/i18n/es.po b/announcement/i18n/es.po new file mode 100644 index 0000000000..7ea5eaa158 --- /dev/null +++ b/announcement/i18n/es.po @@ -0,0 +1,434 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * announcement +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Víctor Martínez \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Poedit 3.4.2\n" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_management_view_form +msgid "Read(s)" +msgstr "Leído(s)" + +#. module: announcement +#: model:announcement.tag,name:announcement.announcement_tag_3 +msgid "Accounting" +msgstr "Contabilidad" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__active +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_search +msgid "Active" +msgstr "Activo" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__allowed_user_ids +msgid "Allowed User" +msgstr "Usuario permitido" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__allowed_users_count +msgid "Allowed Users Count" +msgstr "Número de usuarios permitidos" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "Announce at" +msgstr "Anunciar a" + +#. module: announcement +#. odoo-javascript +#: code:addons/announcement/static/src/js/announcement_menu/announcement_menu.xml:0 +#: model:ir.model.fields,field_description:announcement.field_announcement_log__announcement_id +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__announcement_id +#: model:ir.module.category,name:announcement.category_announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "Announcement" +msgstr "Anuncio" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__announcement_log_ids +msgid "Announcement Log" +msgstr "Registro del anuncio" + +#. module: announcement +#: model:ir.actions.act_window,name:announcement.action_announcement_log +msgid "Announcement Logs" +msgstr "Registro del anuncio" + +#. module: announcement +#: model:res.groups,name:announcement.announcemenent_manager +msgid "Announcement Manager" +msgstr "Responsable de anuncios" + +#. module: announcement +#: model:ir.actions.act_window,name:announcement.announcement_tag_action +#: model:ir.model,name:announcement.model_announcement_tag +#: model:ir.ui.menu,name:announcement.menu_announcement_tag_management +msgid "Announcement Tags" +msgstr "Etiquetas de anuncios" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__announcement_type +msgid "Announcement Type" +msgstr "Tipo de anuncio" + +#. module: announcement +#. odoo-javascript +#: code:addons/announcement/static/src/js/announcement_menu/announcement_menu.xml:0 +#: model:ir.actions.act_window,name:announcement.announcement_action +#: model:ir.ui.menu,name:announcement.menu_announcement_management +msgid "Announcements" +msgstr "Anuncios" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_search +msgid "Archived" +msgstr "Archivado" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__attachment_ids +msgid "Attachments" +msgstr "Adjuntos" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__color +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__color +msgid "Color" +msgstr "Color" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__company_id +msgid "Company" +msgstr "Compañía" + +#. module: announcement +#: model:announcement.tag,name:announcement.announcement_tag_1 +msgid "Company information" +msgstr "Información de la compañía" + +#. module: announcement +#: model:ir.model.fields,help:announcement.field_announcement_tag__company_id +msgid "Company related to this tag" +msgstr "Compañía relacionado con esta etiqueta" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__content +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "Content" +msgstr "Contenido" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__create_uid +#: model:ir.model.fields,field_description:announcement.field_announcement_log__create_uid +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__create_uid +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__create_date +#: model:ir.model.fields,field_description:announcement.field_announcement_log__create_date +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__create_date +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__create_date +msgid "Created on" +msgstr "Creado en" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__display_name +#: model:ir.model.fields,field_description:announcement.field_announcement_log__display_name +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__display_name +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__display_name +msgid "Display Name" +msgstr "Nombre mostrado" + +#. module: announcement +#: model:announcement.tag,name:announcement.announcement_tag_2 +msgid "Employees" +msgstr "Empleados" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__full_name +msgid "Full Name" +msgstr "Nombre completo" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__is_general_announcement +msgid "General Announcement" +msgstr "Anuncio general" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_management_view_form +msgid "Groups" +msgstr "Grupos" + +#. module: announcement +#: model:ir.model,name:announcement.model_ir_http +msgid "HTTP Routing" +msgstr "" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__id +#: model:ir.model.fields,field_description:announcement.field_announcement_log__id +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__id +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__id +msgid "ID" +msgstr "ID (identificación)" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__in_date +msgid "In Date" +msgstr "En fecha" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__write_uid +#: model:ir.model.fields,field_description:announcement.field_announcement_log__write_uid +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__write_uid +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__write_uid +msgid "Last Updated by" +msgstr "Última actualización de" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__write_date +#: model:ir.model.fields,field_description:announcement.field_announcement_log__write_date +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__write_date +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__write_date +msgid "Last Updated on" +msgstr "Última actualización en" + +#. module: announcement +#: model:ir.model,name:announcement.model_announcement_log +msgid "Log user reads" +msgstr "Registrar lecturas de usuario" + +#. module: announcement +#: model:announcement.tag,name:announcement.announcement_tag_5 +msgid "Manufacturing" +msgstr "Fabricación" + +#. module: announcement +#. odoo-javascript +#: code:addons/announcement/static/src/js/announcement_menu/announcement_menu.esm.js:0 +msgid "Mark as read" +msgstr "Marcar como leído" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__name +msgid "Name" +msgstr "Nombre" + +#. module: announcement +#. odoo-javascript +#: code:addons/announcement/static/src/js/announcement_menu/announcement_menu.xml:0 +msgid "No announcements." +msgstr "No hay anuncios." + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__notification_date +msgid "Notification Date" +msgstr "Fecha de notificación" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__notification_end_date +msgid "Notification End Date" +msgstr "Fecha fin de notificación" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__notification_expiry_date +msgid "Notification Expiry Date" +msgstr "Fecha de caducidad de la notificación" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__notification_start_date +msgid "Notification Start Date" +msgstr "Fecha inicio de notificación" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__parent_id +msgid "Parent Tag" +msgstr "Etiqueta padre" + +#. module: announcement +#: model:ir.model.fields.selection,name:announcement.selection__read_announcement_wizard__read_state__read +msgid "Read" +msgstr "Leídos" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_res_users__read_announcement_ids +msgid "Read Announcement" +msgstr "Anuncio leído" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__read_announcement_count +msgid "Read Announcement Count" +msgstr "Número de anuncios leídos" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__date +msgid "Read Date" +msgstr "Fecha de lectura" + +#. module: announcement +#. odoo-python +#: code:addons/announcement/models/announcement.py:0 +msgid "Read Logs" +msgstr "Registros de lectura" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__read_state +msgid "Read State" +msgstr "Estado de lectura" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_management_view_tree +msgid "Reads" +msgstr "Lecturas" + +#. module: announcement +#: model:announcement.tag,name:announcement.announcement_tag_4 +msgid "Sales" +msgstr "Ventas" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__sequence +msgid "Sequence" +msgstr "Secuencia" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "Set here the content of the announcement." +msgstr "Inserta aquí el contenido del anuncio." + +#. module: announcement +#: model:ir.model,name:announcement.model_read_announcement_wizard +msgid "Show altogether users who read and users who didn't" +msgstr "Muestra juntos los usuarios que han leído un anuncio y los que no" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__specific_user_ids +msgid "Specific User" +msgstr "Usuarios específico" + +#. module: announcement +#: model:ir.model.fields.selection,name:announcement.selection__announcement__announcement_type__specific_users +msgid "Specific users" +msgstr "Usuarios específicos" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_search +msgid "Tag" +msgstr "Etiqueta" + +#. module: announcement +#: model:ir.model.constraint,message:announcement.constraint_announcement_tag_name_uniq +msgid "Tag name already exists!" +msgstr "¡El nombre de etiqueta ya existe!" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__tag_ids +msgid "Tags" +msgstr "Etiquetas" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "Tags..." +msgstr "Etiquetas..." + +#. module: announcement +#: model:ir.model.fields,help:announcement.field_announcement__notification_end_date +#: model:ir.model.fields,help:announcement.field_announcement__notification_start_date +msgid "Technical field to display announcements in the calendar view" +msgstr "Campo técnico para mostrar los anuncios en la vista calendario" + +#. module: announcement +#: model:ir.model.fields,help:announcement.field_announcement__color +msgid "Technical field to display items by color in the calendar" +msgstr "Campo técnico para mostrar items por color en el calendario" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__name +msgid "Title" +msgstr "Título" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_management_view_tree +msgid "Total users" +msgstr "Total usuarios" + +#. module: announcement +#: model:ir.model.fields.selection,name:announcement.selection__read_announcement_wizard__read_state__unread +msgid "Unread" +msgstr "Pendientes" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_res_users__unread_announcement_ids +msgid "Unread Announcement" +msgstr "Anuncio pendiente" + +#. module: announcement +#: model:ir.model,name:announcement.model_res_users +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__user_id +msgid "User" +msgstr "Usuario" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__user_group_ids +msgid "User Group" +msgstr "Grupo de usuarios" + +#. module: announcement +#: model:ir.model,name:announcement.model_announcement +msgid "User announcements" +msgstr "Anuncios del usuario" + +#. module: announcement +#: model:ir.model.fields.selection,name:announcement.selection__announcement__announcement_type__user_group +msgid "User groups" +msgstr "Grupos de usuarios" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_management_view_form +msgid "Users" +msgstr "Usuarios" + +#. module: announcement +#: model:res.groups,comment:announcement.announcemenent_manager +msgid "Users allowed to manage and configure announcements." +msgstr "Usuarios autorizados a administrar y configurar anuncios." + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "Valid up to" +msgstr "Válido hasta" + +#. module: announcement +#: model:ir.model.fields,help:announcement.field_announcement__attachment_ids +msgid "You can attach the copy of your Letter" +msgstr "Puedes adjuntar la copia de tu correo" + +#. module: announcement +#. odoo-python +#: code:addons/announcement/models/announcement_tag.py:0 +msgid "You cannot create recursive tags." +msgstr "No puedes crear etiquetas recursivas." + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "e.g. Announcement description..." +msgstr "p.e. Descripción del anuncio..." + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_calendar +msgid "name" +msgstr "nombre" diff --git a/announcement/i18n/it.po b/announcement/i18n/it.po new file mode 100644 index 0000000000..c7f6863e09 --- /dev/null +++ b/announcement/i18n/it.po @@ -0,0 +1,434 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * announcement +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.6.2\n" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_management_view_form +msgid "Read(s)" +msgstr "Lettura(e)" + +#. module: announcement +#: model:announcement.tag,name:announcement.announcement_tag_3 +msgid "Accounting" +msgstr "Contabilità" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__active +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_search +msgid "Active" +msgstr "Attivo" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__allowed_user_ids +msgid "Allowed User" +msgstr "Utente autorizzato" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__allowed_users_count +msgid "Allowed Users Count" +msgstr "Conteggio utenti autorizzati" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "Announce at" +msgstr "Notifica il" + +#. module: announcement +#. odoo-javascript +#: code:addons/announcement/static/src/js/announcement_menu/announcement_menu.xml:0 +#: model:ir.model.fields,field_description:announcement.field_announcement_log__announcement_id +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__announcement_id +#: model:ir.module.category,name:announcement.category_announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "Announcement" +msgstr "Notifiche" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__announcement_log_ids +msgid "Announcement Log" +msgstr "Registro notifica" + +#. module: announcement +#: model:ir.actions.act_window,name:announcement.action_announcement_log +msgid "Announcement Logs" +msgstr "Registri notifica" + +#. module: announcement +#: model:res.groups,name:announcement.announcemenent_manager +msgid "Announcement Manager" +msgstr "Gestore notifica" + +#. module: announcement +#: model:ir.actions.act_window,name:announcement.announcement_tag_action +#: model:ir.model,name:announcement.model_announcement_tag +#: model:ir.ui.menu,name:announcement.menu_announcement_tag_management +msgid "Announcement Tags" +msgstr "Etichette notifica" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__announcement_type +msgid "Announcement Type" +msgstr "Tipo notifica" + +#. module: announcement +#. odoo-javascript +#: code:addons/announcement/static/src/js/announcement_menu/announcement_menu.xml:0 +#: model:ir.actions.act_window,name:announcement.announcement_action +#: model:ir.ui.menu,name:announcement.menu_announcement_management +msgid "Announcements" +msgstr "Notifiche" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_search +msgid "Archived" +msgstr "In archivio" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__attachment_ids +msgid "Attachments" +msgstr "Allegati" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__color +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__color +msgid "Color" +msgstr "Colore" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__company_id +msgid "Company" +msgstr "Azienda" + +#. module: announcement +#: model:announcement.tag,name:announcement.announcement_tag_1 +msgid "Company information" +msgstr "Informazioni azienda" + +#. module: announcement +#: model:ir.model.fields,help:announcement.field_announcement_tag__company_id +msgid "Company related to this tag" +msgstr "Azienda collegata a questa etichetta" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__content +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "Content" +msgstr "Contenuto" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__create_uid +#: model:ir.model.fields,field_description:announcement.field_announcement_log__create_uid +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__create_uid +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__create_uid +msgid "Created by" +msgstr "Creato da" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__create_date +#: model:ir.model.fields,field_description:announcement.field_announcement_log__create_date +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__create_date +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__create_date +msgid "Created on" +msgstr "Creato il" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__display_name +#: model:ir.model.fields,field_description:announcement.field_announcement_log__display_name +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__display_name +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: announcement +#: model:announcement.tag,name:announcement.announcement_tag_2 +msgid "Employees" +msgstr "Dipendenti" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__full_name +msgid "Full Name" +msgstr "Nome completo" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__is_general_announcement +msgid "General Announcement" +msgstr "Notifica generale" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_management_view_form +msgid "Groups" +msgstr "Gruppi" + +#. module: announcement +#: model:ir.model,name:announcement.model_ir_http +msgid "HTTP Routing" +msgstr "Instradamento HTTP" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__id +#: model:ir.model.fields,field_description:announcement.field_announcement_log__id +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__id +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__id +msgid "ID" +msgstr "ID" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__in_date +msgid "In Date" +msgstr "In data" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__write_uid +#: model:ir.model.fields,field_description:announcement.field_announcement_log__write_uid +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__write_uid +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__write_uid +msgid "Last Updated by" +msgstr "Ultimo aggiornamento di" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__write_date +#: model:ir.model.fields,field_description:announcement.field_announcement_log__write_date +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__write_date +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__write_date +msgid "Last Updated on" +msgstr "Ultimo aggiornamento il" + +#. module: announcement +#: model:ir.model,name:announcement.model_announcement_log +msgid "Log user reads" +msgstr "Registro letture utente" + +#. module: announcement +#: model:announcement.tag,name:announcement.announcement_tag_5 +msgid "Manufacturing" +msgstr "Produzione" + +#. module: announcement +#. odoo-javascript +#: code:addons/announcement/static/src/js/announcement_menu/announcement_menu.esm.js:0 +msgid "Mark as read" +msgstr "Segna come letto" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__name +msgid "Name" +msgstr "Nome" + +#. module: announcement +#. odoo-javascript +#: code:addons/announcement/static/src/js/announcement_menu/announcement_menu.xml:0 +msgid "No announcements." +msgstr "Nessuna notifica." + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__notification_date +msgid "Notification Date" +msgstr "Data notifica" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__notification_end_date +msgid "Notification End Date" +msgstr "Data fine notifica" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__notification_expiry_date +msgid "Notification Expiry Date" +msgstr "Data scadenza notifica" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__notification_start_date +msgid "Notification Start Date" +msgstr "Data inizio notifica" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement_tag__parent_id +msgid "Parent Tag" +msgstr "Etichetta padre" + +#. module: announcement +#: model:ir.model.fields.selection,name:announcement.selection__read_announcement_wizard__read_state__read +msgid "Read" +msgstr "Lettura" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_res_users__read_announcement_ids +msgid "Read Announcement" +msgstr "Lettura notifica" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__read_announcement_count +msgid "Read Announcement Count" +msgstr "Conteggio lettura notifica" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__date +msgid "Read Date" +msgstr "Data lettura" + +#. module: announcement +#. odoo-python +#: code:addons/announcement/models/announcement.py:0 +msgid "Read Logs" +msgstr "Registri lettura" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__read_state +msgid "Read State" +msgstr "Stato lettura" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_management_view_tree +msgid "Reads" +msgstr "Letture" + +#. module: announcement +#: model:announcement.tag,name:announcement.announcement_tag_4 +msgid "Sales" +msgstr "Vendite" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__sequence +msgid "Sequence" +msgstr "Sequenza" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "Set here the content of the announcement." +msgstr "Impostare qui il contenuto della notifica." + +#. module: announcement +#: model:ir.model,name:announcement.model_read_announcement_wizard +msgid "Show altogether users who read and users who didn't" +msgstr "Visualizza insieme utenti che leggono o meno" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__specific_user_ids +msgid "Specific User" +msgstr "Utente specifico" + +#. module: announcement +#: model:ir.model.fields.selection,name:announcement.selection__announcement__announcement_type__specific_users +msgid "Specific users" +msgstr "Utenti specifici" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_search +msgid "Tag" +msgstr "Etichetta" + +#. module: announcement +#: model:ir.model.constraint,message:announcement.constraint_announcement_tag_name_uniq +msgid "Tag name already exists!" +msgstr "Il nome dell'etichetta esiste già!" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__tag_ids +msgid "Tags" +msgstr "Etichette" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "Tags..." +msgstr "Etichette..." + +#. module: announcement +#: model:ir.model.fields,help:announcement.field_announcement__notification_end_date +#: model:ir.model.fields,help:announcement.field_announcement__notification_start_date +msgid "Technical field to display announcements in the calendar view" +msgstr "Campo tecnico per visualizzare le notifiche nella vista calendario" + +#. module: announcement +#: model:ir.model.fields,help:announcement.field_announcement__color +msgid "Technical field to display items by color in the calendar" +msgstr "Campo tecnico per visualizzare elementi per colore nel calendario" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__name +msgid "Title" +msgstr "Titolo" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_management_view_tree +msgid "Total users" +msgstr "Utenti totali" + +#. module: announcement +#: model:ir.model.fields.selection,name:announcement.selection__read_announcement_wizard__read_state__unread +msgid "Unread" +msgstr "Non letto" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_res_users__unread_announcement_ids +msgid "Unread Announcement" +msgstr "Notifica non letta" + +#. module: announcement +#: model:ir.model,name:announcement.model_res_users +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard__user_id +msgid "User" +msgstr "Utente" + +#. module: announcement +#: model:ir.model.fields,field_description:announcement.field_announcement__user_group_ids +msgid "User Group" +msgstr "Gruppo utente" + +#. module: announcement +#: model:ir.model,name:announcement.model_announcement +msgid "User announcements" +msgstr "Notifiche utente" + +#. module: announcement +#: model:ir.model.fields.selection,name:announcement.selection__announcement__announcement_type__user_group +msgid "User groups" +msgstr "Gruppi utente" + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_management_view_form +msgid "Users" +msgstr "Utenti" + +#. module: announcement +#: model:res.groups,comment:announcement.announcemenent_manager +msgid "Users allowed to manage and configure announcements." +msgstr "Utenti abilitati a gestire e configurare notifiche." + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "Valid up to" +msgstr "Valida fino al" + +#. module: announcement +#: model:ir.model.fields,help:announcement.field_announcement__attachment_ids +msgid "You can attach the copy of your Letter" +msgstr "Si può allegare la copia della lettera" + +#. module: announcement +#. odoo-python +#: code:addons/announcement/models/announcement_tag.py:0 +msgid "You cannot create recursive tags." +msgstr "Non si possono creare etichette ricorsive." + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_form +msgid "e.g. Announcement description..." +msgstr "es. Descrizione notifica..." + +#. module: announcement +#: model_terms:ir.ui.view,arch_db:announcement.announcement_view_calendar +msgid "name" +msgstr "nome" diff --git a/announcement/models/__init__.py b/announcement/models/__init__.py new file mode 100644 index 0000000000..b71238673c --- /dev/null +++ b/announcement/models/__init__.py @@ -0,0 +1,4 @@ +from . import announcement +from . import announcement_tag +from . import res_users +from . import ir_http diff --git a/announcement/models/announcement.py b/announcement/models/announcement.py new file mode 100644 index 0000000000..11241b15e6 --- /dev/null +++ b/announcement/models/announcement.py @@ -0,0 +1,280 @@ +# Copyright 2022 Tecnativa - David Vidal +# Copyright 2022 Tecnativa - Pilar Vargas +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import api, fields, models + + +class AnnouncementLog(models.Model): + _name = "announcement.log" + _description = "Log user reads" + _order = "create_date desc" + + announcement_id = fields.Many2one(comodel_name="announcement") + + +class Announcement(models.Model): + _name = "announcement" + _description = "User announcements" + _order = "notification_date, sequence asc, id" + + active = fields.Boolean(copy=False) + sequence = fields.Integer() + name = fields.Char(string="Title", required=True) + content = fields.Html() + tag_ids = fields.Many2many( + comodel_name="announcement.tag", + column1="announcement_id", + column2="tag_id", + string="Tags", + ) + is_general_announcement = fields.Boolean("General Announcement") + attachment_ids = fields.Many2many( + comodel_name="ir.attachment", + string="Attachments", + help="You can attach the copy of your Letter", + ) + announcement_type = fields.Selection( + selection=[ + ("specific_users", "Specific users"), + ("user_group", "User groups"), + ], + default="specific_users", + required=True, + ) + specific_user_ids = fields.Many2many( + comodel_name="res.users", + context={"active_test": False}, + domain=[("share", "=", False)], + inverse="_inverse_specific_user_ids", + ) + user_group_ids = fields.Many2many( + comodel_name="res.groups", + compute="_compute_user_group_ids", + store=True, + readonly=False, + ) + allowed_user_ids = fields.Many2many( + comodel_name="res.users", + relation="announcement_res_users_allowed_rel", + compute="_compute_allowed_user_ids", + compute_sudo=True, + store=True, + ) + allowed_users_count = fields.Integer( + compute="_compute_allowed_user_ids", + compute_sudo=True, + store=True, + ) + read_announcement_count = fields.Integer( + compute="_compute_read_announcement_count", + store=True, + ) + notification_date = fields.Datetime() + notification_expiry_date = fields.Datetime() + notification_start_date = fields.Datetime( + compute="_compute_notification_start_date", + help="Technical field to display announcements in the calendar view", + store=True, + ) + notification_end_date = fields.Datetime( + compute="_compute_notification_end_date", + help="Technical field to display announcements in the calendar view", + store=True, + ) + color = fields.Integer( + compute="_compute_color", + help="Technical field to display items by color in the calendar", + ) + in_date = fields.Boolean( + compute="_compute_in_date", search="_search_in_date", compute_sudo=True + ) + announcement_log_ids = fields.One2many( + comodel_name="announcement.log", + inverse_name="announcement_id", + ) + + def _inverse_specific_user_ids(self): + """Used to set users unread announcements when they're set in the announcement + itself""" + for announcement in self: + for user in announcement.specific_user_ids.filtered( + lambda x, announcement=announcement: announcement + not in (x.read_announcement_ids + x.unread_announcement_ids) + ): + user.unread_announcement_ids |= announcement + + @api.depends("specific_user_ids", "user_group_ids") + def _compute_allowed_user_ids(self): + self.allowed_user_ids = False + self.allowed_users_count = False + specific_user_announcements = self.filtered( + lambda x: x.announcement_type == "specific_users" + ) + for announcement in specific_user_announcements: + announcement.allowed_user_ids = announcement.specific_user_ids + announcement.allowed_users_count = len(announcement.specific_user_ids) + for announcement in self - specific_user_announcements: + announcement.allowed_user_ids = announcement.user_group_ids.all_user_ids + announcement.allowed_users_count = len( + announcement.user_group_ids.all_user_ids + ) + + @api.depends("is_general_announcement") + def _compute_user_group_ids(self): + for announcement in self: + if announcement.is_general_announcement: + announcement.announcement_type = "user_group" + announcement.user_group_ids = self.env.ref("base.group_user") + else: + announcement.user_group_ids = False + + @api.depends("announcement_log_ids") + def _compute_read_announcement_count(self): + logs = self.env["announcement.log"].formatted_read_group( + [("announcement_id", "in", self.ids)], + ["announcement_id"], + ["__count"], + ) + result = { + data["announcement_id"][0]: data["__count"] + for data in logs + if data.get("announcement_id") + } + for announcement in self: + announcement.read_announcement_count = result.get(announcement.id, 0) + + @api.depends("notification_date") + def _compute_notification_start_date(self): + """This is a technical field that we'll use so we're able to render + announcements with no defined start date. Otherwise they don't show up""" + for announcement in self: + announcement.notification_start_date = ( + announcement.notification_date or announcement.create_date + ) + + @api.depends("notification_expiry_date", "notification_date") + def _compute_notification_end_date(self): + """This is a technical field that we'll use so we're able to render no end + announcements in the calendar""" + for announcement in self: + announcement.notification_end_date = ( + announcement.notification_expiry_date + # We don't want undefined end announment appearing forever in the + # calendar just because one user didn't read them. So we just + # show them in the date they start + or announcement.notification_start_date + ) + + @api.depends("tag_ids") + def _compute_color(self): + """Get the first tag color if any. Used in the calendar""" + self.color = False + for announcement in self.filtered("tag_ids"): + announcement.color = announcement.tag_ids[0].color + + def _compute_in_date(self): + """The announcement is publishable according to date criterias""" + self.in_date = False + now = fields.Datetime.now() + for record in self: + date_passed = ( + not record.notification_date or record.notification_date <= now + ) + date_unexpired = ( + not record.notification_expiry_date + or record.notification_expiry_date >= now + ) + record.in_date = date_passed and date_unexpired + + def _search_in_date(self, operator, value): + """Used mainly for record rules as time module values will be cached""" + now = fields.Datetime.now() + return [ + "|", + ("notification_date", "=", False), + ("notification_date", "<=", now), + "|", + ("notification_expiry_date", "=", False), + ("notification_expiry_date", ">=", now), + ] + + def _process_attachments(self, vals): + """Assign attachments owner (if not yet set) or create a copy of the added + attachments for making sure that they are accessible to the users that read + the announcement. + """ + if self.env.context.get("bypass_attachment_process"): + return + for command in vals.get("attachment_ids", []): + to_process = [] + if command[0] == 4: + to_process.append(command[1]) + elif command[0] == 6: + to_process += command[2] + for attachment_id in to_process: + attachment = self.env["ir.attachment"].browse(attachment_id) + for record in self: + if not attachment.res_id: + attachment.res_id = record.id + attachment.res_model = record._name + else: + new_attachment = attachment.copy( + {"res_id": record.id, "res_model": record._name} + ) + record.with_context( + bypass_attachment_process=True + ).attachment_ids = [(3, attachment_id), (4, new_attachment.id)] + + @api.model_create_multi + def create(self, vals_list): + """Adjust attachments for being accesible to receivers of the announcement.""" + records = super().create(vals_list) + for vals in vals_list: + records._process_attachments(vals) + return records + + def write(self, vals): + """Adjust attachments for being accesible to receivers of the announcement.""" + res = super().write(vals) + self._process_attachments(vals) + return res + + @api.onchange("announcement_type") + def _onchange_announcement_type(self): + """We want to reset the values on screen""" + if self.announcement_type == "specific_users": + self.user_group_ids = False + elif self.announcement_type == "user_group": + self.specific_user_ids = False + + def action_announcement_log(self): + """See altogether read logs and unread users""" + self.ensure_one() + read_logs = self.env["announcement.log"].search( + [("announcement_id", "in", self.ids)] + ) + unread_users = self.allowed_user_ids.filtered( + lambda x: x not in read_logs.create_uid + ) + read_unread_log = self.env["read.announcement.wizard"].create( + [ + { + "user_id": log.create_uid.id, + "date": log.create_date, + "announcement_id": self.id, + "read_state": "read", + } + for log in read_logs + ] + ) + read_unread_log += self.env["read.announcement.wizard"].create( + [{"user_id": user.id, "read_state": "unread"} for user in unread_users] + ) + return { + "type": "ir.actions.act_window", + "res_model": "read.announcement.wizard", + "views": [[False, "list"]], + "domain": [("id", "in", read_unread_log.ids)], + "context": dict(self.env.context, create=False, group_by=["read_state"]), + "name": self.env._("Read Logs"), + } diff --git a/announcement/models/announcement_tag.py b/announcement/models/announcement_tag.py new file mode 100644 index 0000000000..0c6d92d981 --- /dev/null +++ b/announcement/models/announcement_tag.py @@ -0,0 +1,41 @@ +# Copyright 2023 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import api, fields, models +from odoo.exceptions import ValidationError + + +class AnnouncementTag(models.Model): + _name = "announcement.tag" + _description = "Announcement Tags" + + name = fields.Char(required=True, translate=True) + color = fields.Integer() + parent_id = fields.Many2one( + comodel_name="announcement.tag", + string="Parent Tag", + index=True, + ondelete="cascade", + ) + company_id = fields.Many2one( + comodel_name="res.company", + string="Company", + index=True, + help="Company related to this tag", + ) + + _name_uniq = models.Constraint( + "unique (name)", + "Tag name already exists!", + ) + + @api.constrains("parent_id") + def _check_parent_id(self): + if not self._has_cycle(): + raise ValidationError(self.env._("You cannot create recursive tags.")) + + @api.depends("parent_id", "name") + def _compute_display_name(self): + for item in self: + item.display_name = ( + f"{item.parent_id.name} / {item.name}" if item.parent_id else item.name + ) diff --git a/announcement/models/ir_http.py b/announcement/models/ir_http.py new file mode 100644 index 0000000000..b4b54ff861 --- /dev/null +++ b/announcement/models/ir_http.py @@ -0,0 +1,12 @@ +# Copyright 2024 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import models + + +class IrHttp(models.AbstractModel): + _inherit = "ir.http" + + def session_info(self): + res = super().session_info() + res["announcements"] = self.env["res.users"].get_announcements() + return res diff --git a/announcement/models/res_users.py b/announcement/models/res_users.py new file mode 100644 index 0000000000..ce16c13018 --- /dev/null +++ b/announcement/models/res_users.py @@ -0,0 +1,98 @@ +# Copyright 2022 Tecnativa - David Vidal +# Copyright 2022 Tecnativa - Pilar Vargas +# Copyright 2022 Tecnativa - Pedro M. Baeza +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from markupsafe import Markup + +from odoo import api, fields, models + + +class ResUsers(models.Model): + _inherit = "res.users" + + unread_announcement_ids = fields.Many2many( + comodel_name="announcement", + relation="unread_announcement_user_rel", + ) + read_announcement_ids = fields.Many2many( + comodel_name="announcement", + relation="read_announcement_user_rel", + ) + + @api.model + def announcement_user_count(self): + """The js widget gathers the announcements from this method""" + # It would be better to rely on record rules, but then announcement managers + # would see every announcement, which would be annoying. + group_announcements = self.env["announcement"].search_read( + [ + ("announcement_type", "=", "user_group"), + ("in_date", "=", True), + ("id", "not in", self.env.user.read_announcement_ids.ids), + ], + ["user_group_ids"], + ) + announcements = self.env["announcement"].browse( + { + x["id"] + for x in group_announcements + if any( + [ + g + for g in x["user_group_ids"] + if g in self.env.user.all_group_ids.ids + ] + ) + } + ) + # Unread announcements are directly linked to the user. Normally, only a + # handful of records will be evaluated at best. + announcements |= self.env.user.unread_announcement_ids.filtered("in_date") + return [ + { + "id": announcement.id, + "name": announcement.name, + "content": self._add_attachment_links(announcement), + } + for announcement in announcements.sorted(lambda k: k.sequence) + ] + + @api.model + def get_announcements(self): + announcements = self.announcement_user_count() + return { + "data": announcements, + "count": len(announcements), + } + + def _add_attachment_links(self, announcement): + """In case the announcement has attachments, show the list below the + modal content""" + content = announcement.content or Markup("") + attachment_links = "" + if announcement.attachment_ids: + attachment_links += "
" + for attachment in announcement.attachment_ids: + attachment_url = f"/web/content/{attachment.id}?download=false" + attachment_link = ( + f' ' + f"{attachment.name}" + ) + attachment_links += attachment_link + attachment_links += "
" + if attachment_links: + content += Markup("
") + Markup(attachment_links) + return content + + @api.model + def mark_announcement_as_read(self, announcement_id): + """Used as a controller for the widget""" + announcement = self.env["announcement"].browse(int(announcement_id)) + self.env.user.unread_announcement_ids -= announcement + # Log only the first time. Users with the announcement in multiple windows would + # log multiple reads. We're only interested in the first one. + if announcement not in self.env.user.read_announcement_ids: + self.env.user.read_announcement_ids += announcement + self.env["announcement.log"].create({"announcement_id": announcement.id}) diff --git a/announcement/pyproject.toml b/announcement/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/announcement/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/announcement/readme/CONFIGURE.md b/announcement/readme/CONFIGURE.md new file mode 100644 index 0000000000..08edfe76df --- /dev/null +++ b/announcement/readme/CONFIGURE.md @@ -0,0 +1,23 @@ +To create new announcements a user should be in the *Announcements +Managers* group. When your user has such permissions, this is the way to +create an announcement: + +1. Go to *Discuss \> Announcements* +2. Create a new one and define a title. This title will be shown in the + announcement header. +3. Define the announcement scope: + - Specific users: manually select which users will see the + announcement. + - User groups: users from the selected groups will be the ones to + see the announcement. +4. Define the announcement body. You can use rich formatting and event + paste your own html (editor in debug mode). +5. By default, the announcement will be archived. This is to prevent + the announcement to show up before time. +6. Once the announcement is ready, unarchive it going to the *Actions* + menu an choosing the *Unarchive* option. +7. Optionally you can set an announcement date to schedule the + announcement. The announcement won't show up until that date. +8. If the announcement doesn't make sense once a date is passed, you + can set a due date. From that date, the announcement won't be shown + to anyone. diff --git a/announcement/readme/CONTRIBUTORS.md b/announcement/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..99db49b7dd --- /dev/null +++ b/announcement/readme/CONTRIBUTORS.md @@ -0,0 +1,5 @@ +- [Tecnativa](https://www.tecnativa.com): + - Pedro M. Baeza + - David Vidal + - Carlos Roca + - David Bañón Gil diff --git a/announcement/readme/DESCRIPTION.md b/announcement/readme/DESCRIPTION.md new file mode 100644 index 0000000000..44dbc47576 --- /dev/null +++ b/announcement/readme/DESCRIPTION.md @@ -0,0 +1,3 @@ +This module adds popup announcements in the backend for targeted +internal users. Those announcements can contain rich format and a user +read log is kept for everyone. diff --git a/announcement/readme/ROADMAP.md b/announcement/readme/ROADMAP.md new file mode 100644 index 0000000000..d89222e485 --- /dev/null +++ b/announcement/readme/ROADMAP.md @@ -0,0 +1,3 @@ +- It could be integrated in Discuss app to review past announcements. +- Log other information like geolocation, IP, browser agent, etc when + marking announcement as read. diff --git a/announcement/readme/USAGE.md b/announcement/readme/USAGE.md new file mode 100644 index 0000000000..81356205c3 --- /dev/null +++ b/announcement/readme/USAGE.md @@ -0,0 +1,10 @@ +When a user in the scope of active announcements logs in, those will +popup. The user has to mark them as read to continue working. If the +announcement is set during the user session, the announcement will be +eventually prompted in the top bar on the right part. The user click on +the unread announcements icon (a speaker) and the announcements will +popup for the user to check them. + +Users can go *Discuss \> Announcements* to check current and past +announcements. Announcement managers can also track which users have +read the announcement. diff --git a/announcement/security/announcement_security.xml b/announcement/security/announcement_security.xml new file mode 100644 index 0000000000..fc0f11938e --- /dev/null +++ b/announcement/security/announcement_security.xml @@ -0,0 +1,64 @@ + + + + Announcement + + + + Announcement + + + + + Announcement Manager + + + Users allowed to manage and configure announcements. + + + + + Announcement log per user + + [('create_uid','=', user.id)] + + + + Announcement log manager + + [(1, '=', 1)] + + + + User announcements + + [('active', '=', True), ('allowed_user_ids', 'in', user.id), ('in_date', '=', True)] + + + + Announcement managers + + [(1, '=', 1)] + + + + + + Announcement Tag multi-company + + [('company_id', 'in', [False] + company_ids)] + + diff --git a/announcement/security/ir.model.access.csv b/announcement/security/ir.model.access.csv new file mode 100644 index 0000000000..a326d51a98 --- /dev/null +++ b/announcement/security/ir.model.access.csv @@ -0,0 +1,7 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_announcement_management,announcement.manager,model_announcement,announcement.announcemenent_manager,1,1,1,1 +access_announcement_user,announcement.user,model_announcement,base.group_user,1,0,0,0 +access_announcement_log_all,announcement_log_all,model_announcement_log,base.group_user,1,0,1,0 +access_read_announcement_wizard,access_read_announcement_wizard,model_read_announcement_wizard,announcemenent_manager,1,1,1,1 +access_announcement_tag_management,announcement.tag.manager,model_announcement_tag,announcement.announcemenent_manager,1,1,1,1 +access_announcement_tag_user,announcement.tag.user,model_announcement_tag,base.group_user,1,0,0,0 diff --git a/announcement/static/description/icon.png b/announcement/static/description/icon.png new file mode 100644 index 0000000000..8df47e4948 Binary files /dev/null and b/announcement/static/description/icon.png differ diff --git a/announcement/static/description/icon.svg b/announcement/static/description/icon.svg new file mode 100644 index 0000000000..34c618ad56 --- /dev/null +++ b/announcement/static/description/icon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/announcement/static/description/index.html b/announcement/static/description/index.html new file mode 100644 index 0000000000..1069f2fe74 --- /dev/null +++ b/announcement/static/description/index.html @@ -0,0 +1,489 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Announcement

+ +

Beta License: AGPL-3 OCA/server-ux Translate me on Weblate Try me on Runboat

+

This module adds popup announcements in the backend for targeted +internal users. Those announcements can contain rich format and a user +read log is kept for everyone.

+

Table of contents

+ +
+

Configuration

+

To create new announcements a user should be in the Announcements +Managers group. When your user has such permissions, this is the way to +create an announcement:

+
    +
  1. Go to Discuss > Announcements
  2. +
  3. Create a new one and define a title. This title will be shown in the +announcement header.
  4. +
  5. Define the announcement scope:
      +
    • Specific users: manually select which users will see the +announcement.
    • +
    • User groups: users from the selected groups will be the ones to see +the announcement.
    • +
    +
  6. +
  7. Define the announcement body. You can use rich formatting and event +paste your own html (editor in debug mode).
  8. +
  9. By default, the announcement will be archived. This is to prevent the +announcement to show up before time.
  10. +
  11. Once the announcement is ready, unarchive it going to the Actions +menu an choosing the Unarchive option.
  12. +
  13. Optionally you can set an announcement date to schedule the +announcement. The announcement won’t show up until that date.
  14. +
  15. If the announcement doesn’t make sense once a date is passed, you can +set a due date. From that date, the announcement won’t be shown to +anyone.
  16. +
+
+
+

Usage

+

When a user in the scope of active announcements logs in, those will +popup. The user has to mark them as read to continue working. If the +announcement is set during the user session, the announcement will be +eventually prompted in the top bar on the right part. The user click on +the unread announcements icon (a speaker) and the announcements will +popup for the user to check them.

+

Users can go Discuss > Announcements to check current and past +announcements. Announcement managers can also track which users have +read the announcement.

+
+
+

Known issues / Roadmap

+
    +
  • It could be integrated in Discuss app to review past announcements.
  • +
  • Log other information like geolocation, IP, browser agent, etc when +marking announcement as read.
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Tecnativa
  • +
+
+
+

Contributors

+
    +
  • Tecnativa:
      +
    • Pedro M. Baeza
    • +
    • David Vidal
    • +
    • Carlos Roca
    • +
    • David Bañón Gil
    • +
    +
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/server-ux project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+
+ + diff --git a/announcement/static/src/js/announcement_dialog/announcement_dialog.esm.js b/announcement/static/src/js/announcement_dialog/announcement_dialog.esm.js new file mode 100644 index 0000000000..f5da2dd122 --- /dev/null +++ b/announcement/static/src/js/announcement_dialog/announcement_dialog.esm.js @@ -0,0 +1,7 @@ +/* Copyright 2024 Tecnativa - Carlos Roca + * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ +import {ConfirmationDialog} from "@web/core/confirmation_dialog/confirmation_dialog"; + +// Defined AnnouncementDialog to allow make possible changes in other modules +// like announcement_dialog_size +export class AnnouncementDialog extends ConfirmationDialog {} diff --git a/announcement/static/src/js/announcement_menu/announcement_menu.esm.js b/announcement/static/src/js/announcement_menu/announcement_menu.esm.js new file mode 100644 index 0000000000..76ce62d0f7 --- /dev/null +++ b/announcement/static/src/js/announcement_menu/announcement_menu.esm.js @@ -0,0 +1,103 @@ +/* Copyright 2024 Tecnativa - David Vidal + * Copyright 2024 Tecnativa - Carlos Roca + * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ +import {Component, markup, onMounted, useState} from "@odoo/owl"; +import {AnnouncementDialog} from "../announcement_dialog/announcement_dialog.esm"; +import {Dropdown} from "@web/core/dropdown/dropdown"; +import {DropdownItem} from "@web/core/dropdown/dropdown_item"; +import {deserializeDateTime} from "@web/core/l10n/dates"; +import {registry} from "@web/core/registry"; +import {session} from "@web/session"; +import {_t} from "@web/core/l10n/translation"; +import {useDiscussSystray} from "@mail/utils/common/hooks"; +import {user} from "@web/core/user"; +import {useService} from "@web/core/utils/hooks"; +const {DateTime} = luxon; + +export class AnnouncementMenu extends Component { + setup() { + this.discussSystray = useDiscussSystray(); + this.orm = useService("orm"); + this.dialogService = useService("dialog"); + const announcements_service = useService("announcementService"); + this.announcements = useState(announcements_service.announcements); + // When the user logs in we show him his unread announcements + onMounted(async () => { + // Let's check if the user just logged in and to decide if we popup the + // announcements. This delay is hardcoded to 5 minutes, although we could + // allow to configure it in the future. + const current_user = await this.orm.call("res.users", "read", [ + user.userId, + ["login_date"], + ]); + const minutes_since_last_login = + (DateTime.now().toSeconds() - + deserializeDateTime( + current_user[0] && current_user[0].login_date + ).toSeconds()) / + 60; + const popup_announcement = Boolean(minutes_since_last_login < 5); + const launchPopUp = () => { + if (odoo.isReady) { + if (popup_announcement && this.announcements.count > 0) { + this.openAnnouncement(this.announcements.data[0]); + } + } else { + setTimeout(launchPopUp, 500); + } + }; + setTimeout(launchPopUp, 500); + }); + } + + async getDialogAnnouncementProps(announcement) { + return { + title: announcement.name, + body: markup(announcement.content || ""), + confirm: async () => { + await this.orm.call( + "res.users", + "mark_announcement_as_read", + [announcement.id], + {context: session.user_context} + ); + this.announcements.data = this.announcements.data.filter( + (el) => el.id !== announcement.id + ); + this.announcements.count--; + if (this.announcements.count > 0) { + this.openAnnouncement(this.announcements.data[0]); + } + }, + confirmLabel: _t("Mark as read"), + }; + } + + // ------------------------------------------------------------ + // Handlers + // ------------------------------------------------------------ + + /** + * Show announcement popup + * @private + * @param {MouseEvent} announcement + */ + async openAnnouncement(announcement) { + this.dialogService.add( + AnnouncementDialog, + await this.getDialogAnnouncementProps(announcement) + ); + } +} + +AnnouncementMenu.components = {Dropdown, DropdownItem}; +AnnouncementMenu.props = []; +AnnouncementMenu.template = "announcement.AnnouncementMenu"; + +export const systrayAnnouncement = { + Component: AnnouncementMenu, +}; + +registry + .category("systray") + .add("announcement.announcement_menu", systrayAnnouncement, {sequence: 100}); diff --git a/announcement/static/src/js/announcement_menu/announcement_menu.xml b/announcement/static/src/js/announcement_menu/announcement_menu.xml new file mode 100644 index 0000000000..11dccd4953 --- /dev/null +++ b/announcement/static/src/js/announcement_menu/announcement_menu.xml @@ -0,0 +1,53 @@ + + + + + + +
+
+ No announcements. +
+
+ +
+ Announcement +
+
+
+
+ +
+
+ + + + diff --git a/announcement/static/src/js/announcement_service/announcement_service.esm.js b/announcement/static/src/js/announcement_service/announcement_service.esm.js new file mode 100644 index 0000000000..1c5741b95a --- /dev/null +++ b/announcement/static/src/js/announcement_service/announcement_service.esm.js @@ -0,0 +1,36 @@ +/* Copyright 2024 Tecnativa - David Vidal + * Copyright 2024 Tecnativa - Carlos Roca + * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ +import {reactive} from "@odoo/owl"; +import {registry} from "@web/core/registry"; +import {session} from "@web/session"; + +export const announcementService = { + dependencies: ["orm"], + async start(env, {orm}) { + const announcements = reactive({}); + if (session.announcements) { + Object.assign(announcements, session.announcements); + } else { + Object.assign( + announcements, + await orm.call("res.users", "get_announcements", [], { + context: session.user_context, + }) + ); + } + setInterval(async () => { + Object.assign( + announcements, + await orm.call("res.users", "get_announcements", [], { + context: session.user_context, + }) + ); + }, 60000); + return { + announcements, + }; + }, +}; + +registry.category("services").add("announcementService", announcementService); diff --git a/announcement/tests/__init__.py b/announcement/tests/__init__.py new file mode 100644 index 0000000000..8740d4e000 --- /dev/null +++ b/announcement/tests/__init__.py @@ -0,0 +1 @@ +from . import test_announcement diff --git a/announcement/tests/test_announcement.py b/announcement/tests/test_announcement.py new file mode 100644 index 0000000000..d7c08fbfb0 --- /dev/null +++ b/announcement/tests/test_announcement.py @@ -0,0 +1,87 @@ +from datetime import datetime, timedelta + +from odoo.tests import tagged, users + +from odoo.addons.base.tests.common import BaseCommon + + +@tagged("-at_install", "post_install") +class TestAnnouncement(BaseCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.general_announcement = cls.env["announcement"].create( + { + "name": "Test announcement", + "content": "

Test content for test announcement

", + "is_general_announcement": True, + "notification_date": datetime.now() + timedelta(days=-1), + "notification_expiry_date": datetime.now() + timedelta(days=+1), + "active": True, + } + ) + cls.expired_announcement = cls.env["announcement"].create( + { + "name": "Test expired announcement", + "content": "

Its gone

", + "is_general_announcement": True, + "notification_date": datetime.now() + timedelta(days=-2), + "notification_expiry_date": datetime.now() + timedelta(days=-1), + "active": True, + } + ) + cls.admin_announcement = cls.env["announcement"].create( + { + "name": "Test admin only announcement", + "content": "

Test content for admins only

", + "announcement_type": "user_group", + "user_group_ids": cls.env.ref("base.group_system"), + "notification_date": datetime.now() + timedelta(days=-1), + "notification_expiry_date": datetime.now() + timedelta(days=+1), + "active": True, + } + ) + cls.demo_user = cls.env["res.users"].create( + { + "name": "Demo User", + "login": "demo", + "group_ids": [(6, 0, [cls.env.ref("base.group_user").id])], + } + ) + cls.attachment = cls.env["ir.attachment"].create( + {"name": "Test Attachment", "datas": b"Test data"} + ) + cls.specific_announcement = cls.env["announcement"].create( + { + "name": "Specific Users Announcement", + "announcement_type": "specific_users", + "specific_user_ids": [(6, 0, [cls.demo_user.id])], + "attachment_ids": [(4, cls.attachment.id)], + } + ) + + @users("admin") + def test_announcements_admin(self): + res = self.env.user.get_announcements() + announcement_ids = [announcement["id"] for announcement in res["data"]] + self.assertIn(self.general_announcement.id, announcement_ids) + self.assertIn(self.admin_announcement.id, announcement_ids) + self.assertNotIn(self.expired_announcement.id, announcement_ids) + + @users("demo") + def test_announcements_user(self): + res = self.env.user.get_announcements() + announcement_ids = [announcement["id"] for announcement in res["data"]] + self.assertIn(self.general_announcement.id, announcement_ids) + self.assertNotIn(self.admin_announcement.id, announcement_ids) + self.assertNotIn(self.expired_announcement.id, announcement_ids) + + @users("demo") + def test_demo_user_specific_announcement(self): + res = self.env.user.get_announcements() + announcement_ids = [announcement["id"] for announcement in res["data"]] + self.assertIn(self.general_announcement.id, announcement_ids) + self.assertNotIn(self.admin_announcement.id, announcement_ids) + self.assertNotIn(self.expired_announcement.id, announcement_ids) + self.assertNotIn(self.specific_announcement.id, announcement_ids) + self.assertIn(self.attachment.id, self.specific_announcement.attachment_ids.ids) diff --git a/announcement/views/announcement_tag_views.xml b/announcement/views/announcement_tag_views.xml new file mode 100644 index 0000000000..43472b51b6 --- /dev/null +++ b/announcement/views/announcement_tag_views.xml @@ -0,0 +1,49 @@ + + + + + announcement.tag + + + + + + + + + + announcement.tag + +
+ + + + + + + + + + + + +
+
+
+ + Announcement Tags + ir.actions.act_window + announcement.tag + list,form + {} + + +
diff --git a/announcement/views/announcement_views.xml b/announcement/views/announcement_views.xml new file mode 100644 index 0000000000..abd864547c --- /dev/null +++ b/announcement/views/announcement_views.xml @@ -0,0 +1,231 @@ + + + + + announcement.log + + + + + + + + + Announcement Logs + announcement.log + {} + [('announcement_id', '=', active_id)] + list + + + announcement + + + + + + + + + + + announcement + + + + + + + + + + + + + + announcement + +
+ +
+ +
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + announcement + + + + + + + + + + + + + + + + announcement + + + + + + +
+
+ + +
+
+
+
+
+
+
+ + announcement + + + + + + + + + + announcement + + + + + + + + + + + + + + + + Announcements + ir.actions.act_window + announcement + list,kanban,calendar,form + {} + + + diff --git a/announcement/wizards/__init__.py b/announcement/wizards/__init__.py new file mode 100644 index 0000000000..f1c6a252fe --- /dev/null +++ b/announcement/wizards/__init__.py @@ -0,0 +1 @@ +from . import read_announcement_wizard diff --git a/announcement/wizards/read_announcement_wizard.py b/announcement/wizards/read_announcement_wizard.py new file mode 100644 index 0000000000..a675b2851c --- /dev/null +++ b/announcement/wizards/read_announcement_wizard.py @@ -0,0 +1,14 @@ +# Copyright 2022 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class ReadAnnouncementWizard(models.TransientModel): + _name = "read.announcement.wizard" + _description = "Show altogether users who read and users who didn't" + _order = "date desc" + + date = fields.Datetime(string="Read Date") + user_id = fields.Many2one(comodel_name="res.users") + announcement_id = fields.Many2one(comodel_name="announcement") + read_state = fields.Selection(selection=[("read", "Read"), ("unread", "Unread")]) diff --git a/announcement/wizards/read_announcement_wizard.xml b/announcement/wizards/read_announcement_wizard.xml new file mode 100644 index 0000000000..8638be9f1f --- /dev/null +++ b/announcement/wizards/read_announcement_wizard.xml @@ -0,0 +1,14 @@ + + + + + read.announcement.wizard + + + + + + + +