01-07-2025

Merge branch 'develop'
This commit is contained in:
administrator 2025-07-01 16:29:40 +05:30
commit 23f0376727
90 changed files with 31409 additions and 12 deletions

View File

@ -373,8 +373,8 @@ this.state.attendance_lines = groupedAttendance;
line.color = 'red';
break;
default:
line.state = 'Refused';
line.color = 'red';
line.state = 'Draft';
line.color = '#6871f2';
break;
}
});

View File

@ -186,11 +186,11 @@
<h2>
Attendance
</h2>
<button class="action-button" t-on-click="add_attendance">
<i class="fa fa-plus">
</i>
Add
</button>
<!-- <button class="action-button" t-on-click="add_attendance">-->
<!-- <i class="fa fa-plus">-->
<!-- </i>-->
<!-- Add-->
<!-- </button>-->
</div>
<div class="card-content">
<table class="table">
@ -280,11 +280,11 @@
<h2>
Expenses
</h2>
<button class="action-button" t-on-click="add_expense">
<i class="fa fa-plus">
</i>
Add
</button>
<!-- <button class="action-button" t-on-click="add_expense">-->
<!-- <i class="fa fa-plus">-->
<!-- </i>-->
<!-- Add-->
<!-- </button>-->
</div>
<div class="card-content">
<table class="table">

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models

View File

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Web Gantt',
'category': 'Hidden',
'description': """
Odoo Web Gantt chart view.
=============================
""",
'version': '2.0',
'depends': ['web'],
'assets': {
'web._assets_primary_variables': [
'web_gantt/static/src/gantt_view.variables.scss',
],
'web.assets_backend_lazy': [
'web_gantt/static/src/**/*',
# Don't include dark mode files in light mode
('remove', 'web_gantt/static/src/**/*.dark.scss'),
],
'web.assets_backend_lazy_dark': [
'web_gantt/static/src/**/*.dark.scss',
],
'web.assets_unit_tests': [
'web_gantt/static/tests/**/*',
],
},
'auto_install': True,
}

View File

@ -0,0 +1,363 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Wil Odoo, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Wil Odoo, 2024\n"
"Language-Team: Arabic (https://app.transifex.com/odoo/teams/41243/ar/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: ar\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr "%(hour)s ساعات و%(minute)s "
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr "لا يمكن جدولة %s في الماضي "
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr "%s ساعات "
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "عرض نافذة الإجراء"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "تطبيق"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "هل أنت متأكد من أنك ترغب في حذف هذا السجل؟ "
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "قاعدة "
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "تصغير الصفوف "
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "إنشاء"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "تحرير"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "تكبير الصفوف "
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr "ركّز اليوم "
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "من"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "جانت"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "أداة عرض جانت"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr "يمكن أن يكون تابع غانت إما حقل أو قالب فقط، به %s "
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "يجب أن يكون لعرض غانت خاصية 'date_start' "
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "يجب أن يكون لعرض غانت خاصية 'date_stop' "
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
"يجب أن يكون لنافذة عرض غانت خاصية 'dependency_inverted_field' بمجرد أن يتم "
"تحديد 'dependency_field' "
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr "تاريخ بدء غانت "
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr "تاريخ انتهاء غانت "
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "يمكن أن يحتوي عرض غانت على علامة تصنيف قوالب واحدة فقط "
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr "لا يمكن القيام بالجدولة في الماضي. "
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "لا توجد حقول كافية لعرض غانت! "
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
"خواص غير صالحة (%(invalid_attributes)s) في عرض غانت. يجب أن تكون الخواص في "
"(%(valid_attributes)s) "
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr "default_range '%s' غير صالح في عرض غانت "
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "default_scale '%s' غير صالح في عرض غانت "
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr "display_mode '%s' في نافذة عرض غانت غير صالح "
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "الاسم"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "جديد"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "فتح"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "الخطة "
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "بدء"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "إيقاف"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr "التبعيات غير صالحة. لقد تسببت في إحداث دائرة مغلقة. "
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr "لا يوجد مرشحون صالحون لإعادة التخطيط "
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "هذا الشهر"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr "ربع السنة هذا "
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "هذا الأسبوع "
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "هذا العام"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "اليوم "
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr "قائمة شريط الأدوات "
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "الإجمالي"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "غير محدد %s "
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "أداة العرض"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "نوع واجهة العرض"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr "لا يمكنك تحريك %(record)s نحو %(related_record)s. "
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr "لا يمكنك إعادة جدولة %(main_record)s نحو %(other_record)s. "
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "ساعات"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "دقائق "
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "شهور"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "إلى"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr "{{ props.progressBar.warning }}"

View File

@ -0,0 +1,367 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Petko Karamotchev, 2024
# Martin Trigaux, 2024
# Rosen Vladimirov <vladimirov.rosen@gmail.com>, 2024
# Boyan Rabchev <boyan.rabchev@plana.solutions>, 2024
# Rumena Georgieva <rumena.georgieva@gmail.com>, 2024
# Vladimir Petrov <vladimir.petrov@gmail.com>, 2024
# KeyVillage, 2024
# Albena Mincheva <albena_vicheva@abv.bg>, 2024
# Maria Boyadjieva <marabo2000@gmail.com>, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Maria Boyadjieva <marabo2000@gmail.com>, 2024\n"
"Language-Team: Bulgarian (https://app.transifex.com/odoo/teams/41243/bg/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: bg\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Приложи"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Основа"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Създай"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Редактирай"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "От"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Диаграма на Гант"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Изглед на диаграма на Гант"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Име"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Нов"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Отворен"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Планирайте"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Стартирайте"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Край"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Този месец"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Тази седмица"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Днес"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Обща стойност"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Преглед"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Вид изглед"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "часове"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "минути"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "месеци"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "до"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr ""

View File

@ -0,0 +1,371 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Arnau Ros, 2024
# Ivan Espinola, 2024
# jabiri7, 2024
# Sandra Franch <sandra.franch@upc.edu>, 2024
# Josep Anton Belchi, 2024
# marcescu, 2024
# Martin Trigaux, 2024
# Quim - eccit <quim@eccit.com>, 2024
# RGB Consulting <odoo@rgbconsulting.com>, 2024
# Eric Antones <eantones@users.noreply.github.com>, 2024
# Manel Fernandez Ramirez <manelfera@outlook.com>, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Manel Fernandez Ramirez <manelfera@outlook.com>, 2024\n"
"Language-Team: Catalan (https://app.transifex.com/odoo/teams/41243/ca/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: ca\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Vista de la finestra d'acció"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Aplica"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "¿Està segur d'eliminar aquest registre?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Base"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "Col·lapsar files"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Crear"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Modificar"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "Expandeix les files"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "Des de"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Gràfic de Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Vista Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr "El fill de Gantt només pot ser un camp o una plantilla, té %s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "Gantt ha de tenir un atribut 'date_start'"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "Gantt ha de tenir un atribut \"date_stop\"."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
"Gantt ha de tenir un atribut 'dependency_inverted_field' una vegada s'hagi "
"especificat 'dependency_field' "
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "La vista de Gantt només pot contenir una etiqueta de plantilles"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "¡Camps insuficients per a la vista de Gantt!"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "Escala_per defecte invàlida '%s'a gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Nom"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Nou"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Oberts"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Pla"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Inicia"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Atura"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Aquest mes"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Aquesta setmana"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "Aquest any"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Avui"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Total"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "No definit %s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Vista"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Tipus de vista"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "hores"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "minuts"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "mesos"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "fins"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr ""

View File

@ -0,0 +1,361 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Wil Odoo, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Wil Odoo, 2024\n"
"Language-Team: Czech (https://app.transifex.com/odoo/teams/41243/cs/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: cs\n"
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Akce okna zobrazení"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Použít"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "Opravdu chcete tento záznam smazat?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Jádro"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "Sbalit řádky"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Vytvořit"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Upravit"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "Rozbalte řádky"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "Od"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Gantův diagram"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr "Ganttův potomek může být pouze pole nebo šablona, dostal %s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "Gantt musí mít atribut 'date_start'"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "Gantt musí mít atribut 'date_stop'"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
"Gantt musí mít atribut 'dependency_inverted_field', jakmile je specifikováno"
" 'dependency_field'"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "Zobrazení Gantt může obsahovat pouze jeden štítek šablony"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "Nedostatečná pole pro zobrazení Gantt!"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "Neplatný default_scale '%s' v gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Název"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Nové"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Volný"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Plán"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Začít"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Zastavit"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Tento měsíc"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Tento týden"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "Tento rok"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Dnes"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Celkem"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "Nedefinováno %s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Zobrazení"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Typ zobrazení"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "hodin"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "minut"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "měsíce"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "k"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr ""

View File

@ -0,0 +1,264 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Sanne Kristensen <sanne@vkdata.dk>, 2022
# Mads Søndergaard, 2022
# Mads Søndergaard, 2022
# Martin Trigaux, 2023
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-05-16 16:03+0000\n"
"PO-Revision-Date: 2022-09-22 05:49+0000\n"
"Last-Translator: Martin Trigaux, 2023\n"
"Language-Team: Danish (https://app.transifex.com/odoo/teams/41243/da/)\n"
"Language: da\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"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "Er du sikker på du vil slette dette datasæt?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Basis"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Collapse rows"
msgstr "Sammenfald rækker"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_cell_buttons.xml:0
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Opret"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Day"
msgstr "Dag"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Expand rows"
msgstr "Udvid rækker"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_view.js:0
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Gantt visning"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr "Gantt arving kan kun være et felt eller en skabelon, fik %s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "Gantt skal have en 'date_start' egenskab"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "Gantt skal have en 'date_stop' egenskab"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'dependency_inverted_field' attribute once the 'dependency_field' is specified"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "Gantt visning kan kun indeholde én skabelons tag"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Grouping by date is not supported"
msgstr "Gruppering per dato er ikke understøttet"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "Utilstrækkelig felter til Gantt visning!"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid attributes (%s) in gantt view. Attributes must be in (%s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "Ugyldig default_scale '%s' i gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Month"
msgstr "Måned"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Ny"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Next"
msgstr "Næste"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Åben"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Planlæg"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_cell_buttons.xml:0
msgid "Plan existing"
msgstr "Plan eksisterer"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Previous"
msgstr "Forrige"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Records that are in the past cannot be automatically rescheduled. They should be manually rescheduled instead."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Today"
msgstr "I dag"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "Udefineret %s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Vis"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Week"
msgstr "Uge"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Year"
msgstr "År"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %s towards %s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule tasks that do not follow a direct dependency path. Only the first task has been automatically rescheduled."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr ""

View File

@ -0,0 +1,364 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Wil Odoo, 2024
# Larissa Manderfeld, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Larissa Manderfeld, 2024\n"
"Language-Team: German (https://app.transifex.com/odoo/teams/41243/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr "%(hour)sSt.%(minute)s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr "%s kann nicht in der Vergangenheit liegen"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr "%sSt."
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Ansicht des Aktionsfensters"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr "Dense-Modus aktivieren"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr "Sparse-Modus aktivieren"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Anwenden"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "Sind Sie sicher, dass Sie diesen Datensatz löschen möchten?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Basis"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "Zeilen einklappen"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Erstellen"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Bearbeiten"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "Zeilen erweitern"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr "Heute fokussieren"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "Von"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Gantt-Ansicht"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr "Untergeordnetes Gantt kann nur Feld oder Vorlage sein, %s erhalten"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "Gantt muss ein „date_start“-Attribut haben"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "Gantt muss ein „date_stop“-Attribut haben"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
"Gantt muss ein „dependency_inverted_field“-Attribut haben, sobald das "
"„dependency_field“ bestimmt wurde"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr "Gantt-Startdatum"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr "Gantt-Enddatum"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "Gantt-Ansicht kann nur ein Vorlagen-Stichwort beinhalten"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr "Kann nicht in der Vergangenheit liegen"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "Unzureichende Felder für die Gantt-Ansicht!"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
"Ungültige Attribute (%(invalid_attributes)s) in der Gantt-Ansicht. Attribute"
" müssen in (%(valid_attributes)s) stehen."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr "Ungültige default_range „%s“ in Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "Ungültige default_scale „%s“ in Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr "Ungültiger display_mode „%s“ in Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Namen"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Neu"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Öffnen"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Planen"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr "Verschiebung erfolgreich abgeschlossen."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Loslegen"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Stopp"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr "Die Abhängigkeiten sind nicht gültig, es gibt einen Kreis."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr "Es sind keine gültigen Kandidaten neu zu planen."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Diesen Monat"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr "Dieses Quartal"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Diese Woche"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "Dieses Jahr"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Heute"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr "Symbolleistenmenü"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Gesamt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "%s Undefiniert"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Ansicht"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Ansichtstyp"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr "Sie können %(record)s nicht nach %(related_record)s verschieben."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr "Sie können %(main_record)s nicht nach %(other_record)s verschieben."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "Stunden"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "Minuten"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "Monate"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "bis"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr "{{ props.progressBar.warning }}"

View File

@ -0,0 +1,263 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Martin Trigaux, 2019
# Kostas Goutoudis <goutoudis@gmail.com>, 2019
# Giota Dandidou <giotadandidou@gmail.com>, 2019
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~12.2+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-05-16 16:03+0000\n"
"PO-Revision-Date: 2016-08-05 13:32+0000\n"
"Last-Translator: Giota Dandidou <giotadandidou@gmail.com>, 2019\n"
"Language-Team: Greek (https://www.transifex.com/odoo/teams/41243/el/)\n"
"Language: el\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"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Βάση"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Collapse rows"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_cell_buttons.xml:0
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Δημιουργία"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Day"
msgstr "Ημέρα"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Expand rows"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_view.js:0
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Προβολή Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'dependency_inverted_field' attribute once the 'dependency_field' is specified"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Grouping by date is not supported"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid attributes (%s) in gantt view. Attributes must be in (%s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Month"
msgstr "Μήνας"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Next"
msgstr "Επόμενο"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Ανοιχτό"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Σχεδίασε"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_cell_buttons.xml:0
msgid "Plan existing"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Previous"
msgstr "Προηγούμενο"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Records that are in the past cannot be automatically rescheduled. They should be manually rescheduled instead."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Today"
msgstr "Σήμερα"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Week"
msgstr "Εβδομάδα"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Year"
msgstr "Έτος"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %s towards %s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule tasks that do not follow a direct dependency path. Only the first task has been automatically rescheduled."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr ""

View File

@ -0,0 +1,363 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Wil Odoo, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Wil Odoo, 2024\n"
"Language-Team: Spanish (https://app.transifex.com/odoo/teams/41243/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: es\n"
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr "%(hour)s h %(minute)s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr "%s no se puede programar en el pasado"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr "%s h"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Vista de la ventana de acción"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr "Activar modo denso"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr "Activar modo disperso"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Aplicar"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "¿Está seguro de que desea eliminar este registro?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Base"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "Ocultar filas"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Crear"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Editar"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "Expandir filas"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr "Enfoque de hoy"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "Desde"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Vista Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr "Gantt hijo solo puede ser campo o plantilla, obtuvo %s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "Gantt debe tener un atributo \"date_start\""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "Gantt debe tener un atributo \"date_stop\""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
"Gantt debe tener un atributo 'dependency_inverted_field' cuando se "
"especifique el campo 'dependency_field'."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr "Fecha de inicio de Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr "Fecha de finalización de Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "La vista de Gantt solo puede contener una etiqueta de plantilla"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr "No es posible programar en el pasado."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "¡Campos insuficientes para la vista de Gantt!"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
"Atributos no válidos (%(invalid_attributes)s) en la vista gantt. Los "
"atributos deben estar en (%(valid_attributes)s)"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr "default_range '%s' no válido en Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "Default_scale '%s' no válida en vista de Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr "display_mode \"%s\" no válido en gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Nombre"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Nuevo"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Abrir"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Planificar"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Iniciar"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Detener"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr "Las dependencias no son válidas, hay un ciclo."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr "No hay candidatos válidos para replanificar"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Este mes"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr "Este trimestre"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Esta semana"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "Este año"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Hoy"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr "Menú de barra de herramientas"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Total"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "%s sin definir"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Vista"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Tipo de vista"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr "No puede mover %(record)s a %(related_record)s."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr "No puede reprogramar %(main_record)s a %(other_record)s."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "horas"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "minutos"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "meses"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "a"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr "{{ props.progressBar.warning }}"

View File

@ -0,0 +1,364 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Wil Odoo, 2024
# Patricia Gutiérrez Capetillo <pagc@odoo.com>, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Patricia Gutiérrez Capetillo <pagc@odoo.com>, 2024\n"
"Language-Team: Spanish (Latin America) (https://app.transifex.com/odoo/teams/41243/es_419/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: es_419\n"
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr "%(hour)sh%(minute)s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr "%s no se puede programar en el pasado"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr "%sh"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Vista de acción de ventana"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr "Activar modo denso"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr "Activar modo disperso"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Aplicar"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "¿Está seguro de que desea eliminar este registro?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Base"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "Contraer filas"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Crear"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Editar"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "Expandir filas"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr "Importantes de hoy"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "Desde"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Vista de gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr "Gantt secundario solo puede ser un campo o plantilla, obtuvo %s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "Gantt debe tener un atributo \"date_start\""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "Gantt debe tener un atributo \"date_stopt\""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
"Gantt debe tener un atributo 'dependency_inverted_field' cuando se "
"especifique el campo 'dependency_field'."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr "Fecha de inicio de Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr "Fecha de finalización de Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "La vista de Gantt solo puede contener una etiqueta de plantilla"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr "No es posible programar en el pasado."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "Campos insuficientes para la vista de Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
"Atributos inválidos (%(invalid_attributes)s) en la vista de Gantt. Los "
"atributos deben estar en (%(valid_attributes)s)"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr "default_range '%s' no válido en Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "Default_scale '%s' no válida en vista gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr "display_mode \"%s\" inválido en la vista de Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Nombre"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Nuevo"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Abierto"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Plan"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr "Reprogramación realizada con éxito."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Iniciar"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Detener"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr "Las dependencias no son válidas, hay un ciclo."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr "No hay candidatos válidos para replanificar"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Este mes"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr "Este trimestre"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Esta semana"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "Este año"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Hoy"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr "Menú de barra de herramientas"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Total"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "%s sin definir"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Vista"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Ver tipo"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr "No puede mover %(record)s a %(related_record)s."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr "No puede mover %(main_record)s a %(other_record)s."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "horas"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "minutos"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "meses"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "para"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr "{{ props.progressBar.warning }}"

View File

@ -0,0 +1,364 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Siim Raasuke, 2024
# Stevin Lilla, 2024
# Martin Trigaux, 2024
# Eneli Õigus <enelioigus@gmail.com>, 2024
# Anna, 2024
# Triine Aavik <triine@avalah.ee>, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Triine Aavik <triine@avalah.ee>, 2024\n"
"Language-Team: Estonian (https://app.transifex.com/odoo/teams/41243/et/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: et\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr "%(hour)stundi%(minute)s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr "%stund"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Toimingu akna vaade"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Kinnita"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "Kas oled kindel, et soovid antud kirjet kustutada?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Baas"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "peida read"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Loo"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Muuda"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "Laienda ridu"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "Kellelt?"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Gantti vaade"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "Vigane default_scale '%s' ganti vaates"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Nimi"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Uus"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Avatud"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Plaan"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Alusta"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Peata"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "See kuu"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "See nädal"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "See aasta"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Täna"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Kokku"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "Määramata %s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Vaade"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Vaate tüüp"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "tundi"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "minutit"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "kuud"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "kuni"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr ""

View File

@ -0,0 +1,367 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Tiffany Chang, 2024
# Miku Laitinen <miku.laitinen@gmail.com>, 2024
# Jarmo Kortetjärvi <jarmo.kortetjarvi@gmail.com>, 2024
# Mikko Salmela <salmemik@gmail.com>, 2024
# Ossi Mantylahti <ossi.mantylahti@obs-solutions.fi>, 2024
# Tuomo Aura <tuomo.aura@web-veistamo.fi>, 2024
# Martin Trigaux, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Martin Trigaux, 2024\n"
"Language-Team: Finnish (https://app.transifex.com/odoo/teams/41243/fi/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: fi\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr "%(hour)sh%(minute)s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr "%sh"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Toimintoikkunan näkymä"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Käytä"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "Oletko varma, että haluat poistaa tämän tietueen?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Pohja"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "Supista rivit"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Luo"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Muokkaa"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "Laajenna rivit"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "Alkaa"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Gantt-näkymä"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr "Gantt-kaavion alataso voi olla vain kenttä tai malli, saatiin %s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "Ganttissa on oltava attribuutti 'date_start'"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "Ganttissa on oltava attribuutti 'date_stop'"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
"Ganttissa on oltava attribuutti 'dependency_inverted_field', kun "
"'dependency_field' on määritetty"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "Gantt-näkymä voi sisältää vain yhden mallitunnisteen"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "Kenttiä ei ole tarpeeksi Gantt-näkymään!"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "Ganttissa on virheellinen default_scale '%s'"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Nimi"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Uusi"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Avoin"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Suunnittele"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Aloitus"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Lopeta"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Tässä kuussa"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Tällä viikolla"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "Tämä vuosi"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Tänään"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Yhteensä"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "Määrittelemätön %s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Näytä"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Näkymän tyyppi"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "tunti(a)"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "minuutit"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "kuukaudet"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "->"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr ""

View File

@ -0,0 +1,366 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Wil Odoo, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Wil Odoo, 2024\n"
"Language-Team: French (https://app.transifex.com/odoo/teams/41243/fr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: fr\n"
"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr "%(hour)sh%(minute)s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr "%s ne peut pas être planifié dans le passé"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr "%sh"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Vue de la fenêtre d'action"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr "Activer le mode dense"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr "Activer le mode clairsemé"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Appliquer"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "Êtes-vous sûr de vouloir supprimer cet enregistrement ?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Base"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "Plier les lignes"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Créer"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Modifier"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "Déplier les lignes"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr "Cibler la date d'aujourd'hui"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "À partir de"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Vue de Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr ""
"Une ligne enfant Gantt peut seulement être un champ ou un modèle, vous avez "
"%s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "Gantt doit avoir un attribut 'date_start'"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "Gantt doit avoir un attribut 'date_stop'"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
"Gant doit avoir un attribut 'dependency_inverted_field' dès que le "
"'dependency_field' est défini"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr "Date de début Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr "Date de fin Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "La vue Gantt peut uniquement contenir une étiquette de modèles"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr "Impossible de planifier dans le passé."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "Champs insuffisants pour la vue Gantt !"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
"Des attributs invalides se trouvent (%(invalid_attributes)s) dans la vue "
"Gantt. Les attributs doivent être en (%(valid_attributes)s)"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr "default_range '%s' invalide dans gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "default_scale '%s' invalide dans Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr "Display_mode '%s' invalide dans Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Nom"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Nouveau"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Ouvert"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Planifier"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Lancer"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Arrêter"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr "Les dépendances ne sont pas valides, il y a un cycle."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr "Il n'y a pas de candidats valides à replanifier."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Ce mois"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr "Ce trimestre"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Cette semaine"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "Cette année"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Aujourd'hui"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr "Menu barre d'outils"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Total"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "%s indéfini"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Vue"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Type de vue"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr "Vous ne pouvez pas déplacer %(record)s vers %(related_record)s."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr ""
"Vous ne pouvez pas reprogrammer %(main_record)s vers %(other_record)s."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "heures"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "minutes"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "mois"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "au"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr "{{ props.progressBar.warning }}"

View File

@ -0,0 +1,365 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# שהאב חוסיין <shhab89@gmail.com>, 2024
# MichaelHadar, 2024
# דודי מלכה <Dudimalka6@gmail.com>, 2024
# Yihya Hugirat <hugirat@gmail.com>, 2024
# Lilach Gilliam <lilach.gilliam@gmail.com>, 2024
# ZVI BLONDER <ZVIBLONDER@gmail.com>, 2024
# Martin Trigaux, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Martin Trigaux, 2024\n"
"Language-Team: Hebrew (https://app.transifex.com/odoo/teams/41243/he/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: he\n"
"Plural-Forms: nplurals=3; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: 2;\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "תצוגת חלון פעולה"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "החל"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "האם אתה בטוח שברצונך למחוק רשומה זו?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "בסיס"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "צמצם שורות"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "יצירה"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "ערוך"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "הרחב שורות"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "מ"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "גאנט"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "תצוגת גאנט"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "גאנט מחייב הגדרת \"תאריך התחלה\":"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "גאנט מחייב הגדרת \"תאריך סיום\":"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "אין מספיק שדות לתצוגת גאנט!"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "שם"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "חדש"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "פתח"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "תוכנית"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "התחל"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "עצור"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "חודש נוכחי"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "שבוע נוכחי"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "שנה נוכחית"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "היום"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "סה\"כ"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "%s לא מוגדר"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "תצוגה"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "סוג תצוגה"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "שעות"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "דקות"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "חודשים"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "ל"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr ""

View File

@ -0,0 +1,359 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Martin Trigaux, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Martin Trigaux, 2024\n"
"Language-Team: Hindi (https://app.transifex.com/odoo/teams/41243/hi/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: hi\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "बनाएँ"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "संपादित"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr ""
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "नया"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr ""
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr ""

View File

@ -0,0 +1,365 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Igor Krizanovic <krizanovic.igor@gmail.com>, 2024
# Vladimir Olujić <olujic.vladimir@storm.hr>, 2024
# Karolina Tonković <karolina.tonkovic@storm.hr>, 2024
# Bole <bole@dajmi5.com>, 2024
# Kristina Palaš, 2024
# Tina Milas, 2024
# Martin Trigaux, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Martin Trigaux, 2024\n"
"Language-Team: Croatian (https://app.transifex.com/odoo/teams/41243/hr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: hr\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Radni prozor"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Primjeni"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Osnovica"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Kreiraj"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Uredi"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "Od"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Gantogram"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Gantogram"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Naziv"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Novi"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Otvori"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Plan"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Start"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Kraj"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Ovaj mjesec"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Ovaj tjedan"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "Ove godine"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Danas"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Ukupno"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Pogledaj"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Tip pogleda"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "sati"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "minuta"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "mjeseci"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "do"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr ""

View File

@ -0,0 +1,266 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# gezza <geza.nagy@oregional.hu>, 2022
# Tamás Németh <ntomasz81@gmail.com>, 2022
# Istvan <leki69@gmail.com>, 2022
# Csaba Tóth <i3rendszerhaz@gmail.com>, 2022
# Martin Trigaux, 2022
# krnkris, 2023
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-05-16 16:03+0000\n"
"PO-Revision-Date: 2022-09-22 05:49+0000\n"
"Last-Translator: krnkris, 2023\n"
"Language-Team: Hungarian (https://app.transifex.com/odoo/teams/41243/hu/)\n"
"Language: hu\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"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Alap"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Collapse rows"
msgstr "Sorok becsukása"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_cell_buttons.xml:0
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Létrehozás"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Day"
msgstr "Nap"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Expand rows"
msgstr "Sorok kinyitása"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_view.js:0
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Gantt nézet"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'dependency_inverted_field' attribute once the 'dependency_field' is specified"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Grouping by date is not supported"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid attributes (%s) in gantt view. Attributes must be in (%s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Month"
msgstr "Hónap"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Új"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Next"
msgstr "Következő"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Megnyitás"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Terv"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_cell_buttons.xml:0
msgid "Plan existing"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Previous"
msgstr "Előző"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Records that are in the past cannot be automatically rescheduled. They should be manually rescheduled instead."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Today"
msgstr "Ma"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Megtekintés"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Week"
msgstr "Hét"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Year"
msgstr "Év"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %s towards %s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule tasks that do not follow a direct dependency path. Only the first task has been automatically rescheduled."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr ""

View File

@ -0,0 +1,365 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Wil Odoo, 2024
# Abe Manyo, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Abe Manyo, 2024\n"
"Language-Team: Indonesian (https://app.transifex.com/odoo/teams/41243/id/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: id\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr "%(hour)sj%(minute)s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr "%s tidak dapat dijadwalkan di masa lalu"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr "%sj"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Jendela Tindakan"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr "Aktifkan mode dense"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr "Aktifkan mode sparse"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Terapkan"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "Apakah Anda yakin ingin menghapus record ini?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Base"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "Tutup baris"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Buat"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Edit"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "Perluas baris"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr "Fokus Hari Ini"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "Dari"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Tampilan Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr "Anak Gantt hanya dapat menjadi field atau templat, memiliki %s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "Gantt harus memiliki attribute 'date_start'"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "Gantt harus memiliki attribute 'date_stop'"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
"Gantt harus memiliki attribute 'dependency_inverted_field' setelah "
"'dependency_field' ditentukan"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr "Tanggal mulai Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr "Tanggal berhenti Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "Tampilan Gantt hanya dapat memiliki satu tag templat"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr "Tidak dapat menjadwalkan di masa lalu"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "Field tidak mencukupi untuk Tampilan Gantt!"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
"Atribut tidak valid (%(invalid_attributes)s) di tampilan gantt. Atribut "
"harus dalam (%(valid_attributes)s)"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr "default_range '%s' tidak valid di gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "default_scale '%s' invalid di Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr "display_mode '%s' tidak valid d gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Nama"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Baru"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Terbuka"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Rencana"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr "Dijadwalkan ulang dengan sukses."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Mulai"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Stop"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr "Ketergantungan tidak valid, terdapat cycle."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr "Tidak ada kandidat yang valid untuk direncanakan ulang"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Bulan ini"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr "Triwulan ini"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Minggu ini"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "Tahun ini"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Hari Ini"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr "Menu toolbar"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Total"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "Undefined %s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Tampilan"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Tipe Tampilan"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr "Anda tidak dapat menggerakkan %(record)s ke %(related_record)s."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr ""
"Anda tidak dapat menjadwalkan ulang %(main_record)s ke %(other_record)s."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "jam"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "menit"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "bulan"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "kepada"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr "{{ props.progressBar.warning }}"

View File

@ -0,0 +1,365 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Wil Odoo, 2024
# Marianna Ciofani, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Marianna Ciofani, 2024\n"
"Language-Team: Italian (https://app.transifex.com/odoo/teams/41243/it/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: it\n"
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr "%(hour)s:%(minute)s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr "%s non può essere programmato nel passato"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr "%s "
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Azione visualizzazione finestra"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr "Attiva modalità dense"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr "Attiva modalità sparse"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Applica"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "Eliminare veramente questo record?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Imponibile"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "Contrai righe"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Crea"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Modifica"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "Espandi righe"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr "Focus oggi"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "Da"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Vista Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr ""
"Una riga figlia Gantt può essere solo un campo o un modello, tu hai %s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "Gantt deve possedere un attributo \"date_start\""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "Gantt deve possedere un attributo \"date_stop\""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
"La vista Gantt deve avere un attributo 'dependency_inverted_field' una volta"
" che il 'dependency_field' viene specificato"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr "Data di inizio gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr "Data di fine gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "La vista Gantt può contenere un solo tag templates"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr "Impossibile programmare in passato."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "Campi non sufficienti per la vista Gantt."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
"Attributi non validi (%(invalid_attributes)s) nella vista gantt, devono "
"essere tra (%(valid_attributes)s)"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr "default_range non valido '%s' in gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "default_scale \"%s\" non valido in gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr "display_mode \"%s\" non valida in gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Nome"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Nuovo"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Apri"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Pianificazione"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr "Riprogrammazione avvenuta con successo."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Avvia"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Ferma"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr "Le dipendenze non sono valide, c'è un ciclo."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr "Non sono presenti candidati validi per la ripianificazione"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Questo mese"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr "Questo trimestre"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Questa settimana"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "Questo anno"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Oggi"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr "Menu barra degli strumenti"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Totale"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "%s non definiti"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Vista"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Tipo vista"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr "Impossibile spostare %(record)s in %(related_record)s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr "Impossibile riprogrammare %(main_record)s in %(other_record)s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "ore"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "minuti"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "mesi"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "a"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr "{{ props.progressBar.warning }}"

View File

@ -0,0 +1,362 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Wil Odoo, 2024
# Junko Augias, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Junko Augias, 2024\n"
"Language-Team: Japanese (https://app.transifex.com/odoo/teams/41243/ja/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: ja\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr "%(hour)sh%(minute)s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr "%sは過去に予定することはできません"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr "%sh"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "アクションウィンドウビュー"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr "デンスモードを有効化する"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr "スパースモードを有効化する"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "適用"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "このレコードを削除してもよろしいですか?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "ベース"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "行を折りたたむ"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "作成"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "編集"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "行を展開"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr "今日のフォーカス"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "from"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "ガント"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "ガントビュー"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr "ガントのchildはフィールドまたはテンプレートのみにできますが、%sを取得しました"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "ガントには'date_start'属性が必要です"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "ガントには'date_stop'属性が必要です"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
"'dependency_field' が指定されたら、ガントは 'dependency_inverted_field' を持つ必要があります。"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr "ガント開始日"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr "ガント停止日"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "ガントビューには、テンプレートタグを1つだけ含めることができます"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr "過去に予定することはできません。"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "ガントビューのフィールドが不十分です!"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
"ガントビューの属性 (%(invalid_attributes)s)が無効です。属性は(%(valid_attributes)s)である必要があります。"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr "ガントでの無効な default_range '%s' "
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "ganttのdefault_scale '%s'が無効です"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr "ガントでの無効な display_mode '%s' "
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "名称"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "新規"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "開く"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "計画"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr "正常に再スケジュールされました。"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "開始"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "停止"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr "依存関係が有効でなく、サイクルがあります。"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr "再計画に有効な候補者はいません"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "今月"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr "今四半期"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "今週"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "今年"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "今日"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr "ツールバーメニュー"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "合計"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "未定義の%s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "ビュー"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "ビュータイプ"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr " %(record)sを %(related_record)sへ移動することはできません。"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr " %(record)sを %(related_record)sへリスケジュールすることはできません。"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "時間"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "分"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "か月"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "から"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr "{{ props.progressBar.warning }}"

View File

@ -0,0 +1,363 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Wil Odoo, 2024
# Daye Jeong, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Daye Jeong, 2024\n"
"Language-Team: Korean (https://app.transifex.com/odoo/teams/41243/ko/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: ko\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr "%(hour)s시간 %(minute)s분"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr "%s는 과거로 예약할 수 없습니다."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr "%sh"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "작업 윈도우 보기"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr "dense mode 활성화"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr "sparse mode 활성화"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "적용"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "이 레코드를 삭제 하시겠습니까?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "기준액"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "행 축소"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "작성"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "편집"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "행 확장"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr "오늘의 포커스"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "시작 시간"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "간트 차트"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "간트 화면"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr "간트 하위 메뉴는 필드나 템플릿만 선택 가능하며, 다음 내용입니다 %s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "간트에는 'date_start' 속성을 지정해야 합니다"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "간트에는 'date_stop' 속성을 지정해야 합니다"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
"'dependency_field'가 지정되는 경우, 간트에서 'dependency_inverted_field' 속성을 지정해야 합니다."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr "간트 시작일"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr "간트 종료일"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "간트 뷰에는 서식 태그가 하나만 포함될 수 있습니다"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr "지난 시점으로는 예약할 수 없습니다."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "간트 차트 화면에 대한 필드가 충분하지 않습니다!"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
"간트 보기에 잘못된 속성 (%(invalid_attributes)s)이 있습니다. 속성은 (%(valid_attributes)s)에 "
"위치해야 합니다."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr "간트에 잘못된 default_range '%s'가 있습니다."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "간트에서 default_scale '%s' 가 유효하지 않습니다"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr "간트에 잘못된 display_mode '%s'가 있습니다."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "이름"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "신규"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "열기"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "계획"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr "일정 변경이 성공적으로 완료되었습니다."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "시작"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "중지"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr "종속성이 유효하지 않으며 주기가 있습니다."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr "다시 계획할 수 있는 적격 후보자가 없습니다."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "이번 달"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr "이번 분기"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "이번 주"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "올해"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "오늘"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr "도구 모음 메뉴"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "총계"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "정의되지 않은 %s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "화면"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "화면 유형"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr "%(record)s를 %(related_record)s로 이동할 수 없습니다."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr "%(main_record)s을 %(other_record)s으로 일정을 변경할 수 없습니다. "
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "시간"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "분"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "월"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "종료"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr "{{ props.progressBar.warning }}"

View File

@ -0,0 +1,257 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~12.5+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-05-16 16:03+0000\n"
"PO-Revision-Date: 2019-08-26 09:38+0000\n"
"Language-Team: Luxembourgish (https://www.transifex.com/odoo/teams/41243/lb/)\n"
"Language: lb\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"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Collapse rows"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_cell_buttons.xml:0
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Day"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Expand rows"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_view.js:0
msgid "Gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'dependency_inverted_field' attribute once the 'dependency_field' is specified"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Grouping by date is not supported"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid attributes (%s) in gantt view. Attributes must be in (%s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Month"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Next"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_cell_buttons.xml:0
msgid "Plan existing"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Previous"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Records that are in the past cannot be automatically rescheduled. They should be manually rescheduled instead."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Today"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Week"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Year"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %s towards %s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule tasks that do not follow a direct dependency path. Only the first task has been automatically rescheduled."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr ""

View File

@ -0,0 +1,267 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Audrius Palenskis <audrius.palenskis@gmail.com>, 2022
# Rolandas <info@paninfo.lt>, 2022
# Edgaras Kriukonis <edgaras@focusate.eu>, 2022
# Ramunė ViaLaurea <ramune.vialaurea@gmail.com>, 2022
# Naglis Jonaitis, 2022
# Martin Trigaux, 2022
# Linas Versada <linaskrisiukenas@gmail.com>, 2023
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-05-16 16:03+0000\n"
"PO-Revision-Date: 2022-09-22 05:49+0000\n"
"Last-Translator: Linas Versada <linaskrisiukenas@gmail.com>, 2023\n"
"Language-Team: Lithuanian (https://app.transifex.com/odoo/teams/41243/lt/)\n"
"Language: lt\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=4; plural=(n % 10 == 1 && (n % 100 > 19 || n % 100 < 11) ? 0 : (n % 10 >= 2 && n % 10 <=9) && (n % 100 > 19 || n % 100 < 11) ? 1 : n % 1 != 0 ? 2: 3);\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "Ar tikrai norite ištrinti šį įrašą?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Bazė"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Collapse rows"
msgstr "Sutraukti eilutes"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_cell_buttons.xml:0
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Sukurti"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Day"
msgstr "Diena"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Expand rows"
msgstr "Išskleisti eilutes"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_view.js:0
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Ganto vaizdas"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "Ganto diagrama privalo turėti pradžios ir pabaigos požymius"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'dependency_inverted_field' attribute once the 'dependency_field' is specified"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Grouping by date is not supported"
msgstr "Grupavimas pagal datas nepalaikomas"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid attributes (%s) in gantt view. Attributes must be in (%s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Month"
msgstr "Mėnuo"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Naujas"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Next"
msgstr "Kitas"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Atidaryti"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Suplanuoti"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_cell_buttons.xml:0
msgid "Plan existing"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Previous"
msgstr "Ankstesnis"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Records that are in the past cannot be automatically rescheduled. They should be manually rescheduled instead."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Today"
msgstr "Šiandien"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "Nežinoma %s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Peržiūrėti"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Week"
msgstr "Savaitė"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Year"
msgstr "Metai"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %s towards %s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule tasks that do not follow a direct dependency path. Only the first task has been automatically rescheduled."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr ""

View File

@ -0,0 +1,364 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Bayarkhuu Bataa, 2024
# Batmunkh Ganbat <batmunkh2522@gmail.com>, 2024
# Uuganbayar Batbaatar <uuganaaub33@gmail.com>, 2024
# Baskhuu Lodoikhuu <baskhuujacara@gmail.com>, 2024
# Martin Trigaux, 2024
# hish, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: hish, 2024\n"
"Language-Team: Mongolian (https://app.transifex.com/odoo/teams/41243/mn/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: mn\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Үйлдлийн цонх харагдац"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Хэрэгжүүлэх"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Суурь"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "Мөрүүдийг хумих"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Үүсгэх"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Засах"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "Мөрүүдийг дэлгэх"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "Эхлэх"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Хүснэгтлэх"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Хүснэгтлэн харах"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Нэр"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Шинэ"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Нээх"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Төлөвлөх"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Эхлэх"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Зогс"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Энэ сар"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Энэ долоо хоног"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Өнөөдөр"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Нийт дүн"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Харах"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Дэлгэцийн төрөл"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "цаг"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "минут"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "сарууд"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "дуусах"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr ""

View File

@ -0,0 +1,262 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Marius Stedjan <marius@stedjan.com>, 2022
# Martin Trigaux, 2023
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-05-16 16:03+0000\n"
"PO-Revision-Date: 2022-09-22 05:49+0000\n"
"Last-Translator: Martin Trigaux, 2023\n"
"Language-Team: Norwegian Bokmål (https://app.transifex.com/odoo/teams/41243/nb/)\n"
"Language: nb\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"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Base"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Collapse rows"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_cell_buttons.xml:0
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Opprett"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Day"
msgstr "Dag"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Expand rows"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_view.js:0
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Gantt-visning"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'dependency_inverted_field' attribute once the 'dependency_field' is specified"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Grouping by date is not supported"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid attributes (%s) in gantt view. Attributes must be in (%s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Month"
msgstr "Måned"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Ny"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Next"
msgstr "Neste"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Åpen"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Planlegg"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_cell_buttons.xml:0
msgid "Plan existing"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Previous"
msgstr "Tilbake"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Records that are in the past cannot be automatically rescheduled. They should be manually rescheduled instead."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Today"
msgstr "I dag"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Vis"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Week"
msgstr "Uke"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Year"
msgstr "År"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %s towards %s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule tasks that do not follow a direct dependency path. Only the first task has been automatically rescheduled."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr ""

View File

@ -0,0 +1,364 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Wil Odoo, 2024
# Erwin van der Ploeg <erwin@odooexperts.nl>, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Erwin van der Ploeg <erwin@odooexperts.nl>, 2024\n"
"Language-Team: Dutch (https://app.transifex.com/odoo/teams/41243/nl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: nl\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr "%(hour)su%(minute)s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr "%s kan niet in het verlden plaatsvinden"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr "%su"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Actie venster weergave"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr "Dense mode activeren"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr "Sparse mode activeren"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Toepassen"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "Ben je zeker dat je dit record wilt verwijderen?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Basis"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "Rijen inklappen"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Aanmaken"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Bewerken"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "Rijen uitvouwen"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr "Focus Vandaag"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "Van"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Gantt weergave"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr "Onderliggende Gantt kan alleen een veld of sjabloon zijn, krijg %s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "Gantt moet een 'date_start' kenmerk hebben"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "Gantt moet een 'date_stop' kenmerk hebben"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
"Gant moet een 'dependency_inverted_field' kenmerk hebben zodra het "
"'dependency_field' is gespecifieerd"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr "Gantt-startdatum"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr "Gantt-einddatum"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "De Gantt-weergave kan slechts één sjabloon-label bevatten"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr "Je kan niet in het verleden plannen."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "Niet genoeg velden voor Gantt weergave!"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
"Ongeldige kenmerken (%(invalid_attributes)s) in Gantt-weergave. Kenmerken "
"moeten in (%(valid_attributes)s) zijn"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr "Ongeldige default_range '%s' in gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "Ongeldige default_scale '%s' in gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr "Ongeldige display_mode '%s' in gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Naam"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Nieuw"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Openen"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Plan"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr "Met succes één opnieuw gepland."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Start"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Stop"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr "De afhankelijkheden zijn niet geldig, er is een cyclus."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr "Er zijn geen geldige kandidaten om te herplannen"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Deze maand"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr "Dit kwartaal"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Deze week"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "Dit jaar"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Vandaag"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr "Werkbalkmenu"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Totaal"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "Niet gedefinieerd %s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Bekijk"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Soort weergave"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr "Je kunt %(record)s niet naar %(related_record)s verplaatsen."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr "Je kunt %(main_record)s niet op %(other_record)s herplannen."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "uren"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "minuten"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "maanden"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "aan"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr "{{ props.progressBar.warning }}"

View File

@ -0,0 +1,361 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Wil Odoo, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Wil Odoo, 2024\n"
"Language-Team: Polish (https://app.transifex.com/odoo/teams/41243/pl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: pl\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Widok okna akcji"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Zastosuj"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "Czy na pewno chcesz usunąć ten rekord?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Baza"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "Zwiń wiersze"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Utwórz"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Edytuj"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "Rozwiń wiersze"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "Od"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Wykres Gantta"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Widok Gantta"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr "Dziecko Gantt może być tylko polem lub szablonem, mam %s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "Gantt musi mieć atrybut \"date_start\"."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "Gantt musi mieć atrybut \"date_stop\"."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
"Gantt musi mieć atrybut \"dependency_inverted_field\" po określeniu "
"\"dependency_field\"."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "Widok Gantta może zawierać tylko jeden tag szablonu"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "Niewystarczająca liczba pól dla widoku Gantta!"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "Nieprawidłowy default_scale '%s' w gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Nazwa"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Nowe"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Otwarta"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Plan"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Uruchom"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Zatrzymaj"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Ten miesiąc"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Ten tydzień"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "Ten rok"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Na dzisiaj"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Suma"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "Niezdefiniowany %s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Widok"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Typ widoku"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "godziny"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "minut"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "miesiące"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "do"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr ""

View File

@ -0,0 +1,263 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Martin Trigaux, 2022
# Nuno Silva <nuno.silva@arxi.pt>, 2022
# Manuela Silva <mmsrs@sky.com>, 2023
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-05-16 16:03+0000\n"
"PO-Revision-Date: 2022-09-22 05:49+0000\n"
"Last-Translator: Manuela Silva <mmsrs@sky.com>, 2023\n"
"Language-Team: Portuguese (https://app.transifex.com/odoo/teams/41243/pt/)\n"
"Language: pt\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Base"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Collapse rows"
msgstr "Colapsar linhas"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_cell_buttons.xml:0
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Criar"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Day"
msgstr "Dia"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Expand rows"
msgstr "Expandir linhas"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_view.js:0
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Vista de Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'dependency_inverted_field' attribute once the 'dependency_field' is specified"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Grouping by date is not supported"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid attributes (%s) in gantt view. Attributes must be in (%s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Month"
msgstr "Mês"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Novo"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Next"
msgstr "Seguinte"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Aberto"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Plano"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_cell_buttons.xml:0
msgid "Plan existing"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Previous"
msgstr "Anterior"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Records that are in the past cannot be automatically rescheduled. They should be manually rescheduled instead."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Today"
msgstr "Hoje"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Ver"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Week"
msgstr "Semana"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Year"
msgstr "Ano"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %s towards %s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule tasks that do not follow a direct dependency path. Only the first task has been automatically rescheduled."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr ""

View File

@ -0,0 +1,364 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Wil Odoo, 2024
# Maitê Dietze, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Maitê Dietze, 2024\n"
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/odoo/teams/41243/pt_BR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: pt_BR\n"
"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr "%(hour)sh%(minute)s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr "%s não pode ser agendado no passado"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr "%sh"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Exibição da janela de ação"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr "Ativar o modo denso"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr "Ativar o modo esparso"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Aplicar"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "Você tem certeza de que deseja excluir este registro?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Base"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "Recolher linhas"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Criar"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Editar"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "Expandir linhas"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr "Foco em Hoje"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "De"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Visualização de Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr "Gantts secundários só podem ser campos ou modelos, há %s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "Gantt deve ter um atributo \"date_start\""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "Gantt deve ter um atributo \"date_stop\""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
"Gantt deve ter um atributo \"'dependency_inverted_field\" uma vez que "
"\"dependency_field\" está especificado"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr "Data de início do Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr "Data de término do Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "A visualização de Gantt deve conter somente um marcador do modelo"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr "É impossível fazer agendamentos no passado."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "Campos insuficientes para a visualização de Gantt!"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
"Atributos inválidos (%(invalid_attributes)s) na visualização de Gantt. Os "
"atributos devem estar em (%(valid_attributes)s)"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr "default_range '%s' inválido no Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "Default_scale \"%s\" inválida em Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr "Display_mode '%s' inválido em gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Nome"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Novo"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Aberto"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Plano"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr "Reagendamento feito com sucesso."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Iniciar"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Parar"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr "As dependências não são válidas; há um ciclo."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr "Não há candidatos válidos para replanejar"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Este mês"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr "Este trimestre"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Esta semana"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "Este ano"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Hoje"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr "Menu da barra de ferramentas"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Total"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "Indefinido %s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Visualização"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Tipo de visualização"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr "Você não pode se mover %(record)s para %(related_record)s."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr "Não é possível reagendar %(main_record)s para %(other_record)s."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "horas"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "minutos"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "meses"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "até"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr "{{ props.progressBar.warning }}"

View File

@ -0,0 +1,361 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Lyall Kindmurr, 2024
# Foldi Robert <foldirobert@nexterp.ro>, 2024
# Wil Odoo, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Wil Odoo, 2024\n"
"Language-Team: Romanian (https://app.transifex.com/odoo/teams/41243/ro/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: ro\n"
"Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Acțiune Vizualizare Fereastră"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Aplică"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "Sigur doriți să ștergeți această înregistrare?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Baza"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "Reduceți rândurile"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Creează"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Editare"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "Extindere Rânduri"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "De la"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Vizualizare Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Nume"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Nou"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Afișare"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Plan"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Start"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Stop"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Luna aceasta"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Săpt. curentă"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "Anul Acesta"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Astăzi"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Total"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "Nedefinit %s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Afișare"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Tip vizualizare"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "ore"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "minute"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "luni"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "la"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr ""

View File

@ -0,0 +1,258 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 17.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-01-08 07:03+0000\n"
"PO-Revision-Date: 2024-01-30 15:14+0400\n"
"Last-Translator: \n"
"Language-Team: Russian (https://app.transifex.com/odoo/teams/41243/ru/)\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "Вы уверены, что хотите удалить эту запись?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "База"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Collapse rows"
msgstr "Свернуть ряды"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Создать"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Day"
msgstr "День"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Редактировать"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Expand rows"
msgstr "Расширить ряды"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_view.js:0
msgid "Gantt"
msgstr "Гантт"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Вид Гантт"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr "Дочерний элемент Ганта может быть только полем или шаблоном, получено %s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "Гант должен иметь атрибут 'date_start'"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "Гант должен иметь атрибут 'date_stop'"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'dependency_inverted_field' attribute once the 'dependency_field' is specified"
msgstr "Gantt должен иметь атрибут 'dependency_inverted_field' после указания 'dependency_field'"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "Представление Ганта может содержать только один тег шаблона"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "Недостаточно полей для представления Ганта!"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid attributes (%s) in gantt view. Attributes must be in (%s)"
msgstr "Недопустимые атрибуты (%s) в представлении Ганта. Атрибуты должны быть в (%s)"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "Недопустимая шкала по умолчанию '%s' в гигантской схеме"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Month"
msgstr "Месяц"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Имя"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Новый"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Next"
msgstr "Следующий"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Открыть"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "План"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Previous"
msgstr "Предыдущий"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Records that are in the past cannot be automatically rescheduled. They should be manually rescheduled instead."
msgstr "Записи, которые находятся в прошлом, не могут быть автоматически перенесены. Их следует переносить вручную."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Начало"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Стоп"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.xml:0
msgid "Today"
msgstr "Сегодня"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Всего"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "Неопределено %s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Просмотр"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Week"
msgstr "Неделя"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Year"
msgstr "Год"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %s towards %s."
msgstr "Вы не можете перенести %s на %s."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule tasks that do not follow a direct dependency path. Only the first task has been automatically rescheduled."
msgstr "Вы не можете перенести в график задачи, которые не следуют прямому пути зависимости. Автоматически переносится только первая задача."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "часов"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "минут"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "месяцев"

View File

@ -0,0 +1,359 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Wil Odoo, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Wil Odoo, 2024\n"
"Language-Team: Slovenian (https://app.transifex.com/odoo/teams/41243/sl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: sl\n"
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Pogled okna za ukrep"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Uporabi"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "Ste prepričani, da želite izbrisati ta zapis?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Osnova"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "Strni vrstice"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Ustvari"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Uredi"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "Razširi vrstice"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "Od"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Gantt diagram"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Naziv"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Novo"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Odprto"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Plan"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Prični"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Ustavi"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Ta mesec"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Ta teden"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Danes"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Skupaj"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Prikaz"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Vrsta prikaza"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "ure"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "minute"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "meseci"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "do"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr ""

View File

@ -0,0 +1,367 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Mikael Åkerberg <mikael.akerberg@mariaakerberg.com>, 2024
# 03992e16f8df6e39b9d1cc0ff635887e, 2024
# Martin Wilderoth <martin.wilderoth@linserv.se>, 2024
# Simon S, 2024
# Anders Wallenquist <anders.wallenquist@vertel.se>, 2024
# Chrille Hedberg <hedberg.chrille@gmail.com>, 2024
# Martin Trigaux, 2024
# Kristoffer Grundström <lovaren@gmail.com>, 2024
# Jakob Krabbe <jakob.krabbe@vertel.se>, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Jakob Krabbe <jakob.krabbe@vertel.se>, 2024\n"
"Language-Team: Swedish (https://app.transifex.com/odoo/teams/41243/sv/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: sv\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Vy för åtgärdsfönster"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Verkställ"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Bas"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Skapa"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Redigera"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "Från"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Ganttvy"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Namn"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Ny"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Öppna"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Plan"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Start"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Stoppa"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Denna månad"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Denna vecka"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "Detta år"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Idag"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Totalt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Visa"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Typ av vy"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "timmar"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "minuter"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "månader"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "till"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr ""

View File

@ -0,0 +1,363 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Wil Odoo, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Wil Odoo, 2024\n"
"Language-Team: Thai (https://app.transifex.com/odoo/teams/41243/th/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: th\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr "%(hour)s ชั่วโมง %(minute)s นาที"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr "%s ไม่สามารถกำหนดเวลาในอดีตได้"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr "%s ชั่วโมง"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "มุมมองหน้าต่างการดำเนินการ"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "นำไปใช้"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "คุณแน่ใจหรือว่าต้องการลบบันทึกนี้?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "ฐาน"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "ย่อแถว"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "สร้าง"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "แก้ไข"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "ขยายแถว"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "จาก"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "แกนต์"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "มุมมองแบบแกนต์"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr "แกนต์ย่อยได้เฉพาะฟิลด์หรือเทมเพลตเท่านั้น มี %s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "แกนต์ต้องมี 'date_start' แอตทริบิวต์ "
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "แกนต์ต้องมี \"date_stop\" แอตทริบิวต์ "
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
"Gantt ต้องมีแอตทริบิวต์ 'dependency_inverted_field' เมื่อระบุ "
"'dependency_field' แล้ว"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "มุมมองแกนต์สามารถมีแท็กเทมเพลตได้เพียงแท็กเดียว"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr "ไม่สามารถกำหนดเวลาในอดีตได้"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "ฟิลด์ไม่เพียงพอสำหรับมุมมองแกนต์!"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
"แอตทริบิวต์ไม่ถูกต้อง (%(invalid_attributes)s) ในมุมมองแกนต์ "
"แอตทริบิวต์ต้องอยู่ใน (%(valid_attributes)s)"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "Invalid default_scale '%s' ในแกนต์"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr "display_mode '%s' ไม่ถูกต้องในแกนต์"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "ชื่อ"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "ใหม่"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "เปิด"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "แผน"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "เริ่ม"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "หยุด"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr "การขึ้นต่อกันไม่ถูกต้อง มีวงจรอยู่"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr "ไม่มีผู้สมัครที่ถูกต้องที่จะวางแผนใหม่"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "เดือนนี้"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "สัปดาห์นี้"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "ปีนี้"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "วันนี้"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "รวม"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "ไม่กำหนด %s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "ดู"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "ประเภทมุมมอง"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr "คุณไม่สามารถย้าย %(record)s ไปยัง %( related_record)s ได้"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr "คุณไม่สามารถจัดกำหนดการใหม่ %(main_record)s ไปยัง %(other_record)s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "ชั่วโมง"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "นาที"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "เดือน"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "ถึง"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr ""

View File

@ -0,0 +1,372 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Ahmet Altinisik <aaltinisik@altinkaya.com.tr>, 2024
# Ozlem Cikrikci <ozlemc@eskayazilim.com.tr>, 2024
# Tugay Hatıl <tugayh@projetgrup.com>, 2024
# Gökhan Erdoğdu <gokhan.erdogdu@mechsoft.com.tr>, 2024
# Levent Karakaş <levent@mektup.at>, 2024
# abc Def <hdogan1974@gmail.com>, 2024
# Murat Kaplan <muratk@projetgrup.com>, 2024
# Ediz Duman <neps1192@gmail.com>, 2024
# Ertuğrul Güreş <ertugrulg@projetgrup.com>, 2024
# Halil, 2024
# Murat Durmuş <muratd@projetgrup.com>, 2024
# Martin Trigaux, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Martin Trigaux, 2024\n"
"Language-Team: Turkish (https://app.transifex.com/odoo/teams/41243/tr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: tr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Pencere Aksiyon Görünümü"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Uygula"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "Bu kaydı silmek istediğinizden emin misiniz?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Temel"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "Satırları daralt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Oluştur"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Düzenle"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "Satırları genişlet"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "Başlama"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Gantt Görünümü"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr "Gantt alt öğesi yalnızca alan veya şablon olabilir, %svar"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "Gantt'ın bir 'date_start' özelliği olmalıdır"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "Gantt'ın bir 'date_stop' özelliği olmalıdır"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
"\"dependency_field\" belirtildikten sonra Gantt'ın "
"\"dependency_inverted_field\" özniteliğine sahip olması gerekir"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "Gantt görünümü yalnızca bir şablon etiketi içerebilir"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "Gantt görünümü için yetersiz alanlar!"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "Gantt'ta geçersiz default_scale '%s'"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Adı"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Yeni"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Açık"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Planla"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Başla"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Durdur"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Bu Ay"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Bu Hafta"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "Bu Yıl"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Bugün"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Toplam"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "Tanımsız %s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Görüntüle"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Görünüm Türü"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "saat"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "dakika"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "ay"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "den"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr ""

View File

@ -0,0 +1,363 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Wil Odoo, 2024
# Alina Lisnenko <alina.lisnenko@erp.co.ua>, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Alina Lisnenko <alina.lisnenko@erp.co.ua>, 2024\n"
"Language-Team: Ukrainian (https://app.transifex.com/odoo/teams/41243/uk/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: uk\n"
"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr "%(hour)sгод%(minute)s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr "%sгод"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Перегляд вікна дії"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Застосувати"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "Ви впевнені, що хочете видалити цей запис?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "База"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "Згорнути рядки"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Створити"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Редагувати"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "Розгорнути рядки"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "Від"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Діаграма Ґанта"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Перегляд діаграми Ґанта"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr ""
"Дочірня діаграма Ганта може бути лише полем або шаблоном, отримайте %s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "Діаграма Ганта повинна мати атрибут 'date_start'"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "Діаграма Ганта повинна мати атрибут 'date_stop'"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
"Гант повинен мати атрибут 'dependency_inverted_field', коли вказано "
"'dependency_field'"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "Діаграма Ганта може містити лише один тег шаблону"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "Недостатньо полів для перегляду діаграми Ганта!"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "Недійсне default_scale '%s' у діаграмі Ганта"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Назва"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Новий"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Відкрито"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "План"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Початок"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Зупинити"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Цього місяця"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Цього тижня"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "Цього року"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Сьогодні"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Разом"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "Невизначений %s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Перегляд"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Тип перегляду"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "годин"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "хвилин"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "місяців"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "до"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr ""

View File

@ -0,0 +1,364 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Wil Odoo, 2024
# Thi Huong Nguyen, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Thi Huong Nguyen, 2024\n"
"Language-Team: Vietnamese (https://app.transifex.com/odoo/teams/41243/vi/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: vi\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr "%(hour)sh%(minute)s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr "%s không thể được lên lịch trong quá khứ"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr "%sh"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "Chế độ xem cửa sổ tác vụ"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr "Kích hoạt dense mode"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr "Kích hoạt sparse mode"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "Áp dụng"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "Bạn có chắc muốn xóa bảng ghi này?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "Cơ sở"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "Thu gọn hàng"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "Tạo"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "Chỉnh sửa"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "Mở rộng hàng"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr "Tiêu điểm hôm nay"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "Từ"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "Gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "Biểu đồ Gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr "Phần phụ của Gantt chỉ có thể là trường hoặc mẫu, nhưng đây là %s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "Gantt cần có một thuột tính 'date_start'"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "Gantt cần có một thuột tính 'date_stop'"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
"Gantt phải có thuộc tính 'dependency_inverted_field' sau khi xác định "
"'dependency_field'"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr "Ngày bắt đầu gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr "Ngày ngừng gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "Chế độ xem Gantt chỉ có thể chứa một thẻ mẫu"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr "Không thể lên lịch trong quá khứ"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "Không đủ trường cho Chế độ xem Gantt!"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
"Thuộc tính không hợp lệ (%(invalid_attributes)s) trong chế độ xem gantt. Các"
" thuộc tính phải có dạng (%(valid_attributes)s)"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr "Default_range '%s' không hợp lệ trong gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "Default_scale không hợp hệ '%s' trong gantt"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr "display_mode không hợp hệ '%s' trong gantt"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "Tên"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "Mới"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "Mở"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "Kế hoạch"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr "Đã lên lịch lại thành công."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "Bắt đầu"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "Ngừng"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr "Phần phụ thuộc không hợp lệ, có một chu kỳ."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr "There are no valid candidates to re-plan"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "Tháng này"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr "Quý này"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "Tuần này"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "Năm nay"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "Hôm nay"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr "Menu thanh công cụ"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "Tổng"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "Không xác định %s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "Chế độ xem"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "Dạng hiển thị"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr "Bạn không thể chuyển %(record)s đến %(related_record)s."
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr "Bạn không thể lên lịch lại %(main_record)s sang %(other_record)s."
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "giờ"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "phút"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "tháng"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "đến"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr "{{ props.progressBar.warning }}"

View File

@ -0,0 +1,355 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:29+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr ""
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr ""
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr ""
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr ""
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr ""
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr ""

View File

@ -0,0 +1,360 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Wil Odoo, 2024
# Odoo哥 <vnsoft.he@gmail.com>, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Odoo哥 <vnsoft.he@gmail.com>, 2024\n"
"Language-Team: Chinese (China) (https://app.transifex.com/odoo/teams/41243/zh_CN/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr "%(hour)s小时%(minute)s分钟"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr "%s 无法排期至过往日期"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr "%s小时"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "动作窗口视图"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr "激活密集模式"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr "激活稀疏模式"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "应用"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "确定要删除此记录吗?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "基数"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "折叠行"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "创建"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "编辑"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "展开行"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr "聚焦到今天"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "来自"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "甘特图"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "甘特视图"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr "甘特儿只能是字段或模板,转到%s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "甘特图必须有一个'date_start'属性"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "甘特图必须有一个'date_stop'属性"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr "一旦指定了 \"依赖关系_字段\",甘特图就必须具有 \"依赖关系_反转字段 \"属性"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr "甘特图开始日期"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr "甘特图结束日期"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "甘特图视图只能包含一个模板标签"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr "无法排期到已往的日期。"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "甘特图视图的字段不足!"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr "甘特图视图中的属性(%(invalid_attributes)s无效属性必须在%(valid_attributes)s之中。"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr "甘特图中的default_range'%s'无效"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "甘特图中的default_scale'%s无效"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr "甘特图中的display_mode'%s'无效"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "名称"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "新建"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "打开"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "安排"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr "重新安排成功完成。"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "开始"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "停止"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr "依赖项目无效:存在一个循环。"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr "没有有效的候选人来重新规划"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "本月"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr "本季度"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "本周"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "今年"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "今天"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr "工具栏菜单"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "总计"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "未定义的 %s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "视图"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "视图类型"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr "您不可移动%(record)s至%(related_record)s。"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr "您不可重新排期%(main_record)s至%(other_record)s。"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "小时"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "分钟"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "月"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "到"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr "{{ props.progressBar.warning }}"

View File

@ -0,0 +1,360 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_gantt
#
# Translators:
# Wil Odoo, 2024
# Tony Ng, 2024
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-25 09:29+0000\n"
"PO-Revision-Date: 2024-09-25 09:44+0000\n"
"Last-Translator: Tony Ng, 2024\n"
"Language-Team: Chinese (Taiwan) (https://app.transifex.com/odoo/teams/41243/zh_TW/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: zh_TW\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%(hour)sh%(minute)s"
msgstr "%(hour)s 小時 %(minute)s 分鐘"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "%s cannot be scheduled in the past"
msgstr "%s 不可排期至過往日期"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "%sh"
msgstr "%s 小時"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_ir_actions_act_window_view
msgid "Action Window View"
msgstr "動作窗檢視"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate dense mode"
msgstr "啟動緊密模式"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Activate sparse mode"
msgstr "啟動稀疏模式"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Apply"
msgstr "套用"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Are you sure to delete this record?"
msgstr "確定刪除此記錄?"
#. module: web_gantt
#: model:ir.model,name:web_gantt.model_base
msgid "Base"
msgstr "計稅基數"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Collapse rows"
msgstr "摺疊列"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Create"
msgstr "建立"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Edit"
msgstr "編輯"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Expand rows"
msgstr "展開列"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Focus Today"
msgstr "聚焦至今天"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "From"
msgstr "由"
#. module: web_gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_actions_act_window_view__view_mode__gantt
#: model:ir.model.fields.selection,name:web_gantt.selection__ir_ui_view__type__gantt
msgid "Gantt"
msgstr "甘特圖"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Gantt View"
msgstr "甘特圖檢視"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt child can only be field or template, got %s"
msgstr "甘特圖子項只可以是欄位或範本,但收到 %s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_start' attribute"
msgstr "甘特圖必須有一個 date_start 屬性"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt must have a 'date_stop' attribute"
msgstr "甘特圖必須有一個 date_stop 屬性"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Gantt must have a 'dependency_inverted_field' attribute once the "
"'dependency_field' is specified"
msgstr "甘特圖若有指定 dependency_field就必須有 dependency_inverted_field 屬性"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt start date"
msgstr "甘特圖開始日期"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.js:0
msgid "Gantt stop date"
msgstr "甘特圖結束日期"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Gantt view can contain only one templates tag"
msgstr "甘特圖只可包含一個範本標籤"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Impossible to schedule in the past."
msgstr "無法排期至過往日期。"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Insufficient fields for Gantt View!"
msgstr "欄位數目不足以供甘特圖使用!"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid ""
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must "
"be in (%(valid_attributes)s)"
msgstr "甘特圖檢視畫面中的屬性(%(invalid_attributes)s無效。屬性必須在%(valid_attributes)s"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_range '%s' in gantt"
msgstr "甘特圖的 default_range '%s' 無效"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid default_scale '%s' in gantt"
msgstr "甘特圖的 default_scale '%s' 無效"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/ir_ui_view.py:0
msgid "Invalid display_mode '%s' in gantt"
msgstr "甘特圖檢視模式中的顯示模式 display_mode 無效:%s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Name"
msgstr "名稱"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.xml:0
msgid "New"
msgstr "新增"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_controller.js:0
msgid "Open"
msgstr "開啟"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Plan"
msgstr "計劃"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "Reschedule done successfully."
msgstr "已成功改期。"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Start"
msgstr "開始"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_popover.xml:0
msgid "Stop"
msgstr "停止"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "The dependencies are not valid, there is a cycle."
msgstr "依賴項目無效:存在一個循環。"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "There are no valid candidates to re-plan"
msgstr "沒有有效的候選人可以重新規劃"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This month"
msgstr "本月"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This quarter"
msgstr "本季度"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This week"
msgstr "本周"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "This year"
msgstr "本年度"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "Today"
msgstr "今天"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "Toolbar menu"
msgstr "工具列選單"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
msgid "Total"
msgstr "總計"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_model.js:0
msgid "Undefined %s"
msgstr "未定義 %s"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer.js:0
#: model:ir.model,name:web_gantt.model_ir_ui_view
msgid "View"
msgstr "檢視"
#. module: web_gantt
#: model:ir.model.fields,field_description:web_gantt.field_ir_actions_act_window_view__view_mode
#: model:ir.model.fields,field_description:web_gantt.field_ir_ui_view__type
msgid "View Type"
msgstr "檢視類型"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot move %(record)s towards %(related_record)s."
msgstr "你不可移動 %(record)s 至 %(related_record)s。"
#. module: web_gantt
#. odoo-python
#: code:addons/web_gantt/models/models.py:0
msgid "You cannot reschedule %(main_record)s towards %(other_record)s."
msgstr "你不可重新排期 %(main_record)s 至 %(other_record)s。"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "hours"
msgstr "小時"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "minutes"
msgstr "分鐘"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_arch_parser.js:0
msgid "months"
msgstr "月"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_renderer_controls.xml:0
msgid "to"
msgstr "到"
#. module: web_gantt
#. odoo-javascript
#: code:addons/web_gantt/static/src/gantt_row_progress_bar.xml:0
msgid "{{ props.progressBar.warning }}"
msgstr "{{ props.progressBar.warning }}"

View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
from . import ir_ui_view
from . import ir_actions

View File

@ -0,0 +1,9 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class ActWindowView(models.Model):
_inherit = 'ir.actions.act_window.view'
view_mode = fields.Selection(selection_add=[('gantt', 'Gantt')], ondelete={'gantt': 'cascade'})

View File

@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, _
from odoo.tools import format_list
from lxml import etree
GANTT_VALID_ATTRIBUTES = set([
'__validate__', # ir.ui.view implementation detail
'date_start',
'date_stop',
'default_scale',
'default_range',
'class',
'js_class',
'form_view_id',
'progress',
'consolidation',
'consolidation_max',
'consolidation_exclude',
'string',
'create',
'on_create',
'cell_create',
'edit',
'delete',
'plan',
'default_group_by',
'dynamic_range',
'display_mode',
'display_unavailability',
'disable_drag_drop',
'total_row',
'collapse_first_level',
'offset',
'scales',
'thumbnails',
'precision',
'color',
'decoration-secondary',
'decoration-success',
'decoration-info',
'decoration-warning',
'decoration-danger',
'sample',
'progress_bar',
'dependency_field',
'dependency_inverted_field',
'pill_label',
'groups_limit'
])
class View(models.Model):
_inherit = 'ir.ui.view'
type = fields.Selection(selection_add=[('gantt', 'Gantt')])
def _validate_tag_gantt(self, node, name_manager, node_info):
if not node_info['validate']:
return
templates_count = 0
for child in node.iterchildren(tag=etree.Element):
if child.tag == 'templates':
if not templates_count:
templates_count += 1
else:
msg = _('Gantt view can contain only one templates tag')
self._raise_view_error(msg, child)
elif child.tag != 'field':
msg = _('Gantt child can only be field or template, got %s', child.tag)
self._raise_view_error(msg, child)
default_scale = node.get('default_scale')
if default_scale:
if default_scale not in ('day', 'week', 'week_2', 'month', 'month_3', 'year'):
self._raise_view_error(_("Invalid default_scale '%s' in gantt", default_scale), node)
default_range = node.get('default_range')
if default_range:
if default_range not in ('day', 'week', 'month', 'quarter', 'year'):
self._raise_view_error(_("Invalid default_range '%s' in gantt", default_range), node)
display_mode = node.get('display_mode')
if display_mode:
if display_mode not in ('dense', 'sparse'):
self._raise_view_error(_("Invalid display_mode '%s' in gantt", display_mode), node)
attrs = set(node.attrib)
if 'date_start' not in attrs:
msg = _("Gantt must have a 'date_start' attribute")
self._raise_view_error(msg, node)
if 'date_stop' not in attrs:
msg = _("Gantt must have a 'date_stop' attribute")
self._raise_view_error(msg, node)
if 'dependency_field' in attrs and 'dependency_inverted_field' not in attrs:
msg = _("Gantt must have a 'dependency_inverted_field' attribute once the 'dependency_field' is specified")
self._raise_view_error(msg, node)
remaining = attrs - GANTT_VALID_ATTRIBUTES
if remaining:
msg = _(
"Invalid attributes (%(invalid_attributes)s) in gantt view. Attributes must be in (%(valid_attributes)s)",
invalid_attributes=format_list(self.env, remaining),
valid_attributes=format_list(self.env, GANTT_VALID_ATTRIBUTES),
)
self._raise_view_error(msg, node)
def _get_view_fields(self, view_type, models):
if view_type == 'gantt':
models[self._name] = list(self._fields.keys())
return models
return super()._get_view_fields(view_type, models)
def _get_view_info(self):
return {'gantt': {'icon': 'fa fa-tasks'}} | super()._get_view_info()
def _is_qweb_based_view(self, view_type):
return view_type == 'gantt' or super()._is_qweb_based_view(view_type)

View File

@ -0,0 +1,780 @@
# -*- coding: utf-8 -*-
from collections import defaultdict
from datetime import datetime, timezone, timedelta
from lxml.builder import E
from odoo import api, fields, models
from odoo.exceptions import UserError
from odoo.tools import _, unique, OrderedSet
class Base(models.AbstractModel):
_inherit = 'base'
_start_name = 'date_start' # start field to use for default gantt view
_stop_name = 'date_stop' # stop field to use for default gantt view
# action_gantt_reschedule utils
_WEB_GANTT_RESCHEDULE_FORWARD = 'forward'
_WEB_GANTT_RESCHEDULE_BACKWARD = 'backward'
_WEB_GANTT_LOOP_ERROR = 'loop_error'
_WEB_GANTT_NO_POSSIBLE_ACTION_ERROR = 'no_possible_action_error'
@api.model
def _get_default_gantt_view(self):
""" Generates a default gantt view by trying to infer
time-based fields from a number of pre-set attribute names
:returns: a gantt view
:rtype: etree._Element
"""
view = E.gantt(string=self._description)
gantt_field_names = {
'_start_name': ['date_start', 'start_date', 'x_date_start', 'x_start_date'],
'_stop_name': ['date_stop', 'stop_date', 'date_end', 'end_date', 'x_date_stop', 'x_stop_date', 'x_date_end', 'x_end_date'],
}
for name in gantt_field_names.keys():
if getattr(self, name) not in self._fields:
for dt in gantt_field_names[name]:
if dt in self._fields:
setattr(self, name, dt)
break
else:
raise UserError(_("Insufficient fields for Gantt View!"))
view.set('date_start', self._start_name)
view.set('date_stop', self._stop_name)
return view
@api.model
def get_gantt_data(self, domain, groupby, read_specification, limit=None, offset=0, unavailability_fields=None, progress_bar_fields=None, start_date=None, stop_date=None, scale=None):
"""
Returns the result of a read_group (and optionally search for and read records inside each
group), and the total number of groups matching the search domain.
:param domain: search domain
:param groupby: list of field to group on (see ``groupby``` param of ``read_group``)
:param read_specification: web_read specification to read records within the groups
:param limit: see ``limit`` param of ``read_group``
:param offset: see ``offset`` param of ``read_group``
:param boolean unavailability_fields
:param string start_date: start datetime in utc, e.g. "2024-06-22 23:00:00"
:param string stop_date: stop datetime in utc
:param string scale: among "day", "week", "month" and "year"
:return: {
'groups': [
{
'<groupby_1>': <value_groupby_1>,
...,
'__record_ids': [<ids>]
}
],
'records': [<record data>]
'length': total number of groups
'unavailabilities': {
'<unavailability_fields_1>': <value_unavailability_fields_1>,
...
}
'progress_bars': {
'<progress_bar_fields_1>': <value_progress_bar_fields_1>,
...
}
}
"""
# TODO: group_expand doesn't currently respect the limit/offset
lazy = not limit and not offset and len(groupby) == 1
# Because there is no limit by group, we can fetch record_ids as aggregate
final_result = self.web_read_group(
domain, ['__record_ids:array_agg(id)'], groupby,
limit=limit, offset=offset, lazy=lazy,
)
all_record_ids = tuple(unique(
record_id
for one_group in final_result['groups']
for record_id in one_group['__record_ids']
))
# Do search_fetch to order records (model order can be no-trivial)
all_records = self.with_context(active_test=False).search_fetch([('id', 'in', all_record_ids)], read_specification.keys())
final_result['records'] = all_records.with_env(self.env).web_read(read_specification)
if unavailability_fields is None:
unavailability_fields = []
if progress_bar_fields is None:
progress_bar_fields = []
ordered_set_ids = OrderedSet(all_records._ids)
res_ids_for_unavailabilities = defaultdict(set)
res_ids_for_progress_bars = defaultdict(set)
for group in final_result['groups']:
for field in unavailability_fields:
res_id = group[field][0] if group[field] else False
if res_id:
res_ids_for_unavailabilities[field].add(res_id)
for field in progress_bar_fields:
res_id = group[field][0] if group[field] else False
if res_id:
res_ids_for_progress_bars[field].add(res_id)
# Reorder __record_ids
group['__record_ids'] = list(ordered_set_ids & OrderedSet(group['__record_ids']))
# We don't need these in the gantt view
del group['__domain']
del group[f'{groupby[0]}_count' if lazy else '__count']
group.pop('__fold', None)
if unavailability_fields or progress_bar_fields:
start, stop = fields.Datetime.from_string(start_date), fields.Datetime.from_string(stop_date)
unavailabilities = {}
for field in unavailability_fields:
unavailabilities[field] = self._gantt_unavailability(field, list(res_ids_for_unavailabilities[field]), start, stop, scale)
final_result['unavailabilities'] = unavailabilities
progress_bars = {}
for field in progress_bar_fields:
progress_bars[field] = self._gantt_progress_bar(field, list(res_ids_for_progress_bars[field]), start, stop)
final_result['progress_bars'] = progress_bars
return final_result
@api.model
def web_gantt_reschedule(
self,
direction,
master_record_id, slave_record_id,
dependency_field_name, dependency_inverted_field_name,
start_date_field_name, stop_date_field_name
):
""" Reschedule a record according to the provided parameters.
:param direction: The direction of the rescheduling 'forward' or 'backward'
:param master_record_id: The record that the other one is depending on.
:param slave_record_id: The record that is depending on the other one.
:param dependency_field_name: The field name of the relation between the master and slave records.
:param dependency_inverted_field_name: The field name of the relation between the slave and the parent
records.
:param start_date_field_name: The start date field used in the gantt view.
:param stop_date_field_name: The stop date field used in the gantt view.
:return: dict = {
type: notification type,
message: notification message,
old_vals_per_pill_id: dict = {
pill_id: {
start_date_field_name: planned_date_begin before rescheduling
stop_date_field_name: date_deadline before rescheduling
}
}
}
"""
if direction not in (self._WEB_GANTT_RESCHEDULE_FORWARD, self._WEB_GANTT_RESCHEDULE_BACKWARD):
raise ValueError("Invalid direction %r" % direction)
master_record, slave_record = self.env[self._name].browse([master_record_id, slave_record_id])
search_domain = [(dependency_field_name, 'in', master_record.id), ('id', '=', slave_record.id)]
if not self.env[self._name].search_count(search_domain, limit=1):
raise ValueError("Record '%r' is not a parent record of '%r'" % (master_record.name, slave_record.name))
if not self._web_gantt_reschedule_is_relation_candidate(
master_record, slave_record, start_date_field_name, stop_date_field_name):
return {
'type': 'warning',
'message': _('You cannot reschedule %(main_record)s towards %(other_record)s.',
main_record=master_record.name, other_record=slave_record.name),
}
is_master_prior_to_slave = master_record[stop_date_field_name] <= slave_record[start_date_field_name]
# When records are in conflict, record that is moved is the other one than when there is no conflict.
# This might seem strange at first sight but has been decided during first implementation as when in conflict,
# and especially when the distance between the pills is big, the arrow is interpreted differently as it comes
# from the right to the left (instead of from the left to the right).
if is_master_prior_to_slave ^ (direction == self._WEB_GANTT_RESCHEDULE_BACKWARD):
trigger_record = master_record
related_record = slave_record
else:
trigger_record = slave_record
related_record = master_record
if not trigger_record._web_gantt_reschedule_is_record_candidate(start_date_field_name, stop_date_field_name):
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'type': 'warning',
'message': _(
"You cannot move %(record)s towards %(related_record)s.",
record=trigger_record.name,
related_record=related_record.name,
),
}
}
sp = self.env.cr.savepoint()
log_messages, old_vals_per_pill_id = trigger_record._web_gantt_action_reschedule_candidates(dependency_field_name, dependency_inverted_field_name, start_date_field_name, stop_date_field_name, direction, related_record)
has_errors = bool(log_messages.get("errors"))
sp.close(rollback=has_errors)
notification_type = "success"
message = _("Reschedule done successfully.")
if has_errors or log_messages.get("warnings"):
message = self._web_gantt_get_reschedule_message(log_messages)
notification_type = "warning" if has_errors else "info"
return {
"type": notification_type,
"message": message,
"old_vals_per_pill_id": old_vals_per_pill_id,
}
def action_rollback_scheduling(self, old_vals_per_pill_id):
for record in self:
vals = old_vals_per_pill_id.get(str(record.id), old_vals_per_pill_id.get(record.id))
if vals:
record.write(vals)
@api.model
def _gantt_progress_bar(self, field, res_ids, start, stop):
""" Get progress bar value per record.
This method is meant to be overriden by each related model that want to
implement this feature on Gantt groups. The progressbar is composed
of a value and a max_value given for each groupedby field.
Example:
field = 'foo',
res_ids = [1, 2]
start_date = 01/01/2000, end_date = 01/07/2000,
self = base()
Result:
{
1: {'value': 50, 'max_value': 100},
2: {'value': 25, 'max_value': 200},
}
:param string field: field on which there are progressbars
:param list res_ids: res_ids of related records for which we need to compute progress bar
:param string start_datetime: start date in utc
:param string end_datetime: end date in utc
:returns: dict of value and max_value per record
"""
return {}
@api.model
def _gantt_unavailability(self, field, res_ids, start, stop, scale):
""" Get unavailabilities data for a given set of resources.
This method is meant to be overriden by each model that want to
implement this feature on a Gantt view. A subslot is considered
unavailable (and greyed) when totally covered by an unavailability.
Example:
* start = 01/01/2000 in datetime utc, stop = 01/07/2000 in datetime utc, scale = 'week',
field = "empployee_id", res_ids = [3, 9]
* The expected return value of this function is a dict of the form
{
value: [{
start: <start date of first unavailabity in UTC format>,
stop: <stop date of first unavailabity in UTC format>
}, {
start: <start date of second unavailabity in UTC format>,
stop: <stop date of second unavailabity in UTC format>
}, ...]
...
}
For example Marcel (3) is unavailable January 2 afternoon and
January 4 the whole day, the dict should look like this
{
3: [{
'start': '2018-01-02 14:00:00',
'stop': '2018-01-02 18:00:00'
}, {
'start': '2018-01-04 08:00:00',
'stop': '2018-01-04 18:00:00'
}]
}
Note that John (9) has no unavailabilies and thus 9 is not in
returned dict
:param string field: name of a many2X field
:param list res_ids: list of values for field for which we want unavailabilities (a value is either False or an id)
:param datetime start: start datetime
:param datetime stop: stop datetime
:param string scale: among "day", "week", "month" and "year"
:returns: dict of unavailabilities
"""
return {}
def _web_gantt_get_candidates(self,
dependency_field_name, dependency_inverted_field_name,
start_date_field_name, stop_date_field_name,
related_record, move_forward_without_conflicts,
):
result = {
'warnings': [],
'errors': [],
}
# first get the children of self
self_children_ids = []
pills_to_plan_before = []
pills_to_plan_after = []
if move_forward_without_conflicts:
candidates_to_exclude = {related_record.id}
else:
candidates_to_exclude = {self.id} | set(related_record[dependency_inverted_field_name].ids)
if self._web_gantt_check_cycle_existance_and_get_rescheduling_candidates(
self_children_ids, dependency_inverted_field_name,
start_date_field_name, stop_date_field_name,
candidates_to_exclude,
):
result['errors'].append(self._WEB_GANTT_LOOP_ERROR)
return (result, pills_to_plan_before, pills_to_plan_after, [])
# second, get the ancestors of related_record
related_record_ancestors_ids = []
if move_forward_without_conflicts:
candidates_to_exclude = {related_record.id} | set(self[dependency_field_name].ids)
else:
candidates_to_exclude = {self.id}
if related_record._web_gantt_check_cycle_existance_and_get_rescheduling_candidates(
related_record_ancestors_ids, dependency_field_name,
start_date_field_name, stop_date_field_name,
candidates_to_exclude,
):
result['errors'].append(self._WEB_GANTT_LOOP_ERROR)
return (result, pills_to_plan_before, pills_to_plan_after, [])
# third, get the intersection between self children and related_record ancestors
if move_forward_without_conflicts:
all_pills_ids, pills_to_check_from_ids = self_children_ids, set(related_record_ancestors_ids)
else:
related_record_ancestors_ids.reverse()
all_pills_ids, pills_to_check_from_ids = related_record_ancestors_ids, self_children_ids
for pill_id in all_pills_ids:
if pill_id in pills_to_check_from_ids:
(pills_to_plan_before if move_forward_without_conflicts else pills_to_plan_after).append(pill_id)
else:
(pills_to_plan_after if move_forward_without_conflicts else pills_to_plan_before).append(pill_id)
return (result, pills_to_plan_before, pills_to_plan_after, all_pills_ids)
def _web_gantt_get_reschedule_message_per_key(self, key, params=None):
if key == self._WEB_GANTT_LOOP_ERROR:
return _("The dependencies are not valid, there is a cycle.")
elif key == self._WEB_GANTT_NO_POSSIBLE_ACTION_ERROR:
return _("There are no valid candidates to re-plan")
elif key == "past_error":
if params: # params is the record that is in the past
return _("%s cannot be scheduled in the past", params.display_name)
else:
return _("Impossible to schedule in the past.")
else:
return ""
def _web_gantt_get_reschedule_message(self, log_messages):
def get_messages(logs):
messages = []
for key in logs:
message = self._web_gantt_get_reschedule_message_per_key(key, log_messages.get(key))
if message:
messages.append(message)
return messages
messages = []
errors = log_messages.get("errors")
if errors:
messages = get_messages(log_messages.get("errors"))
else:
messages = get_messages(log_messages.get("warnings", []))
return "\n".join(messages)
def _web_gantt_action_reschedule_candidates(
self,
dependency_field_name, dependency_inverted_field_name,
start_date_field_name, stop_date_field_name,
direction, related_record,
):
""" Prepare the candidates according to the provided parameters and move them.
:param dependency_field_name: The field name of the relation between the master and slave records.
:param dependency_inverted_field_name: The field name of the relation between the slave and the parent
records.
:param start_date_field_name: The start date field used in the gantt view.
:param stop_date_field_name: The stop date field used in the gantt view.
:param direction: The direction of the rescheduling 'forward' or 'backward'
:param related_record: The record that self will be moving to
:return: tuple(valid, message) (valid = True if Successful, message = None or contains the notification text if
text if valid = True or the error text if valid = False.
"""
search_forward = direction == self._WEB_GANTT_RESCHEDULE_FORWARD
# moving forward without conflicts
if search_forward and self[stop_date_field_name] <= related_record[start_date_field_name] and related_record in self[dependency_inverted_field_name]:
log_messages, pills_to_plan_before_related_record, pills_to_plan_after_related_record, all_candidates_ids = self._web_gantt_get_candidates(
dependency_field_name, dependency_inverted_field_name,
start_date_field_name, stop_date_field_name,
related_record, True,
)
if log_messages.get("errors") or not pills_to_plan_before_related_record:
return log_messages, {}
# plan self_children backward from related_record
pills_to_plan_before_related_record.reverse()
log_messages, old_vals_per_pill_id = self._web_gantt_move_candidates(
start_date_field_name, stop_date_field_name,
dependency_field_name, dependency_inverted_field_name,
False, pills_to_plan_before_related_record,
related_record[start_date_field_name],
all_candidates_ids, True,
)
if log_messages.get("errors") or not pills_to_plan_after_related_record:
return log_messages, {} if log_messages.get("errors") else old_vals_per_pill_id
# plan related_record_ancestors forward from related_record
new_log_messages, second_old_vals_per_pill_id = self._web_gantt_move_candidates(
start_date_field_name, stop_date_field_name,
dependency_field_name, dependency_inverted_field_name,
True, pills_to_plan_after_related_record,
self[stop_date_field_name]
)
log_messages.setdefault("errors", []).extend(new_log_messages.get("errors", []))
log_messages.setdefault("warnings", []).extend(new_log_messages.get("warnings", []))
return log_messages, old_vals_per_pill_id | second_old_vals_per_pill_id
# moving backward without conflicts
elif related_record[stop_date_field_name] <= self[start_date_field_name] and related_record in self[dependency_field_name]:
log_messages, pills_to_plan_before_related_record, pills_to_plan_after_related_record, all_candidates_ids = related_record._web_gantt_get_candidates(
dependency_field_name, dependency_inverted_field_name,
start_date_field_name, stop_date_field_name,
self, False,
)
if log_messages.get("errors") or not pills_to_plan_after_related_record:
return log_messages, {}
# plan related_record_children_ids forward from related_record
log_messages, old_vals_per_pill_id = self._web_gantt_move_candidates(
start_date_field_name, stop_date_field_name,
dependency_field_name, dependency_inverted_field_name,
True, pills_to_plan_after_related_record,
related_record[stop_date_field_name],
all_candidates_ids, True,
)
if log_messages.get("errors") or not pills_to_plan_before_related_record:
return log_messages, {} if log_messages.get("errors") else old_vals_per_pill_id
# plan self_ancestors_ids backward from related_record
pills_to_plan_before_related_record.reverse()
new_log_messages, second_old_vals_per_pill_id = self._web_gantt_move_candidates(
start_date_field_name, stop_date_field_name,
dependency_field_name, dependency_inverted_field_name,
False, pills_to_plan_before_related_record,
self[start_date_field_name]
)
log_messages.setdefault("errors", []).extend(new_log_messages.get("errors", []))
log_messages.setdefault("warnings", []).extend(new_log_messages.get("warnings", []))
return log_messages, old_vals_per_pill_id | second_old_vals_per_pill_id
# moving forward or backward with conflicts
else:
candidates_ids = []
dependency = dependency_inverted_field_name if search_forward else dependency_field_name
if self._web_gantt_check_cycle_existance_and_get_rescheduling_candidates(
candidates_ids, dependency,
start_date_field_name, stop_date_field_name,
):
log_messages['errors'].append(self._WEB_GANTT_LOOP_ERROR)
return {
"errors": [self._WEB_GANTT_LOOP_ERROR],
}, {}
if not candidates_ids:
return {
"errors": [self._WEB_GANTT_NO_POSSIBLE_ACTION_ERROR],
}, {}
return self._web_gantt_move_candidates(
start_date_field_name, stop_date_field_name,
dependency_field_name, dependency_inverted_field_name,
search_forward, candidates_ids,
related_record[stop_date_field_name if search_forward else start_date_field_name]
)
def _web_gantt_is_candidate_in_conflict(self, start_date_field_name, stop_date_field_name, dependency_field_name, dependency_inverted_field_name):
return (
any(r[start_date_field_name] and r[stop_date_field_name] and self[start_date_field_name] < r[stop_date_field_name] for r in self[dependency_field_name])
or any(r[start_date_field_name] and r[stop_date_field_name] and self[stop_date_field_name] > r[start_date_field_name] for r in self[dependency_inverted_field_name])
)
def _web_gantt_move_candidates(self, start_date_field_name, stop_date_field_name, dependency_field_name, dependency_inverted_field_name, search_forward, candidates_ids, date_candidate=None, all_candidates_ids=None, move_not_in_conflicts_candidates=False):
""" Move candidates according to the provided parameters.
:param start_date_field_name: The start date field used in the gantt view.
:param stop_date_field_name: The stop date field used in the gantt view.
:param dependency_field_name: The field name of the relation between the master and slave records.
:param dependency_inverted_field_name: The field name of the relation between the slave and the parent
records.
search_forward, candidates_ids, date_candidate
:param search_forward: True if the direction = 'forward'
:param candidates_ids: The candidates to reschdule
:param date_candidate: The first possible date for the rescheduling
:param all_candidates_ids: moving without conflicts is done in 2 steps, candidates_ids contains the candidates
to schedule during the step, and all_candidates_ids contains the candidates to schedule in the 2 steps
:return: dict of list containing 2 keys, errors and warnings
"""
result = {
"errors": [],
"warnings": [],
}
old_vals_per_pill_id = {}
candidates = self.browse(candidates_ids)
for i, candidate in enumerate(candidates):
if not move_not_in_conflicts_candidates and not candidate._web_gantt_is_candidate_in_conflict(start_date_field_name, stop_date_field_name, dependency_field_name, dependency_inverted_field_name):
continue
start_date, end_date = candidate._web_gantt_reschedule_compute_dates(
date_candidate,
search_forward,
start_date_field_name, stop_date_field_name
)
start_date, end_date = start_date.astimezone(timezone.utc), end_date.astimezone(timezone.utc)
old_start_date, old_end_date = candidate[start_date_field_name], candidate[stop_date_field_name]
if not candidate._web_gantt_reschedule_write_new_dates(
start_date, end_date,
start_date_field_name, stop_date_field_name
):
result["errors"].append("past_error")
result["past_error"] = candidate
return result, {}
else:
old_vals_per_pill_id[candidate.id] = {
start_date_field_name: old_start_date,
stop_date_field_name: old_end_date,
}
if i + 1 < len(candidates):
next_candidate = candidates[i + 1]
if search_forward:
ancestors = next_candidate[dependency_field_name]
if ancestors:
date_candidate = max(ancestors.mapped(stop_date_field_name))
else:
date_candidate = end_date
else:
children = next_candidate[dependency_inverted_field_name]
if children:
date_candidate = min(children.mapped(start_date_field_name))
else:
date_candidate = start_date
return result, old_vals_per_pill_id
def _web_gantt_check_cycle_existance_and_get_rescheduling_candidates(self,
candidates_ids, dependency_field_name,
start_date_field_name, stop_date_field_name,
candidates_to_exclude=None, visited=None, ancestors=None,
):
""" Get the current records' related records rescheduling candidates (explained in details
in case 1 and case 2 in the below example)
This method Executes a dfs (depth first search algorithm) on the dependencies tree to:
1- detect cycles (detect if it's not a valid tree)
2- return the topological sorting of the candidates to reschedule
Example:
[4]->[6]
|
v
--->[0]->[1]->[2] [5]->[7]->[8]-----------------
| | | |
| v v |
| [3] [9]->[10] |
| |
---------------------<x>----------------------------
[0]->[1]: pill 0 should be done before 1
<: left arrow to move pill 8 backward pill 0
>: right arrow to move pill 0 forward pill 8
x: delete the dependence
Case 1:
If the right arrow is clicked, pill 0 should move forward. And as 1, 2, 3 are children of 0, they should be done after it,
they should also be moved forward.
This method will return False (no cycles) and a valid order of candidates = [0, 1, 2, 3] that should be scheduled
Case 2:
If the left arrow is clicked, pill 8 should move backward task 0, as 4, 6, 5, 7 are ancestors for 8, they should be done
before it, they should be moved backward also. 9 and 10 should not be impacted as they are not ancestors of 8.
This method will return False (no cycles) and a valid order of candidates = [5, 4, 6, 7, 8] that should be scheduled
Example 2:
modify the previous tree by adding an edge from pill 2 to pill 0 (no more a tree after this added edge)
-----------
| |
v |
[0]->[1]->[2]
This method will return True because there is the cycle illustrated above
:param candidates_ids: empty list that will contain the candidates at the end
:param dependency_field_name: The field name of the relation between the master and slave records.
:param dependency_inverted_field_name: The field name of the relation between the slave and the parent
records.
:param start_date_field_name: The start date field used in the gantt view.
:param stop_date_field_name: The stop date field used in the gantt view.
:param candidates_to_exclude: candidates to exclude
:param visited: set containing all the visited pills
:param ancestors: set containing the visited ancestors for the current pill
:return: bool, True if there is a cycle, else False.
candidates_id will also contain the pills to plan in a valid topological order
"""
if candidates_to_exclude is None:
candidates_to_exclude = []
if visited is None:
visited = set()
if ancestors is None:
ancestors = []
visited.add(self.id)
ancestors.append(self.id)
for child in self[dependency_field_name]:
if child.id in ancestors:
return True
if child.id not in visited and child.id not in candidates_to_exclude and child._web_gantt_check_cycle_existance_and_get_rescheduling_candidates(candidates_ids, dependency_field_name, start_date_field_name, stop_date_field_name, candidates_to_exclude, visited, ancestors):
return True
ancestors.pop()
if self._web_gantt_reschedule_is_record_candidate(start_date_field_name, stop_date_field_name) and self.id not in candidates_to_exclude:
candidates_ids.insert(0, self.id)
return False
def _web_gantt_reschedule_compute_dates(
self, date_candidate, search_forward, start_date_field_name, stop_date_field_name
):
""" Compute start_date and end_date according to the provided arguments.
This method is meant to be overridden when we need to add constraints that have to be taken into account
in the computing of the start_date and end_date.
:param date_candidate: The optimal date, which does not take any constraint into account.
:param start_date_field_name: The start date field used in the gantt view.
:param stop_date_field_name: The stop date field used in the gantt view.
:return: a tuple of (start_date, end_date)
:rtype: tuple(datetime, datetime)
"""
search_factor = (1 if search_forward else -1)
duration = search_factor * (self[stop_date_field_name] - self[start_date_field_name])
return sorted([date_candidate, date_candidate + duration])
@api.model
def _web_gantt_reschedule_is_in_conflict(self, master, slave, start_date_field_name, stop_date_field_name):
""" Get whether the dependency relation between a master and a slave record is in conflict.
This check is By-passed for slave records if moving records forwards and the for
master records if moving records backwards (see _web_gantt_get_rescheduling_candidates and
_web_gantt_reschedule_is_in_conflict_or_force). In order to add condition that would not be
by-passed, rather consider _web_gantt_reschedule_is_relation_candidate.
:param master: The master record.
:param slave: The slave record.
:param start_date_field_name: The start date field used in the gantt view.
:param stop_date_field_name: The stop date field used in the gantt view.
:return: True if there is a conflict, False if not.
:rtype: bool
"""
return master[stop_date_field_name] > slave[start_date_field_name]
@api.model
def _web_gantt_reschedule_is_in_conflict_or_force(
self, master, slave, start_date_field_name, stop_date_field_name, force
):
""" Get whether the dependency relation between a master and a slave record is in conflict.
This check is By-passed for slave records if moving records forwards and the for
master records if moving records backwards. In order to add condition that would not be
by-passed, rather consider _web_gantt_reschedule_is_relation_candidate.
This def purpose is to be able to prevent the default behavior in some modules by overriding
the def and forcing / preventing the rescheduling il all circumstances if needed.
See _web_gantt_get_rescheduling_candidates.
:param master: The master record.
:param slave: The slave record.
:param start_date_field_name: The start date field used in the gantt view.
:param stop_date_field_name: The stop date field used in the gantt view.
:param force: Force returning True
:return: True if there is a conflict, False if not.
:rtype: bool
"""
return force or self._web_gantt_reschedule_is_in_conflict(
master, slave, start_date_field_name, stop_date_field_name
)
def _web_gantt_reschedule_is_record_candidate(self, start_date_field_name, stop_date_field_name):
""" Get whether the record is a candidate for the rescheduling. This method is meant to be overridden when
we need to add a constraint in order to prevent some records to be rescheduled. This method focuses on the
record itself (if you need to have information on the relation (master and slave) rather override
_web_gantt_reschedule_is_relation_candidate).
:param start_date_field_name: The start date field used in the gantt view.
:param stop_date_field_name: The stop date field used in the gantt view.
:return: True if record can be rescheduled, False if not.
:rtype: bool
"""
self.ensure_one()
return self[start_date_field_name] and self[stop_date_field_name] \
and self[start_date_field_name].replace(tzinfo=timezone.utc) > datetime.now(timezone.utc)
def _web_gantt_reschedule_is_relation_candidate(self, master, slave, start_date_field_name, stop_date_field_name):
""" Get whether the relation between master and slave is a candidate for the rescheduling. This method is meant
to be overridden when we need to add a constraint in order to prevent some records to be rescheduled.
This method focuses on the relation between records (if your logic is rather on one record, rather override
_web_gantt_reschedule_is_record_candidate).
:param master: The master record we need to evaluate whether it is a candidate for rescheduling or not.
:param slave: The slave record.
:param start_date_field_name: The start date field used in the gantt view.
:param stop_date_field_name: The stop date field used in the gantt view.
:return: True if record can be rescheduled, False if not.
:rtype: bool
"""
return True
def _web_gantt_reschedule_write_new_dates(
self, new_start_date, new_stop_date, start_date_field_name, stop_date_field_name
):
""" Write the dates values if new_start_date is in the future.
:param new_start_date: The start_date to write.
:param new_stop_date: The stop_date to write.
:param start_date_field_name: The start date field used in the gantt view.
:param stop_date_field_name: The stop date field used in the gantt view.
:return: True if successful, False if not.
:rtype: bool
epsilon = 30 seconds was added because the first valid interval can be now and because of some seconds, it will become < now() at the comparaison moment
it's a matter of some seconds
"""
new_start_date = new_start_date.astimezone(timezone.utc).replace(tzinfo=None)
if new_start_date < datetime.now() + timedelta(seconds=-30):
return False
self.write({
start_date_field_name: new_start_date,
stop_date_field_name: new_stop_date.astimezone(timezone.utc).replace(tzinfo=None)
})
return True

View File

@ -0,0 +1,332 @@
import { getLocalYearAndWeek } from "@web/core/l10n/dates";
import { _t } from "@web/core/l10n/translation";
import { evaluateExpr } from "@web/core/py_js/py";
import { exprToBoolean } from "@web/core/utils/strings";
import { visitXML } from "@web/core/utils/xml";
import { getActiveActions } from "@web/views/utils";
const DECORATIONS = [
"decoration-danger",
"decoration-info",
"decoration-secondary",
"decoration-success",
"decoration-warning",
];
const PARTS = { full: 1, half: 2, quarter: 4 };
const SCALES = {
day: {
// determines subcolumns
cellPrecisions: { full: 60, half: 30, quarter: 15 },
defaultPrecision: "full",
time: "minute",
unitDescription: _t("minutes"),
// determines columns
interval: "hour",
minimalColumnWidth: 40,
// determines column groups
unit: "day",
groupHeaderFormatter: (date) => date.toFormat("dd MMMM yyyy"),
defaultRange: { unit: "day", count: 3 },
},
week: {
cellPrecisions: { full: 24, half: 12 },
defaultPrecision: "half",
time: "hour",
unitDescription: _t("hours"),
interval: "day",
minimalColumnWidth: 192,
colHeaderFormatter: (date) => date.toFormat("dd"),
unit: "week",
groupHeaderFormatter: formatLocalWeekYear,
defaultRange: { unit: "week", count: 3 },
},
week_2: {
cellPrecisions: { full: 24, half: 12 },
defaultPrecision: "half",
time: "hour",
unitDescription: _t("hours"),
interval: "day",
minimalColumnWidth: 96,
colHeaderFormatter: (date) => date.toFormat("dd"),
unit: "week",
groupHeaderFormatter: formatLocalWeekYear,
defaultRange: { unit: "week", count: 6 },
},
month: {
cellPrecisions: { full: 24, half: 12 },
defaultPrecision: "half",
time: "hour",
unitDescription: _t("hours"),
interval: "day",
minimalColumnWidth: 50,
colHeaderFormatter: (date) => date.toFormat("dd"),
unit: "month",
groupHeaderFormatter: (date, env) => date.toFormat(env.isSmall ? "MMM yyyy" : "MMMM yyyy"),
defaultRange: { unit: "month", count: 3 },
},
month_3: {
cellPrecisions: { full: 24, half: 12 },
defaultPrecision: "half",
time: "hour",
unitDescription: _t("hours"),
interval: "day",
minimalColumnWidth: 18,
colHeaderFormatter: (date) => date.toFormat("dd"),
unit: "month",
groupHeaderFormatter: (date, env) => date.toFormat(env.isSmall ? "MMM yyyy" : "MMMM yyyy"),
defaultRange: { unit: "month", count: 6 },
},
year: {
cellPrecisions: { full: 1 },
defaultPrecision: "full",
time: "month",
unitDescription: _t("months"),
interval: "month",
minimalColumnWidth: 60,
colHeaderFormatter: (date, env) => date.toFormat(env.isSmall ? "MMM" : "MMMM"),
unit: "year",
groupHeaderFormatter: (date) => date.toFormat("yyyy"),
defaultRange: { unit: "year", count: 1 },
},
};
/**
* Formats a date to a `'W'W kkkk` datetime string, in the user's locale settings.
*
* @param {Date|luxon.DateTime} date
* @returns {string}
*/
function formatLocalWeekYear(date) {
const { year, week } = getLocalYearAndWeek(date);
return `W${week} ${year}`;
}
function getPreferedScaleId(scaleId, scales) {
// we assume that scales is not empty
if (scaleId in scales) {
return scaleId;
}
const scaleIds = Object.keys(SCALES);
const index = scaleIds.findIndex((id) => id === scaleId);
for (let j = index - 1; j >= 0; j--) {
const id = scaleIds[j];
if (id in scales) {
return id;
}
}
for (let j = index + 1; j < scaleIds.length; j++) {
const id = scaleIds[j];
if (id in scales) {
return id;
}
}
}
const RANGES = {
day: { scaleId: "day", description: _t("Today") },
week: { scaleId: "week", description: _t("This week") },
month: { scaleId: "month", description: _t("This month") },
quarter: { scaleId: "month_3", description: _t("This quarter") },
year: { scaleId: "year", description: _t("This year") },
};
export class GanttArchParser {
parse(arch) {
let infoFromRootNode;
const decorationFields = [];
const popoverArchParams = {
displayGenericButtons: true,
bodyTemplate: null,
footerTemplate: null,
};
visitXML(arch, (node) => {
switch (node.tagName) {
case "gantt": {
infoFromRootNode = getInfoFromRootNode(node);
break;
}
case "field": {
const fieldName = node.getAttribute("name");
decorationFields.push(fieldName);
break;
}
case "templates": {
const body = node.querySelector("[t-name=gantt-popover]") || null;
if (body) {
popoverArchParams.bodyTemplate = body.cloneNode(true);
popoverArchParams.bodyTemplate.removeAttribute("t-name");
const footer = popoverArchParams.bodyTemplate.querySelector("footer");
if (footer) {
popoverArchParams.displayGenericButtons = false;
footer.remove();
const footerTemplate = new Document().createElement("t");
footerTemplate.append(...footer.children);
popoverArchParams.footerTemplate = footerTemplate;
const replace = footer.getAttribute("replace");
if (replace && !exprToBoolean(replace)) {
popoverArchParams.displayGenericButtons = true;
}
}
}
}
}
});
return {
...infoFromRootNode,
decorationFields,
popoverArchParams,
};
}
}
function getInfoFromRootNode(rootNode) {
const attrs = {};
for (const { name, value } of rootNode.attributes) {
attrs[name] = value;
}
const { create: canCreate, delete: canDelete, edit: canEdit } = getActiveActions(rootNode);
const canCellCreate = exprToBoolean(attrs.cell_create, true) && canCreate;
const canPlan = exprToBoolean(attrs.plan, true) && canEdit;
let consolidationMaxField;
let consolidationMaxValue;
const consolidationMax = attrs.consolidation_max ? evaluateExpr(attrs.consolidation_max) : {};
if (Object.keys(consolidationMax).length > 0) {
consolidationMaxField = Object.keys(consolidationMax)[0];
consolidationMaxValue = consolidationMax[consolidationMaxField];
}
const consolidationParams = {
excludeField: attrs.consolidation_exclude,
field: attrs.consolidation,
maxField: consolidationMaxField,
maxValue: consolidationMaxValue,
};
const dependencyField = attrs.dependency_field || null;
const dependencyEnabled = !!dependencyField;
const dependencyInvertedField = attrs.dependency_inverted_field || null;
const allowedScales = [];
if (attrs.scales) {
for (const key of attrs.scales.split(",")) {
if (SCALES[key]) {
allowedScales.push(key);
}
}
}
if (allowedScales.length === 0) {
allowedScales.push(...Object.keys(SCALES));
}
// Cell precision
const cellPrecisions = {};
// precision = {'day': 'hour:half', 'week': 'day:half', 'month': 'day', 'year': 'month:quarter'}
const precisionAttrs = attrs.precision ? evaluateExpr(attrs.precision) : {};
for (const scaleId in SCALES) {
if (precisionAttrs[scaleId]) {
const precision = precisionAttrs[scaleId].split(":"); // hour:half
// Note that precision[0] (which is the cell interval) is not
// taken into account right now because it is no customizable.
if (
precision[1] &&
Object.keys(SCALES[scaleId].cellPrecisions).includes(precision[1])
) {
cellPrecisions[scaleId] = precision[1];
}
}
cellPrecisions[scaleId] ||= SCALES[scaleId].defaultPrecision;
}
const scales = {};
for (const scaleId of allowedScales) {
const precision = cellPrecisions[scaleId];
const referenceScale = SCALES[scaleId];
scales[scaleId] = {
...referenceScale,
cellPart: PARTS[precision],
cellTime: referenceScale.cellPrecisions[precision],
id: scaleId,
unitDescription: referenceScale.unitDescription.toString(),
};
// protect SCALES content
delete scales[scaleId].cellPrecisions;
}
const ranges = {};
for (const rangeId in RANGES) {
const referenceRange = RANGES[rangeId];
ranges[rangeId] = {
...referenceRange,
id: rangeId,
scaleId: getPreferedScaleId(referenceRange.scaleId, scales),
description: referenceRange.description.toString(),
};
}
let pillDecorations = null;
for (const decoration of DECORATIONS) {
if (decoration in attrs) {
if (!pillDecorations) {
pillDecorations = {};
}
pillDecorations[decoration] = attrs[decoration];
}
}
return {
canCellCreate,
canCreate,
canDelete,
canEdit,
canPlan,
colorField: attrs.color,
computePillDisplayName: !!attrs.pill_label,
consolidationParams,
createAction: attrs.on_create || null,
dateStartField: attrs.date_start,
dateStopField: attrs.date_stop,
defaultGroupBy: attrs.default_group_by ? attrs.default_group_by.split(",") : [],
defaultRange: attrs.default_range,
defaultScale: attrs.default_scale || "month",
dependencyEnabled,
dependencyField,
dependencyInvertedField,
disableDrag: exprToBoolean(attrs.disable_drag_drop),
displayMode: attrs.display_mode || "dense",
displayTotalRow: exprToBoolean(attrs.total_row),
displayUnavailability: exprToBoolean(attrs.display_unavailability),
formViewId: attrs.form_view_id ? parseInt(attrs.form_view_id, 10) : false,
offset: attrs.offset,
pagerLimit: attrs.groups_limit ? parseInt(attrs.groups_limit, 10) : null,
pillDecorations,
progressBarFields: attrs.progress_bar ? attrs.progress_bar.split(",") : null,
progressField: attrs.progress || null,
ranges,
scales,
string: attrs.string || _t("Gantt View").toString(),
thumbnails: attrs.thumbnails ? evaluateExpr(attrs.thumbnails) : {},
};
}

View File

@ -0,0 +1,20 @@
import { ViewCompiler } from "@web/views/view_compiler";
export class GanttCompiler extends ViewCompiler {}
GanttCompiler.OWL_DIRECTIVE_WHITELIST = [
...ViewCompiler.OWL_DIRECTIVE_WHITELIST,
"t-name",
"t-esc",
"t-out",
"t-set",
"t-value",
"t-if",
"t-else",
"t-elif",
"t-foreach",
"t-as",
"t-key",
"t-att.*",
"t-call",
"t-translation",
];

View File

@ -0,0 +1,294 @@
import { Component, onWillRender, useEffect, useRef } from "@odoo/owl";
/**
* @typedef {"error" | "warning"} ConnectorAlert
* @typedef {`__connector__${number | "new"}`} ConnectorId
* @typedef {import("./gantt_renderer").Point} Point
*
* @typedef ConnectorProps
* @property {ConnectorId} id
* @property {ConnectorAlert | null} alert
* @property {boolean} highlighted
* @property {boolean} displayButtons
* @property {Point | () => Point | null} sourcePoint
* @property {Point | () => Point | null} targetPoint
*
* @typedef {Object} PathInfo
* @property {Point} sourceControlPoint
* @property {Point} targetControlPoint
* @property {Point} removeButtonPosition
*
* @typedef Point
* @property {number} [x]
* @property {number} [y]
*/
/**
* Gets the stroke's rgba css string corresponding to the provided parameters for both the stroke and its
* hovered state.
*
* @param {number} r [0, 255]
* @param {number} g [0, 255]
* @param {number} b [0, 255]
* @return {{ stroke: string, hoveredStroke: string }} the css colors.
*/
export function getStrokeAndHoveredStrokeColor(r, g, b) {
return {
color: `rgba(${r},${g},${b},0.5)`,
highlightedColor: `rgba(${r},${g},${b},1)`,
};
}
export const COLORS = {
default: getStrokeAndHoveredStrokeColor(143, 143, 143),
error: getStrokeAndHoveredStrokeColor(211, 65, 59),
warning: getStrokeAndHoveredStrokeColor(236, 151, 31),
outline: getStrokeAndHoveredStrokeColor(255, 255, 255),
};
/** @extends {Component<{ reactive: ConnectorProps }, any>} */
export class GanttConnector extends Component {
static props = {
reactive: {
type: Object,
shape: {
id: String,
alert: {
type: [{ value: "error" }, { value: "warning" }, { value: null }],
optional: true,
},
highlighted: { type: Boolean, optional: true },
displayButtons: { type: Boolean, optional: true },
sourcePoint: [
{ value: null },
Function,
{ type: Object, shape: { left: Number, top: Number } },
],
targetPoint: [
{ value: null },
Function,
{ type: Object, shape: { left: Number, top: Number } },
],
},
},
onLeftButtonClick: { type: Function, optional: true },
onRemoveButtonClick: { type: Function, optional: true },
onRightButtonClick: { type: Function, optional: true },
};
static defaultProps = {
highlighted: false,
displayButtons: false,
};
static template = "web_gantt.GanttConnector";
rootRef = useRef("root");
style = {
hoverEaseWidth: 10,
slackness: 0.9,
stroke: { width: 2 },
outlineStroke: { width: 1 },
};
get alert() {
return this.props.reactive.alert;
}
get displayButtons() {
return this.props.reactive.displayButtons;
}
get highlighted() {
return this.props.reactive.highlighted;
}
get id() {
return this.props.reactive.id;
}
get isNew() {
return this.id.endsWith("new");
}
get sourcePoint() {
return this.props.reactive.sourcePoint;
}
get targetPoint() {
return this.props.reactive.targetPoint;
}
setup() {
onWillRender(this.onWillRender);
useEffect(
(el, sourceLeft, sourceTop, targetLeft, targetTop) => {
if (!el) {
return;
}
const { sourceControlPoint, targetControlPoint, removeButtonPosition } =
this.getPathInfo(
{ left: sourceLeft, top: sourceTop },
{ left: targetLeft, top: targetTop },
this.style.slackness
);
const drawingCommands = [
`M`,
`${sourceLeft},${sourceTop}`,
`C`,
`${sourceControlPoint.left},${sourceControlPoint.top}`,
`${targetControlPoint.left},${targetControlPoint.top}`,
`${targetLeft},${targetTop}`,
].join(" ");
const paths = el.querySelectorAll(
".o_connector_stroke, .o_connector_stroke_hover_ease"
);
for (const path of paths) {
path.setAttribute("d", drawingCommands);
}
const svgButtons = el.querySelector(".o_connector_stroke_buttons");
if (svgButtons) {
svgButtons.setAttribute("x", removeButtonPosition.left - 24);
svgButtons.setAttribute("y", removeButtonPosition.top - 8);
}
},
() => this.getEffectDependencies()
);
}
/**
* Refreshes the connector properties from the props.
*
* @param {ConnectorProps} props
*/
computeStyle({ alert, highlighted }) {
const key = highlighted ? "highlightedColor" : "color";
const strokeType = alert || "default";
this.style = {
hoverEaseWidth: 10,
slackness: 0.9,
stroke: {
color: COLORS[strokeType][key],
width: 2,
},
outlineStroke: {
color: COLORS.outline[key],
width: 1,
},
};
}
getEffectDependencies() {
let sourcePoint = this.sourcePoint || { left: 0, top: 0 };
if (typeof sourcePoint === "function") {
sourcePoint = sourcePoint();
}
let targetPoint = this.targetPoint || { left: 0, top: 0 };
if (typeof targetPoint === "function") {
targetPoint = targetPoint();
}
const { x, y } = this.rootRef.el?.getBoundingClientRect() || { x: 0, y: 0 };
return [
this.rootRef.el,
sourcePoint.left - x,
sourcePoint.top - y,
targetPoint.left - x,
targetPoint.top - y,
this.displayButtons,
];
}
/**
* Returns the linear interpolation for a point to be found somewhere on the line startingPoint, endingPoint.
*
* @param {Point} startingPoint
* @param {Point} endingPoint
* @param {number} lambda
* @returns {Point}
*/
getLinearInterpolation(startingPoint, endingPoint, lambda = 0.5) {
return {
left: lambda * startingPoint.left + (1 - lambda) * endingPoint.left,
top: lambda * startingPoint.top + (1 - lambda) * endingPoint.top,
};
}
/**
* Returns the parameters of both the single Bezier curve as well as is decomposition into two beziers curves
* (which allows to get the middle position of the single Bezier curve) for the provided source, target and
* slackness (0 being a straight line).
*
* @param {Point} sourcePoint
* @param {Point} targetPoint
* @param {number} slackness [0, 1]
* @returns {PathInfo}
*/
getPathInfo(sourcePoint, targetPoint, slackness) {
// If the source is on the left of the target, we need to invert the control points.
const xDelta = targetPoint.left - sourcePoint.left;
const yDelta = targetPoint.top - sourcePoint.top;
const directionFactor = Math.sign(xDelta);
// What follows can be seen as magic numbers. And those are indeed such numbers as they have been determined
// by observing their shape while creating short and long connectors. These seems to allow keeping the same
// kind of shape amongst short and long connectors.
const xInc = 100 + (Math.abs(xDelta) * slackness) / 10;
const yInc =
Math.abs(yDelta) < 16 && directionFactor === -1 ? 15 - 0.001 * xDelta * slackness : 0;
const b = {
left: sourcePoint.left + xInc,
top: sourcePoint.top + yInc,
};
// Prevent having the air pin effect when in creation and having target on the left of the source
const c = {
left: targetPoint.left + (this.isNew && directionFactor === -1 ? xInc : -xInc),
top: targetPoint.top + yInc,
};
const e = this.getLinearInterpolation(sourcePoint, b);
const f = this.getLinearInterpolation(b, c);
const g = this.getLinearInterpolation(c, targetPoint);
const h = this.getLinearInterpolation(e, f);
const i = this.getLinearInterpolation(f, g);
const j = this.getLinearInterpolation(h, i);
return {
sourceControlPoint: b,
targetControlPoint: c,
removeButtonPosition: j,
};
}
//-------------------------------------------------------------------------
// Handlers
//-------------------------------------------------------------------------
onLeftButtonClick() {
if (this.props.onLeftButtonClick) {
this.props.onLeftButtonClick();
}
}
onRemoveButtonClick() {
if (this.props.onRemoveButtonClick) {
this.props.onRemoveButtonClick();
}
}
onRightButtonClick() {
if (this.props.onRightButtonClick) {
this.props.onRightButtonClick();
}
}
onWillRender() {
const key = this.highlighted ? "highlightedColor" : "color";
this.style.stroke.color = COLORS[this.alert || "default"][key];
this.style.outlineStroke.color = COLORS.outline[key];
}
}

View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="web_gantt.GanttConnector">
<t t-set="xmlAttributes" t-value="{ version: '1.1', xmlns: 'http://www.w3.org/2000/svg' }" />
<svg t-if="sourcePoint and targetPoint"
t-ref="root"
t-att-data-connector-id="id"
class="o_gantt_connector position-absolute start-0 top-0 w-100 h-100"
t-att-class="{ o_connector_highlighted: highlighted }"
pointer-events="none"
t-att="xmlAttributes"
>
<t t-set="strokeColor" t-value="style.stroke.color" />
<t t-set="outlineStrokeColor" t-value="style.outlineStroke.color" />
<defs>
<marker t-att-id="id"
markerHeight="6"
markerWidth="6"
markerUnits="strokeWidth"
orient="auto"
refX="9"
refY="6"
stroke-linejoin="round"
viewBox="0 0 12 12"
>
<t t-call="web_gantt.ConnectorStrokeHead">
<t t-set="color" t-value="strokeColor" />
<t t-set="width" t-value="style.stroke.width" />
</t>
</marker>
</defs>
<t t-call="web_gantt.ConnectorStroke">
<t t-set="class" t-value="'o_connector_stroke_hover_ease'" />
<t t-set="color" t-value="transparent" />
<t t-set="width" t-value="style.stroke.width + style.hoverEaseWidth + style.outlineStroke.width" />
</t>
<t t-if="style.outlineStroke.width gt 0">
<t t-call="web_gantt.ConnectorStroke">
<t t-set="class" t-value="'o_connector_stroke_outline'" />
<t t-set="color" t-value="outlineStrokeColor" />
<t t-set="width" t-value="style.stroke.width + style.outlineStroke.width" />
</t>
</t>
<t t-call="web_gantt.ConnectorStroke">
<t t-set="class" t-value="'o_connector_stroke'" />
<t t-set="color" t-value="strokeColor" />
<t t-set="markerEnd" t-value="id" />
<t t-set="width" t-value="style.stroke.width" />
</t>
<t t-if="displayButtons">
<svg class="o_connector_stroke_buttons"
width="48"
height="16"
pointer-events="all"
viewBox="0 0 1536 512"
t-att="xmlAttributes"
>
<rect fill="transparent" x="0" y="0" width="1536" height="512" />
<g class="o_connector_stroke_button o_connector_stroke_reschedule_button"
t-on-click.stop="onLeftButtonClick"
>
<rect fill="white" x="20" y="20" width="472" height="472" rx="236" ry="236" />
<g pointer-events="none">
<line x1="192" y1="256" x2="320" y2="128" stroke-width="56" />
<line x1="192" y1="256" x2="320" y2="384" stroke-width="56" />
</g>
</g>
<g class="o_connector_stroke_button o_connector_stroke_remove_button"
t-on-click.stop="onRemoveButtonClick"
>
<rect fill="white" x="532" y="20" width="472" height="472" rx="236" ry="236" />
<g transform="rotate(45,768,256)" pointer-events="none">
<rect x="740" y="100" fill="rgb(221, 60, 79)" width="56" height="312" />
<rect x="612" y="228" fill="rgb(221, 60, 79)" width="312" height="56" />
</g>
</g>
<g class="o_connector_stroke_button o_connector_stroke_reschedule_button"
t-on-click.stop="onRightButtonClick"
>
<rect fill="white" x="1044" y="20" width="472" height="472" rx="236" ry="236" />
<g pointer-events="none">
<line x1="1216" y1="128" x2="1344" y2="256" stroke-width="56" />
<line x1="1216" y1="384" x2="1344" y2="256" stroke-width="56" />
</g>
</g>
</svg>
</t>
</svg>
</t>
<t t-name="web_gantt.ConnectorStroke">
<path
fill="none"
t-att-stroke="color"
t-att-stroke-width="width"
t-att-class="class"
t-att-marker-end="markerEnd ? `url(#${markerEnd})` : false"
t-att-pointer-events="isNew ? 'none' : 'stroke'"
/>
</t>
<t t-name="web_gantt.ConnectorStrokeHead">
<path
d="M2,2 L10,6 L2,10 L6,6 L2,2"
class="o_connector_stroke_head"
t-att-fill="color"
t-att-stroke="color"
t-att="xmlAttributes"
/>
</t>
</templates>

View File

@ -0,0 +1,193 @@
import { _t } from "@web/core/l10n/translation";
import { Component, onWillUnmount, useEffect, useRef, useSubEnv } from "@odoo/owl";
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { FormViewDialog } from "@web/views/view_dialogs/form_view_dialog";
import { Layout } from "@web/search/layout";
import { standardViewProps } from "@web/views/standard_view_props";
import { useModelWithSampleData } from "@web/model/model";
import { usePager } from "@web/search/pager_hook";
import { useService } from "@web/core/utils/hooks";
import { SearchBar } from "@web/search/search_bar/search_bar";
import { useSearchBarToggler } from "@web/search/search_bar/search_bar_toggler";
import { CogMenu } from "@web/search/cog_menu/cog_menu";
import { CallbackRecorder, useSetupAction } from "@web/search/action_hook";
export class GanttController extends Component {
static components = {
CogMenu,
Layout,
SearchBar,
};
static props = {
...standardViewProps,
Model: Function,
Renderer: Function,
buttonTemplate: String,
modelParams: Object,
scrollPosition: { type: Object, optional: true },
};
static template = "web_gantt.GanttController";
setup() {
this.actionService = useService("action");
this.dialogService = useService("dialog");
this.orm = useService("orm");
useSubEnv({
getCurrentFocusDateCallBackRecorder: new CallbackRecorder(),
});
const rootRef = useRef("root");
this.model = useModelWithSampleData(this.props.Model, this.props.modelParams);
useSetupAction({
rootRef,
getLocalState: () => {
return { metaData: this.model.metaData, displayParams: this.model.displayParams };
},
});
onWillUnmount(() => this.closeDialog?.());
usePager(() => {
const { groupedBy, pagerLimit, pagerOffset } = this.model.metaData;
const { count } = this.model.data;
if (pagerLimit !== null && groupedBy.length) {
return {
offset: pagerOffset,
limit: pagerLimit,
total: count,
onUpdate: async ({ offset, limit }) => {
await this.model.updatePagerParams({ offset, limit });
},
};
}
});
useEffect(
(showNoContentHelp) => {
if (showNoContentHelp) {
const realRows = [
...rootRef.el.querySelectorAll(
".o_gantt_row_header:not(.o_sample_data_disabled)"
),
];
// interactive rows created in extensions (fromServer undefined)
const headerContainerWidth =
rootRef.el.querySelector(".o_gantt_header_groups").clientHeight +
rootRef.el.querySelector(".o_gantt_header_columns").clientHeight;
const offset = realRows.reduce(
(current, el) => current + el.clientHeight,
headerContainerWidth
);
const noContentHelperEl = rootRef.el.querySelector(".o_view_nocontent");
noContentHelperEl.style.top = `${offset}px`;
}
},
() => [this.showNoContentHelp]
);
this.searchBarToggler = useSearchBarToggler();
}
get className() {
if (this.env.isSmall) {
const classList = (this.props.className || "").split(" ");
classList.push("o_action_delegate_scroll");
return classList.join(" ");
}
return this.props.className;
}
get showNoContentHelp() {
return this.model.useSampleModel;
}
/**
* @param {Record<string, any>} [context]
*/
create(context) {
const { createAction } = this.model.metaData;
if (createAction) {
this.actionService.doAction(createAction, {
additionalContext: context,
onClose: () => {
this.model.fetchData();
},
});
} else {
this.openDialog({ context });
}
}
/**
* Opens dialog to add/edit/view a record
*
* @param {Record<string, any>} props FormViewDialog props
* @param {Record<string, any>} [options={}]
*/
openDialog(props, options = {}) {
const { canDelete, canEdit, resModel, formViewId: viewId } = this.model.metaData;
const title = props.title || (props.resId ? _t("Open") : _t("Create"));
let removeRecord;
if (canDelete && props.resId) {
removeRecord = () => {
return new Promise((resolve) => {
this.dialogService.add(ConfirmationDialog, {
body: _t("Are you sure to delete this record?"),
confirm: async () => {
await this.orm.unlink(resModel, [props.resId]);
resolve();
},
cancel: () => {},
});
});
};
}
this.closeDialog = this.dialogService.add(
FormViewDialog,
{
title,
resModel,
viewId,
resId: props.resId,
size: props.size,
mode: canEdit ? "edit" : "readonly",
context: props.context,
removeRecord,
},
{
...options,
onClose: () => {
this.closeDialog = null;
this.model.fetchData();
},
}
);
}
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
onAddClicked() {
const { scale } = this.model.metaData;
const focusDate = this.getCurrentFocusDate();
const start = focusDate.startOf(scale.unit);
const stop = focusDate.endOf(scale.unit).plus({ millisecond: 1 });
const context = this.model.getDialogContext({ start, stop, withDefault: true });
this.create(context);
}
getCurrentFocusDate() {
const { callbacks } = this.env.getCurrentFocusDateCallBackRecorder;
if (callbacks.length) {
return callbacks[0]();
}
return this.model.metaData.focusDate;
}
}

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="web_gantt.GanttController">
<div t-att-class="className" t-ref="root">
<Layout className="model.useSampleModel ? 'o_view_sample_data' : ''" display="props.display">
<t t-set-slot="control-panel-create-button">
<button t-if="model.metaData.canCreate" class="o_gantt_button_add btn btn-primary" data-hotkey="r" t-on-click="onAddClicked">
New
</button>
</t>
<t t-set-slot="control-panel-additional-actions">
<CogMenu/>
</t>
<t t-set-slot="layout-buttons">
<t t-call="{{ props.buttonTemplate }}"/>
</t>
<t t-set-slot="layout-actions">
<SearchBar t-if="searchBarToggler.state.showSearchBar"/>
</t>
<t t-set-slot="control-panel-navigation-additional">
<t t-component="searchBarToggler.component" t-props="searchBarToggler.props"/>
</t>
<t t-set-slot="default" t-slot-scope="layout">
<t t-if="showNoContentHelp">
<t t-if="props.info.noContentHelp" t-call="web.ActionHelper">
<t t-set="noContentHelp" t-value="props.info.noContentHelp"/>
</t>
<t t-else="" t-call="web.NoContentHelper"/>
</t>
<t t-component="props.Renderer"
class="model.useSampleModel ? 'o_sample_data_disabled' : ''"
model="model"
arch="props.arch"
create.bind="create"
openDialog.bind="openDialog"
scrollPosition="props.scrollPosition"
contentRef="layout.contentRef"
/>
</t>
</Layout>
</div>
</t>
<t t-name="web_gantt.GanttView.Buttons">
<div class="d-flex flex-wrap o_gantt_buttons_container">
</div>
</t>
</templates>

View File

@ -0,0 +1,745 @@
import { onWillUnmount, status, useComponent, useEffect, useEnv } from "@odoo/owl";
import { getEndOfLocalWeek, getStartOfLocalWeek } from "@web/core/l10n/dates";
import { makePopover, usePopover } from "@web/core/popover/popover_hook";
import { makeDraggableHook } from "@web/core/utils/draggable_hook_builder_owl";
import { useService } from "@web/core/utils/hooks";
import { clamp } from "@web/core/utils/numbers";
import { pick } from "@web/core/utils/objects";
import { GanttPopoverInDialog } from "./gantt_popover_in_dialog";
/** @typedef {luxon.DateTime} DateTime */
/**
* @param {number} target
* @param {number[]} values
* @returns {number}
*/
function closest(target, values) {
return values.reduce(
(prev, val) => (Math.abs(val - target) < Math.abs(prev - target) ? val : prev),
Infinity
);
}
/**
* Adds a time diff to a date keeping the same value even if the offset changed
* during the manipulation. This is typically needed with timezones using DayLight
* Saving offset changes.
*
* @example dateAddFixedOffset(luxon.DateTime.local(), { hour: 1 });
* @param {DateTime} date
* @param {Record<string, number>} plusParams
*/
export function dateAddFixedOffset(date, plusParams) {
const shouldApplyOffset = Object.keys(plusParams).some((key) =>
/^(hour|minute|second)s?$/i.test(key)
);
const result = date.plus(plusParams);
if (shouldApplyOffset) {
const initialOffset = date.offset;
const diff = initialOffset - result.offset;
if (diff) {
const adjusted = result.plus({ minute: diff });
return adjusted.offset === initialOffset ? result : adjusted;
}
}
return result;
}
export function diffColumn(col1, col2, unit) {
return col2.diff(col1, unit).values[`${unit}s`];
}
export function getRangeFromDate(rangeId, date) {
const startDate = localStartOf(date, rangeId);
const stopDate = startDate.plus({ [rangeId]: 1 }).minus({ day: 1 });
return { focusDate: date, startDate, stopDate, rangeId };
}
export function localStartOf(date, unit) {
return unit === "week" ? getStartOfLocalWeek(date) : date.startOf(unit);
}
export function localEndOf(date, unit) {
return unit === "week" ? getEndOfLocalWeek(date) : date.endOf(unit);
}
/**
* @param {number} cellPart
* @param {(0 | 1)[]} subSlotUnavailabilities
* @param {boolean} isToday
* @returns {string | null}
*/
export function getCellColor(cellPart, subSlotUnavailabilities, isToday) {
const sum = subSlotUnavailabilities.reduce((acc, d) => acc + d);
if (!sum) {
return null;
}
switch (cellPart) {
case sum: {
return `background-color:${getCellPartColor(sum, isToday)}`;
}
case 2: {
const [c0, c1] = subSlotUnavailabilities.map((d) => getCellPartColor(d, isToday));
return `background:linear-gradient(90deg,${c0}49%,${c1}50%)`;
}
case 4: {
const [c0, c1, c2, c3] = subSlotUnavailabilities.map((d) =>
getCellPartColor(d, isToday)
);
return `background:linear-gradient(90deg,${c0}24%,${c1}25%,${c1}49%,${c2}50%,${c2}74%,${c3}75%)`;
}
}
}
/**
* @param {0 | 1} availability
* @param {boolean} isToday
* @returns {string}
*/
export function getCellPartColor(availability, isToday) {
if (availability) {
return "var(--Gantt__DayOff-background-color)";
} else if (isToday) {
return "var(--Gantt__DayOffToday-background-color)";
} else {
return "var(--Gantt__Day-background-color)";
}
}
/**
* @param {number | [number, string]} value
* @returns {number}
*/
export function getColorIndex(value) {
if (typeof value === "number") {
return Math.round(value) % NB_GANTT_RECORD_COLORS;
} else if (Array.isArray(value)) {
return value[0] % NB_GANTT_RECORD_COLORS;
}
return 0;
}
/**
* Intervals are supposed to intersect (intersection duration >= 1 milliseconds)
*
* @param {[DateTime, DateTime]} interval
* @param {[DateTime, DateTime]} otherInterval
* @returns {[DateTime, DateTime]}
*/
export function getIntersection(interval, otherInterval) {
const [start, end] = interval;
const [otherStart, otherEnd] = otherInterval;
return [start >= otherStart ? start : otherStart, end <= otherEnd ? end : otherEnd];
}
/**
* Computes intersection of a closed interval with a union of closed intervals ordered and disjoint
* = a union of intersections
*
* @param {[DateTime, DateTime]} interval
* @param {[DateTime, DateTime]} intervals
* @returns {[DateTime, DateTime][]}
*/
export function getUnionOfIntersections(interval, intervals) {
const [start, end] = interval;
const intersecting = intervals.filter((otherInterval) => {
const [otheStart, otherEnd] = otherInterval;
return otherEnd > start && end > otheStart;
});
const len = intersecting.length;
if (len === 0) {
return [];
}
const union = [];
const first = getIntersection(interval, intersecting[0]);
union.push(first);
if (len >= 2) {
const last = getIntersection(interval, intersecting[len - 1]);
union.push(...intersecting.slice(1, len - 1), last);
}
return union;
}
/**
* @param {Object} params
* @param {Ref<HTMLElement>} params.ref
* @param {string} params.selector
* @param {string} params.related
* @param {string} params.className
*/
export function useMultiHover({ ref, selector, related, className }) {
/**
* @param {HTMLElement} el
*/
const findSiblings = (el) =>
ref.el.querySelectorAll(
related
.map((attr) => `[${attr}='${el.getAttribute(attr).replace(/'/g, "\\'")}']`)
.join("")
);
/**
* @param {PointerEvent} ev
*/
const onPointerEnter = (ev) => {
for (const sibling of findSiblings(ev.target)) {
sibling.classList.add(...classList);
classedEls.add(sibling);
}
};
/**
* @param {PointerEvent} ev
*/
const onPointerLeave = (ev) => {
for (const sibling of findSiblings(ev.target)) {
sibling.classList.remove(...classList);
classedEls.delete(sibling);
}
};
const classList = className.split(/\s+/g);
const classedEls = new Set();
useEffect(
(...targets) => {
if (targets.length) {
for (const target of targets) {
target.addEventListener("pointerenter", onPointerEnter);
target.addEventListener("pointerleave", onPointerLeave);
}
return () => {
for (const el of classedEls) {
el.classList.remove(...classList);
}
classedEls.clear();
for (const target of targets) {
target.removeEventListener("pointerenter", onPointerEnter);
target.removeEventListener("pointerleave", onPointerLeave);
}
};
}
},
() => [...ref.el.querySelectorAll(selector)]
);
}
const NB_GANTT_RECORD_COLORS = 12;
function getElementCenter(el) {
const { x, y, width, height } = el.getBoundingClientRect();
return {
x: x + width / 2,
y: y + height / 2,
};
}
// Resizable hook handles
const HANDLE_CLASS_START = "o_handle_start";
const HANDLE_CLASS_END = "o_handle_end";
const handles = {
start: document.createElement("div"),
end: document.createElement("div"),
};
// Draggable hooks
export const useGanttConnectorDraggable = makeDraggableHook({
name: "useGanttConnectorDraggable",
acceptedParams: {
parentWrapper: [String],
},
onComputeParams({ ctx, params }) {
ctx.parentWrapper = params.parentWrapper;
ctx.followCursor = false;
},
onDragStart: ({ ctx, addStyle }) => {
const { current } = ctx;
const parent = current.element.closest(ctx.parentWrapper);
if (!parent) {
return;
}
for (const otherParent of ctx.ref.el.querySelectorAll(ctx.parentWrapper)) {
if (otherParent !== parent) {
addStyle(otherParent, { pointerEvents: "auto" });
}
}
return { sourcePill: parent, ...current.connectorCenter };
},
onDrag: ({ ctx }) => {
ctx.current.connectorCenter = getElementCenter(ctx.current.element);
return pick(ctx.current, "connectorCenter");
},
onDragEnd: ({ ctx }) => pick(ctx.current, "element"),
onDrop: ({ ctx, target }) => {
const { current } = ctx;
const parent = current.element.closest(ctx.parentWrapper);
const targetParent = target.closest(ctx.parentWrapper);
if (!targetParent || targetParent === parent) {
return;
}
return { target: targetParent };
},
onWillStartDrag: ({ ctx }) => {
ctx.current.connectorCenter = getElementCenter(ctx.current.element);
},
});
function getCoordinate(style, name) {
return +style.getPropertyValue(name).slice(1);
}
function getColumnStart(style) {
return getCoordinate(style, "grid-column-start");
}
function getColumnEnd(style) {
return getCoordinate(style, "grid-column-end");
}
export const useGanttDraggable = makeDraggableHook({
name: "useGanttDraggable",
acceptedParams: {
cells: [String, Function],
cellDragClassName: [String, Function],
ghostClassName: [String, Function],
hoveredCell: [Object],
addStickyCoordinates: [Function],
},
onComputeParams({ ctx, params }) {
ctx.cellSelector = params.cells;
ctx.ghostClassName = params.ghostClassName;
ctx.cellDragClassName = params.cellDragClassName;
ctx.hoveredCell = params.hoveredCell;
ctx.addStickyCoordinates = params.addStickyCoordinates;
},
onDragStart({ ctx }) {
const { current, ghostClassName } = ctx;
current.element.before(current.placeHolder);
if (ghostClassName) {
current.placeHolder.classList.add(ghostClassName);
}
return { pill: current.element };
},
onDrag({ ctx, addStyle }) {
const { cellSelector, current, hoveredCell } = ctx;
let { el: cell, part } = hoveredCell;
const isDifferentCell = cell !== current.cell.el;
const isDifferentPart = part !== current.cell.part;
if (cell && !cell.matches(cellSelector)) {
cell = null; // Not a cell
}
current.cell.el = cell;
current.cell.part = part;
if (cell) {
// Recompute cell style if in a different cell
if (isDifferentCell) {
const style = getComputedStyle(cell);
current.cell.gridRow = style.getPropertyValue("grid-row");
current.cell.gridColumnStart = getColumnStart(style) + current.gridColumnOffset;
}
// Assign new grid coordinates if in different cell or different cell part
if (isDifferentCell || isDifferentPart) {
const { pillSpan } = current;
const { gridRow, gridColumnStart: start } = current.cell;
const gridColumnStart = clamp(start + part, 1, current.maxGridColumnStart);
const gridColumnEnd = gridColumnStart + pillSpan;
addStyle(current.cellGhost, {
gridRow,
gridColumn: `c${gridColumnStart} / c${gridColumnEnd}`,
});
const [gridRowStart, gridRowEnd] = /r(\d+) \/ r(\d+)/g.exec(gridRow).slice(1);
ctx.addStickyCoordinates(
[gridRowStart, gridRowEnd],
[gridColumnStart, gridColumnEnd]
);
current.cell.col = gridColumnStart;
}
} else {
current.cell.col = null;
}
// Attach or remove cell ghost
if (isDifferentCell) {
if (cell) {
cell.after(current.cellGhost);
} else {
current.cellGhost.remove();
}
}
return { pill: current.element };
},
onDragEnd({ ctx }) {
return { pill: ctx.current.element };
},
onDrop({ ctx }) {
const { cell, element, initialCol } = ctx.current;
if (cell.col !== null) {
return {
pill: element,
cell: cell.el,
diff: cell.col - initialCol,
};
}
},
onWillStartDrag({ ctx, addCleanup, addClass }) {
const { current } = ctx;
const { el: cell, part } = ctx.hoveredCell;
current.placeHolder = current.element.cloneNode(true);
current.cellGhost = document.createElement("div");
current.cellGhost.className = ctx.cellDragClassName;
current.cell = { el: null, index: null, part: 0 };
const gridStyle = getComputedStyle(cell.parentElement);
const pillStyle = getComputedStyle(current.element);
const cellStyle = getComputedStyle(cell);
const gridTemplateColumns = gridStyle.getPropertyValue("grid-template-columns");
const pGridColumnStart = getColumnStart(pillStyle);
const pGridColumnEnd = getColumnEnd(pillStyle);
const cGridColumnStart = getColumnStart(cellStyle) + part;
let highestGridCol;
for (const e of gridTemplateColumns.split(/\s+/).reverse()) {
const res = /\[c(\d+)\]/g.exec(e);
if (res) {
highestGridCol = +res[1];
break;
}
}
const pillSpan = pGridColumnEnd - pGridColumnStart;
current.initialCol = pGridColumnStart;
current.maxGridColumnStart = highestGridCol - pillSpan;
current.gridColumnOffset = pGridColumnStart - cGridColumnStart;
current.pillSpan = pillSpan;
addClass(ctx.ref.el, "pe-auto");
addCleanup(() => {
current.placeHolder.remove();
current.cellGhost.remove();
});
},
});
export const useGanttUndraggable = makeDraggableHook({
name: "useGanttUndraggable",
onDragStart({ ctx }) {
return { pill: ctx.current.element };
},
onDragEnd({ ctx }) {
return { pill: ctx.current.element };
},
onWillStartDrag({ ctx, addCleanup, addClass, addStyle, getRect }) {
const { x, y, width, height } = getRect(ctx.current.element);
ctx.current.container = document.createElement("div");
addClass(ctx.ref.el, "pe-auto");
addStyle(ctx.current.container, {
position: "fixed",
left: `${x}px`,
top: `${y}px`,
width: `${width}px`,
height: `${height}px`,
});
ctx.current.element.after(ctx.current.container);
addCleanup(() => ctx.current.container.remove());
},
});
export const useGanttResizable = makeDraggableHook({
name: "useGanttResizable",
requiredParams: ["handles"],
acceptedParams: {
innerPills: [String, Function],
handles: [String, Function],
hoveredCell: [Object],
rtl: [Boolean, Function],
cells: [String, Function],
precision: [Number, Function],
showHandles: [Function],
},
onComputeParams({ ctx, params, addCleanup, addEffectCleanup, getRect }) {
const onElementPointerEnter = (ev) => {
if (ctx.dragging || ctx.willDrag) {
return;
}
const pill = ev.target;
const innerPill = pill.querySelector(params.innerPills);
const pillRect = getRect(innerPill);
for (const el of Object.values(handles)) {
el.style.height = `${pillRect.height}px`;
}
const showHandles = params.showHandles ? params.showHandles(pill) : {};
if ("start" in showHandles && !showHandles.start) {
handles.start.remove();
} else {
innerPill.appendChild(handles.start);
}
if ("end" in showHandles && !showHandles.end) {
handles.end.remove();
} else {
innerPill.appendChild(handles.end);
}
};
const onElementPointerLeave = () => {
const remove = () => Object.values(handles).forEach((h) => h.remove());
if (ctx.dragging || ctx.current.element) {
addCleanup(remove);
} else {
remove();
}
};
ctx.cellSelector = params.cells;
ctx.hoveredCell = params.hoveredCell;
ctx.precision = params.precision;
ctx.rtl = params.rtl;
for (const el of ctx.ref.el.querySelectorAll(params.elements)) {
el.addEventListener("pointerenter", onElementPointerEnter);
el.addEventListener("pointerleave", onElementPointerLeave);
addEffectCleanup(() => {
el.removeEventListener("pointerenter", onElementPointerEnter);
el.removeEventListener("pointerleave", onElementPointerLeave);
});
}
handles.start.className = `${params.handles} ${HANDLE_CLASS_START}`;
handles.start.style.cursor = `${params.rtl ? "e" : "w"}-resize`;
handles.end.className = `${params.handles} ${HANDLE_CLASS_END}`;
handles.end.style.cursor = `${params.rtl ? "w" : "e"}-resize`;
// Override "full" and "element" selectors: we want the draggable feature
// to apply to the handles
ctx.pillSelector = ctx.elementSelector;
ctx.fullSelector = ctx.elementSelector = `.${params.handles}`;
// Force the handles to stay in place
ctx.followCursor = false;
},
onDragStart({ ctx, addStyle }) {
addStyle(ctx.current.pill, { zIndex: 15 });
return { pill: ctx.current.pill };
},
onDrag({ ctx, addStyle, getRect }) {
const { cellSelector, current, hoveredCell, pointer, precision, rtl, ref } = ctx;
let { el: cell, part } = hoveredCell;
const point = [pointer.x, current.initialPosition.y];
if (!cell) {
let rect;
cell = document.elementsFromPoint(...point).find((el) => el.matches(cellSelector));
if (!cell) {
const cells = Array.from(ref.el.querySelectorAll(".o_gantt_cells .o_gantt_cell"));
if (pointer.x < current.initialPosition.x) {
cell = rtl ? cells.at(-1) : cells[0];
} else {
cell = rtl ? cells[0] : cells.at(-1);
}
rect = getRect(cell);
point[0] = rtl ? rect.right - 1 : rect.left + 1;
} else {
rect = getRect(cell);
}
const x = Math.floor(rect.x);
const width = Math.floor(rect.width);
part = Math.floor((point[0] - x) / (width / precision));
}
const cellStyle = getComputedStyle(cell);
const cGridColStart = getColumnStart(cellStyle);
const { x, width } = getRect(cell);
const coef = ((rtl ? -1 : 1) * width) / precision;
const startBorder = (rtl ? x + width : x) + part * coef;
const endBorder = startBorder + coef;
const theClosest = closest(point[0], [startBorder, endBorder]);
let diff =
cGridColStart +
part +
(theClosest === startBorder ? 0 : 1) -
(current.isStart ? current.firstCol : current.lastCol);
if (diff === current.lastDiff) {
return;
}
if (current.isStart) {
diff = Math.min(diff, current.initialDiff - 1);
addStyle(current.pill, { "grid-column-start": `c${current.firstCol + diff}` });
} else {
diff = Math.max(diff, 1 - current.initialDiff);
addStyle(current.pill, { "grid-column-end": `c${current.lastCol + diff}` });
}
current.lastDiff = diff;
const isLeftHandle = rtl ? !current.isStart : current.isStart;
const grabbedHandle = isLeftHandle ? "left" : "right";
diff = current.isStart ? -diff : diff;
return { pill: current.pill, grabbedHandle, diff };
},
onDragEnd({ ctx }) {
const { current, pillSelector } = ctx;
const pill = current.element.closest(pillSelector);
return { pill };
},
onDrop({ ctx }) {
const { current } = ctx;
if (!current.lastDiff) {
return;
}
const direction = current.isStart ? "start" : "end";
return { pill: current.pill, diff: current.lastDiff, direction };
},
onWillStartDrag({ ctx, addClass }) {
const { current, pillSelector } = ctx;
const pill = ctx.current.element.closest(pillSelector);
current.pill = pill;
const pillStyle = getComputedStyle(pill);
current.firstCol = getColumnStart(pillStyle);
current.lastCol = getColumnEnd(pillStyle);
current.initialDiff = current.lastCol - current.firstCol;
ctx.cursor = getComputedStyle(current.element).cursor;
current.isStart = current.element.classList.contains(HANDLE_CLASS_START);
addClass(ctx.ref.el, "pe-auto");
},
});
function getCellsOnRow(refEl, rowId) {
return refEl.querySelectorAll(
`.o_gantt_cell:not(.o_gantt_group)[data-row-id='${CSS.escape(rowId)}']`
);
}
function getMinMax(a, b) {
return a <= b ? [a, b] : [b, a];
}
export const useGanttSelectable = makeDraggableHook({
name: "useGanttSelectable",
acceptedParams: {
hoveredCell: [Object],
rtl: [Boolean, Function],
},
onComputeParams({ ctx, params }) {
ctx.followCursor = false;
ctx.hoveredCell = params.hoveredCell;
ctx.rtl = params.rtl;
},
onDrag({ ctx, addClass, getRect, removeClass }) {
const { current, hoveredCell, pointer, ref, rtl } = ctx;
let { el: cell } = hoveredCell;
if (!cell) {
const point = [pointer.x, current.initialPosition.y];
cell = document.elementsFromPoint(...point).find((el) => el.matches(".o_gantt_cell"));
if (!cell) {
const cells = Array.from(ref.el.querySelectorAll(".o_gantt_cells .o_gantt_cell"));
if (pointer.x < current.initialPosition.x) {
cell = rtl ? cells.at(-1) : cells[0];
} else {
cell = rtl ? cells[0] : cells.at(-1);
}
}
}
const col = +cell.dataset.col;
const lastSelectedCol = current.lastSelectedCol;
current.lastSelectedCol = col;
if (lastSelectedCol === col) {
return;
}
const [startCol, stopCol] = getMinMax(current.initialCol, col);
for (const cell of getCellsOnRow(ref.el, current.rowId)) {
const cellCol = +cell.dataset.col;
if (cellCol < startCol || cellCol > stopCol) {
removeClass(cell, "o_drag_hover");
} else {
addClass(cell, "o_drag_hover");
}
}
},
onDrop({ ctx }) {
const { current } = ctx;
const { rowId, initialCol, lastSelectedCol } = current;
const [startCol, stopCol] = getMinMax(initialCol, lastSelectedCol);
return { rowId, startCol, stopCol };
},
onWillStartDrag({ ctx, addClass }) {
const { current, hoveredCell, ref } = ctx;
const { el: cell } = hoveredCell;
current.rowId = cell.dataset.rowId;
current.initialCol = +cell.dataset.col;
addClass(ref.el, "pe-auto");
addClass(cell, "pe-auto");
},
});
/**
* Same as usePopover, but replaces the popover by a dialog when display size is small.
*
* @param {typeof import("@odoo/owl").Component} component
* @param {import("@web/core/popover/popover_service").PopoverServiceAddOptions} [options]
* @returns {import("@web/core/popover/popover_hook").PopoverHookReturnType}
*/
export function useGanttResponsivePopover(dialogTitle, component, options = {}) {
const dialogService = useService("dialog");
const env = useEnv();
const owner = useComponent();
const popover = usePopover(component, options);
const onClose = () => {
if (status(owner) !== "destroyed") {
options.onClose?.();
}
};
const dialogAddFn = (_, comp, props, options) => dialogService.add(comp, props, options);
const popoverInDialog = makePopover(dialogAddFn, GanttPopoverInDialog, { onClose });
const ganttReponsivePopover = {
open: (target, props) => {
if (env.isSmall) {
popoverInDialog.open(target, {
component: component,
componentProps: props,
dialogTitle,
});
} else {
popover.open(target, props);
}
},
close: () => {
popover.close();
popoverInDialog.close();
},
get isOpen() {
return popover.isOpen || popoverInDialog.isOpen;
},
};
onWillUnmount(ganttReponsivePopover.close);
return ganttReponsivePopover;
}

View File

@ -0,0 +1,35 @@
import { registry } from "@web/core/registry";
function _mockGetGanttData(_, { model, kwargs }) {
const lazy = !kwargs.limit && !kwargs.offset && kwargs.groupby.length === 1;
const { groups, length } = this.mockWebReadGroup(model, {
...kwargs,
lazy,
fields: ["__record_ids:array_agg(id)"],
});
const recordIds = [];
for (const group of groups) {
recordIds.push(...(group.__record_ids || []));
}
const { records } = this.mockWebSearchReadUnity(model, [], {
domain: [["id", "in", recordIds]],
context: kwargs.context,
specification: kwargs.read_specification,
});
const unavailabilities = {};
for (const fieldName of kwargs.unavailability_fields || []) {
unavailabilities[fieldName] = {};
}
const progress_bars = {};
for (const fieldName of kwargs.progress_bar_fields || []) {
progress_bars[fieldName] = {};
}
return { groups, length, records, unavailabilities, progress_bars };
}
registry.category("mock_server").add("get_gantt_data", _mockGetGanttData);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,59 @@
import { Component, useRef } from "@odoo/owl";
import { ViewButton } from "@web/views/view_button/view_button";
import { useViewButtons } from "@web/views/view_button/view_button_hook";
import { useViewCompiler } from "@web/views/view_compiler";
import { GanttCompiler } from "./gantt_compiler";
export class GanttPopover extends Component {
static template = "web_gantt.GanttPopover";
static components = { ViewButton };
static props = [
"title",
"displayGenericButtons",
"bodyTemplate?",
"footerTemplate?",
"resModel",
"resId",
"context",
"close",
"reload",
"buttons",
];
setup() {
this.rootRef = useRef("root");
this.templates = { body: "web_gantt.GanttPopover.default" };
const toCompile = {};
const { bodyTemplate, footerTemplate } = this.props;
if (bodyTemplate) {
toCompile.body = bodyTemplate;
if (footerTemplate) {
toCompile.footer = footerTemplate;
}
}
Object.assign(
this.templates,
useViewCompiler(GanttCompiler, toCompile, { recordExpr: "__record__" })
);
useViewButtons(this.rootRef, {
reload: async () => {
await this.props.reload();
this.props.close();
},
});
}
get renderingContext() {
return Object.assign({}, this.props.context, {
__comp__: this,
__record__: { resModel: this.props.resModel, resId: this.props.resId },
});
}
async onClick(button) {
await button.onClick();
this.props.close();
}
}

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="web_gantt.GanttPopover">
<div t-ref="root" style="max-width: 320px;"> <!-- the chosen value is rather arbitrary: the idea was to have enough space (but not too much) for some particular known cases-->
<div class="popover-header d-flex justify-content-between py-2 pe-2">
<h4 class="p-0 pt-1">
<t t-esc="props.title" />
</h4>
<span class="ms-4 mt-1 me-2 cursor-pointer" t-on-click.stop="props.close">
<i class="fa fa-close" />
</span>
</div>
<div class="popover-body">
<t t-call="{{ templates.body }}" t-call-context="renderingContext" />
</div>
<div t-if="(props.displayGenericButtons and props.buttons) or templates.footer" class="popover-footer border-top p-3 d-flex flex-wrap gap-1">
<t t-if="props.displayGenericButtons and props.buttons">
<t t-foreach="props.buttons" t-as="button" t-key="button_index">
<button t-att-class="button.class" t-on-click="() => this.onClick(button)" t-esc="button.text" />
</t>
</t>
<t t-if="templates.footer">
<t t-call="{{ templates.footer }}" t-call-context="renderingContext" />
</t>
</div>
</div>
</t>
<t t-name="web_gantt.GanttPopover.default">
<ul class="p-0 mb-0 list-unstyled">
<li class="pe-2">
<strong>Name</strong>: <span t-esc="name" />
</li>
<li class="pe-2">
<strong>Start</strong>: <span t-esc="start" />
</li>
<li class="pe-2">
<strong>Stop</strong>: <span t-esc="stop" />
</li>
</ul>
</t>
</templates>

View File

@ -0,0 +1,11 @@
import { Component } from "@odoo/owl";
import { Dialog } from "@web/core/dialog/dialog";
export class GanttPopoverInDialog extends Component {
static components = { Dialog };
static props = ["close", "component", "componentProps", "dialogTitle"];
static template = "web_gantt.GanttPopoverInDialog";
get componentProps() {
return { ...this.props.componentProps, close: this.props.close };
}
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="web_gantt.GanttPopoverInDialog">
<Dialog title="props.dialogTitle" footer="false">
<t t-component="props.component" t-props="componentProps"/>
</Dialog>
</t>
</templates>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,255 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="web_gantt.GanttRenderer">
<GanttRendererControls t-props="controlsProps" />
<div t-ref="grid"
class="o_gantt_renderer o_renderer bg-view"
t-att-class="{ o_has_row_headers: hasRowHeaders, 'pe-auto': isDragging }"
t-att-style="getGridStyle()"
t-on-pointermove="throttledComputeHoverParams"
t-on-pointerleave="onPointerLeave"
>
<t t-call="{{ constructor.headerTemplate }}"/>
<t t-if="hasRowHeaders">
<div class="o_gantt_row_headers o_gantt_row_sidebar o_gantt_grid_rows border-end position-sticky start-0 bg-view">
<t t-foreach="rowsToRender" t-as="row" t-key="row.id">
<t t-call="{{ constructor.rowHeaderTemplate }}" />
</t>
</div>
</t>
<div t-ref="cellContainer" class="o_gantt_cells o_gantt_grid_rows o_gantt_grid_columns position-relative">
<t t-foreach="rowsToRender" t-as="row" t-key="row.id">
<t t-call="{{ constructor.rowContentTemplate }}" />
</t>
<t t-foreach="pillsToRender" t-as="pill" t-key="pill.id">
<t t-set="row" t-value="getRowFromPill(pill)"/>
<t t-if="row.isGroup">
<t t-call="{{ constructor.groupPillTemplate }}" />
</t>
<t t-else="" >
<t t-call="{{ constructor.pillTemplate }}" />
</t>
</t>
<t t-if="shouldRenderConnectors()">
<t t-foreach="connectorsToRender" t-as="connector" t-key="connector.id">
<GanttConnector
reactive="connector"
onRemoveButtonClick="() => this.onRemoveButtonClick(connector.id)"
onLeftButtonClick="() => this.onRescheduleButtonClick('backward', connector.id)"
onRightButtonClick="() => this.onRescheduleButtonClick('forward', connector.id)"
/>
</t>
</t>
<GanttResizeBadge reactive="resizeBadgeReactive" />
</div>
<t t-if="totalRow">
<t t-call="{{ constructor.totalRowTemplate }}"/>
</t>
</div>
</t>
<t t-name="web_gantt.GanttRenderer.Header">
<t t-if="hasRowHeaders">
<b
class="o_gantt_title d-flex align-items-center justify-content-center bg-100 position-sticky start-0 p-2 border-end"
t-esc="model.metaData.string"
/>
</t>
<div class="o_gantt_header_groups o_gantt_grid_columns bg-100 position-sticky">
<t t-foreach="columnsGroups" t-as="column" t-key="column.id">
<div class="o_gantt_header_title position-sticky d-flex align-items-center ps-2 overflow-hidden bg-100"
t-att-style="getGridPosition({column: column.grid.column})"
>
<t t-esc="model.metaData.scale.groupHeaderFormatter(column.start, env)"/>
</div>
</t>
</div>
<div class="o_gantt_header_columns o_gantt_grid_columns bg-view position-sticky">
<t t-foreach="columns" t-as="column" t-key="column.id">
<div class="o_gantt_header_cell d-flex align-items-center justify-content-center py-2"
t-att-class="{ o_gantt_today: column.isToday, 'bg-100': !column.isToday }"
t-att-style="getGridPosition({column: column.grid.column})"
>
<t t-if="'colHeaderFormatter' in model.metaData.scale">
<t t-esc="model.metaData.scale.colHeaderFormatter(column.start, env)"/>
</t>
<small t-else="" class="text-center">
<t t-if="is24HourFormat">
<b t-esc="column.start.toFormat('H')"/>
</t>
<t t-else="">
<b t-esc="column.start.toFormat('h')"/>
<div class="d-xl-inline-block" t-esc="column.start.toFormat('a').toLowerCase()"/>
</t>
</small>
</div>
</t>
</div>
</t>
<t t-name="web_gantt.GanttRenderer.ConnectorCreator">
<div
class="o_connector_creator_wrapper position-absolute w-100"
t-attf-class="o_connector_creator_wrapper_{{ alignment.vertical }} {{ '_color' in pill ? 'o_gantt_color_' + pill._color : '' }}"
>
<div
class="o_connector_creator position-absolute"
t-attf-class="o_connector_creator_{{ alignment.vertical }} o_connector_creator_{{ alignment.horizontal }}"
>
<div class="o_connector_creator_bullet position-absolute rounded-circle" />
</div>
</div>
</t>
<t t-name="web_gantt.GanttRenderer.RowHeader">
<div
class="o_gantt_row_header o_gantt_row_sidebar position-sticky start-0 bg-view align-items-center"
t-att-class="{
o_sample_data_disabled: isDisabled(row),
o_gantt_row_sidebar_empty: !row.name,
o_gantt_group: row.isGroup,
o_gantt_hoverable: isHoverable(row),
o_mobile_progress_bar: row.progressBar and isTouchDevice,
o_group_open: !model.isClosed(row.id),
}"
t-att-style="getGridPosition({ row: row.grid.row })"
t-att-data-row-id="row.id"
t-on-click.synthetic="() => row.isGroup ? this.model.toggleRow(row.id) : this.focusFirstPill(row.id)"
>
<div
class="o_gantt_row_title d-flex align-items-center h-100 w-100 pe-1"
t-att-class="{ 'fw-bold': row.isGroup }"
t-att-style="getRowTitleStyle(row)"
t-att-title="row.name or ''"
>
<i t-if="row.isGroup" t-attf-class="o_group_caret fa fa-fw me-1 fa-caret-{{ model.isClosed(row.id) ? 'right' : 'down' }}" />
<span t-if="row.thumbnailUrl and row.resId"
class="o_gantt_row_thumbnail_wrapper"
t-att-class="{ 'me-1' : row.isGroup }"
>
<img t-att-src="row.thumbnailUrl" class="o_gantt_row_thumbnail o_avatar rounded"/>
</span>
<span class="text-truncate w-0 flex-grow-1">
<t t-esc="row.name" />
</span>
</div>
<t t-if="row.progressBar">
<GanttRowProgressBar t-props="getProgressBarProps(row)" />
</t>
</div>
</t>
<t t-name="web_gantt.GanttRenderer.RowContent">
<!-- Cells -->
<t t-foreach="columns" t-as="column" t-key="column.id">
<t t-set="col" t-value="column.grid.column[0]" />
<div
class="o_gantt_cell"
t-att-class="ganttCellAttClass(row, column)"
t-attf-style="{{ getGridPosition({ column: column.grid.column, row: row.grid.row }) }};{{ row.cellColors[column.id] || '' }}"
t-att-data-row-id="row.id"
t-att-data-col="col"
t-on-click.synthetic="(ev) => row.isGroup ? this.model.toggleRow(row.id) : this.onCellClicked(row.id, col)"
/>
</t>
</t>
<t t-name="web_gantt.GanttRenderer.Pill">
<t t-set="renderConnectors" t-value="shouldRenderRecordConnectors(pill.record)" />
<div class="o_gantt_pill_wrapper"
t-att-class="{
o_sample_data_disabled: isDisabled(row),
o_draggable: !pill.disableDrag,
o_undraggable: pill.disableDrag,
o_resizable: !pill.disableStartResize or !pill.disableStopResize,
'position-relative': renderConnectors
}"
t-att-style="getGridPosition(pill.grid)"
t-att-data-pill-id="pill.id"
>
<t t-if="renderConnectors" t-call="{{ constructor.connectorCreatorTemplate }}">
<t t-set="alignment" t-value="getConnectorCreatorAlignment('top')" />
</t>
<div class="o_gantt_pill position-relative h-100 d-flex align-items-center"
t-att-class="pill.className"
t-on-click.synthetic="(ev) => this.onPillClicked(ev, pill)"
>
<span
t-if="pill._progress"
class="position-absolute h-100 o_gantt_progress"
t-attf-style="width:{{ pill._progress }}%;"
/>
<span class="o_gantt_pill_title text-truncate mx-1" t-esc="pill.displayName" />
<div t-if="pill.disableDrag" class="o_gantt_lock fa fa-lock ms-auto me-2" />
</div>
<t t-if="renderConnectors" t-call="{{ constructor.connectorCreatorTemplate }}">
<t t-set="alignment" t-value="getConnectorCreatorAlignment('bottom')" />
</t>
</div>
</t>
<t t-name="web_gantt.GanttRenderer.GroupPill">
<div
class="o_gantt_pill_wrapper o_gantt_group_pill align-items-center"
t-att-class="{
o_sample_data_disabled: isDisabled(row),
o_group_open: !model.isClosed(row.id),
}"
t-att-style="getGridPosition(pill.grid)"
>
<div class="o_gantt_pill o_gantt_consolidated_pill position-relative overflow-visible"
t-att-class="pill.className"
t-att-title="pill.displayName"
>
<span
t-if="pill._progress"
class="position-absolute h-100 o_gantt_progress"
t-attf-style="width:{{ pill._progress }}%;"
/>
</div>
<span class="o_gantt_pill_title bg-view text-truncate px-1 z-1" t-esc="pill.displayName" />
</div>
</t>
<t t-name="web_gantt.GanttRenderer.TotalRow">
<t t-if="hasRowHeaders">
<div class="o_gantt_row_total o_gantt_row_sidebar border-end position-sticky start-0 bg-view d-flex justify-content-end">
<h4
class="o_gantt_row_title d-flex align-items-center pe-3 my-0 fw-bold"
t-att-class="{ o_sample_data_disabled: isDisabled() }"
t-att-title="totalRow.name"
t-esc="totalRow.name"
/>
</div>
</t>
<div class="o_gantt_row_total o_gantt_grid_columns bg-view">
<t t-foreach="columns" t-as="column" t-key="column.id">
<div
class="o_gantt_cell"
t-att-class="{ o_gantt_today: column.isToday, o_sample_data_disabled: isDisabled() }"
t-att-style="getGridPosition({ column: column.grid.column, row: [1, 2] })"
/>
</t>
<t t-foreach="totalRow.pills" t-as="pill" t-key="pill.id">
<div
class="o_gantt_pill_wrapper position-relative p-0 h-100 d-flex align-items-end"
t-att-class="{ o_sample_data_disabled: isDisabled() }"
t-att-style="getGridPosition({ column: pill.grid.column, row: [1, 2] })"
>
<div class="o_gantt_pill o_gantt_consolidated_pill w-100 d-flex align-items-end justify-content-center"
t-att-title="pill.displayName"
t-attf-style="height: {{ totalRow.factor * pill.aggregateValue }}%;"
>
<span
class="o_gantt_consolidated_pill_title bg-view text-truncate px-1 mb-1"
t-att-class="{ 'o_gantt_consolidated_pill_small': this.isPillSmall(pill) }"
t-esc="pill.displayName"
/>
</div>
</div>
</t>
</div>
</t>
</templates>

View File

@ -0,0 +1,219 @@
import { Component, useState } from "@odoo/owl";
import { useDateTimePicker } from "@web/core/datetime/datetime_hook";
import { Dropdown } from "@web/core/dropdown/dropdown";
import { useDropdownState } from "@web/core/dropdown/dropdown_hooks";
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { formatDate } from "@web/core/l10n/dates";
import { _t } from "@web/core/l10n/translation";
import { pick } from "@web/core/utils/objects";
import { debounce } from "@web/core/utils/timing";
import {
diffColumn,
getRangeFromDate,
localStartOf,
useGanttResponsivePopover,
} from "./gantt_helpers";
const { DateTime } = luxon;
const KEYS = ["startDate", "stopDate", "rangeId", "focusDate"];
export class GanttRendererControls extends Component {
static template = "web_gantt.GanttRendererControls";
static components = {
Dropdown,
DropdownItem,
};
static props = ["model", "displayExpandCollapseButtons", "focusToday", "getCurrentFocusDate"];
static toolbarContentTemplate = "web_gantt.GanttRendererControls.ToolbarContent";
static rangeMenuTemplate = "web_gantt.GanttRendererControls.RangeMenu";
setup() {
this.model = this.props.model;
this.updateMetaData = debounce(() => this.model.fetchData(this.makeParams()), 500);
const { metaData } = this.model;
this.state = useState({
scaleIndex: this.getScaleIndex(metaData.scale.id),
...pick(metaData, ...KEYS),
});
this.pickerValues = useState({
startDate: metaData.startDate,
stopDate: metaData.stopDate,
});
this.scalesRange = { min: 0, max: Object.keys(metaData.scales).length - 1 };
const getPickerProps = (key) => ({ type: "date", value: this.pickerValues[key] });
this.startPicker = useDateTimePicker({
target: "start-picker",
onApply: (date) => {
this.pickerValues.startDate = date;
if (this.pickerValues.stopDate < date) {
this.pickerValues.stopDate = date;
} else if (date.plus({ year: 10, day: -1 }) < this.pickerValues.stopDate) {
this.pickerValues.stopDate = date.plus({ year: 10, day: -1 });
}
},
get pickerProps() {
return getPickerProps("startDate");
},
createPopover: (...args) => useGanttResponsivePopover(_t("Gantt start date"), ...args),
ensureVisibility: () => false,
});
this.stopPicker = useDateTimePicker({
target: "stop-picker",
onApply: (date) => {
this.pickerValues.stopDate = date;
if (date < this.pickerValues.startDate) {
this.pickerValues.startDate = date;
} else if (this.pickerValues.startDate.plus({ year: 10, day: -1 }) < date) {
this.pickerValues.startDate = date.minus({ year: 10, day: -1 });
}
},
get pickerProps() {
return getPickerProps("stopDate");
},
createPopover: (...args) => useGanttResponsivePopover(_t("Gantt stop date"), ...args),
ensureVisibility: () => false,
});
this.dropdownState = useDropdownState();
}
get dateDescription() {
const { focusDate, rangeId } = this.state;
switch (rangeId) {
case "quarter":
return focusDate.toFormat(`Qq yyyy`);
case "day":
return formatDate(focusDate);
default:
return this.model.metaData.scales[rangeId].groupHeaderFormatter(
focusDate,
this.env
);
}
}
getFormattedDate(date) {
return formatDate(date);
}
getScaleIdFromIndex(index) {
const keys = Object.keys(this.model.metaData.scales);
return keys[keys.length - 1 - index];
}
getScaleIndex(scaleId) {
const keys = Object.keys(this.model.metaData.scales);
return keys.length - 1 - keys.findIndex((id) => id === scaleId);
}
getScaleIndexFromRangeId(rangeId) {
const { ranges } = this.model.metaData;
const scaleId = ranges[rangeId].scaleId;
return this.getScaleIndex(scaleId);
}
/**
* @param {1|-1} inc
*/
incrementScale(inc) {
if (
inc === 1
? this.state.scaleIndex < this.scalesRange.max
: this.scalesRange.min < this.state.scaleIndex
) {
this.state.scaleIndex += inc;
this.updateMetaData();
}
}
isSelected(rangeId) {
if (rangeId === "custom") {
return (
this.state.rangeId === rangeId ||
!localStartOf(this.state.focusDate, this.state.rangeId).equals(
localStartOf(DateTime.now(), this.state.rangeId)
)
);
}
return (
this.state.rangeId === rangeId &&
localStartOf(this.state.focusDate, rangeId).equals(
localStartOf(DateTime.now(), rangeId)
)
);
}
makeParams() {
return {
currentFocusDate: this.props.getCurrentFocusDate(),
scaleId: this.getScaleIdFromIndex(this.state.scaleIndex),
...pick(this.state, ...KEYS),
};
}
onApply() {
this.state.startDate = this.pickerValues.startDate;
this.state.stopDate = this.pickerValues.stopDate;
this.state.rangeId = "custom";
this.updateMetaData();
this.dropdownState.close();
}
onTodayClicked() {
const success = this.props.focusToday();
if (success) {
return;
}
this.state.focusDate = DateTime.local().startOf("day");
if (this.state.rangeId === "custom") {
const diff = diffColumn(this.state.startDate, this.state.stopDate, "day");
const n = Math.floor(diff / 2);
const m = diff - n;
this.state.startDate = this.state.focusDate.minus({ day: n });
this.state.stopDate = this.state.focusDate.plus({ day: m - 1 });
} else {
this.state.startDate = this.state.focusDate.startOf(this.state.rangeId);
this.state.stopDate = this.state.focusDate.endOf(this.state.rangeId).startOf("day");
}
this.updatePickerValues();
this.updateMetaData();
}
selectRange(direction) {
const sign = direction === "next" ? 1 : -1;
const { focusDate, rangeId, startDate, stopDate } = this.state;
if (rangeId === "custom") {
const diff = diffColumn(startDate, stopDate, "day") + 1;
this.state.focusDate = focusDate.plus({ day: sign * diff });
this.state.startDate = startDate.plus({ day: sign * diff });
this.state.stopDate = stopDate.plus({ day: sign * diff });
} else {
Object.assign(
this.state,
getRangeFromDate(rangeId, focusDate.plus({ [rangeId]: sign }))
);
}
this.updatePickerValues();
this.updateMetaData();
}
selectRangeId(rangeId) {
Object.assign(this.state, getRangeFromDate(rangeId, DateTime.now().startOf("day")));
this.state.scaleIndex = this.getScaleIndexFromRangeId(rangeId);
this.updatePickerValues();
this.updateMetaData();
}
selectScale(index) {
this.state.scaleIndex = Number(index);
this.updateMetaData();
}
updatePickerValues() {
this.pickerValues.startDate = this.state.startDate;
this.pickerValues.stopDate = this.state.stopDate;
}
}

View File

@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="web_gantt.GanttRendererControls">
<div
class="o_gantt_renderer_controls d-inline-flex d-print-none flex-wrap column-gap-2 align-items-center w-100 bg-view border-bottom sticky-top start-0"
t-att-class="{ 'p-1': !env.isSmall, 'gap-1 py-1': env.isSmall }"
>
<t t-call="{{ constructor.rangeMenuTemplate }}" />
<hr t-if="env.isSmall" class="my-0 w-100"/>
<button
class="o_gantt_button_today btn btn-secondary"
t-att-class="{ 'ms-3': env.isSmall }"
title="Focus Today"
data-hotkey="t"
t-on-click="onTodayClicked"
>
<i class="fa fa-crosshairs"/>
</button>
<button class="btn btn-secondary"
t-att-disabled="state.scaleIndex === scalesRange.min"
data-hotkey="j"
t-on-click="() => this.incrementScale(-1)"
>
<i class="fa fa-search-minus"/>
</button>
<input
type="range"
class="form-range flex-grow-1 w-0"
t-att-min="scalesRange.min" t-att-max="scalesRange.max" step="1"
t-att-value="state.scaleIndex"
t-on-change="(ev) => this.selectScale(ev.target.value)"
/>
<button class="btn btn-secondary"
t-att-disabled="state.scaleIndex === scalesRange.max"
data-hotkey="i"
t-on-click="() => this.incrementScale(1)"
>
<i class="fa fa-search-plus"/>
</button>
<t t-if="env.isSmall">
<div class="flex-grow-1 w-0"/>
<Dropdown>
<button class="btn btn-secondary me-3" aria-label="Toolbar menu">
<i class="fa fa-cog"/>
</button>
<t t-set-slot="content">
<t t-call="{{ constructor.toolbarContentTemplate }}" />
</t>
</Dropdown>
</t>
<t t-else="" t-call="{{ constructor.toolbarContentTemplate }}" />
</div>
</t>
<t t-name="web_gantt.GanttRendererControls.ToolbarContent">
<t t-if="!env.isSmall">
<div class="btn-toolbar gap-2" role="toolbar" name="ganttToolbar">
<button class="btn btn-secondary fa"
t-att-class="{ 'fa-compress': model.displayParams.displayMode === 'sparse', 'fa-expand': model.displayParams.displayMode === 'dense' }"
t-att-title="model.displayParams.displayMode === 'dense' ? 'Activate sparse mode' : 'Activate dense mode'"
t-on-click="model.toggleDisplayMode.bind(model)"
>
</button>
<div class="btn-group" t-att-class="{ invisible: !props.displayExpandCollapseButtons }" name="expandCollapseButtons">
<button class="o_gantt_button_expand_rows btn btn-secondary" title="Expand rows" t-on-click="model.expandRows.bind(model)">
<i class="fa fa-caret-square-o-right"/>
</button>
<button class="o_gantt_button_collapse_rows btn btn-secondary" title="Collapse rows" t-on-click="model.collapseRows.bind(model)">
<i class="fa fa-caret-square-o-down"/>
</button>
</div>
</div>
</t>
<t t-else="">
<DropdownItem onSelected="model.toggleDisplayMode.bind(model)">
<t t-if="model.displayParams.displayMode === 'dense'">
<i class="fa fa-fw fa-expand"/>
<span class="ms-1">Activate sparse mode</span>
</t>
<t t-else="">
<i class="fa fa-fw fa-compress"/>
<span class="ms-1">Activate dense mode</span>
</t>
</DropdownItem>
<t t-if="props.displayExpandCollapseButtons">
<div class="dropdown-divider" role="separator"/>
<DropdownItem onSelected="model.expandRows.bind(model)">
<i class="fa fa-fw fa-caret-square-o-right"/>
<span class="ms-1">Expand rows</span>
</DropdownItem>
<DropdownItem onSelected="model.collapseRows.bind(model)">
<i class="fa fa-fw fa-caret-square-o-down"/>
<span class="ms-1">Collapse rows</span>
</DropdownItem>
</t>
</t>
</t>
<t t-name="web_gantt.GanttRendererControls.RangeMenu">
<div class="btn-group" t-att-class="{ 'ms-3': env.isSmall }">
<button class="btn btn-secondary" data-hotkey="p" t-on-click="() => this.selectRange('previous')" >
<i class="fa fa-arrow-left"/>
</button>
<button class="btn btn-secondary" data-hotkey="n" t-on-click="() => this.selectRange('next')">
<i class="fa fa-arrow-right"/>
</button>
</div>
<Dropdown state="dropdownState" menuClass="'o_gantt_range_menu'">
<div class="btn btn-secondary">
<i class="fa fa-calendar me-1"/>
<t t-if="state.rangeId === 'custom'">
<t t-esc="`From: ${getFormattedDate(state.startDate)} to: ${getFormattedDate(state.stopDate)}`"/>
</t>
<t t-else="">
<t t-esc="dateDescription"/>
</t>
</div>
<t t-set-slot="content">
<t t-foreach="Object.entries(model.metaData.ranges)" t-as="range" t-key="range[0]">
<DropdownItem class="{ 'selected': isSelected(range[0]) }" onSelected="() => this.selectRangeId(range[0])">
<t t-esc="range[1].description" />
</DropdownItem>
</t>
<div class="dropdown-divider"/>
<DropdownItem class="{ 'o_gantt_range_custom_item py-0': true, 'selected': isSelected('custom') }" closingMode="'none'">
<div class="d-flex align-items-center gap-1">
<label>From </label>
<span class="o_gantt_picker o_input cursor-pointer px-1" t-ref="start-picker" t-on-click="() => startPicker.open()">
<t t-esc="getFormattedDate(pickerValues.startDate)" />
</span>
<label>to </label>
<span class="o_gantt_picker o_input cursor-pointer px-1" t-ref="stop-picker" t-on-click="() => stopPicker.open()">
<t t-esc="getFormattedDate(pickerValues.stopDate)" />
</span>
<button class="btn btn-sm btn-primary ms-1" t-on-click="onApply">Apply</button>
</div>
</DropdownItem>
</t>
</Dropdown>
</t>
</templates>

View File

@ -0,0 +1,44 @@
import { Component } from "@odoo/owl";
export class GanttResizeBadge extends Component {
static props = {
reactive: {
type: Object,
shape: {
position: {
type: Object,
shape: {
top: Number,
right: { type: Number, optional: true },
left: { type: Number, optional: true },
},
optional: true,
},
diff: { type: Number, optional: true },
scale: { type: String, optional: true },
},
},
};
static template = "web_gantt.GanttResizeBadge";
get diff() {
return this.props.reactive.diff || 0;
}
get diffText() {
const { diff, props } = this;
const prefix = this.diff > 0 ? "+" : "";
return `${prefix}${diff} ${props.reactive.scale}`;
}
get positionStyle() {
const { position } = this.props.reactive;
const style = [`top:${position.top}px`];
if ("left" in position) {
style.push(`left:${position.left}px`);
} else {
style.push(`right:${position.right}px`);
}
return style.join(";");
}
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="web_gantt.GanttResizeBadge">
<span
t-if="props.reactive.position"
class="o_gantt_pill_resize_badge badge rounded-pill bg-view position-fixed"
t-att-class="{ 'text-success': diff &gt; 0, 'text-danger': diff &lt; 0 }"
t-att-style="positionStyle"
t-esc="diffText"
/>
</t>
</templates>

View File

@ -0,0 +1,36 @@
import { Component } from "@odoo/owl";
import { hasTouch, isMobileOS } from "@web/core/browser/feature_detection";
export class GanttRowProgressBar extends Component {
static props = {
reactive: {
type: Object,
shape: {
hoveredRowId: [String, { value: null }],
},
},
rowId: String,
progressBar: {
type: Object,
shape: {
max_value: Number,
max_value_formatted: String,
ratio: Number,
value_formatted: String,
warning: { type: String, optional: true },
"*": true,
},
},
};
static template = "web_gantt.GanttRowProgressBar";
get show() {
const { reactive, rowId } = this.props;
return reactive.hoveredRowId === rowId || isMobileOS() || hasTouch();
}
get status() {
const { ratio } = this.props.progressBar;
return ratio > 100 ? "danger" : ratio > 0 ? "success" : null;
}
}

View File

@ -0,0 +1,36 @@
<?xml version="1.1" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="web_gantt.GanttRowProgressBar">
<span
class="o_gantt_progress_bar position-relative h-100 pe-none"
t-att-class="`o_gantt_group_${status || 'none'}`"
>
<t t-if="props.progressBar.max_value gt 0">
<span
class="bg-opacity-25 position-absolute top-0 end-0 h-100"
t-att-class="status and `bg-${status}`"
t-attf-style="width:{{ Math.min(props.progressBar.ratio, 100) }}%;"
/>
<span
t-if="show"
class="position-absolute top-0 end-0 h-100 d-flex align-items-center px-1"
t-att-class="status ? `text-bg-${status}` : 'bg-view'"
>
<span
class="o_gantt_group_hours"
t-esc="`${props.progressBar.value_formatted} / ${props.progressBar.max_value_formatted}`"
/>
</span>
</t>
<t t-elif="show and props.progressBar.warning">
<span
class="o_gantt_group_hours position-absolute top-0 end-0 h-100 d-flex align-items-center px-1 pe-auto bg-view"
t-attf-title="{{ props.progressBar.warning }}"
>
<t t-esc="props.progressBar.value_formatted"/>
<i class="fa fa-exclamation-triangle" />
</span>
</t>
</span>
</t>
</templates>

View File

@ -0,0 +1,43 @@
import { registry } from "@web/core/registry";
function _mockGetGanttData(params) {
const lazy = !params.limit && !params.offset && params.groupby.length === 1;
let { groups, length } = this._mockWebReadGroup({
...params,
lazy,
fields: ["__record_ids:array_agg(id)"],
});
if (params.limit) {
// we don't care about pager feature in sample mode
// but we want to present something coherent
groups = groups.slice(0, params.limit);
length = groups.length;
}
groups.forEach((g) => (g.__record_ids = g.id)); // the sample server does not use the key __record_ids
const recordIds = [];
for (const group of groups) {
recordIds.push(...(group.__record_ids || []));
}
const { records } = this._mockWebSearchReadUnity({
model: params.model,
domain: [["id", "in", recordIds]],
context: params.context,
specification: params.read_specification,
});
const unavailabilities = {};
for (const fieldName of params.unavailability_fields || []) {
unavailabilities[fieldName] = {};
}
const progress_bars = {};
for (const fieldName of params.progress_bar_fields || []) {
progress_bars[fieldName] = {};
}
return { groups, length, records, unavailabilities, progress_bars };
}
registry.category("sample_server").add("get_gantt_data", _mockGetGanttData);

View File

@ -0,0 +1,21 @@
// = Gantt View
// ============================================================================
// No CSS hacks, variables overrides only
.o_web_client .o_gantt_view {
--Gantt__DayOff-background-color: rgba(255, 255, 255, .05);
// Mix between $gantt-highlight-today-bg and $o-view-background-color
// to simulate the superposition of these two colors
--Gantt__DayOffToday-background-color: #553F3A;
.o_gantt_connector {
--Connector__ButtonBackground-color: #{$o-view-background-color};
--Connector__ButtonReschedule-color: #{darken($o-component-active-border, 10%)};
--Connector__ButtonBorder-color: #{$o-gray-500};
--Connector__ButtonAccent-color: #{$o-black};
}
.o_gantt_renderer {
--Gantt__DayOff-background-color: #{$o-gray-300};
}
}

View File

@ -0,0 +1,62 @@
import { registry } from "@web/core/registry";
import { scrollSymbol } from "@web/search/action_hook";
import { GanttArchParser } from "./gantt_arch_parser";
import { GanttController } from "./gantt_controller";
import { GanttModel } from "./gantt_model";
import { GanttRenderer } from "./gantt_renderer";
import { omit } from "@web/core/utils/objects";
const viewRegistry = registry.category("views");
export const ganttView = {
type: "gantt",
Controller: GanttController,
Renderer: GanttRenderer,
Model: GanttModel,
ArchParser: GanttArchParser,
searchMenuTypes: ["filter", "groupBy", "favorite"],
buttonTemplate: "web_gantt.GanttView.Buttons",
props: (genericProps, view, config) => {
const modelParams = {};
let scrollPosition;
if (genericProps.state) {
scrollPosition = genericProps.state[scrollSymbol];
modelParams.metaData = genericProps.state.metaData;
modelParams.displayParams = genericProps.state.displayParams;
} else {
const { arch, fields, resModel } = genericProps;
const parser = new view.ArchParser();
const archInfo = parser.parse(arch);
let formViewId = archInfo.formViewId;
if (!formViewId) {
const formView = config.views.find((v) => v[1] === "form");
if (formView) {
formViewId = formView[0];
}
}
modelParams.metaData = {
...omit(archInfo, "displayMode"),
fields,
resModel,
formViewId,
};
modelParams.displayParams = {
displayMode: archInfo.displayMode,
};
}
return {
...genericProps,
modelParams,
Model: view.Model,
Renderer: view.Renderer,
buttonTemplate: view.buttonTemplate,
scrollPosition,
};
},
};
viewRegistry.add("gantt", ganttView);

View File

@ -0,0 +1,682 @@
.o_gantt_view {
--Gantt__Buttons-height: 45px;
@media (max-width: 767px) {
--Gantt__Buttons-height: 91px; // = 2 times the height in normal display + 1px for the <hr> element
}
user-select: none;
.o_view_nocontent {
@include o-gantt-zindex(view-nocontent);
position: fixed;
top: 225px;
}
.o_view_sample_data .o_sample_data_disabled {
@include o-sample-data-disabled;
}
.o_gantt_renderer_controls {
@include o-gantt-zindex(controls);
height: var(--Gantt__Buttons-height) !important;
input[type="range"] {
min-width: 80px;
max-width: 80px;
}
}
@include media-only(print) {
> .o_content {
overflow: auto;
}
}
}
.o_gantt_range_menu {
.o_gantt_range_custom_item.dropdown-item:not(.disabled):not(:disabled) {
background-color: inherit !important;
cursor: default !important;
label {
cursor: default !important;
}
&.selected {
font-weight: 400;
}
}
}
.o_gantt_view .o_gantt_renderer {
// Renderer grid
display: grid;
grid-template-columns:
[row-headers] var(--Gantt__RowHeader-width) [content] 1fr;
--Gantt__RowHeader-template-column: 16px;
// Allows to use color variables in js
--Gantt__Day-background-color: #{$o-view-background-color};
--Gantt__DayOff-background-color: #e9ecef;
--Gantt__DayOffToday-background-color: #fffaeb;
// Group colors
--Gantt__Group-background: linear-gradient(#{darken($gantt-row-open-bg, 5%)},
#{$gantt-row-open-bg});
--Gantt__GroupOpen-background: linear-gradient(#{$gantt-row-open-bg},
#{darken($gantt-row-open-bg, 5%)});
--Gantt__GroupToday-background: #{mix($gantt-row-open-bg, $gantt-highlight-today-bg)};
// ============================= Main Layout ==============================
// ========================================================================
// Utilities to use the grid-template-rows or grid-template-columns
.o_gantt_grid_rows {
display: grid;
grid-template-rows: var(--Gantt__GridRows-grid-template-rows);
}
.o_gantt_grid_columns {
display: grid;
grid-column: 2 / span 2;
grid-template-columns: var(--Gantt__GridColumns-grid-template-columns);
}
// Row headers
.o_gantt_row_header {
cursor: pointer;
display: grid;
grid-column: 1 / -1;
grid-row: 3;
grid-template-columns: repeat(auto-fill,
minmax(var(--Gantt__RowHeader-template-column), 1fr));
line-height: var(--Gantt__Pill-height);
.o_gantt_progress_bar {
grid-row: 1;
grid-column: 1 / -1;
}
.o_gantt_row_title {
grid-row: 1;
}
&.o_gantt_group {
line-height: initial;
}
&.o_mobile_progress_bar {
grid-template-rows: 1fr var(--Gantt__Pill-height);
.o_gantt_progress_bar {
grid-row: 2;
}
}
}
// =============================== Buttons ================================
// ========================================================================
.o_gantt_buttons_container {
gap: 0.25rem 1rem;
}
// All rows (Regular, Group Header and Total)
// ==========================================
.o_gantt_row_thumbnail_wrapper .o_gantt_row_thumbnail {
width: auto;
max-height: var(--Gantt__Thumbnail-max-height);
}
// =============== Cursors while dragging ==============
// =======================================================
&.o_grabbing,
&.o_grabbing .o_gantt_pill {
cursor: move !important;
}
&.o_copying,
&.o_copying .o_gantt_pill {
cursor: copy !important;
}
&.o_grabbing_locked,
&.o_grabbing_locked .o_gantt_pill {
cursor: not-allowed !important;
}
@include media-breakpoint-down(md) {
& {
width: max-content;
}
}
.o_dragged_pill_ghost {
opacity: 0.5;
}
// ================ Header ===============
// =======================================
.o_gantt_title {
top: var(--Gantt__Buttons-height);
@include o-gantt-zindex(title);
border-bottom: 1px solid $gantt-border-color;
grid-row: 1 / span 2;
}
.o_gantt_header_groups {
top: var(--Gantt__Buttons-height);
@include o-gantt-zindex(column-header-groups);
grid-row: 1;
}
.o_gantt_header_columns {
top: var(--Gantt__Buttons-height);
@include o-gantt-zindex(headers);
grid-row: 2;
margin-top: calc(var(--Gantt__Pill-height) * -1);
padding-top: var(--Gantt__Pill-height);
}
.o_gantt_header_cell {
border: 1px solid transparent;
border-bottom-color: $gantt-border-color;
border-right-color: $gantt-border-color;
height: var(--Gantt__Pill-height);
color: $headings-color;
position: relative;
@include o-gantt-cell;
@include media-breakpoint-down(md) {
min-width: 0;
}
}
.o_gantt_header_title {
height: var(--Gantt__Pill-height);
left: calc(var(--Gantt__RowHeader-width) - 1px);
border: solid $gantt-border-color;
border-width: 0 0 1px 1px;
margin-left: -1px;
}
// ======= All sidebar headers (Header, Regular, Groups and Total) ========
// ========================================================================
.o_gantt_row_sidebar {
@include o-gantt-zindex(headers);
grid-column: 1;
color: $headings-color;
&:not(.o_gantt_row_headers) {
border-bottom: 1px solid $gantt-border-color;
}
.o_gantt_progressbar,
.o_gantt_text_hoverable {
right: 0;
height: 100%;
background-color: inherit;
}
}
// =================== "Regular" & "Group Header" cells ===================
// ========================================================================
.o_gantt_cell {
border: solid $gantt-border-color;
border-width: 0 1px 1px 0;
@include o-gantt-cell;
@include o-gantt-zindex(grid);
&.o_drag_hover {
background: $gantt-highlight-cell-color !important;
@include o-gantt-zindex(grid-interact);
}
}
// ================================ Pills =================================
// ========================================================================
.o_gantt_pill_wrapper {
padding: 2px 2px 3px 3px;
min-height: var(--Gantt__Pill-height);
@include o-gantt-zindex(pill);
// Group pills
&.o_gantt_group_pill {
pointer-events: none;
min-height: auto;
display: grid;
.o_gantt_pill {
grid-area: 1 / 1;
background-color: $primary;
border-color: $primary;
height: 2px;
&:before,
&:after {
content: "";
border-top: 4px solid transparent;
border-bottom: 5px solid transparent;
}
&:before {
border-left: 5px solid;
border-left-color: inherit;
@include o-position-absolute($top: -3px, $left: 0);
}
&:after {
border-right: 5px solid;
border-right-color: inherit;
@include o-position-absolute($top: -3px, $right: 0);
}
}
&.o_group_open .o_gantt_pill {
&:before,
&:after {
top: 2px;
border: 2px solid transparent;
border-top-color: inherit;
}
&:before {
border-left-color: inherit;
}
&:after {
border-right-color: inherit;
}
}
.o_gantt_pill_title {
grid-area: 1 / 1;
width: fit-content;
}
}
&.o_resizable {
.o_resize_handle {
width: 0.5rem
/* 6px */
;
pointer-events: auto;
position: absolute;
top: 0;
@include o-gantt-zindex(interact);
&.o_handle_start {
left: 0;
}
&.o_handle_end {
right: 0;
}
}
@include o-gantt-hover() {
&:not(.o_resized) .o_resize_handle {
background-color: rgba(230, 230, 230, 0.5);
&:hover {
background-color: rgba(230, 230, 230, 0.8);
}
}
}
}
&.o_draggable,
&.o_undraggable {
transition: transform 0.6s, box-shadow 0.3s;
&.o_dragged {
opacity: 0.8;
transform: rotate(-3deg);
box-shadow: 0 5px 25px -10px black;
@include o-gantt-zindex(interact);
.o_gantt_pill {
box-shadow: 0 5px 25px -10px black;
}
.o_resize_handle {
visibility: hidden;
}
}
}
&.o_undraggable:not(.o_dragged) .o_gantt_lock {
display: none;
}
&.o_resizable.o_resized {
.o_gantt_pill {
cursor: inherit;
}
.o_resize_handle {
background-color: rgba(black, 0.5);
@include o-gantt-zindex(interact);
}
}
.o_gantt_consolidated_pill_title {
background: none !important;
color: $headings-color;
position: absolute;
top: 21px;
font-size: 0.7em;
&.o_gantt_consolidated_pill_small {
transform: rotate(75deg);
}
}
&:not(.o_connector_creator_lock):not(.o_connector_creator_highlight) .o_connector_creator_wrapper {
display: none;
}
@include o-gantt-hover() {
.o_connector_creator_wrapper {
display: inline;
}
}
// used for `color` attribute on <gantt>
@for $index from 1 through length($o-colors-complete) {
$color: nth($o-colors-complete, $index);
.o_gantt_pill.o_gantt_color_#{$index - 1} .o_gantt_progress {
opacity: 0.2;
background-color: darken($color, 30%);
}
&.highlight .o_gantt_pill.o_gantt_color_#{$index - 1} {
color: color-contrast($color);
background-color: $color;
}
}
}
// ========================== Main pill content ===========================
// ========================================================================
.o_gantt_cells {
grid-row: 3;
}
.o_gantt_cells .o_gantt_pill {
overflow: hidden;
user-select: none;
box-sizing: content-box;
cursor: pointer;
@include o-gantt-hoverable-colors(nth($o-colors-complete, 1));
.o_gantt_pill_title {
// Prevent displaying pill's description when size is smaller than 50px
max-width: calc((100% - 50px) * 9999);
}
.o_gantt_pill_avatar {
// Prevent displaying pill's avatar when size is smaller than 100px
max-width: calc((100% - 100px) * 9999);
}
&.decoration-info {
@include o-gantt-gradient-decorations(nth($o-colors-complete, 1));
}
// used for `color` attribute on <gantt>
@for $index from 1 through length($o-colors-complete) {
&.o_gantt_color_#{$index - 1} {
$gantt-color: nth($o-colors-complete, $index);
@include o-gantt-hoverable-colors($gantt-color);
&.decoration-info {
@include o-gantt-gradient-decorations($gantt-color);
}
.o_gantt_progress {
opacity: 0.2;
background-color: darken($gantt-color, 30%);
}
}
}
@each $color, $value in $theme-colors {
&.decoration-#{$color}:before {
@include o-gantt-ribbon-decoration($value);
}
}
}
// ========================= "Group Header" rows ==========================
// ========================================================================
.o_gantt_group {
background: var(--Gantt__Group-background);
&.o_gantt_today {
background: var(--Gantt__GroupToday-background);
}
&.o_gantt_group_hovered:not(.o_gantt_today) {
background: var(--Gantt__GroupOpen-background);
}
&.o_group_open {
border-left-width: 0;
background: var(--Gantt__GroupOpen-background);
&.o_gantt_group_hovered {
background: var(--Gantt__Group-background);
}
}
&.o_gantt_row_header b {
font-weight: bold;
}
}
// ========================== Connector creators ==========================
// ========================================================================
.o_connector_creator_wrapper {
height: $o-connector-wrapper-height;
@include o-gantt-zindex(interact);
// used for `color` attribute on <gantt>
@for $index from 1 through length($o-colors-complete) {
&.o_gantt_color_#{$index - 1} {
$color: nth($o-colors-complete, $index);
.o_connector_creator_bullet {
background-color: $color;
color: color-contrast($color);
@include o-grab-cursor;
}
.o_connector_creator_top {
border-top: solid 1px $color;
}
.o_connector_creator_right {
/*rtl:ignore*/
border-left: solid 1px $color;
}
.o_connector_creator_bottom {
border-bottom: solid 1px $color;
}
.o_connector_creator_left {
/*rtl:ignore*/
border-right: solid 1px $color;
}
}
}
}
.o_connector_creator_wrapper_top {
top: -1 * $o-connector-wrapper-height;
}
.o_connector_creator_wrapper_bottom {
bottom: -1 * $o-connector-wrapper-height;
}
.o_connector_creator {
height: $o-connector-creator-size;
width: $o-connector-creator-size;
}
.o_connector_creator_bullet {
height: $o-connector-creator-bullet-diameter;
width: $o-connector-creator-bullet-diameter;
}
.o_connector_creator_top {
bottom: 0;
.o_connector_creator_bullet {
top: -0.5 * $o-connector-creator-bullet-diameter;
}
}
.o_connector_creator_right {
/*rtl:ignore*/
right: $o-connector-creator-size;
.o_connector_creator_bullet {
/*rtl:ignore*/
right: -0.5 * $o-connector-creator-bullet-diameter;
}
}
.o_connector_creator_bottom {
top: 0;
.o_connector_creator_bullet {
bottom: -0.5 * $o-connector-creator-bullet-diameter;
}
}
.o_connector_creator_left {
/*rtl:ignore*/
left: $o-connector-creator-size;
.o_connector_creator_bullet {
/*rtl:ignore*/
left: -0.5 * $o-connector-creator-bullet-diameter;
}
}
// ============================= "TOTAL" row ==============================
// ========================================================================
.o_gantt_row_total {
grid-row: 4;
.o_gantt_cell,
.o_gantt_row_title,
.o_gantt_pill_wrapper {
min-height: calc(var(--Gantt__Pill-height) * 1.6);
}
.o_gantt_pill {
color: inherit;
margin-left: 1px;
background-color: rgba($o-brand-odoo, 0.5);
}
.o_gantt_pill_wrapper:hover {
overflow: visible;
.o_gantt_pill {
color: inherit;
background-color: rgba($o-brand-odoo, 0.8);
}
&:before {
content: "";
border: 1px solid $o-brand-odoo;
border-width: 0 1px;
background: rgba($o-brand-odoo, 0.1);
height: 100vh;
pointer-events: none;
@include o-gantt-zindex(interact);
@include o-position-absolute(auto, -1px, 0, 0);
}
}
.o_gantt_cell:last-child .o_gantt_pill_wrapper:hover:before {
border-right: 0px;
right: 0;
}
}
.o_gantt_pill_resize_badge {
transition: all 0.15s ease-in-out;
box-shadow: 0 1px 2px 0 rgba(black, 0.28);
@include o-gantt-zindex(badge);
}
.o_gantt_connector {
--Connector__ButtonBorder-color: #091124;
--Connector__ButtonBackground-color: #ffffff;
--Connector__ButtonReschedule-color: #00a09d;
--Connector__ButtonRemove-color: #dd3c4f;
--Connector__ButtonAccent-color: #ffffff;
&.o_connector_highlighted {
@include o-gantt-zindex(interact);
}
.o_connector_stroke_button {
>rect {
cursor: pointer;
fill: var(--Connector__ButtonBackground-color);
stroke: var(--Connector__ButtonBorder-color);
stroke-width: 24px;
transition: fill 0.15s;
}
&.o_connector_stroke_reschedule_button {
line {
stroke: var(--Connector__ButtonReschedule-color);
transition: stroke 0.15s;
}
&:hover {
>rect {
fill: var(--Connector__ButtonReschedule-color);
}
line {
stroke: var(--Connector__ButtonAccent-color);
}
}
}
&.o_connector_stroke_remove_button {
g rect {
fill: var(--Connector__ButtonRemove-color);
transition: fill 0.15s;
}
&:hover {
>rect {
fill: var(--Connector__ButtonRemove-color);
}
g rect {
fill: var(--Connector__ButtonAccent-color);
}
}
}
}
}
}

View File

@ -0,0 +1,9 @@
// = Gantt View Variables
// ============================================================================
// No CSS hacks, variables overrides only
$gantt-highlight-today-border: rgba($o-warning, 0.5) !default;
$gantt-highlight-today-bg: rgba($o-warning, 0.15)!default;
$gantt-highlight-hover-row: rgba($o-brand-primary, .1) !default;
$gantt-row-open-bg: $o-gray-100 !default;
$gantt-unavailability-bg: $o-gray-200 !default;

View File

@ -0,0 +1,104 @@
// = Gantt View Variables
// ============================================================================
// Define the necessary conditions to provide visual feedback on hover,
// focus, drag, clone and resize.
@mixin o-gantt-hover() {
&:hover,
&:focus,
&.o_dragged_pill,
&.ui-resizable-resize {
// Avoid visual feedback if 'o_gantt_renderer' has class 'o_grabbing', 'o_copying', 'o_no_dragging' or 'o_connect'
@at-root #{selector-replace(&, ".o_gantt_renderer", ".o_gantt_renderer:not(.o_grabbing):not(.o_copying):not(.o_no_dragging):not(.o_connect)")} {
@content;
}
}
}
// Generate background and text for each color.
@mixin o-gantt-hoverable-colors($color) {
$color-subdle: mix($color, white, 60%);
color: color-contrast($color-subdle);
background-color: $color-subdle;
@include o-gantt-hover() {
background-color: $color;
color: color-contrast($color);
}
}
// Generate stripes decorations for each color.
@mixin o-gantt-gradient-decorations($color) {
$color-subdle: mix($color, white, 60%);
background-image: repeating-linear-gradient(
-45deg,
$color-subdle 0 10px,
lighten($color-subdle, 6%) 10px 20px
);
@include o-gantt-hover() {
background-image: repeating-linear-gradient(
-45deg,
$color 0 10px,
lighten($color, 6%) 10px 20px
);
}
}
@mixin o-gantt-ribbon-decoration($color) {
content: "";
width: 20px;
height: 16px;
@include o-position-absolute(-11px, $left: -13px);
box-shadow: 1px 1px 0 white;
background: $color;
transform: rotate(45deg);
}
@mixin o-gantt-cell {
&.o_gantt_today {
background-color: $gantt-highlight-today-bg;
border-left-color: $gantt-highlight-today-border;
border-top-color: $gantt-highlight-today-border;
+ .o_gantt_header_cell,
+ .o_gantt_cell {
border-left-color: $gantt-highlight-today-border;
}
}
}
@mixin o-gantt-zindex($level) {
z-index: map-get(
(
// Grid and grid interactions level
grid: 0,
grid-interact: 1,
// Pills level
pill: 10,
// Interactions => over pills
interact: 20,
// No-content helper
view-nocontent: 25,
headers: 30,
// Top-most elements
column-header-groups: 35,
title: 40,
controls: 50,
badge: 50,
),
$level
);
}
$gantt-border-color: $o-gray-300 !default;
$gantt-highlight-cell-color: rgba(0, 160, 157, 0.3) !default;
$gantt-highlight-today-border: #dca665 !default;
$gantt-highlight-today-bg: #fffaeb !default;
$gantt-highlight-hover-row: rgba($o-brand-primary, 0.1) !default;
$gantt-row-open-bg: $o-gray-100 !default;
$gantt-unavailability-bg: $o-gray-200 !default;
$o-connector-creator-bullet-radius: 3px !default;
$o-connector-creator-size: 8px !default;
$o-connector-creator-bullet-diameter: 2 * $o-connector-creator-bullet-radius !default;
$o-connector-wrapper-height: $o-connector-creator-size + $o-connector-creator-bullet-radius !default;

View File

@ -0,0 +1,921 @@
import { beforeEach, describe, expect, test } from "@odoo/hoot";
import {
hover,
pointerDown,
queryAll,
queryFirst,
queryOne,
queryRect,
resize,
} from "@odoo/hoot-dom";
import { advanceFrame, animationFrame, mockDate, runAllTimers } from "@odoo/hoot-mock";
import {
contains,
defineModels,
fields,
findComponent,
models,
onRpc,
patchWithCleanup,
} from "@web/../tests/web_test_helpers";
import {
clickConnectorButton,
getConnector,
getConnectorMap,
} from "@web_gantt/../tests/gantt_dependency_helpers";
import { COLORS } from "@web_gantt/gantt_connector";
import {
CLASSES,
SELECTORS,
getPill,
getPillWrapper,
mountGanttView,
} from "./web_gantt_test_helpers";
import { GanttRenderer } from "@web_gantt/gantt_renderer";
/** @typedef {import("@web_gantt/gantt_renderer").ConnectorProps} ConnectorProps */
/** @typedef {import("@web_gantt/gantt_renderer").PillId} PillId */
/**
* @typedef {`[${ResId},${ResId},${ResId},${ResId}]`} ConnectorTaskIds
* In the following order: [masterTaskId, masterTaskUserId, taskId, taskUserId]
*/
/** @typedef {number | false} ResId */
const ganttViewParams = {
resModel: "project.task",
arch: /* xml */ `<gantt date_start="planned_date_begin" date_stop="date_deadline" default_scale="month" dependency_field="depend_on_ids" color="color" />`,
groupBy: ["user_ids"],
};
let nextColor = 1;
class ProjectTask extends models.Model {
_name = "project.task";
name = fields.Char();
planned_date_begin = fields.Datetime({ string: "Start Date" });
date_deadline = fields.Datetime({ string: "Stop Date" });
user_ids = fields.Many2many({ string: "Assignees", relation: "res.users" });
allow_task_dependencies = fields.Boolean({ default: true });
depend_on_ids = fields.One2many({ string: "Depends on", relation: "project.task" });
display_warning_dependency_in_gantt = fields.Boolean({ default: true });
color = fields.Integer({ default: () => nextColor++ });
_records = [
{
id: 1,
name: "Task 1",
planned_date_begin: "2021-10-11 18:30:00",
date_deadline: "2021-10-11 19:29:59",
user_ids: [1],
depend_on_ids: [],
},
{
id: 2,
name: "Task 2",
planned_date_begin: "2021-10-12 11:30:00",
date_deadline: "2021-10-12 12:29:59",
user_ids: [1, 3],
depend_on_ids: [1],
},
{
id: 3,
name: "Task 3",
planned_date_begin: "2021-10-13 06:30:00",
date_deadline: "2021-10-13 07:29:59",
user_ids: [],
depend_on_ids: [2],
},
{
id: 4,
name: "Task 4",
planned_date_begin: "2021-10-14 22:30:00",
date_deadline: "2021-10-14 23:29:59",
user_ids: [2, 3],
depend_on_ids: [2],
},
{
id: 5,
name: "Task 5",
planned_date_begin: "2021-10-15 01:53:10",
date_deadline: "2021-10-15 02:34:34",
user_ids: [],
depend_on_ids: [],
},
{
id: 6,
name: "Task 6",
planned_date_begin: "2021-10-16 23:00:00",
date_deadline: "2021-10-16 23:21:01",
user_ids: [1, 3],
depend_on_ids: [4, 5],
},
{
id: 7,
name: "Task 7",
planned_date_begin: "2021-10-17 10:30:12",
date_deadline: "2021-10-17 11:29:59",
user_ids: [1, 2, 3],
depend_on_ids: [6],
},
{
id: 8,
name: "Task 8",
planned_date_begin: "2021-10-18 06:30:12",
date_deadline: "2021-10-18 07:29:59",
user_ids: [1, 3],
depend_on_ids: [7],
},
{
id: 9,
name: "Task 9",
planned_date_begin: "2021-10-19 06:30:12",
date_deadline: "2021-10-19 07:29:59",
user_ids: [2],
depend_on_ids: [8],
},
{
id: 10,
name: "Task 10",
planned_date_begin: "2021-10-19 06:30:12",
date_deadline: "2021-10-19 07:29:59",
user_ids: [2],
depend_on_ids: [],
},
{
id: 11,
name: "Task 11",
planned_date_begin: "2021-10-18 06:30:12",
date_deadline: "2021-10-18 07:29:59",
user_ids: [2],
depend_on_ids: [10],
},
{
id: 12,
name: "Task 12",
planned_date_begin: "2021-10-18 06:30:12",
date_deadline: "2021-10-19 07:29:59",
user_ids: [2],
depend_on_ids: [],
},
{
id: 13,
name: "Task 13",
planned_date_begin: "2021-10-18 07:29:59",
date_deadline: "2021-10-20 07:29:59",
user_ids: [2],
depend_on_ids: [12],
},
];
}
class ResUsers extends models.Model {
_name = "res.users";
name = fields.Char();
_records = [
{ id: 1, name: "User 1" },
{ id: 2, name: "User 2" },
{ id: 3, name: "User 3" },
{ id: 4, name: "User 4" },
];
}
defineModels([ProjectTask, ResUsers]);
describe.current.tags("desktop");
beforeEach(() => mockDate("2021-10-10T08:00:00", +1));
test("Connectors are correctly computed and rendered.", async () => {
/**
* @type {Map<ConnectorTaskIds, keyof typeof COLORS>}
* => Check that there is a connector between masterTaskId from group masterTaskUserId and taskId from group taskUserId with normal|error color.
*/
const testMap = new Map([
["[1,1,2,1]", "default"],
["[1,1,2,3]", "default"],
["[2,1,3,false]", "default"],
["[2,3,3,false]", "default"],
["[2,1,4,2]", "default"],
["[2,3,4,3]", "default"],
["[4,2,6,1]", "default"],
["[4,3,6,3]", "default"],
["[5,false,6,1]", "default"],
["[5,false,6,3]", "default"],
["[6,1,7,1]", "default"],
["[6,1,7,2]", "default"],
["[6,3,7,2]", "default"],
["[6,3,7,3]", "default"],
["[7,1,8,1]", "default"],
["[7,2,8,1]", "default"],
["[7,2,8,3]", "default"],
["[7,3,8,3]", "default"],
["[8,1,9,2]", "default"],
["[8,3,9,2]", "default"],
["[10,2,11,2]", "error"],
["[12,2,13,2]", "warning"],
]);
const view = await mountGanttView(ganttViewParams);
const renderer = findComponent(view, (c) => c instanceof GanttRenderer);
const connectorMap = getConnectorMap(renderer);
for (const [testKey, colorCode] of testMap.entries()) {
const [masterTaskId, masterTaskUserId, taskId, taskUserId] = JSON.parse(testKey);
expect(connectorMap.has(testKey)).toBe(true, {
message: `There should be a connector between task ${masterTaskId} from group user ${masterTaskUserId} and task ${taskId} from group user ${taskUserId}.`,
});
const connector = connectorMap.get(testKey);
const connectorEl = getConnector(connector.id);
expect(connectorEl).not.toBe(null);
const connectorStroke = queryFirst(SELECTORS.connectorStroke, { root: connectorEl });
expect(connectorStroke).toHaveAttribute("stroke", COLORS[colorCode].color);
}
expect(testMap).toHaveLength(connectorMap.size);
expect(SELECTORS.connector).toHaveCount(testMap.size);
});
test("Connectors are correctly rendered.", async () => {
patchWithCleanup(GanttRenderer.prototype, {
shouldRenderRecordConnectors(record) {
return record.id !== 1;
},
});
ProjectTask._records = [
{
id: 1,
name: "Task 1",
planned_date_begin: "2021-10-11 18:30:00",
date_deadline: "2021-10-11 19:29:59",
user_ids: [1],
depend_on_ids: [],
},
{
id: 2,
name: "Task 2",
planned_date_begin: "2021-10-12 11:30:00",
date_deadline: "2021-10-12 12:29:59",
user_ids: [1],
depend_on_ids: [1],
},
{
id: 3,
name: "Task 3",
planned_date_begin: "2021-10-13 06:30:00",
date_deadline: "2021-10-13 07:29:59",
user_ids: [],
depend_on_ids: [1, 2],
},
];
const view = await mountGanttView(ganttViewParams);
const renderer = findComponent(view, (c) => c instanceof GanttRenderer);
const connectorMap = getConnectorMap(renderer);
expect([...connectorMap.keys()]).toEqual(["[2,1,3,false]"], {
message: "The only rendered connector should be the one from task_id 2 to task_id 3",
});
});
test("Connectors are correctly computed and rendered when consolidation is active.", async () => {
ProjectTask._records = [
{
id: 1,
name: "Task 1",
planned_date_begin: "2021-10-11 18:30:00",
date_deadline: "2021-10-11 19:29:59",
user_ids: [1],
depend_on_ids: [],
},
{
id: 2,
name: "Task 2",
planned_date_begin: "2021-10-12 11:30:00",
date_deadline: "2021-10-12 12:29:59",
user_ids: [1, 3],
depend_on_ids: [1],
},
{
id: 3,
name: "Task 3",
planned_date_begin: "2021-10-13 06:30:00",
date_deadline: "2021-10-13 07:29:59",
user_ids: [],
depend_on_ids: [2],
},
{
id: 4,
name: "Task 4",
planned_date_begin: "2021-10-14 22:30:00",
date_deadline: "2021-10-14 23:29:59",
user_ids: [2, 3],
depend_on_ids: [2],
},
{
id: 5,
name: "Task 5",
planned_date_begin: "2021-10-15 01:53:10",
date_deadline: "2021-10-15 02:34:34",
user_ids: [],
depend_on_ids: [],
},
{
id: 6,
name: "Task 6",
planned_date_begin: "2021-10-16 23:00:00",
date_deadline: "2021-10-16 23:21:01",
user_ids: [1, 3],
depend_on_ids: [4, 5],
},
{
id: 7,
name: "Task 7",
planned_date_begin: "2021-10-17 10:30:12",
date_deadline: "2021-10-17 11:29:59",
user_ids: [1, 2, 3],
depend_on_ids: [6],
},
{
id: 8,
name: "Task 8",
planned_date_begin: "2021-10-18 06:30:12",
date_deadline: "2021-10-18 07:29:59",
user_ids: [1, 3],
depend_on_ids: [7],
},
{
id: 9,
name: "Task 9",
planned_date_begin: "2021-10-19 06:30:12",
date_deadline: "2021-10-19 07:29:59",
user_ids: [2],
depend_on_ids: [8],
},
{
id: 10,
name: "Task 10",
planned_date_begin: "2021-10-19 06:30:12",
date_deadline: "2021-10-19 07:29:59",
user_ids: [2],
depend_on_ids: [],
},
{
id: 11,
name: "Task 11",
planned_date_begin: "2021-10-18 06:30:12",
date_deadline: "2021-10-18 07:29:59",
user_ids: [2],
depend_on_ids: [10],
},
{
id: 12,
name: "Task 12",
planned_date_begin: "2021-10-18 06:30:12",
date_deadline: "2021-10-19 07:29:59",
user_ids: [2],
depend_on_ids: [],
},
{
id: 13,
name: "Task 13",
planned_date_begin: "2021-10-18 07:29:59",
date_deadline: "2021-10-20 07:29:59",
user_ids: [2],
depend_on_ids: [12],
},
];
await mountGanttView({
...ganttViewParams,
arch: /* xml */ `<gantt date_start="planned_date_begin" date_stop="date_deadline" default_scale="month" dependency_field="depend_on_ids" consolidation_max="{'user_ids': 100 }"/>`,
});
// groups have been created of r
expect(".o_gantt_row_header.o_gantt_group.o_group_open").toHaveCount(4);
function getGroupRow(index) {
return queryAll(".o_gantt_row_header.o_gantt_group")[index];
}
expect(SELECTORS.connector).toHaveCount(22);
await contains(getGroupRow(1)).click();
expect(getGroupRow(1)).not.toHaveClass("o_group_open");
expect(SELECTORS.connector).toHaveCount(13);
await contains(getGroupRow(1)).click();
expect(SELECTORS.connector).toHaveCount(22);
await contains(getGroupRow(1)).click();
expect(SELECTORS.connector).toHaveCount(13);
await contains(getGroupRow(2)).click();
expect(SELECTORS.connector).toHaveCount(6);
await contains(getGroupRow(0)).click();
expect(SELECTORS.connector).toHaveCount(4);
await contains(getGroupRow(3)).click();
expect(SELECTORS.connector).toHaveCount(0);
});
test("Connector hovered state is triggered and color is set accordingly.", async () => {
await mountGanttView(ganttViewParams);
expect(getConnector(1)).not.toHaveClass(CLASSES.highlightedConnector);
expect(queryFirst(SELECTORS.connectorStroke, { root: getConnector(1) })).toHaveAttribute(
"stroke",
COLORS.default.color
);
await hover(getConnector(1));
await animationFrame();
expect(getConnector(1)).toHaveClass(CLASSES.highlightedConnector);
expect(queryFirst(SELECTORS.connectorStroke, { root: getConnector(1) })).toHaveAttribute(
"stroke",
COLORS.default.highlightedColor
);
});
test("Buttons are displayed when hovering a connector.", async () => {
await mountGanttView(ganttViewParams);
expect(queryAll(SELECTORS.connectorStrokeButton, { root: getConnector(1) })).toHaveCount(0);
await hover(getConnector(1));
await animationFrame();
expect(queryAll(SELECTORS.connectorStrokeButton, { root: getConnector(1) })).toHaveCount(3);
});
test("Buttons are displayed when hovering a connector after a pill has been hovered.", async () => {
await mountGanttView(ganttViewParams);
expect(queryAll(SELECTORS.connectorStrokeButton, { root: getConnector(1) })).toHaveCount(0);
await hover(getPill("Task 1"));
await animationFrame();
expect(queryAll(SELECTORS.connectorStrokeButton, { root: getConnector(1) })).toHaveCount(0);
expect(getConnector(1)).toHaveClass(CLASSES.highlightedConnector);
await hover(getConnector(1));
await animationFrame();
expect(getConnector(1)).toHaveClass(CLASSES.highlightedConnector);
expect(queryAll(SELECTORS.connectorStrokeButton, { root: getConnector(1) })).toHaveCount(3);
});
test("Connector buttons: remove a dependency", async () => {
onRpc(({ method, model, args }) => {
if (model === "project.task" && ["web_gantt_reschedule", "write"].includes(method)) {
expect.step([method, args]);
return true;
}
});
await mountGanttView(ganttViewParams);
await clickConnectorButton(getConnector(1), "remove");
expect.verifySteps([["write", [[2], { depend_on_ids: [[3, 1, false]] }]]]);
});
test("Connector buttons: reschedule task backward date.", async () => {
onRpc(({ method, model, args }) => {
if (model === "project.task" && ["web_gantt_reschedule", "write"].includes(method)) {
expect.step([method, args]);
return {};
}
});
await mountGanttView(ganttViewParams);
await clickConnectorButton(getConnector(1), "reschedule-backward");
expect.verifySteps([
[
"web_gantt_reschedule",
["backward", 1, 2, "depend_on_ids", null, "planned_date_begin", "date_deadline"],
],
]);
});
test("Connector buttons: reschedule task forward date.", async () => {
onRpc(({ args, method, model }) => {
if (model === "project.task" && ["web_gantt_reschedule", "write"].includes(method)) {
expect.step([method, args]);
return {};
}
});
await mountGanttView(ganttViewParams);
await clickConnectorButton(getConnector(1), "reschedule-forward");
expect.verifySteps([
[
"web_gantt_reschedule",
["forward", 1, 2, "depend_on_ids", null, "planned_date_begin", "date_deadline"],
],
]);
});
test("Connector buttons: reschedule task start backward, different data.", async () => {
onRpc(({ method, model, args }) => {
if (model === "project.task" && ["web_gantt_reschedule", "write"].includes(method)) {
expect.step([method, args]);
return {};
}
});
await mountGanttView(ganttViewParams);
await clickConnectorButton(getConnector(1), "reschedule-backward");
expect.verifySteps([
[
"web_gantt_reschedule",
["backward", 1, 2, "depend_on_ids", null, "planned_date_begin", "date_deadline"],
],
]);
});
test("Connector buttons: reschedule task forward, different data.", async () => {
onRpc(({ method, model, args }) => {
if (model === "project.task" && ["web_gantt_reschedule", "write"].includes(method)) {
expect.step([method, args]);
return {};
}
});
await mountGanttView(ganttViewParams);
await clickConnectorButton(getConnector(1), "reschedule-forward");
expect.verifySteps([
[
"web_gantt_reschedule",
["forward", 1, 2, "depend_on_ids", null, "planned_date_begin", "date_deadline"],
],
]);
});
test("Hovering a task pill should highlight related tasks and dependencies", async () => {
/** @type {Map<ConnectorTaskIds, boolean>} */
const testMap = new Map([
["[1,1,2,1]", true],
["[1,1,2,3]", true],
["[2,1,3,false]", true],
["[2,3,3,false]", true],
["[2,1,4,2]", true],
["[2,3,4,3]", true],
["[10,2,11,2]", false],
]);
ProjectTask._records = [
{
id: 1,
name: "Task 1",
planned_date_begin: "2021-10-10 18:30:00",
date_deadline: "2021-10-11 19:29:59",
user_ids: [1],
depend_on_ids: [],
},
{
id: 2,
name: "Task 2",
planned_date_begin: "2021-10-12 11:30:00",
date_deadline: "2021-10-12 12:29:59",
user_ids: [1, 3],
depend_on_ids: [1],
},
{
id: 3,
name: "Task 3",
planned_date_begin: "2021-10-13 06:30:00",
date_deadline: "2021-10-13 07:29:59",
user_ids: [],
depend_on_ids: [2],
},
{
id: 4,
name: "Task 4",
planned_date_begin: "2021-10-14 22:30:00",
date_deadline: "2021-10-14 23:29:59",
user_ids: [2, 3],
depend_on_ids: [2],
},
{
id: 10,
name: "Task 10",
planned_date_begin: "2021-10-19 06:30:12",
date_deadline: "2021-10-19 07:29:59",
user_ids: [2],
depend_on_ids: [],
display_warning_dependency_in_gantt: false,
},
{
id: 11,
name: "Task 11",
planned_date_begin: "2021-10-18 06:30:12",
date_deadline: "2021-10-18 07:29:59",
user_ids: [2],
depend_on_ids: [10],
},
];
const view = await mountGanttView(ganttViewParams);
const renderer = findComponent(view, (c) => c instanceof GanttRenderer);
const connectorMap = getConnectorMap(renderer);
const pills = [];
for (const wrapper of queryAll(SELECTORS.pillWrapper)) {
const pillId = wrapper.dataset.pillId;
pills.push({
el: queryFirst(SELECTORS.pill, { root: wrapper }),
recordId: renderer.pills[pillId].record.id,
});
}
const task2Pills = pills.filter((p) => p.recordId === 2);
expect(task2Pills).toHaveLength(2);
expect(CLASSES.highlightedPill).toHaveCount(0);
// Check that all connectors are not in hover state.
for (const testKey of testMap.keys()) {
expect(getConnector(connectorMap.get(testKey).id)).not.toHaveClass(
CLASSES.highlightedConnector
);
}
await contains(getPill("Task 2", { nth: 1 })).hover();
// Both pills should be highlighted
expect(getPillWrapper("Task 2", { nth: 1 })).toHaveClass(CLASSES.highlightedPill);
expect(getPillWrapper("Task 2", { nth: 2 })).toHaveClass(CLASSES.highlightedPill);
// The rest of the pills should not be highlighted nor display connector creators
for (const { el, recordId } of pills) {
if (recordId !== 2) {
expect(el).not.toHaveClass(CLASSES.highlightedPill);
}
}
// Check that all connectors are in the expected hover state.
for (const [testKey, shouldBeHighlighted] of testMap.entries()) {
const connector = getConnector(connectorMap.get(testKey).id);
if (shouldBeHighlighted) {
expect(connector).toHaveClass(CLASSES.highlightedConnector);
} else {
expect(connector).not.toHaveClass(CLASSES.highlightedConnector);
}
expect(queryAll(SELECTORS.connectorStrokeButton, { root: connector })).toHaveCount(0);
}
});
test("Hovering a connector should cause the connected pills to get highlighted.", async () => {
await mountGanttView(ganttViewParams);
expect(SELECTORS.highlightedConnector).toHaveCount(0);
expect(SELECTORS.highlightedPill).toHaveCount(0);
await contains(getConnector(1)).hover();
expect(SELECTORS.highlightedConnector).toHaveCount(1);
expect(SELECTORS.highlightedPill).toHaveCount(2);
});
test("Connectors are displayed behind pills, except on hover.", async () => {
const getZIndex = (el) => Number(getComputedStyle(el).zIndex) || 0;
await mountGanttView(ganttViewParams);
expect(getZIndex(getPillWrapper("Task 2"))).toBeGreaterThan(getZIndex(getConnector(1)));
await contains(getConnector(1)).hover();
expect(getZIndex(getPillWrapper("Task 2"))).toBeLessThan(getZIndex(getConnector(1)));
});
test("Create a connector from the gantt view.", async () => {
onRpc("write", ({ args, method }) => expect.step([method, args]));
await mountGanttView(ganttViewParams);
// Explicitly shows the connector creator wrapper since its "display: none"
// disappears on native CSS hover, which cannot be programatically emulated.
const rightWrapper = queryFirst(SELECTORS.connectorCreatorWrapper);
rightWrapper.classList.add("d-block");
await contains(
`${SELECTORS.connectorCreatorWrapper} ${SELECTORS.connectorCreatorBullet}:first`
).dragAndDrop(getPill("Task 2"));
expect.verifySteps([["write", [[2], { depend_on_ids: [[4, 3, false]] }]]]);
});
test("Create a connector from the gantt view: going fast", async () => {
await mountGanttView({
...ganttViewParams,
domain: [["id", "in", [1, 3]]],
});
// Explicitly shows the connector creator wrapper since its "display: none"
// disappears on native CSS hover, which cannot be programatically emulated.
const rightWrapper = queryFirst(SELECTORS.connectorCreatorWrapper, {
root: getPillWrapper("Task 1"),
});
rightWrapper.classList.add("d-block");
const connectorBullet = queryFirst(SELECTORS.connectorCreatorBullet, { root: rightWrapper });
const bulletRect = queryRect(connectorBullet);
const initialPosition = {
x: Math.floor(bulletRect.left + bulletRect.width / 2), // floor to avoid sub-pixel positioning
y: Math.floor(bulletRect.top + bulletRect.height / 2), // floor to avoid sub-pixel positioning
};
await pointerDown(connectorBullet, {
position: { clientX: initialPosition.x, clientY: initialPosition.y },
});
// Here we simulate a fast move, using arbitrary values.
const currentPosition = {
x: Math.floor(initialPosition.x + 123), // floor to avoid sub-pixel positioning
y: Math.floor(initialPosition.y + 12), // floor to avoid sub-pixel positioning
};
await hover(SELECTORS.cellContainer, {
position: { clientX: currentPosition.x, clientY: currentPosition.y },
});
await animationFrame();
// Then we check that the connector stroke is correctly positioned.
const connectorStroke = queryOne(SELECTORS.connectorStroke, { root: getConnector("new") });
expect(connectorStroke).toHaveRect({
top: initialPosition.y,
right: currentPosition.x,
bottom: currentPosition.y,
left: initialPosition.x,
});
});
test("Connectors should be rendered if connected pill is not visible", async () => {
// We first need to bump all the ids for users 2, 3 and 4 to make them disappear.
for (const record of ResUsers._records.slice(1)) {
record.id += 1000;
}
for (const record of ProjectTask._records) {
record.user_ids = record.user_ids.map((id) => (id > 1 ? id + 1000 : id));
}
// Generate a lot of users so that the connectors are far beyond the visible
// viewport, hence generating fake extra pills to render the connectors.
for (let i = 0; i < 100; i++) {
const id = 100 + i;
ResUsers._records.push({ id, name: `User ${id}` });
ProjectTask._records.push({
id,
name: `Task ${id}`,
planned_date_begin: "2021-10-11 18:30:00",
date_deadline: "2021-10-11 19:29:59",
user_ids: [id],
depend_on_ids: [],
});
}
ProjectTask._records[12].user_ids = [199];
await mountGanttView(ganttViewParams);
expect(queryAll(SELECTORS.connector, { visible: true })).toHaveCount(13);
});
test("No display of resize handles when creating a connector", async () => {
await mountGanttView(ganttViewParams);
// Explicitly shows the connector creator wrapper since its "display: none"
// disappears on native CSS hover, which cannot be programatically emulated.
const rightWrapper = queryFirst(SELECTORS.connectorCreatorWrapper);
rightWrapper.classList.add("d-block");
// Creating a connector and hover another pill while dragging it
const { cancel, moveTo } = await contains(SELECTORS.connectorCreatorBullet, {
root: rightWrapper,
}).drag();
await moveTo(getPill("Task 2"));
expect(SELECTORS.resizeHandle).toHaveCount(0);
await cancel();
});
test("Renderer in connect mode when creating a connector", async () => {
await mountGanttView(ganttViewParams);
// Explicitly shows the connector creator wrapper since its "display: none"
// disappears on native CSS hover, which cannot be programatically emulated.
const rightWrapper = queryFirst(SELECTORS.connectorCreatorWrapper);
rightWrapper.classList.add("d-block");
// Creating a connector and hover another pill while dragging it
const { cancel, moveTo } = await contains(SELECTORS.connectorCreatorBullet, {
root: rightWrapper,
}).drag();
await moveTo(getPill("Task 2"));
expect(SELECTORS.renderer).toHaveClass("o_connect");
await cancel();
});
test("Connector creators of initial pill are highlighted when creating a connector", async () => {
await mountGanttView(ganttViewParams);
// Explicitly shows the connector creator wrapper since its "display: none"
// disappears on native CSS hover, which cannot be programatically emulated.
const rightWrapper = queryFirst`${SELECTORS.pillWrapper} ${SELECTORS.connectorCreatorWrapper}`;
rightWrapper.classList.add("d-block");
// Creating a connector and hover another pill while dragging it
const { cancel, moveTo } = await contains(SELECTORS.connectorCreatorBullet, {
root: rightWrapper,
}).drag();
await moveTo(getPill("Task 2"));
expect(`${SELECTORS.pillWrapper}:first`).toHaveClass(CLASSES.lockedConnectorCreator);
await cancel();
});
test("Connector creators of hovered pill are highlighted when creating a connector", async () => {
await mountGanttView(ganttViewParams);
// Explicitly shows the connector creator wrapper since its "display: none"
// disappears on native CSS hover, which cannot be programatically emulated.
const rightWrapper = queryFirst(SELECTORS.connectorCreatorWrapper);
rightWrapper.classList.add("d-block");
// Creating a connector and hover another pill while dragging it
const { cancel, moveTo } = await contains(SELECTORS.connectorCreatorBullet, {
root: rightWrapper,
}).drag();
const destinationWrapper = getPillWrapper("Task 2");
const destinationPill = queryFirst(SELECTORS.pill, { root: destinationWrapper });
await moveTo(destinationPill);
// moveTo only triggers a pointerenter event on destination pill,
// a pointermove event is still needed to highlight it
await contains(destinationPill).hover();
expect(destinationWrapper).toHaveClass(CLASSES.highlightedConnectorCreator);
await cancel();
});
test("Switch to full-size browser: the connections between pills should be diplayed", async () => {
await resize({ width: 375, height: 667 });
await mountGanttView(ganttViewParams);
// Mobile view
expect("svg.o_gantt_connector").toHaveCount(0, {
message: "Gantt connectors should not be visible in small/mobile view",
});
// Resizing browser to leave mobile view
await resize({ width: 1366, height: 768 });
await runAllTimers();
expect("svg.o_gantt_connector").toHaveCount(22, {
message: "Gantt connectors should be visible when switching to desktop view",
});
});
test("Connect two very distant pills", async () => {
ProjectTask._records = [
ProjectTask._records[0],
{
id: 2,
name: "Task 2",
planned_date_begin: "2021-11-18 08:00:00",
date_deadline: "2021-11-18 16:00:00",
user_ids: [2],
depend_on_ids: [],
},
];
onRpc("write", ({ args }) => {
expect.step(JSON.stringify(args));
});
await mountGanttView({
...ganttViewParams,
context: {
default_start_date: "2021-10-01",
default_stop_date: "2021-11-30",
},
});
expect(SELECTORS.connector).toHaveCount(0);
// Explicitly shows the connector creator wrapper since its "display: none"
// disappears on native CSS hover, which cannot be programatically emulated.
const rightWrapper = queryFirst(SELECTORS.connectorCreatorWrapper);
rightWrapper.classList.add("d-block");
// Creating a connector and hover another pill while dragging it
const { drop, moveTo } = await contains(SELECTORS.connectorCreatorBullet, {
root: rightWrapper,
}).drag();
const selector = `${SELECTORS.pill}:contains('Task 2')`;
expect(selector).toHaveCount(0);
await moveTo(SELECTORS.pill, { relative: true, position: { x: 1500 } });
await advanceFrame(200);
await drop(selector);
expect.verifySteps([`[[2],{"depend_on_ids":[[4,1,false]]}]`]);
expect(SELECTORS.connector).toHaveCount(1);
});

View File

@ -0,0 +1,83 @@
import { hover, queryFirst } from "@odoo/hoot-dom";
import { runAllTimers } from "@odoo/hoot-mock";
import { contains } from "@web/../tests/web_test_helpers";
import { SELECTORS } from "./web_gantt_test_helpers";
/**
* @param {import("@odoo/hoot-dom").Target} target
* @param {"remove" | "reschedule-forward" | "reschedule-backward"} button
*/
export async function clickConnectorButton(target, button) {
await hover(target);
await runAllTimers();
let element = null;
switch (button) {
case "remove": {
element = queryFirst(SELECTORS.connectorRemoveButton, { root: target });
break;
}
case "reschedule-backward": {
element = queryFirst(`${SELECTORS.connectorRescheduleButton}:first-of-type`, {
root: target,
});
break;
}
case "reschedule-forward": {
element = queryFirst(`${SELECTORS.connectorRescheduleButton}:last-of-type`, {
root: target,
});
break;
}
}
return contains(element).click();
}
/**
* @param {number | "new"} id
*/
export function getConnector(id) {
if (!/^__connector__/.test(id)) {
id = `__connector__${id}`;
}
return queryFirst(
`${SELECTORS.cellContainer} ${SELECTORS.connector}[data-connector-id='${id}']`
);
}
export function getConnectorMap(renderer) {
/**
* @param {PillId} pillId
*/
const getIdAndUserIdFromPill = (pillId) => {
/** @type {[ResId, ResId]} */
const result = [renderer.pills[pillId]?.record.id || false, false];
if (result[0]) {
const pills = renderer.mappingRecordToPillsByRow[result[0]]?.pills;
if (pills) {
const pillEntry = Object.entries(pills).find((e) => e[1].id === pillId);
if (pillEntry) {
const [firstGroup] = JSON.parse(pillEntry[0]);
if (firstGroup.user_ids?.length) {
result[1] = firstGroup.user_ids[0] || false;
}
}
}
}
return result;
};
/** @type {Map<ConnectorTaskIds, ConnectorProps>} */
const connectorMap = new Map();
for (const connector of Object.values(renderer.connectors)) {
const { sourcePillId, targetPillId } = renderer.mappingConnectorToPills[connector.id];
if (!sourcePillId || !targetPillId) {
continue;
}
const key = JSON.stringify([
...getIdAndUserIdFromPill(sourcePillId),
...getIdAndUserIdFromPill(targetPillId),
]);
connectorMap.set(key, connector);
}
return connectorMap;
}

View File

@ -0,0 +1,182 @@
import { defineModels, fields, models } from "@web/../tests/web_test_helpers";
export const TASKS_STAGE_SELECTION = [
["todo", "To Do"],
["in_progress", "In Progress"],
["done", "Done"],
["cancel", "Cancelled"],
];
export class Project extends models.Model {
name = fields.Char();
_records = [
{ id: 1, name: "Project 1" },
{ id: 2, name: "Project 2" },
];
}
export class ResUsers extends models.Model {
_name = "res.users";
name = fields.Char();
has_group() {
return true;
}
_records = [
{ id: 1, name: "User 1" },
{ id: 2, name: "User 2" },
];
}
export class Stage extends models.Model {
name = fields.Char();
sequence = fields.Integer();
_records = [
{
id: 1,
name: "in_progress",
sequence: 2,
},
{
id: 2,
name: "todo",
sequence: 1,
},
{
id: 3,
name: "cancel",
sequence: 4,
},
{
id: 4,
name: "done",
sequence: 3,
},
];
}
export class Tasks extends models.Model {
name = fields.Char();
start = fields.Datetime({ string: "Start Date" });
stop = fields.Datetime({ string: "Stop Date" });
allocated_hours = fields.Float({ string: "Allocated Hours" });
stage = fields.Selection({
selection: TASKS_STAGE_SELECTION,
});
color = fields.Integer();
progress = fields.Integer();
exclude = fields.Boolean({ string: "Excluded from Consolidation" });
project_id = fields.Many2one({ relation: "project" });
stage_id = fields.Many2one({ relation: "stage" });
user_id = fields.Many2one({ string: "Assign To", relation: "res.users" });
_records = [
{
id: 1,
name: "Task 1",
start: "2018-11-30 18:30:00",
stop: "2018-12-31 18:29:59",
stage: "todo",
stage_id: 1,
project_id: 1,
user_id: 1,
color: 0,
progress: 0,
},
{
id: 2,
name: "Task 2",
start: "2018-12-17 11:30:00",
stop: "2018-12-22 06:29:59",
stage: "done",
stage_id: 4,
project_id: 1,
user_id: 2,
color: 2,
progress: 30,
},
{
id: 3,
name: "Task 3",
start: "2018-12-27 06:30:00",
stop: "2019-01-03 06:29:59",
stage: "cancel",
stage_id: 3,
project_id: 1,
user_id: 2,
color: 10,
progress: 60,
},
{
id: 4,
name: "Task 4",
start: "2018-12-20 02:30:00",
stop: "2018-12-20 06:29:59",
stage: "in_progress",
stage_id: 3,
project_id: 1,
user_id: 1,
color: 1,
exclude: false,
},
{
id: 5,
name: "Task 5",
start: "2018-11-08 01:53:10",
stop: "2018-12-04 01:34:34",
stage: "done",
stage_id: 2,
project_id: 2,
user_id: 1,
color: 2,
progress: 100,
exclude: true,
},
{
id: 6,
name: "Task 6",
start: "2018-11-19 23:00:00",
stop: "2018-11-20 04:21:01",
stage: "in_progress",
stage_id: 4,
project_id: 2,
user_id: 1,
color: 1,
},
{
id: 7,
name: "Task 7",
start: "2018-12-20 12:30:12",
stop: "2018-12-20 18:29:59",
stage: "cancel",
stage_id: 1,
project_id: 2,
user_id: 2,
color: 10,
progress: 80,
},
{
id: 8,
name: "Task 8",
start: "2020-03-28 06:30:12",
stop: "2020-03-28 18:29:59",
stage: "in_progress",
stage_id: 1,
project_id: 2,
user_id: 2,
color: 10,
progress: 80,
},
];
_views = {
search: /* xml */ `<search />`,
};
}
export function defineGanttModels() {
defineModels([Stage, Project, ResUsers, Tasks]);
}

View File

@ -0,0 +1,37 @@
import { makeKwArgs } from "@web/../tests/web_test_helpers";
import { registry } from "@web/core/registry";
function _mockGetGanttData({ kwargs, model }) {
kwargs = makeKwArgs(kwargs);
const lazy = !kwargs.limit && !kwargs.offset && kwargs.groupby.length === 1;
const { groups, length } = this.env[model].web_read_group({
...kwargs,
lazy,
fields: ["__record_ids:array_agg(id)"],
});
const recordIds = [];
for (const group of groups) {
recordIds.push(...(group.__record_ids || []));
}
const { records } = this.env[model].web_search_read(
[["id", "in", recordIds]],
kwargs.read_specification,
makeKwArgs({ context: kwargs.context })
);
const unavailabilities = {};
for (const fieldName of kwargs.unavailability_fields || []) {
unavailabilities[fieldName] = {};
}
const progress_bars = {};
for (const fieldName of kwargs.progress_bar_fields || []) {
progress_bars[fieldName] = {};
}
return { groups, length, records, unavailabilities, progress_bars };
}
registry.category("mock_rpc").add("get_gantt_data", _mockGetGanttData);

View File

@ -0,0 +1,527 @@
import { beforeEach, describe, expect, test } from "@odoo/hoot";
import { mockDate } from "@odoo/hoot-mock";
import { onRpc } from "@web/../tests/web_test_helpers";
import { defineGanttModels } from "./gantt_mock_models";
import {
SELECTORS,
getCellColorProperties,
getGridContent,
mountGanttView,
} from "./web_gantt_test_helpers";
describe.current.tags("desktop");
defineGanttModels();
beforeEach(() => mockDate("2018-12-20T08:00:00", +1));
test("empty sparse gantt", async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" display_mode="sparse" />`,
domain: [["id", "=", 0]],
});
const { viewTitle, range, columnHeaders, rows } = getGridContent();
expect(viewTitle).toBe("Gantt View");
expect(range).toBe("From: 12/01/2018 to: 02/28/2019");
expect(columnHeaders).toHaveLength(34);
expect(rows).toEqual([{ title: "" }]);
expect(SELECTORS.noContentHelper).toHaveCount(0);
});
test("sparse gantt", async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" display_mode="sparse" />`,
domain: [["id", "=", 1]],
});
const { viewTitle, range, columnHeaders, rows } = getGridContent();
expect(viewTitle).toBe("Gantt View");
expect(range).toBe("From: 12/01/2018 to: 02/28/2019");
expect(columnHeaders).toHaveLength(34);
expect(rows).toEqual([
{
pills: [
{
colSpan: "Out of bounds (1) -> 31 December 2018",
level: 0,
title: "Task 1",
},
],
title: "Task 1",
},
]);
expect(SELECTORS.noContentHelper).toHaveCount(0);
});
test("sparse grouped gantt", async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" display_mode="sparse" />`,
groupBy: ["stage"],
});
const { viewTitle, range, columnHeaders, rows } = getGridContent();
expect(viewTitle).toBe("Gantt View");
expect(range).toBe("From: 12/01/2018 to: 02/28/2019");
expect(columnHeaders).toHaveLength(34);
expect(rows).toEqual([
{
isGroup: true,
pills: [
{
colSpan: "Out of bounds (1) -> 31 December 2018",
title: "1",
},
],
title: "To Do",
},
{
pills: [
{
colSpan: "Out of bounds (1) -> 31 December 2018",
level: 0,
title: "Task 1",
},
],
title: "Task 1",
},
{
isGroup: true,
pills: [
{
colSpan: "20 December 2018 -> 20 (1/2) December 2018",
title: "1",
},
],
title: "In Progress",
},
{
pills: [
{
colSpan: "20 December 2018 -> 20 (1/2) December 2018",
level: 0,
title: "Task 4",
},
],
title: "Task 4",
},
{
isGroup: true,
pills: [
{
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
title: "1",
},
],
title: "Done",
},
{
title: "Task 5",
},
{
pills: [
{
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
level: 0,
title: "Task 2",
},
],
title: "Task 2",
},
{
isGroup: true,
pills: [
{
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
title: "1",
},
{
colSpan: "27 December 2018 -> 03 (1/2) January 2019",
title: "1",
},
],
title: "Cancelled",
},
{
pills: [
{
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
level: 0,
title: "Task 7",
},
],
title: "Task 7",
},
{
pills: [
{
colSpan: "27 December 2018 -> 03 (1/2) January 2019",
level: 0,
title: "Task 3",
},
],
title: "Task 3",
},
]);
expect(SELECTORS.noContentHelper).toHaveCount(0);
});
test("sparse gantt with consolidation", async () => {
await mountGanttView({
resModel: "tasks",
arch: `
<gantt
date_start="start"
date_stop="stop"
consolidation="progress"
consolidation_max="{'user_id': 100}"
display_mode="sparse"
/>
`,
groupBy: ["stage"],
});
const { viewTitle, range, columnHeaders, rows } = getGridContent();
expect(viewTitle).toBe("Gantt View");
expect(range).toBe("From: 12/01/2018 to: 02/28/2019");
expect(columnHeaders).toHaveLength(34);
expect(rows).toEqual([
{
isGroup: true,
pills: [
{
colSpan: "Out of bounds (1) -> 31 December 2018",
title: "1",
},
],
title: "To Do",
},
{
pills: [
{
colSpan: "Out of bounds (1) -> 31 December 2018",
level: 0,
title: "Task 1",
},
],
title: "Task 1",
},
{
isGroup: true,
pills: [
{
colSpan: "20 December 2018 -> 20 (1/2) December 2018",
title: "1",
},
],
title: "In Progress",
},
{
pills: [
{
colSpan: "20 December 2018 -> 20 (1/2) December 2018",
level: 0,
title: "Task 4",
},
],
title: "Task 4",
},
{
isGroup: true,
pills: [
{
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
title: "1",
},
],
title: "Done",
},
{
title: "Task 5",
},
{
pills: [
{
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
level: 0,
title: "Task 2",
},
],
title: "Task 2",
},
{
isGroup: true,
pills: [
{
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
title: "1",
},
{
colSpan: "27 December 2018 -> 03 (1/2) January 2019",
title: "1",
},
],
title: "Cancelled",
},
{
pills: [
{
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
level: 0,
title: "Task 7",
},
],
title: "Task 7",
},
{
pills: [
{
colSpan: "27 December 2018 -> 03 (1/2) January 2019",
level: 0,
title: "Task 3",
},
],
title: "Task 3",
},
]);
expect(SELECTORS.noContentHelper).toHaveCount(0);
});
test("sparse gantt with a group expand", async () => {
onRpc("get_gantt_data", () => {
return {
groups: [
{
stage: "todo",
__record_ids: [],
},
{
stage: "in_progress",
__record_ids: [4],
},
],
length: 2,
records: [
{
display_name: "Task 4",
id: 4,
progress: 0,
stage: "in_progress",
start: "2018-12-20 02:30:00",
stop: "2018-12-20 06:29:59",
},
],
};
});
await mountGanttView({
resModel: "tasks",
arch: `
<gantt
date_start="start"
date_stop="stop"
display_mode="sparse"
/>
`,
groupBy: ["stage"],
});
const { viewTitle, range, columnHeaders, rows } = getGridContent();
expect(viewTitle).toBe("Gantt View");
expect(range).toBe("From: 12/01/2018 to: 02/28/2019");
expect(columnHeaders).toHaveLength(34);
expect(rows).toEqual([
{
isGroup: true,
title: "To Do",
},
{
title: "",
},
{
isGroup: true,
pills: [
{
colSpan: "20 December 2018 -> 20 (1/2) December 2018",
title: "1",
},
],
title: "In Progress",
},
{
pills: [
{
colSpan: "20 December 2018 -> 20 (1/2) December 2018",
level: 0,
title: "Task 4",
},
],
title: "Task 4",
},
]);
expect(SELECTORS.noContentHelper).toHaveCount(0);
});
test("empty sparse gantt with unavailabilities", async () => {
const unavailabilities = [
{
start: "2018-12-18 23:00:00",
stop: "2018-12-19 23:00:00",
},
];
onRpc("get_gantt_data", async ({ parent, kwargs }) => {
expect.step("get_gantt_data");
const result = await parent();
expect(kwargs.unavailability_fields).toEqual([]);
result.unavailabilities.__default = { false: unavailabilities };
return result;
});
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" display_mode="sparse" display_unavailability="1" />`,
domain: [["id", "=", 0]],
});
expect.verifySteps(["get_gantt_data"]);
// Full unavailability
expect(getCellColorProperties("19 December 2018")).toEqual([
"--Gantt__DayOff-background-color",
]);
});
test("sparse gantt with unavailabilities", async () => {
const unavailabilities = [
{
start: "2018-12-18 23:00:00",
stop: "2018-12-19 23:00:00",
},
];
onRpc("get_gantt_data", async ({ parent, kwargs }) => {
expect.step("get_gantt_data");
const result = await parent();
expect(kwargs.unavailability_fields).toEqual([]);
result.unavailabilities.__default = { false: unavailabilities };
return result;
});
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" display_mode="sparse" display_unavailability="1" />`,
domain: [["id", "=", 1]],
});
expect.verifySteps(["get_gantt_data"]);
// Full unavailability
expect(getCellColorProperties("19 December 2018")).toEqual([
"--Gantt__DayOff-background-color",
]);
});
test("sparse grouped gantt with unavailabilities", async () => {
const unavailabilities = [
{
start: "2018-12-18 23:00:00",
stop: "2018-12-19 23:00:00",
},
];
onRpc("get_gantt_data", async ({ parent, kwargs }) => {
expect.step("get_gantt_data");
const result = await parent();
expect(kwargs.unavailability_fields).toEqual(["user_id"]);
result.unavailabilities.user_id = { 1: unavailabilities };
return result;
});
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" display_mode="sparse" display_unavailability="1" />`,
groupBy: ["user_id"],
});
expect.verifySteps(["get_gantt_data"]);
// Full unavailability
expect(getCellColorProperties("19 December 2018", "Task 5")).toEqual([
"--Gantt__DayOff-background-color",
]);
});
test("sparse gantt with consolidation with unavailabilities", async () => {
const unavailabilities = [
{
start: "2018-12-18 23:00:00",
stop: "2018-12-19 23:00:00",
},
];
onRpc("get_gantt_data", async ({ parent, kwargs }) => {
expect.step("get_gantt_data");
const result = await parent();
expect(kwargs.unavailability_fields).toEqual(["user_id"]);
result.unavailabilities.user_id = { 1: unavailabilities };
return result;
});
await mountGanttView({
resModel: "tasks",
arch: `
<gantt
date_start="start"
date_stop="stop"
consolidation="progress"
consolidation_max="{'user_id': 100}"
display_mode="sparse"
display_unavailability="1"
/>
`,
groupBy: ["user_id"],
});
expect.verifySteps(["get_gantt_data"]);
// Full unavailability
expect(getCellColorProperties("19 December 2018", "", { num: 2 })).toEqual([
"--Gantt__DayOff-background-color",
]);
});
test("sparse gantt with a group expand and unavailabilities", async () => {
const unavailabilities = [
{
start: "2018-12-18 23:00:00",
stop: "2018-12-19 23:00:00",
},
];
onRpc("get_gantt_data", ({ kwargs }) => {
expect.step("get_gantt_data");
expect(kwargs.unavailability_fields).toEqual(["user_id"]);
return {
groups: [
{
user_id: [1, "Charles"],
__record_ids: [],
},
{
user_id: [2, "Louis"],
__record_ids: [4],
},
],
length: 2,
records: [
{
display_name: "Task 4",
id: 4,
progress: 0,
user_id: 1,
start: "2018-12-20 02:30:00",
stop: "2018-12-20 06:29:59",
},
],
unavailabilities: {
user_id: { 1: unavailabilities, 2: [] },
},
};
});
await mountGanttView({
resModel: "tasks",
arch: `
<gantt
date_start="start"
date_stop="stop"
display_mode="sparse"
display_unavailability="1"
/>
`,
groupBy: ["user_id"],
});
expect.verifySteps(["get_gantt_data"]);
expect(getCellColorProperties("19 December 2018", "", { num: 2 })).toEqual([
"--Gantt__DayOff-background-color",
]);
});

View File

@ -0,0 +1,986 @@
import { beforeEach, describe, expect, test } from "@odoo/hoot";
import { click, leave, queryAll, queryOne, queryFirst } from "@odoo/hoot-dom";
import { animationFrame, mockDate } from "@odoo/hoot-mock";
import { contains, defineParams, onRpc } from "@web/../tests/web_test_helpers";
import { Tasks, defineGanttModels } from "./gantt_mock_models";
import {
SELECTORS,
clickCell,
getActiveScale,
getCell,
getCellColorProperties,
getGridContent,
getPill,
getPillWrapper,
hoverGridCell,
mountGanttView,
resizePill,
} from "./web_gantt_test_helpers";
describe.current.tags("desktop");
defineGanttModels();
beforeEach(() => {
mockDate("2018-12-20T07:00:00", +1);
defineParams({
lang_parameters: {
time_format: "%I:%M:%S",
},
});
});
test("create attribute", async () => {
Tasks._views.list = `<list><field name="name"/></list>`;
Tasks._views.search = `<search><field name="name"/></search>`;
onRpc("has_group", () => true);
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" create="0"/>`,
});
expect(".o_dialog").toHaveCount(0);
await hoverGridCell("06 December 2018");
await clickCell("06 December 2018");
expect(".o_dialog").toHaveCount(1);
expect(".modal-title").toHaveText("Plan");
expect(".o_create_button").toHaveCount(0);
});
test("plan attribute", async () => {
Tasks._views.form = `<form><field name="name"/></form>`;
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" plan="0"/>`,
});
expect(".o_dialog").toHaveCount(0);
await hoverGridCell("06 December 2018");
await clickCell("06 December 2018");
expect(".o_dialog").toHaveCount(1);
expect(".modal-title").toHaveText("Create");
});
test("edit attribute", async () => {
Tasks._views.form = `<form><field name="name"/></form>`;
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" edit="0"/>`,
});
expect(SELECTORS.resizable).toHaveCount(0);
expect(SELECTORS.draggable).toHaveCount(0);
expect(getGridContent().rows).toEqual([
{
pills: [
{
title: "Task 5",
level: 0,
colSpan: "Out of bounds (1) -> 04 (1/2) December 2018",
},
{ title: "Task 1", level: 1, colSpan: "Out of bounds (1) -> 31 December 2018" },
{
title: "Task 2",
level: 0,
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
},
{
title: "Task 4",
level: 2,
colSpan: "20 December 2018 -> 20 (1/2) December 2018",
},
{
title: "Task 7",
level: 2,
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
},
{ title: "Task 3", level: 0, colSpan: "27 December 2018 -> 03 (1/2) January 2019" },
],
},
]);
await contains(getPill("Task 1")).click();
expect(`.o_popover button.btn-primary`).toHaveText(/view/i);
await contains(`.o_popover button.btn-primary`).click();
expect(".modal .o_form_readonly").toHaveCount(1);
});
test("total_row attribute", async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" total_row="1"/>`,
});
const { rows } = getGridContent();
expect(rows).toEqual([
{
pills: [
{
title: "Task 5",
level: 0,
colSpan: "Out of bounds (1) -> 04 (1/2) December 2018",
},
{ title: "Task 1", level: 1, colSpan: "Out of bounds (1) -> 31 December 2018" },
{
title: "Task 2",
level: 0,
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
},
{
title: "Task 4",
level: 2,
colSpan: "20 December 2018 -> 20 (1/2) December 2018",
},
{
title: "Task 7",
level: 2,
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
},
{ title: "Task 3", level: 0, colSpan: "27 December 2018 -> 03 (1/2) January 2019" },
],
},
{
isTotalRow: true,
pills: [
{
colSpan: "Out of bounds (1) -> 04 (1/2) December 2018",
level: 0,
title: "2",
},
{
colSpan: "04 (1/2) December 2018 -> 17 (1/2) December 2018",
level: 0,
title: "1",
},
{
colSpan: "17 (1/2) December 2018 -> 19 December 2018",
level: 0,
title: "2",
},
{
colSpan: "20 December 2018 -> 20 (1/2) December 2018",
level: 0,
title: "3",
},
{
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
level: 0,
title: "3",
},
{
colSpan: "21 December 2018 -> 22 (1/2) December 2018",
level: 0,
title: "2",
},
{
colSpan: "22 (1/2) December 2018 -> 26 December 2018",
level: 0,
title: "1",
},
{
colSpan: "27 December 2018 -> 31 December 2018",
level: 0,
title: "2",
},
{
colSpan: "01 January 2019 -> 03 (1/2) January 2019",
level: 0,
title: "1",
},
],
},
]);
});
test("default_scale attribute", async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" default_scale="day"/>`,
});
expect(getActiveScale()).toBe(5); // day scale
const { columnHeaders, range } = getGridContent();
expect(range).toBe("From: 12/20/2018 to: 12/22/2018");
expect(columnHeaders).toHaveLength(38);
});
test("scales attribute", async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" scales="month,day,trololo"/>`,
});
expect(queryOne(".o_gantt_renderer_controls input").max).toBe("1", {
message: "there are only 2 valid scales (starting from 0)",
});
expect(getActiveScale()).toBe(1);
});
test("precision attribute", async () => {
onRpc("write", ({ args }) => expect.step(args));
await mountGanttView({
resModel: "tasks",
arch: `
<gantt
date_start="start"
date_stop="stop"
precision="{'day': 'hour:quarter', 'week': 'day:half', 'month': 'day', 'year': 'month:quarter'}"
default_scale="day"
/>
`,
domain: [["id", "=", 7]],
});
// resize of a quarter
const drop = await resizePill(getPillWrapper("Task 7"), "end", 0.25, false);
await animationFrame();
expect(SELECTORS.resizeBadge).toHaveText("+15 minutes");
// manually trigger the drop to trigger a write
await drop();
await animationFrame();
expect(SELECTORS.resizeBadge).toHaveCount(0);
expect.verifySteps([[[7], { stop: "2018-12-20 18:44:59" }]]);
});
test("progress attribute", async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt string="Tasks" date_start="start" date_stop="stop" progress="progress"/>`,
groupBy: ["project_id"],
});
expect(`${SELECTORS.pill} .o_gantt_progress`).toHaveCount(3);
expect(
queryAll(SELECTORS.pill).map((el) => ({
text: el.innerText,
progress: el.querySelector(".o_gantt_progress")?.style?.width || null,
}))
).toEqual([
{ text: "Task 1", progress: null },
{ text: "Task 2", progress: "30%" },
{ text: "Task 4", progress: null },
{ text: "Task 3", progress: "60%" },
{ text: "Task 7", progress: "80%" },
]);
});
test("form_view_id attribute", async () => {
Tasks._views[["form", 42]] = `<form><field name="name"/></form>`;
onRpc("get_views", ({ kwargs }) => expect.step(["get_views", kwargs.views]));
await mountGanttView({
resModel: "tasks",
arch: `<gantt string="Tasks" date_start="start" date_stop="stop" form_view_id="42"/>`,
groupBy: ["project_id"],
});
await contains(queryFirst(SELECTORS.addButton + ":visible")).click();
expect(".modal .o_form_view").toHaveCount(1);
expect.verifySteps([
[
"get_views",
[
[123456789, "gantt"],
[987654321, "search"],
],
], // initial get_views
["get_views", [[42, "form"]]], // get_views when form view dialog opens
]);
});
test("decoration attribute", async () => {
await mountGanttView({
resModel: "tasks",
arch: `
<gantt date_start="start" date_stop="stop" decoration-info="stage == 'todo'">
<field name="stage"/>
'</gantt>
`,
});
expect(getPill("Task 1")).toHaveClass("decoration-info");
expect(getPill("Task 2")).not.toHaveClass("decoration-info");
});
test("decoration attribute with date", async () => {
mockDate("2018-12-19T12:00:00");
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" decoration-danger="start &lt; today"/>`,
});
expect(getPill("Task 1")).toHaveClass("decoration-danger");
expect(getPill("Task 2")).toHaveClass("decoration-danger");
expect(getPill("Task 5")).toHaveClass("decoration-danger");
expect(getPill("Task 3")).not.toHaveClass("decoration-danger");
expect(getPill("Task 4")).not.toHaveClass("decoration-danger");
expect(getPill("Task 7")).not.toHaveClass("decoration-danger");
});
test("consolidation feature", async () => {
await mountGanttView({
resModel: "tasks",
arch: `
<gantt
date_start="start"
date_stop="stop"
consolidation="progress"
consolidation_max="{'user_id': 100}"
consolidation_exclude="exclude"
progress="progress"
/>
`,
groupBy: ["user_id", "project_id", "stage"],
});
const { rows } = getGridContent();
expect(rows).toHaveLength(18);
expect(rows.filter((r) => r.isGroup)).toHaveLength(12);
expect(".o_gantt_row_headers").toHaveCount(1);
// Check grouped rows
expect(rows[0].isGroup).toBe(true);
expect(rows[0].title).toBe("User 1");
expect(rows[9].isGroup).toBe(true);
expect(rows[9].title).toBe("User 2");
// Consolidation
// 0 over the size of Task 5 (Task 5 is 100 but is excluded!) then 0 over the rest of Task 1, cut by Task 4 which has progress 0
expect(rows[0].pills).toEqual([
{ colSpan: "Out of bounds (8) -> 19 December 2018", title: "0" },
{ colSpan: "20 December 2018 -> 20 (1/2) December 2018", title: "0" },
{ colSpan: "20 (1/2) December 2018 -> 31 December 2018", title: "0" },
]);
// 30 over Task 2 until Task 7 then 110 (Task 2 (30) + Task 7 (80)) then 30 again until end of task 2 then 60 over Task 3
expect(rows[9].pills).toEqual([
{ colSpan: "17 (1/2) December 2018 -> 20 (1/2) December 2018", title: "30" },
{ colSpan: "20 (1/2) December 2018 -> 20 December 2018", title: "110" },
{ colSpan: "21 December 2018 -> 22 (1/2) December 2018", title: "30" },
{ colSpan: "27 December 2018 -> 03 (1/2) January 2019", title: "60" },
]);
const withStatus = [];
for (const el of queryAll(".o_gantt_consolidated_pill")) {
if (el.classList.contains("bg-success") || el.classList.contains("bg-danger")) {
withStatus.push({
title: el.title,
danger: el.classList.contains("border-danger"),
});
}
}
expect(withStatus).toEqual([
{ title: "0", danger: false },
{ title: "0", danger: false },
{ title: "0", danger: false },
{ title: "30", danger: false },
{ title: "110", danger: true },
{ title: "30", danger: false },
{ title: "60", danger: false },
]);
});
test("consolidation feature (single level)", async () => {
Tasks._views.form = `
<form>
<field name="name"/>
<field name="start"/>
<field name="stop"/>
<field name="project_id"/>
</form>
`;
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" consolidation="progress" consolidation_max="{'user_id': 100}" consolidation_exclude="exclude"/>`,
groupBy: ["user_id"],
});
const { rows, range } = getGridContent();
expect(range).toBe("From: 12/01/2018 to: 02/28/2019");
expect(".o_gantt_button_expand_rows").toHaveCount(1);
expect(rows).toEqual([
{
isGroup: true,
pills: [
{
colSpan: "Out of bounds (8) -> 19 December 2018",
title: "0",
},
{
colSpan: "20 December 2018 -> 20 (1/2) December 2018",
title: "0",
},
{
colSpan: "20 (1/2) December 2018 -> 31 December 2018",
title: "0",
},
],
title: "User 1",
},
{
pills: [
{
colSpan: "Out of bounds (1) -> 31 December 2018",
level: 1,
title: "Task 1",
},
{
colSpan: "20 December 2018 -> 20 (1/2) December 2018",
level: 0,
title: "Task 4",
},
],
title: "",
},
{
isGroup: true,
pills: [
{
colSpan: "17 (1/2) December 2018 -> 20 (1/2) December 2018",
title: "30",
},
{
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
title: "110",
},
{
colSpan: "21 December 2018 -> 22 (1/2) December 2018",
title: "30",
},
{
colSpan: "27 December 2018 -> 03 (1/2) January 2019",
title: "60",
},
],
title: "User 2",
},
{
pills: [
{
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
level: 0,
title: "Task 2",
},
{
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
level: 1,
title: "Task 7",
},
{
colSpan: "27 December 2018 -> 03 (1/2) January 2019",
level: 0,
title: "Task 3",
},
],
title: "",
},
]);
});
test("color attribute", async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" color="color"/>`,
});
expect(getPill("Task 1")).toHaveClass("o_gantt_color_0");
expect(getPill("Task 2")).toHaveClass("o_gantt_color_2");
});
test("color attribute in multi-level grouped", async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" color="color"/>`,
groupBy: ["user_id", "project_id"],
domain: [["id", "=", 1]],
});
expect(`${SELECTORS.pill}.o_gantt_consolidated_pill`).not.toHaveClass("o_gantt_color_0");
expect(`${SELECTORS.pill}:not(.o_gantt_consolidated_pill)`).toHaveClass("o_gantt_color_0");
});
test("color attribute on a many2one", async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" color="project_id"/>`,
});
expect(getPill("Task 1")).toHaveClass("o_gantt_color_1");
expect(`${SELECTORS.pill}.o_gantt_color_1`).toHaveCount(4);
expect(`${SELECTORS.pill}.o_gantt_color_2`).toHaveCount(2);
});
test(`Today style with unavailabilities ("week": "day:half")`, async () => {
const unavailabilities = [
{
start: "2018-12-18 10:00:00",
stop: "2018-12-20 14:00:00",
},
];
onRpc("get_gantt_data", async ({ parent }) => {
const result = await parent();
result.unavailabilities.__default = { false: unavailabilities };
return result;
});
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" display_unavailability="1" default_scale="week" scales="week" precision="{'week': 'day:half'}"/>`,
});
// Normal day / unavailability
expect(getCellColorProperties("18 W51 2018")).toEqual([
"--Gantt__Day-background-color",
"--Gantt__DayOff-background-color",
]);
// Full unavailability
expect(getCellColorProperties("19 W51 2018")).toEqual(["--Gantt__DayOff-background-color"]);
// Unavailability / today
expect(getCell("20 W51 2018")).toHaveClass("o_gantt_today");
expect(getCellColorProperties("20 W51 2018")).toEqual([
"--Gantt__DayOff-background-color",
"--Gantt__DayOffToday-background-color",
]);
});
test("Today style of group rows", async () => {
const unavailabilities = [
{
start: "2018-12-18 10:00:00",
stop: "2018-12-20 14:00:00",
},
];
Tasks._records = [Tasks._records[3]]; // id: 4
onRpc("get_gantt_data", async ({ parent }) => {
expect.step("get_gantt_data");
const result = await parent();
result.unavailabilities.project_id = { 1: unavailabilities };
return result;
});
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" display_unavailability="1" default_scale="week" scales="week" precision="{'week': 'day:half'}"/>`,
groupBy: ["user_id", "project_id"],
});
expect.verifySteps(["get_gantt_data"]);
// Normal group cell: open
let cell4 = getCell("19 W51 2018");
expect(cell4).not.toHaveClass("o_gantt_today");
expect(cell4).toHaveClass("o_group_open");
expect(cell4).toHaveStyle({
backgroundImage: "linear-gradient(rgb(249, 250, 251), rgb(234, 237, 241))",
});
// Today group cell: open
let cell5 = getCell("20 W51 2018");
expect(cell5).toHaveClass("o_gantt_today");
expect(cell5).toHaveClass("o_group_open");
expect(cell5).toHaveStyle({
backgroundImage: "linear-gradient(rgb(249, 250, 251), rgb(234, 237, 241))",
});
await contains(SELECTORS.group).click(); // fold group
await leave();
// Normal group cell: closed
cell4 = getCell("19 W51 2018");
expect(cell4).not.toHaveClass("o_gantt_today");
expect(cell4).not.toHaveClass("o_group_open");
expect(cell4).toHaveStyle({
backgroundImage: "linear-gradient(rgb(234, 237, 241), rgb(249, 250, 251))",
});
// Today group cell: closed
cell5 = getCell("20 W51 2018");
expect(cell5).toHaveClass("o_gantt_today");
expect(cell5).not.toHaveClass("o_group_open");
expect(cell5).toHaveStyle({ backgroundImage: "none" });
expect(cell5).toHaveStyle({ backgroundColor: "rgb(252, 250, 243)" });
});
test("style without unavailabilities", async () => {
mockDate("2018-12-05T02:00:00");
onRpc("get_gantt_data", ({ kwargs }) => {
expect.step("get_gantt_data");
expect(kwargs.unavailability_fields).toEqual([]);
});
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" display_unavailability="1"/>`,
});
expect.verifySteps(["get_gantt_data"]);
const cell5 = getCell("05 December 2018");
expect(cell5).toHaveClass("o_gantt_today");
expect(cell5).toHaveAttribute("style", "grid-column:c9/c11;grid-row:r1/r5;");
const cell6 = getCell("06 December 2018");
expect(cell6).toHaveAttribute("style", "grid-column:c11/c13;grid-row:r1/r5;");
});
test(`Unavailabilities ("month": "day:half")`, async () => {
mockDate("2018-12-05T02:00:00");
const unavailabilities = [
{
start: "2018-12-05 09:30:00",
stop: "2018-12-07 08:00:00",
},
{
start: "2018-12-16 09:00:00",
stop: "2018-12-18 13:00:00",
},
];
onRpc("get_gantt_data", ({ model, kwargs, parent }) => {
expect.step("get_gantt_data");
expect(model).toBe("tasks");
expect(kwargs.unavailability_fields).toEqual([]);
expect(kwargs.start_date).toBe("2018-11-30 23:00:00");
expect(kwargs.stop_date).toBe("2019-02-28 23:00:00");
const result = parent();
result.unavailabilities = { __default: { false: unavailabilities } };
return result;
});
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" display_unavailability="1"/>`,
});
expect.verifySteps(["get_gantt_data"]);
expect(getCell("05 December 2018")).toHaveClass("o_gantt_today");
expect(getCellColorProperties("05 December 2018")).toEqual([
"--Gantt__DayOffToday-background-color",
"--Gantt__DayOff-background-color",
]);
expect(getCellColorProperties("06 December 2018")).toEqual([
"--Gantt__DayOff-background-color",
]);
expect(getCellColorProperties("07 December 2018")).toEqual([]);
expect(getCellColorProperties("16 December 2018")).toEqual([
"--Gantt__Day-background-color",
"--Gantt__DayOff-background-color",
]);
expect(getCellColorProperties("17 December 2018")).toEqual([
"--Gantt__DayOff-background-color",
]);
expect(getCellColorProperties("18 December 2018")).toEqual([
"--Gantt__DayOff-background-color",
"--Gantt__Day-background-color",
]);
});
test(`Unavailabilities ("day": "hours:quarter")`, async () => {
Tasks._records = [];
const unavailabilities = [
// in utc
{
start: "2018-12-20 08:15:00",
stop: "2018-12-20 08:30:00",
},
{
start: "2018-12-20 10:35:00",
stop: "2018-12-20 12:29:00",
},
{
start: "2018-12-20 20:15:00",
stop: "2018-12-20 20:50:00",
},
];
onRpc("get_gantt_data", ({ kwargs, parent }) => {
expect(kwargs.unavailability_fields).toEqual([]);
const result = parent();
result.unavailabilities = { __default: { false: unavailabilities } };
return result;
});
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" display_unavailability="1" default_scale="day" scales="day" precision="{'day': 'hours:quarter'}"/>`,
});
expect(getCellColorProperties("9am 20 December 2018")).toEqual([
"--Gantt__Day-background-color",
"--Gantt__DayOff-background-color",
"--Gantt__DayOff-background-color",
"--Gantt__Day-background-color",
"--Gantt__Day-background-color",
"--Gantt__Day-background-color",
]);
expect(getCellColorProperties("11am 20 December 2018")).toEqual([
"--Gantt__Day-background-color",
"--Gantt__Day-background-color",
"--Gantt__Day-background-color",
"--Gantt__Day-background-color",
"--Gantt__Day-background-color",
"--Gantt__DayOff-background-color",
]);
expect(getCellColorProperties("12pm 20 December 2018")).toEqual([
"--Gantt__DayOff-background-color",
]);
expect(getCellColorProperties("1pm 20 December 2018")).toEqual([
"--Gantt__DayOff-background-color",
"--Gantt__Day-background-color",
"--Gantt__Day-background-color",
"--Gantt__Day-background-color",
"--Gantt__Day-background-color",
"--Gantt__Day-background-color",
]);
expect(getCellColorProperties("9pm 20 December 2018")).toEqual([
"--Gantt__Day-background-color",
"--Gantt__DayOff-background-color",
"--Gantt__DayOff-background-color",
"--Gantt__DayOff-background-color",
"--Gantt__DayOff-background-color",
"--Gantt__Day-background-color",
]);
});
test("offset attribute", async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" offset="-4" default_scale="day"/>`,
});
const { range } = getGridContent();
expect(range).toBe("From: 12/16/2018 to: 12/18/2018", {
message: "gantt view should be set to 4 days before initial date",
});
});
test("default_group_by attribute", async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" default_group_by="user_id"/>`,
});
const { rows } = getGridContent();
expect(rows).toEqual([
{
title: "User 1",
pills: [
{
colSpan: "Out of bounds (1) -> 31 December 2018",
level: 1,
title: "Task 1",
},
{
colSpan: "20 December 2018 -> 20 (1/2) December 2018",
level: 0,
title: "Task 4",
},
],
},
{
title: "User 2",
pills: [
{
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
level: 0,
title: "Task 2",
},
{
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
level: 1,
title: "Task 7",
},
{
colSpan: "27 December 2018 -> 03 (1/2) January 2019",
level: 0,
title: "Task 3",
},
],
},
]);
});
test("default_group_by attribute with groupBy", async () => {
// The default_group_by attribute should be ignored if a groupBy is given.
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" default_group_by="user_id"/>`,
groupBy: ["project_id"],
});
const { rows } = getGridContent();
expect(rows).toEqual([
{
title: "Project 1",
pills: [
{
colSpan: "Out of bounds (1) -> 31 December 2018",
level: 0,
title: "Task 1",
},
{
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
level: 1,
title: "Task 2",
},
{
colSpan: "20 December 2018 -> 20 (1/2) December 2018",
level: 2,
title: "Task 4",
},
{
colSpan: "27 December 2018 -> 03 (1/2) January 2019",
level: 1,
title: "Task 3",
},
],
},
{
title: "Project 2",
pills: [
{
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
level: 0,
title: "Task 7",
},
],
},
]);
});
test("default_group_by attribute with 2 fields", async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" default_group_by="user_id,project_id"/>`,
});
const { rows } = getGridContent();
expect(rows).toEqual([
{
title: "User 1",
isGroup: true,
pills: [
{
colSpan: "Out of bounds (8) -> 19 December 2018",
title: "1",
},
{
colSpan: "20 December 2018 -> 20 (1/2) December 2018",
title: "2",
},
{
colSpan: "20 (1/2) December 2018 -> 31 December 2018",
title: "1",
},
],
},
{
title: "Project 1",
pills: [
{
colSpan: "Out of bounds (1) -> 31 December 2018",
level: 0,
title: "Task 1",
},
{
colSpan: "20 December 2018 -> 20 (1/2) December 2018",
level: 1,
title: "Task 4",
},
],
},
{
title: "Project 2",
},
{
title: "User 2",
isGroup: true,
pills: [
{
colSpan: "17 (1/2) December 2018 -> 20 (1/2) December 2018",
title: "1",
},
{
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
title: "2",
},
{
colSpan: "21 December 2018 -> 22 (1/2) December 2018",
title: "1",
},
{
colSpan: "27 December 2018 -> 03 (1/2) January 2019",
title: "1",
},
],
},
{
title: "Project 1",
pills: [
{
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
level: 0,
title: "Task 2",
},
{
colSpan: "27 December 2018 -> 03 (1/2) January 2019",
level: 0,
title: "Task 3",
},
],
},
{
title: "Project 2",
pills: [
{
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
level: 0,
title: "Task 7",
},
],
},
]);
});
test("default_range attribute", async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" default_range="day"/>`,
});
expect(getActiveScale()).toBe(2); // month scale
const { columnHeaders, range } = getGridContent();
expect(range).toBe("12/20/2018");
expect(columnHeaders).toHaveLength(1);
await click(SELECTORS.rangeMenuToggler);
await animationFrame();
const firstRangeMenuItem = queryFirst(`${SELECTORS.rangeMenu} .dropdown-item`);
expect(firstRangeMenuItem).toHaveClass("selected");
expect(firstRangeMenuItem).toHaveText("Today");
});
test("consolidation and unavailabilities", async () => {
const unavailabilities = [
{
start: "2018-12-18 10:00:00",
stop: "2018-12-20 14:00:00",
},
];
onRpc("get_gantt_data", async ({ parent, kwargs }) => {
expect.step("get_gantt_data");
const result = await parent();
expect(kwargs.unavailability_fields).toEqual(["user_id"]);
result.unavailabilities.user_id = { 1: unavailabilities };
return result;
});
await mountGanttView({
resModel: "tasks",
arch: `
<gantt
date_start="start"
date_stop="stop"
consolidation="progress"
consolidation_max="{'user_id': 100}"
consolidation_exclude="exclude"
display_unavailability="1"
/>
`,
groupBy: ["user_id"],
});
expect.verifySteps(["get_gantt_data"]);
// Normal day / unavailability
expect(getCellColorProperties("18 December 2018", "", { num: 2 })).toEqual([
"--Gantt__Day-background-color",
"--Gantt__DayOff-background-color",
]);
// Full unavailability
expect(getCellColorProperties("19 December 2018", "", { num: 2 })).toEqual([
"--Gantt__DayOff-background-color",
]);
// Unavailability / today
expect(getCell("20 December 2018")).toHaveClass("o_gantt_today");
expect(getCellColorProperties("20 December 2018", "", { num: 2 })).toEqual([
"--Gantt__DayOff-background-color",
"--Gantt__DayOffToday-background-color",
]);
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,454 @@
import { beforeEach, describe, expect, test } from "@odoo/hoot";
import { Deferred, animationFrame, mockDate } from "@odoo/hoot-mock";
import { click } from "@odoo/hoot-dom";
import { onPatched } from "@odoo/owl";
import {
onRpc,
patchWithCleanup,
toggleMenuItem,
toggleSearchBarMenu,
} from "@web/../tests/web_test_helpers";
import { GanttRenderer } from "@web_gantt/gantt_renderer";
import { Tasks, defineGanttModels } from "./gantt_mock_models";
import {
SELECTORS,
editPill,
ganttControlsChanges,
getActiveScale,
getCellColorProperties,
getGridContent,
getPillWrapper,
mountGanttView,
resizePill,
selectGanttRange,
setScale,
} from "./web_gantt_test_helpers";
describe.current.tags("desktop");
defineGanttModels();
beforeEach(() => mockDate("2018-12-20T08:00:00", +1));
test("concurrent scale switches return in inverse order", async () => {
let model;
patchWithCleanup(GanttRenderer.prototype, {
setup() {
super.setup(...arguments);
model = this.model;
onPatched(() => {
expect.step("patched");
});
},
});
let firstReloadProm = null;
let reloadProm = null;
onRpc("get_gantt_data", () => reloadProm);
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop"/>`,
});
expect.verifySteps(["patched"]);
let content = getGridContent();
expect(getActiveScale()).toBe(2);
expect(content.groupHeaders.map((gh) => gh.title)).toEqual(["December 2018", "January 2019"]);
expect(content.range).toBe("From: 12/01/2018 to: 02/28/2019");
expect(model.data.records).toHaveLength(6);
// switch to 'week' scale (this rpc will be delayed)
firstReloadProm = new Deferred();
reloadProm = firstReloadProm;
await setScale(4);
await ganttControlsChanges();
content = getGridContent();
expect(getActiveScale()).toBe(4);
expect(content.groupHeaders.map((gh) => gh.title)).toEqual(["December 2018", "January 2019"]);
expect(content.range).toBe("From: 12/01/2018 to: 02/28/2019");
expect(model.data.records).toHaveLength(6);
// switch to 'year' scale
reloadProm = null;
await setScale(0);
await ganttControlsChanges();
content = getGridContent();
expect(getActiveScale()).toBe(0);
expect(content.groupHeaders.map((gh) => gh.title)).toEqual(["2018", "2019"]);
expect(content.range).toBe("From: 12/01/2018 to: 02/28/2019");
expect(model.data.records).toHaveLength(6);
firstReloadProm.resolve();
await animationFrame();
content = getGridContent();
expect(getActiveScale()).toBe(0);
expect(content.groupHeaders.map((gh) => gh.title)).toEqual(["2018", "2019"]);
expect(content.range).toBe("From: 12/01/2018 to: 02/28/2019");
expect(model.data.records).toHaveLength(6);
expect.verifySteps(["patched"]);
});
test("concurrent scale switches return with gantt unavailabilities", async () => {
const unavailabilities = [
[{ start: "2018-12-10 23:00:00", stop: "2018-12-11 23:00:00" }],
[{ start: "2018-12-10 23:00:00", stop: "2018-12-11 23:00:00" }],
[
{ start: "2018-07-30 23:00:00", stop: "2018-08-31 23:00:00" },
{ start: "2018-12-10 23:00:00", stop: "2018-12-11 23:00:00" },
],
[{ start: "2018-07-30 23:00:00", stop: "2018-08-31 23:00:00" }],
];
let model;
patchWithCleanup(GanttRenderer.prototype, {
setup() {
super.setup(...arguments);
model = this.model;
onPatched(() => {
expect.step("patched");
});
},
});
let firstReloadProm = null;
let reloadProm = null;
onRpc("get_gantt_data", async ({ parent }) => {
const result = await parent();
result.unavailabilities.__default = { false: unavailabilities.shift() };
await reloadProm;
return result;
});
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" display_unavailability="true"/>`,
});
expect.verifySteps(["patched"]);
let content = getGridContent();
expect(getActiveScale()).toBe(2);
expect(content.range).toBe("From: 12/01/2018 to: 02/28/2019");
expect(content.groupHeaders.map((h) => h.title)).toEqual(["December 2018", "January 2019"]);
expect(model.data.records).toHaveLength(6);
expect(getCellColorProperties("08 December 2018")).toEqual([]);
expect(getCellColorProperties("11 December 2018")).toEqual([
"--Gantt__DayOff-background-color",
]);
// switch to 'week' scale (this rpc will be delayed)
firstReloadProm = new Deferred();
reloadProm = firstReloadProm;
await setScale(4);
await ganttControlsChanges();
content = getGridContent();
expect(getActiveScale()).toBe(4);
expect(content.range).toBe("From: 12/01/2018 to: 02/28/2019");
expect(content.groupHeaders.map((h) => h.title)).toEqual(["December 2018", "January 2019"]);
expect(model.data.records).toHaveLength(6);
expect(getCellColorProperties("08 December 2018")).toEqual([]);
expect(getCellColorProperties("11 December 2018")).toEqual([
"--Gantt__DayOff-background-color",
]);
// switch to 'year' scale
reloadProm = null;
await setScale(0);
await ganttControlsChanges();
expect.verifySteps(["patched"]);
await selectGanttRange({ startDate: "2018-01-01", stopDate: "2018-12-31" });
expect.verifySteps(["patched"]);
content = getGridContent();
expect(getActiveScale()).toBe(0);
expect(content.range).toBe("From: 01/01/2018 to: 12/31/2018");
expect(content.groupHeaders.map((h) => h.title)).toEqual(["2018"]);
expect(model.data.records).toHaveLength(7);
expect(getCellColorProperties("August 2018")).toEqual(["--Gantt__DayOff-background-color"]);
expect(getCellColorProperties("November 2018")).toEqual([]);
firstReloadProm.resolve();
await animationFrame();
content = getGridContent();
expect(getActiveScale()).toBe(0);
expect(content.range).toBe("From: 01/01/2018 to: 12/31/2018");
expect(content.groupHeaders.map((h) => h.title)).toEqual(["2018"]);
expect(model.data.records).toHaveLength(7);
expect(getCellColorProperties("August 2018")).toEqual(["--Gantt__DayOff-background-color"]);
expect(getCellColorProperties("November 2018")).toEqual([]);
expect.verifySteps([]);
});
test("concurrent range selections", async () => {
let reloadProm = null;
let firstReloadProm = null;
onRpc("get_gantt_data", () => reloadProm);
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop"/>`,
});
let content = getGridContent();
expect(getActiveScale()).toBe(2);
expect(content.range).toBe("From: 12/01/2018 to: 02/28/2019");
reloadProm = new Deferred();
firstReloadProm = reloadProm;
await selectGanttRange({ startDate: "2019-01-01", stopDate: "2019-02-28" });
reloadProm = null;
await selectGanttRange({ startDate: "2019-01-01", stopDate: "2019-01-31" });
firstReloadProm.resolve();
content = getGridContent();
expect(content.range).toBe("From: 01/01/2019 to: 01/31/2019");
});
test("concurrent pill resize and groupBy change", async () => {
let awaitWriteDef = false;
const writeDef = new Deferred();
onRpc(({ args, method }) => {
expect.step([method, args]);
if (method === "write" && awaitWriteDef) {
return writeDef;
}
});
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop"/>`,
searchViewArch: `
<search>
<filter name="group_by" string="Project" domain="[]" context="{ 'group_by': 'project_id' }"/>
</search>
`,
domain: [["id", "in", [2, 5]]],
});
expect.verifySteps([
["get_views", []],
["get_gantt_data", []],
]);
expect(getGridContent().rows).toEqual([
{
pills: [
{
colSpan: "Out of bounds (1) -> 04 (1/2) December 2018",
level: 0,
title: "Task 5",
},
{
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
level: 0,
title: "Task 2",
},
],
},
]);
// resize "Task 2" to 1 cell smaller (-1 day) ; this RPC will be delayed
awaitWriteDef = true;
await resizePill(getPillWrapper("Task 2"), "end", -1);
expect.verifySteps([["write", [[2], { stop: "2018-12-21 06:29:59" }]]]);
await toggleSearchBarMenu();
await toggleMenuItem("Project");
expect.verifySteps([["get_gantt_data", []]]);
expect(getGridContent().rows).toEqual([
{
pills: [
{
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
level: 0,
title: "Task 2",
},
],
title: "Project 1",
},
{
pills: [
{
colSpan: "Out of bounds (1) -> 04 (1/2) December 2018",
level: 0,
title: "Task 5",
},
],
title: "Project 2",
},
]);
writeDef.resolve();
await animationFrame();
expect.verifySteps([["get_gantt_data", []]]);
expect(getGridContent().rows).toEqual([
{
pills: [
{
colSpan: "17 (1/2) December 2018 -> 21 (1/2) December 2018",
level: 0,
title: "Task 2",
},
],
title: "Project 1",
},
{
pills: [
{
colSpan: "Out of bounds (1) -> 04 (1/2) December 2018",
level: 0,
title: "Task 5",
},
],
title: "Project 2",
},
]);
});
test("concurrent pill resizes return in inverse order", async () => {
let awaitWriteDef = false;
const writeDef = new Deferred();
onRpc(({ args, method }) => {
expect.step([method, args]);
if (method === "write" && awaitWriteDef) {
return writeDef;
}
});
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop"/>`,
domain: [["id", "=", 2]],
});
// resize to 1 cell smaller (-1 day) ; this RPC will be delayed
awaitWriteDef = true;
await resizePill(getPillWrapper("Task 2"), "end", -1);
// resize to two cells larger (+2 days) ; no delay
awaitWriteDef = false;
await resizePill(getPillWrapper("Task 2"), "end", +2);
writeDef.resolve();
await animationFrame();
expect.verifySteps([
["get_views", []],
["get_gantt_data", []],
["write", [[2], { stop: "2018-12-21 06:29:59" }]],
["get_gantt_data", []],
["write", [[2], { stop: "2018-12-24 06:29:59" }]],
["get_gantt_data", []],
]);
});
test("concurrent pill resizes and open, dialog show updated number", async () => {
Tasks._views = {
form: `
<form>
<field name="name"/>
<field name="start"/>
<field name="stop"/>
</form>
`,
};
const def = new Deferred();
onRpc("write", () => def);
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop"/>`,
domain: [["id", "=", 2]],
});
await resizePill(getPillWrapper("Task 2"), "end", +2);
await editPill("Task 2");
def.resolve();
await animationFrame();
expect(`.modal [name=stop] input`).toHaveValue("12/24/2018 07:29:59");
});
test("concurrent display mode change and fetch", async () => {
let def;
onRpc("get_gantt_data", () => def);
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop"/>`,
domain: [["id", "in", [1, 2]]],
});
let content = getGridContent();
expect(content.range).toBe("From: 12/01/2018 to: 02/28/2019");
const initialRows = [
{
pills: [
{ title: "Task 1", level: 0, colSpan: "Out of bounds (1) -> 31 December 2018" },
{
title: "Task 2",
level: 1,
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
},
],
},
];
expect(content.rows).toEqual(initialRows);
def = new Deferred();
await selectGanttRange({ startDate: "2018-12-01", stopDate: "2019-06-15" });
content = getGridContent();
expect(content.range).toBe("From: 12/01/2018 to: 06/15/2019");
expect(content.rows).toEqual(initialRows);
await click(SELECTORS.sparse);
await animationFrame();
content = getGridContent();
expect(content.range).toBe("From: 12/01/2018 to: 06/15/2019");
expect(content.rows).toEqual([
{
pills: [
{
colSpan: "Out of bounds (1) -> 31 December 2018",
level: 0,
title: "Task 1",
},
],
title: "Task 1",
},
{
pills: [
{
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
level: 0,
title: "Task 2",
},
],
title: "Task 2",
},
]);
def.resolve();
await animationFrame();
content = getGridContent();
expect(content.range).toBe("From: 12/01/2018 to: 06/15/2019");
expect(content.rows).toEqual([
{
pills: [
{
colSpan: "Out of bounds (1) -> 31 December 2018",
level: 0,
title: "Task 1",
},
],
title: "Task 1",
},
{
pills: [
{
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
level: 0,
title: "Task 2",
},
],
title: "Task 2",
},
]);
});

View File

@ -0,0 +1,102 @@
import { beforeEach, expect, test } from "@odoo/hoot";
import { queryFirst } from "@odoo/hoot-dom";
import { mockDate } from "@odoo/hoot-mock";
import { mountGanttView } from "./web_gantt_test_helpers";
import { ResUsers, TASKS_STAGE_SELECTION, Tasks, defineGanttModels } from "./gantt_mock_models";
function randomName(length) {
const CHARS = "abcdefghijklmnopqrstuvwxyzàùéèâîûêôäïüëö";
return [...Array(length)]
.map(() => {
const char = CHARS[Math.floor(Math.random() * CHARS.length)];
return Math.random() < 0.5 ? char : char.toUpperCase();
})
.join("");
}
defineGanttModels();
beforeEach(() => mockDate("2018-12-20T08:00:00", +1));
test.tags("manual testing").skip("large amount of records (ungrouped)", async () => {
const NB_TASKS = 10000;
Tasks._records = [...Array(NB_TASKS)].map((_, i) => ({
id: i + 1,
name: `Task ${i + 1}`,
start: `2018-12-01 00:00:00`,
stop: `2018-12-01 23:00:00`,
}));
console.time("makeView");
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop"/>`,
});
console.timeEnd("makeView");
expect(1).toBe(1);
});
test.tags("manual testing").skip("large amount of records (one level grouped)", async () => {
const NB_USERS = 10000;
const NB_TASKS = 10000;
ResUsers._records = [...Array(NB_USERS)].map((_, i) => ({
id: i + 1,
name: `${randomName(Math.floor(Math.random() * 8) + 8)} (${i + 1})`,
}));
Tasks._records = [...Array(NB_TASKS)].map((_, i) => {
let day1 = (i % 30) + 1;
let day2 = (i % 30) + 2;
if (day1 < 10) {
day1 = "0" + day1;
}
if (day2 < 10) {
day2 = "0" + day2;
}
return {
id: i + 1,
name: `Task ${i + 1}`,
user_id: Math.floor(Math.random() * Math.floor(NB_USERS)) + 1,
start: `2018-12-${day1} 00:00:00`,
stop: `2018-12-${day2} 00:00:00`,
};
});
console.time("makeView");
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop"/>`,
groupBy: ["user_id"],
});
console.timeEnd("makeView");
queryFirst(".o_content").style = "max-height: 600px; overflow-y: scroll;";
expect(1).toBe(1);
});
test.tags("manual testing").skip("large amount of records (two level grouped)", async () => {
const NB_USERS = 100;
const NB_TASKS = 10000;
ResUsers._records = [...Array(NB_USERS)].map((_, i) => ({
id: i + 1,
name: `${randomName(Math.floor(Math.random() * 8) + 8)} (${i + 1})`,
}));
Tasks._records = [...Array(NB_TASKS)].map((_, i) => ({
id: i + 1,
name: `Task ${i + 1}`,
stage: TASKS_STAGE_SELECTION[i % 2][0],
user_id: (i % NB_USERS) + 1,
start: "2018-12-01 00:00:00",
stop: "2018-12-02 00:00:00",
}));
console.time("makeView");
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop"/>`,
groupBy: ["user_id", "stage"],
});
console.timeEnd("makeView");
expect(1).toBe(1);
});

View File

@ -0,0 +1,378 @@
import { beforeEach, describe, expect, test } from "@odoo/hoot";
import { queryAll, queryAllTexts, queryFirst } from "@odoo/hoot-dom";
import { animationFrame, mockDate } from "@odoo/hoot-mock";
import { contains, getService, mountWithCleanup, onRpc } from "@web/../tests/web_test_helpers";
import { Domain } from "@web/core/domain";
import { deserializeDateTime } from "@web/core/l10n/dates";
import { WebClient } from "@web/webclient/webclient";
import { Tasks, defineGanttModels } from "./gantt_mock_models";
import {
CLASSES,
SELECTORS,
getActiveScale,
getGridContent,
mountGanttView,
} from "./web_gantt_test_helpers";
defineGanttModels();
describe.current.tags("mobile");
beforeEach(() => mockDate("2018-12-20T08:00:00", +1));
test("empty ungrouped gantt rendering", async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" />`,
domain: [["id", "=", 0]],
});
const { viewTitle, range, columnHeaders, rows } = getGridContent();
expect(viewTitle).toBe(null);
expect(range).toBe("From: 12/01/2018 to: 02/28/2019");
expect(columnHeaders).toHaveLength(10);
expect(columnHeaders.at(0).title).toBe("15");
expect(columnHeaders.at(-1).title).toBe("24");
expect(rows).toEqual([{}]);
expect(SELECTORS.noContentHelper).toHaveCount(0);
});
test("ungrouped gantt rendering", async () => {
const task2 = Tasks._records[1];
const startDateLocalString = deserializeDateTime(task2.start).toFormat("f");
const stopDateLocalString = deserializeDateTime(task2.stop).toFormat("f");
Tasks._views.gantt = `<gantt date_start="start" date_stop="stop"/>`;
Tasks._views.search = `<search/>`;
onRpc("get_gantt_data", ({ model }) => expect.step(model));
await mountWithCleanup(WebClient);
await getService("action").doAction({
res_model: "tasks",
type: "ir.actions.act_window",
views: [[false, "gantt"]],
});
expect.verifySteps(["tasks"]);
await animationFrame();
const { viewTitle, range, columnHeaders, rows } = getGridContent();
expect(viewTitle).toBe(null);
expect(range).toBe("From: 12/01/2018 to: 02/28/2019");
expect(columnHeaders).toHaveLength(10);
expect(columnHeaders.at(0).title).toBe("15");
expect(columnHeaders.at(-1).title).toBe("24");
expect(getActiveScale()).toBe(2);
expect(SELECTORS.expandCollapseButtons).not.toBeVisible();
expect(rows).toEqual([
{
pills: [
{ title: "Task 1", level: 1, colSpan: "Out of bounds (1) -> Out of bounds (63) " },
{
title: "Task 2",
level: 0,
colSpan: "17 (1/2) Dec 2018 -> 22 (1/2) Dec 2018",
},
{
title: "Task 4",
level: 2,
colSpan: "20 Dec 2018 -> 20 (1/2) Dec 2018",
},
{
title: "Task 7",
level: 2,
colSpan: "20 (1/2) Dec 2018 -> 20 Dec 2018",
},
],
},
]);
// test popover and local timezone
expect(`.o_popover`).toHaveCount(0);
const task2Pill = queryAll(SELECTORS.pill)[1];
expect(task2Pill).toHaveText("Task 2");
await contains(task2Pill).click();
expect(`.o_popover`).toHaveCount(1);
expect(queryAllTexts`.o_popover .popover-body span`).toEqual([
"Task 2",
startDateLocalString,
stopDateLocalString,
]);
await contains(`.o_popover .popover-header i.fa.fa-close`).click();
expect(`.o_popover`).toHaveCount(0);
});
test("ordered gantt view", async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" progress="progress"/>`,
groupBy: ["stage_id"],
});
const { viewTitle, range, columnHeaders, rows } = getGridContent();
expect(viewTitle).toBe("Gantt View");
expect(range).toBe("From: 12/01/2018 to: 02/28/2019");
expect(columnHeaders).toHaveLength(10);
expect(columnHeaders.at(0).title).toBe("16");
expect(columnHeaders.at(-1).title).toBe("25");
expect(SELECTORS.noContentHelper).toHaveCount(0);
expect(rows).toEqual([
{
title: "todo",
},
{
title: "in_progress",
pills: [
{ level: 0, colSpan: "Out of bounds (1) -> Out of bounds (63) ", title: "Task 1" },
{
level: 1,
colSpan: "20 (1/2) Dec 2018 -> 20 Dec 2018",
title: "Task 7",
},
],
},
{
title: "done",
pills: [
{
level: 0,
colSpan: "17 (1/2) Dec 2018 -> 22 (1/2) Dec 2018",
title: "Task 2",
},
],
},
{
title: "cancel",
pills: [
{
level: 0,
colSpan: "20 Dec 2018 -> 20 (1/2) Dec 2018",
title: "Task 4",
},
],
},
]);
});
test("empty single-level grouped gantt rendering", async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop"/>`,
groupBy: ["project_id"],
domain: Domain.FALSE.toList(),
});
const { viewTitle, range, columnHeaders, rows } = getGridContent();
expect(viewTitle).toBe("Gantt View");
expect(range).toBe("From: 12/01/2018 to: 02/28/2019");
expect(columnHeaders).toHaveLength(10);
expect(columnHeaders.at(0).title).toBe("16");
expect(columnHeaders.at(-1).title).toBe("25");
expect(rows).toEqual([{ title: "" }]);
expect(SELECTORS.noContentHelper).toHaveCount(0);
});
test("single-level grouped gantt rendering", async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt string="Tasks" date_start="start" date_stop="stop"/>`,
groupBy: ["project_id"],
});
expect(getActiveScale()).toBe(2);
expect(SELECTORS.expandCollapseButtons).not.toBeVisible();
const { range, viewTitle, columnHeaders, rows } = getGridContent();
expect(range).toBe("From: 12/01/2018 to: 02/28/2019");
expect(viewTitle).toBe("Tasks");
expect(columnHeaders).toHaveLength(10);
expect(columnHeaders.at(0).title).toBe("16");
expect(columnHeaders.at(-1).title).toBe("25");
expect(rows).toEqual([
{
title: "Project 1",
pills: [
{
title: "Task 1",
colSpan: "Out of bounds (1) -> Out of bounds (63) ",
level: 0,
},
{
title: "Task 2",
colSpan: "17 (1/2) Dec 2018 -> 22 (1/2) Dec 2018",
level: 1,
},
{
title: "Task 4",
colSpan: "20 Dec 2018 -> 20 (1/2) Dec 2018",
level: 2,
},
],
},
{
title: "Project 2",
pills: [
{
title: "Task 7",
colSpan: "20 (1/2) Dec 2018 -> 20 Dec 2018",
level: 0,
},
],
},
]);
});
test("Controls: rendering is mobile friendly", async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" />`,
});
// check toolbar's dropdown
await contains("button.dropdown-toggle").click();
expect(queryAllTexts`.o-dropdown-item`).toEqual(["Activate sparse mode"]);
// check that pickers open in dialog
await contains(SELECTORS.rangeMenuToggler).click();
expect(".modal").toHaveCount(0);
await contains(SELECTORS.startDatePicker).click();
expect(".modal").toHaveCount(1);
expect(".modal-title").toHaveText("Gantt start date");
expect(".modal-body .o_datetime_picker").toHaveCount(1);
await contains(".modal-header .btn").click();
expect(".modal").toHaveCount(0);
await contains(SELECTORS.stopDatePicker).click();
expect(".modal").toHaveCount(1);
expect(".modal-title").toHaveText("Gantt stop date");
expect(".modal-body .o_datetime_picker").toHaveCount(1);
await contains(".modal-header .btn").click();
expect(".modal").toHaveCount(0);
});
test("Progressbar: check the progressbar percentage visibility.", async () => {
onRpc("get_gantt_data", async ({ kwargs, parent }) => {
expect.step("get_gantt_data");
const result = await parent();
expect(kwargs.progress_bar_fields).toEqual(["user_id"]);
result.progress_bars.user_id = {
1: { value: 50, max_value: 100 },
2: { value: 25, max_value: 200 },
};
return result;
});
await mountGanttView({
resModel: "tasks",
arch: `
<gantt date_start="start" date_stop="stop" default_scale="week" scales="week" default_group_by="user_id" progress_bar="user_id">
<field name="user_id"/>
</gantt>
`,
});
expect.verifySteps(["get_gantt_data"]);
expect(SELECTORS.progressBar).toHaveCount(2);
const [progressBar1, progressBar2] = queryAll(SELECTORS.progressBar);
expect(progressBar1).toHaveClass("o_gantt_group_success");
expect(progressBar2).toHaveClass("o_gantt_group_success");
const [rowHeader1, rowHeader2] = [progressBar1.parentElement, progressBar2.parentElement];
expect(rowHeader1.matches(SELECTORS.rowHeader)).toBe(true);
expect(rowHeader2.matches(SELECTORS.rowHeader)).toBe(true);
expect(rowHeader1).not.toHaveClass(CLASSES.group);
expect(rowHeader2).not.toHaveClass(CLASSES.group);
expect(queryAll(SELECTORS.progressBarBackground).map((el) => el.style.width)).toEqual([
"50%",
"12.5%",
]);
expect(SELECTORS.progressBarForeground).toHaveCount(2);
expect(queryAllTexts(SELECTORS.progressBarForeground)).toEqual(["50h / 100h", "25h / 200h"]);
// Check the style of one of the progress bars
expect(rowHeader1.children).toHaveLength(2);
const rowTitle1 = rowHeader1.children[0];
expect(rowTitle1.matches(SELECTORS.rowTitle)).toBe(true);
expect(rowTitle1.nextElementSibling).toBe(progressBar1);
expect(rowHeader1).toHaveStyle({ gridTemplateRows: "36px 35px" });
expect(rowTitle1).toHaveStyle({ height: "36px" });
expect(progressBar1).toHaveStyle({ height: "35px" });
});
test("Progressbar: grouped row", async () => {
onRpc("get_gantt_data", async ({ kwargs, parent }) => {
expect.step("get_gantt_data");
const result = await parent();
expect(kwargs.progress_bar_fields).toEqual(["user_id"]);
result.progress_bars.user_id = {
1: { value: 50, max_value: 100 },
2: { value: 25, max_value: 200 },
};
return result;
});
await mountGanttView({
resModel: "tasks",
arch: `
<gantt date_start="start" date_stop="stop" default_scale="week" scales="week" default_group_by="user_id,user_id" progress_bar="user_id">
<field name="user_id"/>
</gantt>
`,
});
expect.verifySteps(["get_gantt_data"]);
expect(SELECTORS.progressBar).toHaveCount(4);
const [progressBar1, progressBar2] = queryAll(SELECTORS.progressBar);
expect(progressBar1).toHaveClass("o_gantt_group_success");
expect(progressBar2).toHaveClass("o_gantt_group_success");
const [rowHeader1, rowHeader2] = [progressBar1.parentElement, progressBar2.parentElement];
expect(rowHeader1.matches(SELECTORS.rowHeader)).toBe(true);
expect(rowHeader2.matches(SELECTORS.rowHeader)).toBe(true);
expect(rowHeader1).toHaveClass(CLASSES.group);
expect(rowHeader2).not.toHaveClass(CLASSES.group);
expect(queryAll(SELECTORS.progressBarBackground).map((el) => el.style.width)).toEqual([
"50%",
"50%",
"12.5%",
"12.5%",
]);
expect(SELECTORS.progressBarForeground).toHaveCount(4);
expect(queryAllTexts(SELECTORS.progressBarForeground)).toEqual([
"50h / 100h",
"50h / 100h",
"25h / 200h",
"25h / 200h",
]);
// Check the style of one of the progress bars
expect(rowHeader1.children).toHaveLength(2);
const rowTitle1 = rowHeader1.children[0];
expect(rowTitle1.matches(SELECTORS.rowTitle)).toBe(true);
expect(rowTitle1.nextElementSibling).toBe(progressBar1);
expect(rowHeader1).toHaveStyle({ gridTemplateRows: "24px 35px" });
expect(rowTitle1).toHaveStyle({ height: "24px" });
expect(progressBar1).toHaveStyle({ height: "35px" });
});
test("horizontal scroll applies to the content [SMALL SCREEN]", async () => {
Tasks._views.search = `<search/>`;
Tasks._views.gantt = `<gantt date_start="start" date_stop="stop"><field name="user_id"/></gantt>`;
await mountWithCleanup(WebClient);
await getService("action").doAction({
res_model: "tasks",
type: "ir.actions.act_window",
views: [[false, "gantt"]],
});
await animationFrame();
const o_view_controller = queryFirst(".o_view_controller");
const o_content = queryFirst(".o_content");
const firstColumnHeader = queryFirst(SELECTORS.columnHeader);
const initialXHeaderCell = firstColumnHeader.getBoundingClientRect().x;
expect(o_view_controller).toHaveClass("o_action_delegate_scroll");
expect(o_view_controller).toHaveStyle({ overflow: "hidden" });
expect(o_content).toHaveStyle({ overflow: "auto" });
expect(o_content).toHaveProperty("scrollLeft", 762);
// Horizontal scroll
const newScrollLeft = o_content.scrollLeft - 50;
await contains(".o_content").scroll({ left: newScrollLeft });
expect(o_content).toHaveProperty("scrollLeft", newScrollLeft);
expect(firstColumnHeader.getBoundingClientRect().x).toBe(initialXHeaderCell + 50);
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,214 @@
import { beforeEach, describe, expect, test } from "@odoo/hoot";
import { queryFirst, queryAll } from "@odoo/hoot-dom";
import { mockDate, animationFrame } from "@odoo/hoot-mock";
import { markup } from "@odoo/owl";
import {
getService,
mountWithCleanup,
switchView,
toggleMenuItem,
toggleSearchBarMenu,
} from "@web/../tests/web_test_helpers";
import { Tasks, defineGanttModels } from "./gantt_mock_models";
import { SELECTORS, mountGanttView } from "./web_gantt_test_helpers";
import { Domain } from "@web/core/domain";
import { WebClient } from "@web/webclient/webclient";
describe.current.tags("desktop");
defineGanttModels();
beforeEach(() => mockDate("2018-12-20T08:00:00", +1));
test(`empty grouped gantt with sample="1"`, async () => {
Tasks._views = {
gantt: `<gantt date_start="start" date_stop="stop" sample="1"/>`,
graph: `<graph/>`,
search: `<search/>`,
};
await mountWithCleanup(WebClient);
await getService("action").doAction({
res_model: "tasks",
type: "ir.actions.act_window",
views: [
[false, "gantt"],
[false, "graph"],
],
domain: Domain.FALSE.toList(),
groupBy: ["project_id"],
});
await animationFrame();
expect(SELECTORS.viewContent).toHaveClass("o_view_sample_data");
expect(queryAll(SELECTORS.pill).length).toBeWithin(0, 16);
expect(SELECTORS.noContentHelper).toHaveCount(1);
const content = queryFirst(SELECTORS.viewContent).innerHTML;
await switchView("gantt");
await animationFrame();
expect(SELECTORS.viewContent).toHaveClass("o_view_sample_data");
expect(SELECTORS.viewContent).toHaveProperty("innerHTML", content);
expect(SELECTORS.noContentHelper).toHaveCount(1);
});
test("empty gantt with sample data and default_group_by", async () => {
Tasks._views = {
gantt: `<gantt date_start="start" date_stop="stop" sample="1" default_group_by="project_id"/>`,
graph: `<graph/>`,
search: `<search/>`,
};
await mountWithCleanup(WebClient);
await getService("action").doAction({
res_model: "tasks",
type: "ir.actions.act_window",
views: [
[false, "gantt"],
[false, "graph"],
],
domain: Domain.FALSE.toList(),
});
await animationFrame();
expect(SELECTORS.viewContent).toHaveClass("o_view_sample_data");
expect(queryAll(SELECTORS.pill).length).toBeWithin(0, 16);
expect(SELECTORS.noContentHelper).toHaveCount(1);
const content = queryFirst(SELECTORS.viewContent).innerHTML;
await switchView("gantt");
await animationFrame();
expect(SELECTORS.viewContent).toHaveClass("o_view_sample_data");
expect(SELECTORS.viewContent).toHaveProperty("innerHTML", content);
expect(SELECTORS.noContentHelper).toHaveCount(1);
});
test("empty gantt with sample data and default_group_by (switch view)", async () => {
Tasks._views = {
gantt: `<gantt date_start="start" date_stop="stop" sample="1" default_group_by="project_id"/>`,
list: `<list/>`,
search: `<search/>`,
};
await mountWithCleanup(WebClient);
await getService("action").doAction({
res_model: "tasks",
type: "ir.actions.act_window",
views: [
[false, "gantt"],
[false, "list"],
],
domain: Domain.FALSE.toList(),
});
await animationFrame();
// the gantt view should be in sample mode
expect(SELECTORS.viewContent).toHaveClass("o_view_sample_data");
expect(queryAll(SELECTORS.pill).length).toBeWithin(0, 16);
expect(SELECTORS.noContentHelper).toHaveCount(1);
const content = queryFirst(SELECTORS.viewContent).innerHTML;
// switch to list view
await switchView("list");
expect(SELECTORS.view).toHaveCount(0);
// go back to gantt view
await switchView("gantt");
await animationFrame();
expect(SELECTORS.view).toHaveCount(1);
// the gantt view should be still in sample mode
expect(SELECTORS.viewContent).toHaveClass("o_view_sample_data");
expect(SELECTORS.noContentHelper).toHaveCount(1);
expect(SELECTORS.viewContent).toHaveProperty("innerHTML", content);
});
test(`empty gantt with sample="1"`, async () => {
Tasks._views = {
gantt: `<gantt date_start="start" date_stop="stop" sample="1"/>`,
graph: `<graph/>`,
search: `<search/>`,
};
await mountWithCleanup(WebClient);
await getService("action").doAction({
res_model: "tasks",
type: "ir.actions.act_window",
views: [
[false, "gantt"],
[false, "graph"],
],
domain: Domain.FALSE.toList(),
});
await animationFrame();
expect(SELECTORS.viewContent).toHaveClass("o_view_sample_data");
expect(queryAll(SELECTORS.pill).length).toBeWithin(0, 16);
expect(SELECTORS.noContentHelper).toHaveCount(1);
const content = queryFirst(SELECTORS.viewContent).innerHTML;
await switchView("gantt");
await animationFrame();
expect(SELECTORS.viewContent).toHaveClass("o_view_sample_data");
expect(SELECTORS.viewContent).toHaveProperty("innerHTML", content);
expect(SELECTORS.noContentHelper).toHaveCount(1);
});
test(`non empty gantt with sample="1"`, async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" default_scale="year" sample="1"/>`,
searchViewArch: `
<search>
<filter name="filter" string="False Domain" domain="[(0, '=', 1)]"/>
</search>
`,
});
expect(SELECTORS.viewContent).not.toHaveClass("o_view_sample_data");
expect(SELECTORS.cell).toHaveCount(12);
expect(SELECTORS.pill).toHaveCount(7);
expect(SELECTORS.noContentHelper).toHaveCount(0);
await toggleSearchBarMenu();
await toggleMenuItem("False Domain");
expect(SELECTORS.viewContent).not.toHaveClass("o_view_sample_data");
expect(SELECTORS.pill).toHaveCount(0);
expect(SELECTORS.noContentHelper).toHaveCount(0);
expect(SELECTORS.cell).toHaveCount(12);
});
test(`non empty grouped gantt with sample="1"`, async () => {
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" default_scale="year" sample="1"/>`,
groupBy: ["project_id"],
searchViewArch: `
<search>
<filter name="filter" string="False Domain" domain="[(0, '=', 1)]"/>
</search>
`,
});
expect(SELECTORS.viewContent).not.toHaveClass("o_view_sample_data");
expect(SELECTORS.cell).toHaveCount(24);
expect(SELECTORS.pill).toHaveCount(7);
await toggleSearchBarMenu();
await toggleMenuItem("False Domain");
expect(SELECTORS.viewContent).not.toHaveClass("o_view_sample_data");
expect(SELECTORS.pill).toHaveCount(0);
expect(SELECTORS.noContentHelper).toHaveCount(0);
expect(SELECTORS.cell).toHaveCount(12);
});
test("no content helper from action when no data and sample mode", async () => {
Tasks._records = [];
await mountGanttView({
resModel: "tasks",
arch: `<gantt date_start="start" date_stop="stop" sample="1"/>`,
noContentHelp: markup(`<p class="hello">click to add a partner</p>`),
});
expect(SELECTORS.noContentHelper).toHaveCount(1);
expect(`${SELECTORS.noContentHelper} p.hello:contains(add a partner)`).toHaveCount(1);
});

View File

@ -0,0 +1,580 @@
import {
click,
hover,
queryAll,
queryAllTexts,
queryFirst,
queryOne,
queryText,
setInputRange,
} from "@odoo/hoot-dom";
import { advanceTime, animationFrame, runAllTimers } from "@odoo/hoot-mock";
import { getPickerCell, zoomOut } from "@web/../tests/core/datetime/datetime_test_helpers";
import { contains, mountView } from "@web/../tests/web_test_helpers";
/**
* @typedef CellHelperOptions
* @property {number} [part=1] -- starts at 1
* @property {boolean} [ignoreHoverableClass=false]
*/
/**
* @typedef PillHelperOptions
* @property {number} [nth=1] -- starts at 1
*/
/**
* @typedef DragPillHelpers
* @property {() => Promise<void>} cancel
* @property {(params: DragParams) => Promise<void>} drop
* @property {(params: DragParams) => Promise<void>} moveTo
*/
/**
* @template T
* @typedef {(columnHeader: string, rowHeader: string, options: CellHelperOptions) => T} CellHelper
*/
/**
* @template T
* @typedef {(text: string, options: PillHelperOptions) => T} PillHelper
*/
/** @typedef {CellHelperOptions & { row: number, column: number }} DragGridParams */
/** @typedef {PillHelperOptions & { pill: string }} DragPillParams */
/** @typedef {DragGridParams | DragPillParams} DragParams */
/**
* @template {String} T
* @param {T} key
* @returns {`.${T}`}
*/
function makeClassSelector(key) {
return `.${key}`;
}
export const CLASSES = {
draggable: "o_draggable",
group: "o_gantt_group",
highlightedPill: "highlight",
resizable: "o_resizable",
// Connectors
highlightedConnector: "o_connector_highlighted",
highlightedConnectorCreator: "o_connector_creator_highlight",
lockedConnectorCreator: "o_connector_creator_lock", // Connector creators highlight for initial pill
};
export const SELECTORS = {
addButton: ".o_gantt_button_add",
cell: ".o_gantt_cell",
cellContainer: ".o_gantt_cells",
collapseButton: ".o_gantt_button_collapse_rows",
dense: ".fa-compress",
sparse: ".fa-expand",
draggable: makeClassSelector(CLASSES.draggable),
expandButton: ".o_gantt_button_expand_rows",
expandCollapseButtons: ".o_gantt_button_expand_rows, .o_gantt_button_collapse_rows",
group: makeClassSelector(CLASSES.group),
groupHeader: ".o_gantt_header_title",
columnHeader: ".o_gantt_header_cell",
highlightedPill: makeClassSelector(CLASSES.highlightedPill),
hoverable: ".o_gantt_hoverable",
noContentHelper: ".o_view_nocontent",
pill: ".o_gantt_pill",
pillWrapper: ".o_gantt_pill_wrapper",
progressBar: ".o_gantt_row_header .o_gantt_progress_bar",
progressBarBackground: ".o_gantt_row_header .o_gantt_progress_bar > span.bg-opacity-25",
progressBarForeground:
".o_gantt_row_header .o_gantt_progress_bar > span > .o_gantt_group_hours",
progressBarWarning:
".o_gantt_row_header .o_gantt_progress_bar > .o_gantt_group_hours > .fa-exclamation-triangle",
renderer: ".o_gantt_renderer",
resizable: makeClassSelector(CLASSES.resizable),
resizeBadge: ".o_gantt_pill_resize_badge",
resizeEndHandle: ".o_handle_end",
resizeHandle: ".o_resize_handle",
resizeStartHandle: ".o_handle_start",
rowHeader: ".o_gantt_row_header",
rowTitle: ".o_gantt_row_title",
rowTotal: ".o_gantt_row_total",
startDatePicker: ".o_gantt_picker:nth-child(2)",
stopDatePicker: ".o_gantt_picker:nth-child(4)",
thumbnail: ".o_gantt_row_thumbnail",
rangeMenu: ".o_gantt_range_menu",
rangeMenuToggler: ".o_gantt_renderer_controls div.dropdown:nth-child(2)",
todayButton: ".o_gantt_button_today",
toolbar: ".o_gantt_renderer_controls div[name='ganttToolbar']",
undraggable: ".o_undraggable",
view: ".o_gantt_view",
viewContent: ".o_gantt_view .o_content",
previousButton: ".o_gantt_renderer_controls button:has(> .fa-arrow-left)",
nextButton: ".o_gantt_renderer_controls button:has(> .fa-arrow-right)",
minusButton: ".o_gantt_renderer_controls button:has(> .fa-search-minus)",
plusButton: ".o_gantt_renderer_controls button:has(> .fa-search-plus)",
// Connectors
connector: ".o_gantt_connector",
connectorCreatorBullet: ".o_connector_creator_bullet",
connectorCreatorRight: ".o_connector_creator_right",
connectorCreatorWrapper: ".o_connector_creator_wrapper",
connectorRemoveButton: ".o_connector_stroke_remove_button",
connectorRescheduleButton: ".o_connector_stroke_reschedule_button",
connectorStroke: ".o_connector_stroke",
connectorStrokeButton: ".o_connector_stroke_button",
highlightedConnector: makeClassSelector(CLASSES.highlightedConnector),
};
export async function mountGanttView(params) {
const gantt = await mountView({ ...params, type: "gantt" });
await animationFrame();
return gantt;
}
export async function ganttControlsChanges() {
await runAllTimers();
await animationFrame();
await animationFrame(); // for potential focusDate
}
/**
* @param {string} selector
* @param {DateTime} datetime
*/
async function selectDateInDatePicker(selector, datetime) {
await contains(selector).click();
for (let i = 0; i < 3; i++) {
await zoomOut();
}
await contains(getPickerCell(datetime.year - (datetime.year % 10))).click();
await contains(getPickerCell(datetime.year)).click();
await contains(getPickerCell(datetime.monthShort)).click();
await contains(getPickerCell(datetime.day, true)).click();
}
/**
* @param {Object} param0
* @param {string} [param0.startDate]
* @param {string} [param0.stopDate]
*/
export async function selectGanttRange({ startDate, stopDate }) {
const {
startDatePicker: START_SELECTOR,
stopDatePicker: STOP_SELECTOR,
rangeMenuToggler,
} = SELECTORS;
await click(rangeMenuToggler);
await animationFrame();
if (startDate) {
await selectDateInDatePicker(START_SELECTOR, luxon.DateTime.fromISO(startDate));
}
if (stopDate) {
await selectDateInDatePicker(STOP_SELECTOR, luxon.DateTime.fromISO(stopDate));
}
await click(".dropdown-item button:contains(Apply)");
await ganttControlsChanges();
}
export async function selectRange(label) {
await click(SELECTORS.rangeMenuToggler);
await animationFrame();
await click(`${SELECTORS.rangeMenu} .dropdown-item:contains(/^${label}$/)`);
await ganttControlsChanges();
}
export function getActiveScale() {
return Number(queryFirst(".o_gantt_renderer_controls input").value);
}
/**
* @param {Number} scale
*/
export async function setScale(scale) {
await setInputRange(".o_gantt_renderer_controls input", scale);
}
export async function focusToday() {
await click(SELECTORS.todayButton);
}
/** @type {PillHelper<Promise<DragPillHelpers>>} */
export async function dragPill(text, options) {
/**
* @param {DragParams} [params]
*/
const drop = async (params) => {
if (params) {
await moveTo(params);
}
await dragActions.drop();
};
/**
* @param {DragParams} params
*/
const moveTo = async (params) => {
let cell;
if (params?.column) {
cell = await hoverGridCell(params.column, params.row, params);
} else if (params?.pill) {
({ cell } = await hoverPillCell(getPillWrapper(params.pill, params)));
}
return dragActions.moveTo(cell, {
position: getCellPositionOffset(cell, params.part),
relative: true,
});
};
const pill = getPillWrapper(text, options);
pill.scrollIntoView({ behavior: "instant", inline: "center" });
const { cell, part } = await hoverPillCell(pill);
const dragActions = await contains(pill).drag({
// D&D needs the correct initial position since it will attempt an implicit
// hover on the pill.
position: getCellPositionOffset(cell, part - 1),
relative: true,
});
return { ...dragActions, drop, moveTo };
}
/** @type {PillHelper<Promise<void>>} */
export async function editPill(text, options) {
await contains(getPill(text, options)).click();
await contains(".o_popover .popover-footer .btn-primary").click();
}
/**
* @param {string} header
*/
function findColumnFromHeader(header) {
const columnHeaders = getHeaders(SELECTORS.columnHeader);
const groupHeaders = getHeaders(SELECTORS.groupHeader);
const columnHeader = header.substring(0, header.indexOf(" "));
const groupHeader = header.substring(header.indexOf(" ") + 1);
const groupRange = groupHeaders.find((header) => header.title === groupHeader).range;
return columnHeaders.find(
(header) =>
header.title === columnHeader &&
header.range[0] >= groupRange[0] &&
header.range[1] <= groupRange[1]
).range[0];
}
/** @type {CellHelper<HTMLElement>} */
export function getCell(columnHeader, rowHeader = null, options) {
const columnIndex = findColumnFromHeader(columnHeader);
const cells = queryAll(`${SELECTORS.cell}[data-col='${columnIndex}']`);
if (!cells.length) {
throw new Error(`Could not find cell at column ${columnHeader}`);
}
if (rowHeader === null) {
return cells[0];
}
const row = queryAll(`.o_gantt_row_header:contains(${rowHeader})`)?.[(options?.num || 1) - 1];
if (!row) {
throw new Error(`Could not find row ${rowHeader}`);
}
const rowId = row.getAttribute("data-row-id");
return cells.find((cell) => cell.getAttribute("data-row-id") === rowId);
}
/** @type {CellHelper<string[]>} */
export function getCellColorProperties(columnHeader, rowHeader = null, options) {
const cell = getCell(columnHeader, rowHeader, options);
const cssVarRegex = /(--[\w-]+)/g;
if (cell.style.background) {
return cell.style.background.match(cssVarRegex);
} else if (cell.style.backgroundColor) {
return cell.style.backgroundColor.match(cssVarRegex);
} else if (cell.style.backgroundImage) {
return cell.style.backgroundImage.match(cssVarRegex);
}
return [];
}
/**
* @param {HTMLElement} pill
* @returns {HTMLElement}
*/
export function getCellFromPill(pill) {
if (!pill.matches(SELECTORS.pillWrapper)) {
pill = pill.closest(SELECTORS.pillWrapper);
}
const { row, column } = getGridStyle(pill);
for (const cell of queryAll(SELECTORS.cell)) {
const { row: cellRow, column: cellColumn } = getGridStyle(cell);
if (row[0] < cellRow[1] && column[0] < cellColumn[1]) {
return cell;
}
}
throw new Error(`Could not find hoverable cell for pill "${queryText(pill)}".`);
}
/**
* @param {string} str
*/
function parseNumber(str) {
return parseInt(str.match(/\d+/)?.[0]) || 1;
}
/**
* @param {string} selector
*/
function getHeaders(selector) {
const groupHeaders = [];
for (const el of queryAll(selector)) {
const { column: range } = getGridStyle(el);
groupHeaders.push({
range,
title: el.textContent,
});
}
return groupHeaders;
}
export function getGridContent() {
const columnHeaders = getHeaders(SELECTORS.columnHeader);
const groupHeaders = getHeaders(SELECTORS.groupHeader);
const range = queryAllTexts(SELECTORS.rangeMenuToggler)[0] || null;
const viewTitle = queryAllTexts(".o_gantt_title")[0] || null;
const colsRange = queryFirst(SELECTORS.columnHeader)
.style.getPropertyValue("grid-column")
.split("/");
const cellParts = parseNumber(colsRange[1]) - parseNumber(colsRange[0]);
const pillEls = new Set(queryAll(`${SELECTORS.cellContainer} ${SELECTORS.pillWrapper}`));
const rowEls = queryAll(`.o_gantt_row_headers > ${SELECTORS.rowHeader}`);
const singleRowMode = rowEls.length === 0;
if (singleRowMode) {
rowEls.push(document.createElement("div"));
}
const totalRow = queryFirst(SELECTORS.rowTotal);
const totalPillEls = new Set(queryAll(`.o_gantt_row_total ${SELECTORS.pillWrapper}`));
if (totalRow) {
totalRow._isTotal = true;
rowEls.push(totalRow);
}
const rows = [];
for (const rowEl of rowEls) {
const isGroup = rowEl.classList.contains(CLASSES.group);
const { row: gridRow } = getGridStyle(rowEl);
const row = singleRowMode ? {} : { title: queryText(rowEl) };
if (isGroup) {
row.isGroup = true;
}
if (rowEl._isTotal) {
row.isTotalRow = true;
}
const pills = [];
for (const pillEl of rowEl._isTotal ? totalPillEls : pillEls) {
const pillRowLevel = parseNumber(pillEl.style.gridRowStart);
const { column: gridColumn } = getGridStyle(pillEl);
const pillInRow = pillRowLevel >= gridRow[0] && pillRowLevel < gridRow[1];
if (singleRowMode || pillInRow || rowEl._isTotal) {
let start = columnHeaders.find(
(header) => gridColumn[0] >= header.range[0] && gridColumn[0] < header.range[1]
)?.title;
let end = columnHeaders.find(
(header) => gridColumn[1] > header.range[0] && gridColumn[1] <= header.range[1]
)?.title;
const startPart = (gridColumn[0] - 1) % cellParts;
const endPart = (gridColumn[1] - 1) % cellParts;
if (startPart && start) {
start += ` (${startPart}/${cellParts})`;
}
if (endPart && end) {
end += ` (${endPart}/${cellParts})`;
}
const pill = {
title: queryText(pillEl),
colSpan: `${start || "Out of bounds (" + gridColumn[0] + ")"} ${
start
? groupHeaders.find(
(header) =>
gridColumn[0] >= header.range[0] &&
gridColumn[0] < header.range[1]
).title
: ""
} -> ${end || "Out of bounds (" + gridColumn[1] + ")"} ${
end
? groupHeaders.find(
(header) =>
gridColumn[1] > header.range[0] &&
gridColumn[1] <= header.range[1]
).title
: ""
}`,
};
if (!isGroup) {
pill.level = singleRowMode ? pillRowLevel - 1 : pillRowLevel - gridRow[0];
}
pills.push(pill);
pillEls.delete(pillEl);
}
}
if (pills.length) {
row.pills = pills;
}
rows.push(row);
}
return { columnHeaders, groupHeaders, range, rows, viewTitle };
}
/**
* @param {HTMLElement} el
*/
export function getGridStyle(el) {
/**
* @param {"row" | "column"} prop
* @returns {[number, number]}
*/
const getGridProp = (prop) => {
return [
parseNumber(style.getPropertyValue(`grid-${prop}-start`)),
parseNumber(style.getPropertyValue(`grid-${prop}-end`)),
];
};
const style = getComputedStyle(el);
return {
row: getGridProp("row"),
column: getGridProp("column"),
};
}
function getCellPositionOffset(cell, part) {
const position = { x: 1 };
if (part > 1) {
const rect = cell.getBoundingClientRect();
// Calculate cell parts
const colsRange = queryFirst(SELECTORS.columnHeader)
.style.getPropertyValue("grid-column")
.split("/");
const cellParts = parseNumber(colsRange[1]) - parseNumber(colsRange[0]);
const partWidth = rect.width / cellParts;
position.x += Math.ceil(partWidth * (part - 1));
}
return position;
}
/**
* @param {HTMLElement} cell
* @param {CellHelperOptions} [options]
*/
async function hoverCell(cell, options) {
const part = options?.part ?? 1;
await hover(cell, { position: getCellPositionOffset(cell, part), relative: true });
await animationFrame();
await advanceTime(1000);
}
/**
* Hovers a cell found from given grid coordinates.
* @type {CellHelper<Promise<HTMLElement>>}
*/
export async function hoverGridCell(columnHeader, rowHeader = null, options) {
const cell = getCell(columnHeader, rowHeader, options);
await hoverCell(cell, options);
return cell;
}
/**
* Click on a cell found from given grid coordinates.
* @type {CellHelper<Promise<HTMLElement>>}
*/
export async function clickCell(columnHeader, rowHeader = null, options) {
const cell = getCell(columnHeader, rowHeader, options);
await contains(cell).click();
}
/**
* Hovers a cell found from a pill element.
* @param {HTMLElement} pill
*/
async function hoverPillCell(pill) {
const cell = getCellFromPill(pill);
const pStart = getGridStyle(pill).column[0];
const cellStyle = getGridStyle(cell).column[0];
const part = pStart - cellStyle + 1;
await hoverCell(cell, { part });
return { cell, part };
}
/**
* @param {HTMLElement} pill
* @param {"start" | "end"} side
* @param {number | { x: number }} deltaOrPosition
* @param {boolean} [shouldDrop=true]
*/
export async function resizePill(pill, side, deltaOrPosition, shouldDrop = true) {
await hover(pill);
const { row, column } = getGridStyle(pill);
// Calculate cell parts
const colsRange = queryFirst(SELECTORS.columnHeader)
.style.getPropertyValue("grid-column")
.split("/");
const cellParts = parseNumber(colsRange[1]) - parseNumber(colsRange[0]);
// Calculate delta or position
const delta = typeof deltaOrPosition === "object" ? 0 : deltaOrPosition;
const position = typeof deltaOrPosition === "object" ? deltaOrPosition : {};
const targetColumn = (side === "start" ? column[0] : column[1]) + delta * cellParts;
let targetCell;
let targetPart;
for (const cell of queryAll(SELECTORS.cell)) {
const { row: cRow, column: cCol } = getGridStyle(cell);
if (cRow[0] > row[0] || cRow[1] < row[1]) {
continue;
}
if (cCol[1] < targetColumn) {
continue;
}
if (targetColumn < cCol[0]) {
break;
}
targetCell = cell;
targetPart = targetColumn - cCol[0];
}
// Assign position if delta
if (!position.x) {
const { width } = targetCell.getBoundingClientRect();
position.x = targetPart * Math.floor(width / cellParts);
}
// Actual drag actions
const { moveTo, drop } = await contains(
pill.querySelector(
side === "start" ? SELECTORS.resizeStartHandle : SELECTORS.resizeEndHandle
)
).drag();
await moveTo(targetCell, { position, relative: true });
if (shouldDrop) {
await drop();
} else {
return drop;
}
}
/** @type {PillHelper<HTMLElement>} */
export function getPill(text, options) {
return queryOne(`${SELECTORS.pill}:contains(${text}):eq(${(options?.nth ?? 1) - 1})`);
}
/** @type {PillHelper<HTMLElement>} */
export function getPillWrapper(text, options) {
return getPill(text, options).closest(SELECTORS.pillWrapper);
}

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_acl

View File

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from lxml import etree
from odoo.addons.base.tests.common import TransactionCaseWithUserDemo
class TestACL(TransactionCaseWithUserDemo):
def setUp(self):
super().setUp()
self.user_manager = self.env['res.users'].create({
'login': 'demo123',
'password': 'demo',
'partner_id': self.partner_demo.id,
'groups_id': [(6, 0, [self.env.ref('base.group_system').id])],
})
self.env["ir.ui.view"].create({
"name": "Add delete attribute on gantt view",
"model": "res.company",
"type": 'gantt',
"arch": """
<gantt date_start="date" date_stop="" string="Test">
<field name="partner_id"/>
</gantt>
""",
})
def test_view_delete_button_visibility(self):
# the demo user can't unlink
company_view = self.env['res.company']\
.with_user(self.user_demo)\
.get_view(False, 'gantt')
view_arch = etree.fromstring(company_view['arch'])
self.assertEqual(view_arch.get('delete'), 'False')
# the manager user can unlink
company_view = self.env['res.company']\
.with_user(self.user_manager)\
.get_view(False, 'gantt')
view_arch = etree.fromstring(company_view['arch'])
self.assertIsNone(view_arch.get('delete'))