diff --git a/.copier-answers.yml b/.copier-answers.yml index f00f66b849..35d29a67ba 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Do NOT update manually; changes here will be overwritten by Copier -_commit: v1.21.1 +_commit: v1.24 _src_path: gh:oca/oca-addons-repo-template additional_ruff_rules: [] ci: GitHub diff --git a/.eslintrc.yml b/.eslintrc.yml index fed88d70d2..0b38203958 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -4,7 +4,7 @@ env: # See https://github.com/OCA/odoo-community.org/issues/37#issuecomment-470686449 parserOptions: - ecmaVersion: 2019 + ecmaVersion: 2022 overrides: - files: diff --git a/.gitignore b/.gitignore index 0090721f5d..2b045db399 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,15 @@ var/ *.egg *.eggs +# Debian packages +*.deb + +# Redhat packages +*.rpm + +# MacOS packages +*.dmg + # Installer logs pip-log.txt pip-delete-this-directory.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9bc4619326..f252c4022d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,7 +43,7 @@ repos: hooks: - id: whool-init - repo: https://github.com/oca/maintainer-tools - rev: 9a170331575a265c092ee6b24b845ec508e8ef75 + rev: d5fab7ee87fceee858a3d01048c78a548974d935 hooks: # update the NOT INSTALLABLE ADDONS section above - id: oca-update-pre-commit-excluded-addons diff --git a/announcement/README.rst b/announcement/README.rst new file mode 100644 index 0000000000..727dee3401 --- /dev/null +++ b/announcement/README.rst @@ -0,0 +1,132 @@ +============ +Announcement +============ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:784e888a875fb1f37b9d3a48ca5c5c63bdec23a06cce24e648a5910f4907891d + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/licence-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/17.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-17-0/server-ux-17-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=17.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 + +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..05d3062336 --- /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": "17.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..93e59ccc2b --- /dev/null +++ b/announcement/i18n/announcement.pot @@ -0,0 +1,447 @@ +# 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: \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 +#, python-format +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 +#: 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 +#, python-format +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_tag__full_name +msgid "Full Name" +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____last_update +#: model:ir.model.fields,field_description:announcement.field_announcement_log____last_update +#: model:ir.model.fields,field_description:announcement.field_announcement_tag____last_update +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard____last_update +msgid "Last Modified on" +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 +#, python-format +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 +#, python-format +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 +#, python-format +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 +#, python-format +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..0766098b80 --- /dev/null +++ b/announcement/i18n/es.po @@ -0,0 +1,450 @@ +# 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" +"POT-Creation-Date: 2024-04-12 14:41+0000\n" +"PO-Revision-Date: 2024-04-12 16:45+0200\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 +#, python-format +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 +#, python-format +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____last_update +#: model:ir.model.fields,field_description:announcement.field_announcement_log____last_update +#: model:ir.model.fields,field_description:announcement.field_announcement_tag____last_update +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard____last_update +msgid "Last Modified on" +msgstr "Última modificación en" + +#. 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 +#, python-format +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 +#, python-format +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 +#, python-format +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 +#, python-format +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..dc2bda6150 --- /dev/null +++ b/announcement/i18n/it.po @@ -0,0 +1,450 @@ +# 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" +"PO-Revision-Date: 2024-04-30 10:35+0000\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 4.17\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 +#, python-format +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 +#: 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 +#, python-format +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____last_update +#: model:ir.model.fields,field_description:announcement.field_announcement_log____last_update +#: model:ir.model.fields,field_description:announcement.field_announcement_tag____last_update +#: model:ir.model.fields,field_description:announcement.field_read_announcement_wizard____last_update +msgid "Last Modified on" +msgstr "Ultima modifica il" + +#. 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 +#, python-format +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 +#, python-format +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 adre" + +#. 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 +#, python-format +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 +#, python-format +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..e20e7a6937 --- /dev/null +++ b/announcement/models/announcement.py @@ -0,0 +1,274 @@ +# 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", + ) + notification_end_date = fields.Datetime( + compute="_compute_notification_end_date", + help="Technical field to display announcements in the calendar view", + ) + 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.users + announcement.allowed_users_count = len(announcement.user_group_ids.users) + + @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"].read_group( + [("announcement_id", "in", self.ids)], + ["announcement_id"], + ["announcement_id"], + ) + result = { + data["announcement_id"][0]: (data["announcement_id_count"]) for data in logs + } + 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, "tree"]], + "domain": [("id", "in", read_unread_log.ids)], + "context": dict(self.env.context, create=False, group_by=["read_state"]), + "name": _("Read Logs"), + } diff --git a/announcement/models/announcement_tag.py b/announcement/models/announcement_tag.py new file mode 100644 index 0000000000..a56831ebc1 --- /dev/null +++ b/announcement/models/announcement_tag.py @@ -0,0 +1,45 @@ +# 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", + ) + full_name = fields.Char(compute="_compute_full_name") + company_id = fields.Many2one( + comodel_name="res.company", + string="Company", + index=True, + help="Company related to this tag", + ) + + _sql_constraints = [("name_uniq", "unique (name)", "Tag name already exists!")] + + @api.constrains("parent_id") + def _check_parent_id(self): + if not self._check_recursion(): + raise ValidationError(_("You cannot create recursive tags.")) + + @api.depends("parent_id", "name") + def _compute_full_name(self): + for item in self: + item.full_name = ( + item.parent_id.name + " / " + item.name if item.parent_id else item.name + ) + + def name_get(self): + res = [] + for item in self: + res.append((item.id, item.full_name)) + return res 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..dbc01ca4aa --- /dev/null +++ b/announcement/models/res_users.py @@ -0,0 +1,94 @@ +# 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.groups_id.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 = "/web/content/%s?download=false" % attachment.id + 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..af5070d308 --- /dev/null +++ b/announcement/readme/CONTRIBUTORS.md @@ -0,0 +1,4 @@ +- [Tecnativa](https://www.tecnativa.com): + - Pedro M. Baeza + - David Vidal + - Carlos Roca 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..24472aa881 --- /dev/null +++ b/announcement/security/announcement_security.xml @@ -0,0 +1,58 @@ + + + + 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..9592dd60d5 Binary files /dev/null and b/announcement/static/description/icon.png differ diff --git a/announcement/static/description/index.html b/announcement/static/description/index.html new file mode 100644 index 0000000000..c61b955cc2 --- /dev/null +++ b/announcement/static/description/index.html @@ -0,0 +1,482 @@ + + + + + +Announcement + + + +
+

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
    • +
    +
  • +
+
+
+

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..043b46d834 --- /dev/null +++ b/announcement/static/src/js/announcement_dialog/announcement_dialog.esm.js @@ -0,0 +1,8 @@ +/* @odoo-module */ +/* 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..8e18787ca7 --- /dev/null +++ b/announcement/static/src/js/announcement_menu/announcement_menu.esm.js @@ -0,0 +1,102 @@ +/* @odoo-module */ +/* 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 {_lt} from "@web/core/l10n/translation"; +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 {useDiscussSystray} from "@mail/utils/common/hooks"; +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 user = await this.orm.call("res.users", "read", [ + session.uid, + ["login_date"], + ]); + const minutes_since_last_login = + (DateTime.now().toSeconds() - + deserializeDateTime(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: _lt("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..a17fb3c76f --- /dev/null +++ b/announcement/static/src/js/announcement_menu/announcement_menu.xml @@ -0,0 +1,56 @@ + + + + + + + + + +
+
+ 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..5e0d2c5beb --- /dev/null +++ b/announcement/static/src/js/announcement_service/announcement_service.esm.js @@ -0,0 +1,37 @@ +/** @odoo-module */ +/* 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/views/announcement_tag_views.xml b/announcement/views/announcement_tag_views.xml new file mode 100644 index 0000000000..56e8e1dfbd --- /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 + tree,form + {} + + +
diff --git a/announcement/views/announcement_views.xml b/announcement/views/announcement_views.xml new file mode 100644 index 0000000000..43e4e7c575 --- /dev/null +++ b/announcement/views/announcement_views.xml @@ -0,0 +1,241 @@ + + + + + announcement.log + + + + + + + + + Announcement Logs + announcement.log + {} + [('announcement_id', '=', active_id)] + tree + + + announcement + + + + + + + + + + + announcement + + + + + + + + + + + + + + announcement + +
+ +
+ +
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + announcement + + + + + + + + + + + + + + + + + announcement + + + + + + +
+
+
+ + +
+
+
+
+
+
+
+
+ + announcement + + + + + + + + + + announcement + + + + + + + + + + + + + + + + Announcements + ir.actions.act_window + announcement + tree,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..b4dcd81368 --- /dev/null +++ b/announcement/wizards/read_announcement_wizard.xml @@ -0,0 +1,14 @@ + + + + + read.announcement.wizard + + + + + + + +