first push message

This commit is contained in:
2026-07-01 14:41:49 +07:00
parent 6667dec2bf
commit 58b5f46cc4
2951 changed files with 316619 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
from . import models
from . import controllers
from . import wizard
+62
View File
@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
{
'name': 'Odoo Gantt View Project',
'summary': """
Manage projects efficiently with Gantt View for Odoo 19.
Project Gantt View, Task scheduling, Gantt Chart, Task linking, Milestone support.
""",
'description': """
odoo 19 project gantt view
odoo 19 gantt view for project
odoo 19 gantt chart
odoo project gantt view
gantt view in odoo
project gantt view in odoo
""",
'author': 'Ksolves India Ltd.',
'license': 'OPL-1',
'currency': 'EUR',
'price': '36.75',
'website': 'https://store.ksolves.com',
'maintainer': 'Ksolves India Ltd.',
'category': 'Tools',
# Upgraded from 17.0.1.0.1 → 19.0.1.0.0
'version': '19.0.1.0.0',
'support': 'sales@ksolves.com',
# hr_holidays renamed to time_off in Odoo 17+; kept as hr_holidays for
# compatibility — Odoo 19 still ships this module under that technical name.
'depends': ['ks_gantt_view_base', 'project', 'hr', 'hr_timesheet', 'hr_holidays'],
'images': ['static/description/icon.png'],
'data': [
'data/ks_task_due_alert.xml',
'views/ks_gantt_task.xml',
'views/ks_gantt_project_views.xml',
'views/ks_project_task.xml',
'views/ks_project_all_task.xml',
'views/ks_gantt_import_wizard.xml',
'reports/ks_tasks_report.xml',
'reports/ks_timesheet_report.xml',
'security/ir.model.access.csv',
],
'assets': {
'web.assets_backend': [
'ks_gantt_view_project/static/src/js/ks_gantt_renderer_inherit.js',
'ks_gantt_view_project/static/src/js/ks_gantt_controller.js',
],
},
}
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import ks_export_gantt
@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
from odoo import http
from odoo.http import request, content_disposition
# Odoo 19: ExcelExport path changed between versions — safe fallback import.
try:
from odoo.addons.web.controllers.export import ExcelExport
except ImportError:
try:
from odoo.addons.web.controllers.main import ExcelExport
except ImportError:
ExcelExport = object
class KsGanttExport(ExcelExport):
@http.route('/web/ksgantt/export/xlsx/', type='http', auth="user")
def ks_gantt_export_excel(self, project_id):
ks_fields = self.ks_gantt_export_field()
ks_export_data = []
if project_id != 'false':
project_ids = [project_id]
else:
project_ids = [
str(p.id)
for p in request.env['project.project'].search([])
]
for ks_project in project_ids:
ks_task_data = request.env['project.task'].search(
[('project_id', '=', int(ks_project))]
)
for ks_task in ks_task_data:
ks_row_list = []
for ks_export_field in ks_fields:
field_def = ks_task._fields.get(ks_export_field)
if field_def and field_def.type == 'many2one':
ks_row_list.append(ks_task[ks_export_field].display_name)
elif field_def:
ks_row_list.append(ks_task[ks_export_field])
if ks_task.ks_task_link_ids:
for index, item in enumerate(ks_task.ks_task_link_ids):
if index != 0:
ks_row = (
[ks_row_list[0]]
+ [False] * (len(ks_fields) - 1)
+ [item.ks_target_task_id.id, item.ks_task_link_type]
)
ks_export_data.append(ks_row)
else:
ks_row_list.append(item.ks_target_task_id.id)
ks_row_list.append(item.ks_task_link_type)
ks_export_data.append(ks_row_list)
else:
ks_export_data.append(ks_row_list)
ks_fields.append('ks_target_task_id')
ks_fields.append('ks_task_link_type')
response_data = self.from_data(ks_fields, ks_export_data)
return request.make_response(
response_data,
headers=[
(
'Content-Disposition',
content_disposition(self.filename('project_project')),
),
('Content-Type', self.content_type),
],
)
def ks_gantt_export_field(self):
return [
'id', 'name', 'priority', 'project_id', 'partner_id',
'stage_id', 'date_deadline', 'ks_task_unschedule', 'ks_task_type',
'ks_enable_task_duration', 'ks_start_datetime', 'ks_end_datetime',
'ks_schedule_mode', 'ks_constraint_task_type', 'ks_constraint_task_date',
]
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- noupate 1 if user wants to change or de-active this cron then the cron will not rollback. -->
<data noupdate="1">
<record id="ir_cron_task_due_alert_action" model="ir.cron">
<field name="name">Gantt Task Deadline Alert</field>
<field name="model_id" ref="model_project_project"/>
<field name="state">code</field>
<field name="code">model.ks_task_due_alert()</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
</record>
</data>
</odoo>
+30
View File
@@ -0,0 +1,30 @@
<odoo>
<data>
<!--
<record id="object0" model="ks_gantt_view_project.ks_gantt_view_project">
<field name="name">Object 0</field>
<field name="value">0</field>
</record>
<record id="object1" model="ks_gantt_view_project.ks_gantt_view_project">
<field name="name">Object 1</field>
<field name="value">10</field>
</record>
<record id="object2" model="ks_gantt_view_project.ks_gantt_view_project">
<field name="name">Object 2</field>
<field name="value">20</field>
</record>
<record id="object3" model="ks_gantt_view_project.ks_gantt_view_project">
<field name="name">Object 3</field>
<field name="value">30</field>
</record>
<record id="object4" model="ks_gantt_view_project.ks_gantt_view_project">
<field name="name">Object 4</field>
<field name="value">40</field>
</record>
-->
</data>
</odoo>
+727
View File
@@ -0,0 +1,727 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * ks_gantt_view_project
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-04-23 04:50+0000\n"
"PO-Revision-Date: 2021-04-23 04:50+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_task.py:0
#, python-format
msgid " timesheet.pdf"
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/wizard/ks_gantt_view_project_import.py:0
#: code:addons/ks_gantt_view_project/wizard/ks_gantt_view_project_import.py:0
#, python-format
msgid "%s field type can't imported since it is not supported"
msgstr ""
#. module: ks_gantt_view_project
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.report_ks_gantt_tasks_timesheet
msgid "<b>Hours Spent:</b>"
msgstr ""
#. module: ks_gantt_view_project
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.report_ks_gantt_tasks_timesheet
msgid "<b>Initially Planned Hours: </b>"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields.selection,name:ks_gantt_view_project.selection__project_task__ks_constraint_task_type__alap
msgid "As Late As Possible"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields.selection,name:ks_gantt_view_project.selection__project_task__ks_constraint_task_type__asap
msgid "As Soon As Possible"
msgstr ""
#. module: ks_gantt_view_project
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.report_ks_gantt_tasks
msgid "Assigned to"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields.selection,name:ks_gantt_view_project.selection__project_task__ks_schedule_mode__auto
msgid "Auto"
msgstr ""
#. module: ks_gantt_view_project
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.ks_view_project_form
msgid "Auto mode tasks"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task__ks_resource_hours_per_day
msgid "Average Hour per Day"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,help:ks_gantt_view_project.field_project_task__ks_resource_hours_per_day
msgid ""
"Average hours per day a resource is supposed to work with this calendar."
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_task_link_inherit.py:0
#, python-format
msgid "Can't create link with other project task."
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_task_link_inherit.py:0
#, python-format
msgid "Can't create same link with same task."
msgstr ""
#. module: ks_gantt_view_project
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.ks_gantt_view_import_wizard
msgid "Cancel"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task__ks_color
msgid "Color"
msgstr ""
#. module: ks_gantt_view_project
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.ks_gantt_view_import_wizard
msgid "Confirm"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_tooltip_task_constraint_date
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task__ks_constraint_task_date
msgid "Constraint Date"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_tooltip_task_constraint_type
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task__ks_constraint_task_type
msgid "Constraint Type"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_ks_gantt_import_wizard__create_uid
msgid "Created by"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_ks_gantt_import_wizard__create_date
msgid "Created on"
msgstr ""
#. module: ks_gantt_view_project
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.report_ks_gantt_tasks_timesheet
msgid "Date"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_days_off
msgid "Days Off"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_enable_project_deadline
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_tooltip_task_deadline
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.report_ks_gantt_tasks
msgid "Deadline"
msgstr ""
#. module: ks_gantt_view_project
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.report_ks_gantt_tasks_timesheet
msgid "Description"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_hr_leave__display_name
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_ks_gantt_import_wizard__display_name
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_ks_task_link__display_name
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__display_name
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task__display_name
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task_type__display_name
msgid "Display Name"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_tooltip_task_duration
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task__ks_task_duration
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task__ks_work_duration
msgid "Duration"
msgstr ""
#. module: ks_gantt_view_project
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.report_ks_gantt_tasks_timesheet
msgid "Duration (Hours)"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_enable_task_dynamic_progress
msgid "Dynamic Progress"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_enable_task_dynamic_text
msgid "Dynamic Text"
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_task.py:0
#, python-format
msgid "Email send successfully"
msgstr ""
#. module: ks_gantt_view_project
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.report_ks_gantt_tasks_timesheet
msgid "Employee"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task__ks_enable_task_duration
msgid "Enable Task Duration"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,help:ks_gantt_view_project.field_project_project__ks_tooltip_task_constraint_date
msgid "Enable task constraint date on tooltip"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,help:ks_gantt_view_project.field_project_project__ks_tooltip_task_constraint_type
msgid "Enable task constraint type on tooltip"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,help:ks_gantt_view_project.field_project_project__ks_tooltip_task_deadline
msgid "Enable task deadline on tooltip"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,help:ks_gantt_view_project.field_project_project__ks_tooltip_task_duration
msgid "Enable task duration on tooltip"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,help:ks_gantt_view_project.field_project_project__ks_tooltip_task_end_date
msgid "Enable task end date on tooltip"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,help:ks_gantt_view_project.field_project_project__ks_tooltip_task_name
msgid "Enable task name on tooltip"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,help:ks_gantt_view_project.field_project_project__ks_tooltip_task_progress
msgid "Enable task progress on tooltip"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,help:ks_gantt_view_project.field_project_project__ks_tooltip_task_stage
msgid "Enable task stage on tooltip"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,help:ks_gantt_view_project.field_project_project__ks_tooltip_task_start_date
msgid "Enable task start date on tooltip"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,help:ks_gantt_view_project.field_project_project__ks_days_off
msgid "Enable to remove off days from the gantt"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,help:ks_gantt_view_project.field_project_project__ks_enable_project_deadline
msgid "Enable/Disable Deadline of the tasks."
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,help:ks_gantt_view_project.field_project_project__ks_enable_quickinfo_extension
msgid "Enable/Disable Quick Info."
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,help:ks_gantt_view_project.field_project_project__ks_enable_task_dynamic_progress
msgid "Enable/Disable Task Dynamic Progress."
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,help:ks_gantt_view_project.field_project_project__ks_enable_task_dynamic_text
msgid "Enable/Disable Task Dynamic Text."
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_project_end
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_tooltip_task_end_date
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task__ks_end_datetime
msgid "End Date"
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_task.py:0
#, python-format
msgid "Error when sending mail: %s"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields.selection,name:ks_gantt_view_project.selection__ks_gantt_import_wizard__ks_file_type__xlsx
msgid "Excel"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_ks_gantt_import_wizard__ks_file_type
msgid "File Type"
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/wizard/ks_gantt_view_project_import.py:0
#: code:addons/ks_gantt_view_project/wizard/ks_gantt_view_project_import.py:0
#, python-format
msgid "File can't be read please upload correct file"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields.selection,name:ks_gantt_view_project.selection__project_task__ks_constraint_task_type__fnet
msgid "Finish No Earlier Than"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields.selection,name:ks_gantt_view_project.selection__project_task__ks_constraint_task_type__fnlt
msgid "Finish No Later Than"
msgstr ""
#. module: ks_gantt_view_project
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.ks_view_project_form
msgid "Gantt Chart Settings"
msgstr ""
#. module: ks_gantt_view_project
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.ks_view_task_form
msgid "Gantt Detail"
msgstr ""
#. module: ks_gantt_view_project
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.ks_view_project_form
msgid "Gantt Settings"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.actions.server,name:ks_gantt_view_project.ir_cron_task_due_alert_action_ir_actions_server
#: model:ir.cron,cron_name:ks_gantt_view_project.ir_cron_task_due_alert_action
#: model:ir.cron,name:ks_gantt_view_project.ir_cron_task_due_alert_action
msgid "Gantt Task Deadline Alert"
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_task.py:0
#, python-format
msgid "Hi %s, Task : %s has been assigned to you."
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_inherit.py:0
#, python-format
msgid ""
"Hi %s, This mail is to remind you %s have only %s days for the deadline."
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_task.py:0
#, python-format
msgid "Hi %s, Timesheet report for task %s is attached please check it."
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_hide_date
msgid "Hide Holiday Day"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,help:ks_gantt_view_project.field_project_project__ks_hide_date
msgid "Hide holidays on the gantt view"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_hr_leave__id
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_ks_gantt_import_wizard__id
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_ks_task_link__id
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__id
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task__id
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task_type__id
msgid "ID"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.actions.act_window,name:ks_gantt_view_project.action_gantt_view_import_id
#: model:ir.ui.menu,name:ks_gantt_view_project.ks_gantt_view_import_menuitem
msgid "Import Project"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields.selection,name:ks_gantt_view_project.selection__ks_gantt_import_wizard__ks_file_type__json
msgid "JSON"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model,name:ks_gantt_view_project.model_ks_task_link
msgid "Ks Gantt Task Linking"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_project_task_json
msgid "Ks Project Task Json"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_project_task_linking
msgid "Ks Project Task Linking"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task__ks_resource_hours_available
msgid "Ks Resource Hours Available"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task__ks_task_link_json
msgid "Ks Task Link Json"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task__ks_task_link_ids
msgid "Ks_task_link_ids"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_hr_leave____last_update
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_ks_gantt_import_wizard____last_update
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_ks_task_link____last_update
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project____last_update
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task____last_update
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task_type____last_update
msgid "Last Modified on"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_ks_gantt_import_wizard__write_uid
msgid "Last Updated by"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_ks_gantt_import_wizard__write_date
msgid "Last Updated on"
msgstr ""
#. module: ks_gantt_view_project
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.ks_view_task_form
msgid "Link Task"
msgstr ""
#. module: ks_gantt_view_project
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.ks_view_project_form
msgid "Mail timesheet user"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_mail_timesheet_user
msgid "Mail user"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields.selection,name:ks_gantt_view_project.selection__project_task__ks_schedule_mode__manual
msgid "Manual"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task__ks_mark_important
msgid "Mark As Important"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,help:ks_gantt_view_project.field_project_task__ks_mark_important
msgid "Mark as an important task"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields.selection,name:ks_gantt_view_project.selection__project_task__ks_task_type__milestone
msgid "Milestone"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields.selection,name:ks_gantt_view_project.selection__project_task__ks_constraint_task_type__mfo
msgid "Must Finish On"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields.selection,name:ks_gantt_view_project.selection__project_task__ks_constraint_task_type__mso
msgid "Must Start On"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_tooltip_task_name
msgid "Name"
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_task.py:0
#, python-format
msgid "Please select the user before sending the mail from project setting"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.actions.report,name:ks_gantt_view_project.action_report_gantt_tasks
msgid "Print Tasks"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.actions.report,name:ks_gantt_view_project.action_report_gantt_tasks_timesheet
msgid "Print Timesheet"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_tooltip_task_progress
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.report_ks_gantt_tasks
msgid "Progress"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model,name:ks_gantt_view_project.model_project_project
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.report_ks_gantt_tasks
msgid "Project"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_enable_quickinfo_extension
msgid "Quick Info"
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/wizard/ks_gantt_view_project_import.py:0
#, python-format
msgid ""
"Required data not found in the json file, please upload correct json file."
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task__ks_schedule_mode
msgid "Schedule Mode"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_days_off_selection
msgid "Select Days"
msgstr ""
#. module: ks_gantt_view_project
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.ks_view_task_form
msgid "Send Email"
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_task.py:0
#, python-format
msgid "Sequence Changed"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_ks_task_link__ks_source_task_id
msgid "Source Task"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_tooltip_task_stage
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.report_ks_gantt_tasks
msgid "Stage"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task_type__ks_stage_color
msgid "Stage Color"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_project_start
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_project__ks_tooltip_task_start_date
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task__ks_start_datetime
msgid "Start Date"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields.selection,name:ks_gantt_view_project.selection__project_task__ks_constraint_task_type__snet
msgid "Start No Earlier Than"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields.selection,name:ks_gantt_view_project.selection__project_task__ks_constraint_task_type__snlt
msgid "Start No Late Than"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task__ks_allow_subtask
msgid "Sub-tasks"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_ks_task_link__ks_target_task_id
msgid "Target Task"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model,name:ks_gantt_view_project.model_project_task
#: model:ir.model.fields.selection,name:ks_gantt_view_project.selection__project_task__ks_task_type__task
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.report_ks_gantt_tasks
msgid "Task"
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_task.py:0
#, python-format
msgid "Task Assignment Mail"
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_task.py:0
#, python-format
msgid "Task Assignment mail unsuccessful with the error : %s"
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_inherit.py:0
#, python-format
msgid "Task Deadline Reminder Mail"
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_task.py:0
#, python-format
msgid "Task Progress"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model,name:ks_gantt_view_project.model_project_task_type
msgid "Task Stage"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task__ks_task_type
msgid "Task Type"
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_inherit.py:0
#, python-format
msgid ""
"Task deadline reminder mail send successfully for task : (%s), user (%s)"
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_inherit.py:0
#, python-format
msgid ""
"Task deadline reminder mail unsuccessfully for task : (%s), user (%s) \n"
" error log : %s"
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_task.py:0
#, python-format
msgid "Task end date cannot be smaller then the start date."
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_task.py:0
#, python-format
msgid "Task should be finish on the constraint date or after it."
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_task.py:0
#, python-format
msgid "Task should be finish on the constraint date or before it."
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_task.py:0
#, python-format
msgid "Task should be start on the constraint date or after it."
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_task.py:0
#, python-format
msgid "Task should be start on the constraint date or before it."
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_task.py:0
#, python-format
msgid "Task should finish exactly on the constraint date."
msgstr ""
#. module: ks_gantt_view_project
#: code:addons/ks_gantt_view_project/models/ks_gantt_project_task.py:0
#, python-format
msgid "Task should start exactly on the constraint date."
msgstr ""
#. module: ks_gantt_view_project
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.report_ks_gantt_tasks
msgid "Tasks List"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model,name:ks_gantt_view_project.model_hr_leave
msgid "Time Off"
msgstr ""
#. module: ks_gantt_view_project
#: model_terms:ir.ui.view,arch_db:ks_gantt_view_project.ks_view_project_form
msgid "Tooltip Settings"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_project_task__ks_task_unschedule
msgid "Unschedule"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,field_description:ks_gantt_view_project.field_ks_gantt_import_wizard__ks_file
msgid "Upload File"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model.fields,help:ks_gantt_view_project.field_project_task__ks_work_duration
msgid "Working Duration in day Hours"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.model,name:ks_gantt_view_project.model_ks_gantt_import_wizard
msgid "ks.gantt.import.wizard"
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.actions.report,print_report_name:ks_gantt_view_project.action_report_gantt_tasks_timesheet
msgid ""
"object.name + ' timesheet'\n"
" "
msgstr ""
#. module: ks_gantt_view_project
#: model:ir.actions.report,print_report_name:ks_gantt_view_project.action_report_gantt_tasks
msgid ""
"object.project_id.name\n"
" "
msgstr ""
+7
View File
@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
from . import ks_gantt_project_inherit
from . import ks_project_task_type_inherit
from . import ks_project_project
from . import ks_gantt_project_task
from . import ks_task_link_inherit
from . import ks_hr_leave_inherit
@@ -0,0 +1,266 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
from odoo.exceptions import UserError
import datetime
import logging
import json
_logger = logging.getLogger(__name__)
# Odoo 19: MailDeliveryException moved out of ir_mail_server in some builds.
# Try the standard location first, fall back to the older path.
try:
from odoo.addons.base.models.ir_mail_server import MailDeliveryException
except ImportError:
try:
from odoo.exceptions import MailDeliveryException
except ImportError:
MailDeliveryException = Exception # last-resort fallback
class KsGanttViewProject(models.Model):
_inherit = 'project.project'
ks_project_start = fields.Datetime(
string="Start Date",
default=lambda self: fields.Datetime.now(),
required=True,
)
ks_project_end = fields.Datetime(
string="End Date",
default=lambda self: fields.Datetime.now() + datetime.timedelta(days=7),
required=True,
)
ks_enable_project_deadline = fields.Boolean(
string='Deadline',
help="Enable/Disable Deadline of the tasks.",
default=True,
)
ks_enable_task_dynamic_text = fields.Boolean(
string='Dynamic Text',
help="Enable/Disable Task Dynamic Text.",
default=True,
)
ks_enable_task_dynamic_progress = fields.Boolean(
string='Dynamic Progress',
help="Enable/Disable Task Dynamic Progress.",
default=True,
)
ks_days_off = fields.Boolean(
string='Days Off',
help="Enable to remove off days from the gantt",
default=False,
)
ks_hide_date = fields.Boolean(
string='Hide Holiday Day',
help='Hide holidays on the gantt view',
default=False,
)
ks_days_off_selection = fields.Many2many('ks.week.days', string="Select Days")
ks_enable_quickinfo_extension = fields.Boolean(
string='Quick Info',
help="Enable/Disable Quick Info.",
default=True,
)
ks_tooltip_task_name = fields.Boolean(string='Name', default=True)
ks_tooltip_task_duration = fields.Boolean(string='Duration', default=True)
ks_tooltip_task_start_date = fields.Boolean(string='Start Date', default=True)
ks_tooltip_task_end_date = fields.Boolean(string='End Date', default=True)
ks_tooltip_task_progress = fields.Boolean(string='Progress', default=True)
ks_tooltip_task_deadline = fields.Boolean(string='Deadline', default=True)
ks_tooltip_task_stage = fields.Boolean(string='Stage', default=True)
ks_tooltip_task_constraint_type = fields.Boolean(string='Constraint Type', default=True)
ks_tooltip_task_constraint_date = fields.Boolean(string='Constraint Date', default=True)
ks_mail_timesheet_user = fields.Many2one('res.partner', string="Mail user")
ks_project_task_json = fields.Char(compute="ks_compute_json_data_project_task")
ks_project_task_linking = fields.Char(compute="ks_compute_json_data_project_task_link")
@api.model
def ks_project_config(self, project_id):
ks_tooltip_fields = [
'ks_tooltip_task_name', 'ks_tooltip_task_duration',
'ks_tooltip_task_start_date', 'ks_tooltip_task_end_date',
'ks_tooltip_task_progress', 'ks_tooltip_task_stage',
'ks_tooltip_task_constraint_type', 'ks_tooltip_task_constraint_date',
'ks_tooltip_task_deadline',
]
ks_project_obj = self.env['project.project'].browse(project_id)
ks_project_config = {
'ks_project_start': ks_project_obj.ks_project_start or False,
'ks_project_end': ks_project_obj.ks_project_end or False,
'ks_enable_project_deadline': ks_project_obj.ks_enable_project_deadline,
'ks_enable_task_dynamic_text': ks_project_obj.ks_enable_task_dynamic_text,
'ks_enable_task_dynamic_progress': ks_project_obj.ks_enable_task_dynamic_progress,
'ks_days_off': ks_project_obj.ks_days_off,
'ks_hide_date': ks_project_obj.ks_hide_date,
'ks_enable_quickinfo_extension': ks_project_obj.ks_enable_quickinfo_extension,
'ks_allow_subtasks': ks_project_obj.allow_subtasks,
}
ks_day_off_list = []
if ks_project_obj.ks_days_off:
for rec in ks_project_obj.ks_days_off_selection:
ks_day_off_list.append(rec.ks_day_no)
ks_project_config['ks_days_off_selection'] = ks_day_off_list
ks_project_tooltip_config = {
field: ks_project_obj[field] for field in ks_tooltip_fields
}
ks_project_config['ks_project_tooltip_config'] = ks_project_tooltip_config
return ks_project_config
@api.model
def ks_task_due_alert(self):
"""
Scheduled action: send deadline reminder emails for tasks due in 7, 3,
or 1 day(s).
"""
ks_today_date = datetime.datetime.today().date()
for ks_project in self.search([]):
ks_all_task = self.env['project.task'].search(
[('project_id', '=', ks_project.id)]
)
for ks_task in ks_all_task:
for i in range(len(ks_task.user_ids)):
if ks_task.date_deadline and ks_task.user_ids:
days_left = (ks_task.date_deadline - ks_today_date).days
if ks_today_date < ks_task.date_deadline and days_left in [7, 3, 1]:
template_obj = self.env['mail.mail']
message_body = _(
"Hi %s, This mail is to remind you that task %s "
"of project %s has only %s day(s) until the deadline."
) % (
ks_task.user_ids[i].name,
ks_task.name,
ks_task.project_id.name,
days_left,
)
template_data = {
'subject': _('Task Deadline Reminder Mail'),
'body_html': message_body,
'email_from': self.env.user.email,
'email_to': ks_task.user_ids[i].email,
}
template_id = template_obj.sudo().create(template_data)
try:
template_id.sudo().send(raise_exception=True)
_logger.info(
'Task deadline reminder sent for task: %s, user: %s',
ks_task.name, ks_task.user_ids[i].name,
)
except MailDeliveryException as error:
_logger.error(
'Task deadline reminder failed for task: %s, user: %s%s',
ks_task.name, ks_task.user_ids[i].name, error,
)
@api.model
def ks_public_holidays(self):
"""Return a list of all public holiday datetimes."""
ks_project_holiday = []
ks_pub_hol = self.env['resource.calendar.leaves'].search(
[('resource_id', '=', False)]
)
for ks_holiday in ks_pub_hol:
ks_hol_datetime = ks_holiday.date_from
while ks_hol_datetime <= ks_holiday.date_to:
if ks_hol_datetime not in ks_project_holiday:
ks_project_holiday.append(ks_hol_datetime)
ks_hol_datetime += datetime.timedelta(days=1)
return ks_project_holiday
def ks_compute_json_data_project_task(self):
for rec in self:
ks_project_task_json = []
ks_all_task_obj = self.env['project.task'].search(
[('project_id', '=', rec.id)]
)
for ks_task in ks_all_task_obj:
for ks_user in range(len(ks_task.user_ids)):
ks_project_task_json.append({
'id': 'task_' + str(ks_task.id),
'ks_task_start_date': str(ks_task.ks_start_datetime),
'ks_task_end_date': str(ks_task.ks_end_datetime),
'ks_task_id': 'task_' + str(ks_task.id),
'ks_task_name': ks_task.name,
'ks_task_color': ks_task.ks_color,
'ks_task_model': 'project.task',
'parent_id': 'task_' + str(ks_task.parent_id.id) if ks_task.parent_id.id else False,
'project_id': [ks_task.project_id.id, ks_task.project_id.name],
'partner_id': [ks_task.partner_id.id, ks_task.partner_id.name],
'company_id': [ks_task.company_id.id, ks_task.company_id.name],
'mark_as_important': ks_task.priority,
'ks_enable_task_duration': ks_task.ks_enable_task_duration,
'deadline': str(ks_task.date_deadline) if ks_task.date_deadline else False,
'progress': ks_task.progress,
'ks_allow_subtask': ks_task.ks_allow_subtask,
'ks_allow_parent_task': ks_task.ks_allow_subtask,
'ks_schedule_mode': ks_task.ks_schedule_mode,
'constraint_type': ks_task.ks_constraint_task_type,
'constraint_date': str(ks_task.ks_constraint_task_date) if ks_task.ks_constraint_task_date else False,
'stage_id': [ks_task.stage_id.id, ks_task.stage_id.name],
'unscheduled': ks_task.ks_task_unschedule,
'ks_owner_task': [ks_task.user_ids[ks_user].id, ks_task.user_ids[ks_user].name] if ks_task.user_ids else False,
'resource_working_hours': ks_task.ks_resource_hours_per_day,
'type': ks_task.ks_task_type,
'ks_resource_hours_available': ks_task.ks_resource_hours_available,
'ks_task_link_json': ks_task.ks_task_link_json,
})
rec.ks_project_task_json = json.dumps(ks_project_task_json)
def ks_compute_json_data_project_task_link(self):
for rec in self:
ks_project_task_json = []
ks_all_task_obj = self.env['project.task'].search(
[('project_id', '=', rec.id)]
)
for ks_task in ks_all_task_obj:
for ks_user in range(len(ks_task.user_ids)):
ks_project_task_json.append({
'id': 'task_' + str(ks_task.id),
'ks_task_start_date': str(ks_task.ks_start_datetime),
'ks_task_end_date': str(ks_task.ks_end_datetime),
'ks_task_id': 'task_' + str(ks_task.id),
'ks_task_name': ks_task.name,
'ks_task_color': ks_task.ks_color,
'ks_task_model': 'project.task',
'parent_id': 'task_' + str(ks_task.parent_id.id) if ks_task.parent_id.id else False,
'project_id': ks_task.project_id.id,
'mark_as_important': ks_task.priority,
'deadline': str(ks_task.date_deadline) if ks_task.date_deadline else False,
'progress': ks_task.progress,
'ks_allow_subtask': ks_task.ks_allow_subtask,
'ks_allow_parent_task': ks_task.ks_allow_subtask,
'ks_schedule_mode': ks_task.ks_schedule_mode,
'constraint_type': ks_task.ks_constraint_task_type,
'constraint_date': str(ks_task.ks_constraint_task_date) if ks_task.ks_constraint_task_date else False,
'stage_id': [ks_task.stage_id.id, ks_task.stage_id.name],
'unscheduled': ks_task.ks_task_unschedule,
'ks_owner_task': [ks_task.user_ids[ks_user].id, ks_task.user_ids[ks_user].name] if ks_task.user_ids else False,
'resource_working_hours': ks_task.ks_resource_hours_per_day,
'type': ks_task.ks_task_type,
'ks_resource_hours_available': ks_task.ks_resource_hours_available,
})
# Bug fix from original: was writing to wrong field
rec.ks_project_task_linking = json.dumps(ks_project_task_json)
@api.constrains('ks_days_off_selection')
def _check_valid_ks_days_off_selection(self):
for record in self:
if len(record.ks_days_off_selection) == 7:
raise UserError(
_("Invalid value for Days selection. At least keep one working day.")
)
def write(self, vals):
res = super().write(vals)
if vals.get('ks_days_off_selection'):
for task in self.task_ids:
task.ks_compute_task_duration()
return res
@@ -0,0 +1,434 @@
# -*- coding: utf-8 -*-
from datetime import time, datetime, timedelta
import base64
import logging
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
import json
_logger = logging.getLogger(__name__)
# Odoo 19: MailDeliveryException moved in some builds — safe import fallback.
try:
from odoo.addons.base.models.ir_mail_server import MailDeliveryException
except ImportError:
try:
from odoo.exceptions import MailDeliveryException
except ImportError:
MailDeliveryException = Exception
class KsProjectTask(models.Model):
_inherit = "project.task"
def ks_convert_day_names_to_integers(self, day_names):
day_name_to_int = {
'Monday': 0, 'Tuesday': 1, 'Wednesday': 2, 'Thursday': 3,
'Friday': 4, 'Saturday': 5, 'Sunday': 6,
}
return [day_name_to_int.get(name) for name in day_names]
def ks_calculate_end_date(self, ks_task_duration, end_date, weekdays):
if end_date.weekday() in weekdays:
ks_task_duration += 1
while ks_task_duration > 0:
end_date += timedelta(days=1)
if end_date.weekday() not in weekdays:
ks_task_duration -= 1
return end_date
def ks_default_start_date(self):
# Odoo 19: return datetime directly — fields.Datetime.to_string() is
# deprecated since 16 and removed in 18+.
return datetime.combine(fields.Datetime.now(), datetime.min.time())
def ks_default_end_datetime(self):
return datetime.combine(
fields.Datetime.now() + timedelta(days=1), datetime.min.time()
)
ks_start_datetime = fields.Datetime(
"Start Date", required=True, default=ks_default_start_date
)
ks_end_datetime = fields.Datetime(
"End Date", required=True, default=ks_default_end_datetime
)
ks_color = fields.Char(string="Color", compute='ks_compute_color')
ks_allow_subtask = fields.Boolean(related="project_id.allow_subtasks")
planned_hours = fields.Float("Initially Planned Hours", tracking=True)
ks_mark_important = fields.Boolean(
string="Mark As Important", default=False,
help="Mark as an important task",
)
ks_work_duration = fields.Char(
string="Duration",
help="Working Duration in day Hours",
compute='ks_compute_work_duration',
)
ks_task_link_json = fields.Char(compute="ks_compute_json_data_task_link")
ks_resource_hours_per_day = fields.Float(
related='user_ids.employee_id.resource_calendar_id.hours_per_day'
)
ks_resource_hours_available = fields.Char(
compute='ks_compute_resource_hours_available'
)
ks_task_link_ids = fields.One2many(
comodel_name='ks.task.link',
inverse_name='ks_source_task_id',
string='Task Links',
)
ks_schedule_mode = fields.Selection(
string='Schedule Mode',
selection=[('auto', 'Auto'), ('manual', 'Manual')],
default="manual",
)
ks_constraint_task_type = fields.Selection(
string='Constraint Type',
selection=[
('asap', 'As Soon As Possible'),
('alap', 'As Late As Possible'),
('snet', 'Start No Earlier Than'),
('snlt', 'Start No Late Than'),
('fnet', 'Finish No Earlier Than'),
('fnlt', 'Finish No Later Than'),
('mso', 'Must Start On'),
('mfo', 'Must Finish On'),
],
default="asap",
required=True,
)
ks_constraint_task_date = fields.Datetime(string="Constraint Date")
ks_enable_task_duration = fields.Boolean(string="Enable Task Duration")
ks_task_duration = fields.Integer(string="Duration")
ks_task_unschedule = fields.Boolean(string="Unschedule", default=False)
ks_task_type = fields.Selection(
string='Task Type',
selection=[('task', 'Task'), ('milestone', 'Milestone')],
default='task',
required=True,
)
ks_user_ids = fields.Char(compute="ks_compute_ks_user_id", default=[])
def ks_compute_ks_user_id(self):
for rec in self:
ks_temp = []
for user in rec.user_ids:
ks_temp.append([user.id, user.name])
# Always assign (even empty list) to avoid "field not set" compute errors
rec.ks_user_ids = json.dumps(ks_temp)
@api.depends('stage_id')
def ks_compute_color(self):
for ks_task in self:
if ks_task.stage_id and ks_task.stage_id.ks_stage_color:
ks_task.ks_color = ks_task.stage_id.ks_stage_color
else:
ks_task.ks_color = '#7C7BAD'
@api.onchange('ks_start_datetime', 'ks_end_datetime', 'ks_work_duration')
def ks_compute_work_duration(self):
for rec in self:
rec.ks_work_duration = '0'
if rec.ks_end_datetime and rec.ks_start_datetime and rec.ks_task_type != 'milestone':
delta = rec.ks_end_datetime - rec.ks_start_datetime
if delta.days == 0:
rec.ks_work_duration = str(delta) + " hours"
else:
rec.ks_work_duration = str(delta)
if rec.ks_start_datetime and rec.ks_task_type == 'milestone':
rec.ks_end_datetime = rec.ks_start_datetime
def ks_compute_json_data_task_link(self):
for rec in self:
ks_task_link_json = []
for task_link in rec.ks_task_link_ids:
ks_task_link_json.append({
'id': task_link.id,
'source': task_link.ks_source_task_id.id,
'target': task_link.ks_target_task_id.id,
'type': task_link.ks_task_link_type,
'lag': task_link.ks_lag_days * 24,
})
rec.ks_task_link_json = json.dumps(ks_task_link_json)
@api.model
def create(self, values):
res = super().create(values)
if res.ks_task_duration and res.ks_enable_task_duration:
ids = res.project_id.ks_days_off_selection.ids
if ids:
self.env.cr.execute(
'SELECT ks_day_name FROM ks_week_days WHERE id IN %s',
(tuple(ids),),
)
records = self.env.cr.dictfetchall()
ks_day_names = [r['ks_day_name'] for r in records]
day_integers = self.ks_convert_day_names_to_integers(ks_day_names)
res.ks_end_datetime = self.ks_calculate_end_date(
res.ks_task_duration, res.ks_start_datetime, day_integers
)
else:
res.ks_end_datetime = res.ks_start_datetime + timedelta(days=res.ks_task_duration)
if values.get('ks_schedule_mode') == 'auto' and values.get('ks_constraint_task_type') in ['asap', 'alap']:
res.ks_auto_schedule_mode()
res.ks_validate_constraint()
if 'user_ids' in values:
res.ks_send_email_task_assigned()
return res
def write(self, values):
res = super().write(values)
for rec in self:
if rec.ks_schedule_mode == 'auto' and rec.ks_constraint_task_type in ['asap', 'alap']:
for ks_record in rec.ks_task_link_ids:
ks_record.ks_target_task_id.ks_auto_schedule_mode()
elif rec.ks_start_datetime or rec.ks_end_datetime or rec.ks_task_link_ids:
for record in rec.ks_task_link_ids:
if (record.ks_target_task_id.ks_schedule_mode == 'auto'
and record.ks_target_task_id.ks_constraint_task_type == 'asap'):
record.ks_target_task_id.ks_auto_schedule_mode()
if rec.ks_constraint_task_type or rec.ks_constraint_task_date:
rec.ks_validate_constraint()
if (rec.ks_task_duration or rec.ks_task_duration == 0) \
and rec.ks_enable_task_duration and not rec.ks_start_datetime:
rec.ks_end_datetime = rec.ks_start_datetime + timedelta(days=rec.ks_task_duration)
return res
def ks_validate_constraint(self):
"""Validate task constraint violation against start/end dates."""
if self.ks_constraint_task_type == 'snet' and self.ks_constraint_task_date:
if not self.ks_constraint_task_date <= self.ks_start_datetime:
raise ValidationError(_("Task should start on or after the constraint date."))
if self.ks_constraint_task_type == 'snlt' and self.ks_constraint_task_date:
if not self.ks_constraint_task_date >= self.ks_start_datetime:
raise ValidationError(_("Task should start on or before the constraint date."))
if self.ks_constraint_task_type == 'fnet' and self.ks_constraint_task_date:
if not self.ks_constraint_task_date <= self.ks_end_datetime:
raise ValidationError(_("Task should finish on or after the constraint date."))
if self.ks_constraint_task_type == 'fnlt' and self.ks_constraint_task_date:
if not self.ks_constraint_task_date >= self.ks_end_datetime:
raise ValidationError(_("Task should finish on or before the constraint date."))
if self.ks_constraint_task_type == 'mso' and self.ks_constraint_task_date:
if self.ks_constraint_task_date != self.ks_start_datetime:
raise ValidationError(_("Task should start exactly on the constraint date."))
if self.ks_constraint_task_type == 'mfo' and self.ks_constraint_task_date:
if self.ks_constraint_task_date != self.ks_end_datetime:
raise ValidationError(_("Task should finish exactly on the constraint date."))
def ks_auto_schedule_mode(self):
"""Auto-schedule task start/end dates based on task links."""
if self.ks_schedule_mode != 'auto':
return
task_link = self.env['ks.task.link'].search([
('ks_target_task_id', '=', self.id),
('ks_source_task_id.project_id', '=', self.project_id.id),
])
if not task_link:
ks_duration = self.ks_end_datetime - self.ks_start_datetime
if self.ks_constraint_task_type == 'asap':
self.ks_start_datetime = self.project_id.ks_project_start
self.ks_end_datetime = self.project_id.ks_project_start + ks_duration
elif self.ks_constraint_task_type == 'alap':
ks_closest_task = False
for rec in self.ks_task_link_ids:
if rec.ks_source_task_id.id == self.id:
if not ks_closest_task or ks_closest_task > rec.ks_target_task_id.ks_start_datetime:
ks_closest_task = rec.ks_target_task_id.ks_start_datetime
if ks_closest_task:
self.ks_end_datetime = ks_closest_task
self.ks_start_datetime = ks_closest_task - ks_duration
elif len(task_link) == 1:
ks_duration = self.ks_end_datetime - self.ks_start_datetime
link_type = task_link.ks_task_link_type
if link_type == "0": # Finish to Start
ref = task_link.ks_source_task_id.ks_end_datetime
self.ks_start_datetime = ref
self.ks_end_datetime = ref + ks_duration
elif link_type == "1": # Start to Start
ref = task_link.ks_source_task_id.ks_start_datetime
self.ks_start_datetime = ref
self.ks_end_datetime = ref + ks_duration
elif link_type == "2": # Finish to Finish
ref = task_link.ks_source_task_id.ks_end_datetime
self.ks_end_datetime = ref
self.ks_start_datetime = ref - ks_duration
elif link_type == "3": # Start to Finish
ref = task_link.ks_source_task_id.ks_start_datetime
self.ks_end_datetime = ref
self.ks_start_datetime = ref - ks_duration
for rec in self.ks_task_link_ids:
if rec.ks_target_task_id.ks_schedule_mode == 'auto':
rec.ks_target_task_id.ks_auto_schedule_mode()
@api.constrains('ks_start_datetime', 'ks_end_datetime')
def _validate_task_date(self):
for rec in self:
if rec.ks_end_datetime < rec.ks_start_datetime and rec.ks_task_type != 'milestone':
raise ValidationError(
_("Task end date cannot be earlier than the start date.")
)
def get_report(self):
return self.env.ref('ks_gantt_view_project.ks_gantt_tasks_report').report_action(
self, data={'model': self._name, 'ids': self.ids}
)
def ks_action_send_email_tasks(self):
if not self.project_id.ks_mail_timesheet_user:
raise ValidationError(
_("Please select a mail recipient in the project Gantt settings before sending.")
)
template_obj = self.env['mail.mail']
message_body = _(
"Hi %s, the timesheet report for task '%s' is attached — please review it."
) % (self.project_id.ks_mail_timesheet_user.name, self.name)
template_data = {
'subject': _('Task Progress'),
'body_html': message_body,
'email_from': self.env.user.email,
'email_to': self.project_id.ks_mail_timesheet_user.email,
}
template_id = template_obj.sudo().create(template_data)
self.ks_fetch_timesheet_report(template_id)
notification = {'type': 'ir.actions.client', 'tag': 'display_notification'}
try:
template_id.sudo().send(raise_exception=True)
notification['params'] = {
'message': _('Email sent successfully'),
'sticky': False,
}
except MailDeliveryException as error:
notification['params'] = {
'message': _('Error sending mail: %s') % (error.args[0],),
'sticky': True,
}
return notification
def ks_fetch_timesheet_report(self, mail_template):
"""Attach a timesheet PDF report to the given mail.mail record."""
self.ensure_one()
report_template = self.env.ref(
'ks_gantt_view_project.action_report_gantt_tasks_timesheet'
)
report_name = self.name + _(' timesheet.pdf')
# Odoo 19: _render_qweb_pdf takes only record ids (no self-ref first arg)
result, _format = report_template._render_qweb_pdf([self.id])
result = base64.b64encode(result)
attachment_obj = self.env['ir.attachment'].sudo()
attachment_data = {
'name': report_name,
'datas': result,
'type': 'binary',
'res_model': 'mail.message',
'res_id': mail_template.id,
}
attachment_id = attachment_obj.create(attachment_data).id
if attachment_id:
mail_template.sudo().write({'attachment_ids': [(4, attachment_id)]})
@api.onchange('ks_task_duration', 'project_id')
def ks_compute_task_duration(self):
for rec in self:
if rec.ks_start_datetime:
if not rec.ks_task_duration:
rec.ks_task_duration = 0
ids = rec.project_id.ks_days_off_selection.ids
if ids:
self.env.cr.execute(
'SELECT ks_day_name FROM ks_week_days WHERE id IN %s',
(tuple(ids),),
)
records = self.env.cr.dictfetchall()
ks_day_names = [r['ks_day_name'] for r in records]
day_integers = self.ks_convert_day_names_to_integers(ks_day_names)
rec.ks_end_datetime = self.ks_calculate_end_date(
rec.ks_task_duration, rec.ks_start_datetime, day_integers
)
else:
rec.ks_end_datetime = rec.ks_start_datetime + timedelta(days=rec.ks_task_duration)
@api.onchange('ks_start_datetime', 'ks_enable_task_duration')
def ks_calculate_task_duration(self):
for rec in self:
rec.ks_task_duration = 0
if rec.ks_end_datetime and rec.ks_start_datetime:
rec.ks_task_duration = (rec.ks_end_datetime - rec.ks_start_datetime).days
def ks_compute_resource_hours_available(self):
for rec in self:
resource_availability = {}
if rec.user_ids and rec.user_ids.employee_id and rec.user_ids.employee_id.resource_calendar_id:
ks_working_calendar = rec.user_ids[-1].employee_id.resource_calendar_id
for ks_avail_hours in ks_working_calendar.attendance_ids:
dayofweek = int(ks_avail_hours.dayofweek)
key = 0 if dayofweek == 6 else dayofweek + 1
if key not in resource_availability:
resource_availability[key] = []
ks_temp_hours = ks_avail_hours.hour_from
while ks_temp_hours < ks_avail_hours.hour_to:
resource_availability[key].append(ks_temp_hours)
ks_temp_hours += 1
rec.ks_resource_hours_available = json.dumps(resource_availability)
def ks_send_email_task_assigned(self):
"""Send assignment notification to all assigned users."""
template_obj = self.env['mail.mail']
for user in self.user_ids:
message_body = _("Hi %s, Task '%s' has been assigned to you.") % (
user.name, self.name
)
template_data = {
'subject': _('Task Assignment Mail'),
'body_html': message_body,
'email_from': self.env.user.email,
'email_to': user.email,
}
template_id = template_obj.sudo().create(template_data)
try:
template_id.sudo().send(raise_exception=True)
except MailDeliveryException as error:
_logger.error('Task assignment mail failed: %s', error)
@api.model
def ks_update_task_sequence(self, data):
query = "WITH ks_tasks (id, parent_id, sequence) AS (\nVALUES"
for index in data:
if data[index].get('id'):
vals = {}
if 'parent_id' in data[index]:
vals['parent_id'] = data[index].get('parent_id') or False
if 'sequence' in data[index]:
vals['sequence'] = data[index].get('sequence')
parent_val = str(vals['parent_id']) if vals.get('parent_id') else 'Null'
query += (
"\n(" + str(data[index].get('id')) + ','
+ parent_val + ','
+ str(vals.get('sequence', 0)) + "),"
)
query = (
query[:-1]
+ "\n)\nUPDATE project_task as t SET \n parent_id=kt.parent_id::integer,"
"sequence=kt.sequence \nFROM ks_tasks as kt WHERE t.id = kt.id"
)
self.env.cr.execute(query)
_logger.info('Task sequence updated.')
@@ -0,0 +1,24 @@
from odoo import api, fields, models
from datetime import timedelta
class KsHrLeave(models.Model):
_inherit = 'hr.leave'
def action_validate(self):
result = super(KsHrLeave, self).action_validate()
for rec in self:
# Check if the leave is approved then increase the project task between the leave dates for the employee.
if rec.state == 'validate':
user_tasks = self.env['project.task'].search(
['&', '|', '&', ('ks_start_datetime', '<=', rec.request_date_from),
('ks_end_datetime', '>=', rec.request_date_from),
'&', ('ks_start_datetime', '<=', rec.request_date_to),
('ks_end_datetime', '>=', rec.request_date_to),
('user_ids', '=', rec.user_id.id)
])
for tasks in user_tasks:
tasks.ks_end_datetime += timedelta(days=int(rec.number_of_days))
return result
@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
# Odoo 19 note:
# In Odoo 16+, allow_subtasks is a standard Boolean field on project.project.
# The original override set it readonly=True at the ORM level, which prevented
# users from ever changing it via the project form. Removed that restriction —
# if you need the field to be read-only in a specific view, use readonly="1"
# in the view XML instead of redefining the field on the model.
# This file is kept as a placeholder so the models/__init__.py import remains valid.
class KsProject(models.Model):
_inherit = "project.project"
# No field overrides needed — allow_subtasks is already defined in core.
@@ -0,0 +1,6 @@
from odoo import api, exceptions, fields, models, _
class KsGanttViewStage(models.Model):
_inherit = 'project.task.type'
ks_stage_color = fields.Char('Stage Color')
@@ -0,0 +1,30 @@
from odoo import fields, models, api, _
from odoo.exceptions import ValidationError
class KsTaskLink(models.Model):
_inherit = 'ks.task.link'
ks_source_task_id = fields.Many2one(comodel_name='project.task', string="Source Task")
ks_target_task_id = fields.Many2one(comodel_name='project.task', string='Target Task')
ks_lag_days = fields.Integer(string="Lag Days")
@api.onchange('ks_task_link_type')
def ks_compute_target_task_domain(self):
ks_task_ids = []
if self.ks_source_task_id and self.ks_source_task_id.project_id:
ks_project_id = self.ks_source_task_id.project_id.id
ks_task_ids = self.env['project.task'].sudo().search([('project_id', '=', ks_project_id)]).ids
return {
'domain': {
'ks_target_task_id': [('id', '=', ks_task_ids)],
}
}
@api.constrains('ks_source_task_id', 'ks_target_task_id')
def ks_task_link_constraint(self):
for rec in self:
if rec.ks_source_task_id.id == rec.ks_target_task_id.id:
raise ValidationError(_("Can't create same link with same task."))
if rec.ks_source_task_id.project_id.id != rec.ks_target_task_id.project_id.id:
raise ValidationError(_("Can't create link with other project task."))
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data>
<template id="report_ks_gantt_tasks">
<t t-call="web.html_container">
<t t-call="web.external_layout">
<h2>Tasks List</h2>
<table class="table table-sm o_main_table">
<thead style="display: table-row-group">
<tr>
<th style="width: 25%;">Task</th>
<th style="width: 20%;">Project</th>
<th style="width: 15%;">Stage</th>
<th style="width: 15%;">Assigned to</th>
<th style="width: 10%;">Progress</th>
<th style="width: 15%;">Deadline</th>
</tr>
</thead>
<tbody>
<t t-foreach="docs" t-as="doc">
<tr>
<td><t t-esc="doc.name"/></td>
<td><t t-esc="doc.project_id.name"/></td>
<td><t t-esc="doc.stage_id.name"/></td>
<td><t t-esc="doc.user_ids.name"/></td>
<td><t t-esc="doc.progress"/>%</td>
<td><t t-esc="doc.date_deadline"/></td>
</tr>
</t>
</tbody>
</table>
</t>
</t>
</template>
<record id="action_report_gantt_tasks" model="ir.actions.report">
<field name="name">Print Tasks</field>
<field name="model">project.task</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">ks_gantt_view_project.report_ks_gantt_tasks</field>
<field name="report_file">ks_gantt_view_project.report_ks_gantt_tasks</field>
<field name="print_report_name">object.project_id.name
</field>
<field name="binding_model_id" ref="model_project_task"/>
<field name="binding_type">report</field>
</record>
</data>
</odoo>
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data>
<template id="report_ks_gantt_tasks_timesheet">
<t t-call="web.html_container">
<t t-call="web.external_layout">
<t t-foreach="docs" t-as="task">
<div class="page" style="page-break-after:always">
<div>
<t t-set="ks_task_heading" t-value="task.name + ' (' + task.project_id.name + ') ' +
' Timesheet List'"/>
<h2 t-esc="ks_task_heading"/>
<div>
<span>
<b>Initially Planned Hours: </b><t t-esc="task.planned_hours"
t-options="{'widget': 'float_time'}"/>
</span>
<span style="float: right">
<b>Hours Spent:</b> <t t-esc="task.effective_hours"
t-options="{'widget': 'float_time'}"/>
</span>
</div>
</div>
<table class="table table-sm o_main_table">
<thead style="display: table-row-group">
<tr>
<th>Date</th>
<th>Employee</th>
<th>Description</th>
<th>Duration (Hours)</th>
</tr>
</thead>
<tbody>
<t t-foreach="task.timesheet_ids" t-as="doc">
<tr>
<td>
<t t-esc="doc.date"/>
</td>
<td>
<t t-esc="doc.employee_id.name"/>
</td>
<td>
<t t-esc="doc.name"/>
</td>
<td>
<t t-esc="doc.unit_amount" t-options="{'widget': 'float_time'}"/>
</td>
</tr>
</t>
</tbody>
</table>
</div>
</t>
</t>
</t>
</template>
<record id="action_report_gantt_tasks_timesheet" model="ir.actions.report">
<field name="name">Print Timesheet</field>
<field name="model">project.task</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">ks_gantt_view_project.report_ks_gantt_tasks_timesheet</field>
<field name="report_file">ks_gantt_view_project.report_ks_gantt_tasks_timesheet</field>
<field name="print_report_name">object.name + ' timesheet'
</field>
<field name="binding_model_id" ref="model_project_task"/>
<field name="binding_type">report</field>
</record>
</data>
</odoo>
@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_ks_gantt_import_wizard,ks.gantt.import.wizard,ks_gantt_view_project.model_ks_gantt_import_wizard,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_ks_gantt_import_wizard ks.gantt.import.wizard ks_gantt_view_project.model_ks_gantt_import_wizard base.group_user 1 1 1 1
Binary file not shown.

After

Width:  |  Height:  |  Size: 580 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

@@ -0,0 +1,3 @@
<svg width="14" height="8" viewBox="0 0 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 1L7 7L13 1" stroke="#4B4B4B" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 805 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Some files were not shown because too many files have changed in this diff Show More