first push message
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
from . import controllers
|
||||
from . import models
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
'name': "gantt_view_ck",
|
||||
|
||||
'summary': "Short (1 phrase/line) summary of the module's purpose",
|
||||
|
||||
'description': """
|
||||
Long description of module's purpose
|
||||
""",
|
||||
|
||||
'author': "My Company",
|
||||
'website': "https://www.yourcompany.com",
|
||||
|
||||
# Categories can be used to filter modules in modules listing
|
||||
# Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml
|
||||
# for the full list
|
||||
'category': 'Uncategorized',
|
||||
'version': '0.1',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': ['base','project'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
# 'security/ir.model.access.csv',
|
||||
'views/project_view.xml',
|
||||
'views/views.xml',
|
||||
],
|
||||
# only loaded in demonstration mode
|
||||
'demo': [
|
||||
'demo/demo.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
# 1. Load the Frappe Gantt Library FIRST
|
||||
'gantt_view_ck/static/src/lib/frappe-gantt.min.css',
|
||||
'gantt_view_ck/static/src/lib/frappe-gantt.min.js',
|
||||
|
||||
# 2. Load your custom module CSS and OWL components
|
||||
'gantt_view_ck/static/src/css/gantt_styles.css',
|
||||
'gantt_view_ck/static/src/xml/gantt_view_template.xml',
|
||||
'gantt_view_ck/static/src/js/gantt_controller.js',
|
||||
],
|
||||
},
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from . import controllers
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,39 @@
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
import json
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
class GanttExportController(http.Controller):
|
||||
|
||||
@http.route('/gantt_view_ck/export/data', type='http', auth='user')
|
||||
def export_data(self, format='json'):
|
||||
# For simplicity, exporting all tasks. You can filter by active project.
|
||||
tasks = request.env['project.task'].search([])
|
||||
|
||||
if format == 'json':
|
||||
data = [{'name': t.name, 'start': str(t.gantt_start_date), 'end': str(t.gantt_end_date)} for t in tasks]
|
||||
return request.make_json_response(data)
|
||||
|
||||
elif format == 'xml':
|
||||
root = ET.Element("GanttProject")
|
||||
for t in tasks:
|
||||
task_el = ET.SubElement(root, "Task")
|
||||
task_el.set("name", t.name)
|
||||
task_el.set("start", str(t.gantt_start_date))
|
||||
return request.make_response(ET.tostring(root), headers=[('Content-Type', 'application/xml')])
|
||||
|
||||
elif format == 'ical':
|
||||
ical = "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//CK Gantt//EN\n"
|
||||
for t in tasks:
|
||||
if t.gantt_start_date:
|
||||
ical += "BEGIN:VEVENT\n"
|
||||
ical += f"UID:{t.id}@odoo\n"
|
||||
ical += f"DTSTART:{t.gantt_start_date.strftime('%Y%m%d')}T000000Z\n"
|
||||
if t.gantt_end_date:
|
||||
ical += f"DTEND:{t.gantt_end_date.strftime('%Y%m%d')}T000000Z\n"
|
||||
ical += f"SUMMARY:{t.name}\nEND:VEVENT\n"
|
||||
ical += "END:VCALENDAR"
|
||||
return request.make_response(ical, headers=[('Content-Type', 'text/calendar')])
|
||||
|
||||
return request.not_found()
|
||||
@@ -0,0 +1,30 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<!--
|
||||
<record id="object0" model="gantt_view_ck.gantt_view_ck">
|
||||
<field name="name">Object 0</field>
|
||||
<field name="value">0</field>
|
||||
</record>
|
||||
|
||||
<record id="object1" model="gantt_view_ck.gantt_view_ck">
|
||||
<field name="name">Object 1</field>
|
||||
<field name="value">10</field>
|
||||
</record>
|
||||
|
||||
<record id="object2" model="gantt_view_ck.gantt_view_ck">
|
||||
<field name="name">Object 2</field>
|
||||
<field name="value">20</field>
|
||||
</record>
|
||||
|
||||
<record id="object3" model="gantt_view_ck.gantt_view_ck">
|
||||
<field name="name">Object 3</field>
|
||||
<field name="value">30</field>
|
||||
</record>
|
||||
|
||||
<record id="object4" model="gantt_view_ck.gantt_view_ck">
|
||||
<field name="name">Object 4</field>
|
||||
<field name="value">40</field>
|
||||
</record>
|
||||
-->
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,3 @@
|
||||
from . import project_project
|
||||
from . import project_task
|
||||
from . import project_task_type
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
from odoo import models, fields
|
||||
|
||||
class ProjectProject(models.Model):
|
||||
_inherit = 'project.project'
|
||||
|
||||
gantt_enable_dynamic_text = fields.Boolean(string="Dynamic Text", default=True)
|
||||
gantt_enable_dynamic_progress = fields.Boolean(string="Dynamic Progress", default=True)
|
||||
gantt_hide_holidays = fields.Boolean(string="Hide Holiday Day")
|
||||
gantt_days_off = fields.Char(string="Days Off (e.g., 5,6 for Sat,Sun)", default="5,6")
|
||||
gantt_mail_timesheet_user = fields.Many2many('res.users', string="Mail Timesheet Users")
|
||||
@@ -0,0 +1,31 @@
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class ProjectTask(models.Model):
|
||||
_inherit = 'project.task'
|
||||
|
||||
gantt_start_date = fields.Date(string="Start Date", default=fields.Date.today)
|
||||
gantt_end_date = fields.Date(string="End Date")
|
||||
task_progress = fields.Float(string="Progress (%)", default=0.0)
|
||||
constraint_type = fields.Selection([
|
||||
('asap', 'As Soon As Possible'),
|
||||
('snet', 'Start No Earlier Than'),
|
||||
('snlt', 'Start No Later Than'),
|
||||
('fnet', 'Finish No Earlier Than'),
|
||||
('fnlt', 'Finish No Later Than'),
|
||||
], string="Constraint Type", default='asap')
|
||||
constraint_date = fields.Datetime(string="Constraint Date")
|
||||
is_milestone = fields.Boolean(string="Milestone")
|
||||
unschedule = fields.Boolean(string="Unschedule")
|
||||
schedule_mode = fields.Selection([
|
||||
('auto', 'Auto'),
|
||||
('manual', 'Manual')
|
||||
], string="Schedule Mode", default='manual')
|
||||
|
||||
# Computed color based on task stage
|
||||
stage_color = fields.Char(string="Stage Color", compute='_compute_stage_color', store=True)
|
||||
|
||||
@api.depends('stage_id.gantt_stage_color')
|
||||
def _compute_stage_color(self):
|
||||
for task in self:
|
||||
task.stage_color = task.stage_id.gantt_stage_color or '#17a2b8'
|
||||
@@ -0,0 +1,11 @@
|
||||
from odoo import models, fields
|
||||
|
||||
class ProjectTaskType(models.Model):
|
||||
_inherit = 'project.task.type'
|
||||
|
||||
gantt_stage_color = fields.Selection([
|
||||
('#28a745', 'Green (Completed)'),
|
||||
('#dc3545', 'Red (Not Started)'),
|
||||
('#ffc107', 'Yellow (In Progress)'),
|
||||
('#17a2b8', 'Blue (Default)'),
|
||||
], string="Gantt Stage Color", default='#17a2b8')
|
||||
@@ -0,0 +1,2 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_gantt_view_ck_gantt_view_ck,gantt_view_ck.gantt_view_ck,model_gantt_view_ck_gantt_view_ck,base.group_user,1,1,1,1
|
||||
|
@@ -0,0 +1,14 @@
|
||||
.o_gantt_view_container {
|
||||
display: flex; flex-direction: column; height: 100%; background: #fff;
|
||||
}
|
||||
.o_gantt_action_bar {
|
||||
padding: 12px; border-bottom: 1px solid #dee2e6; display: flex; gap: 10px;
|
||||
}
|
||||
.o_gantt_layout { display: flex; flex: 1; overflow: hidden; }
|
||||
.o_gantt_left_grid {
|
||||
width: 40%; border-right: 1px solid #dee2e6; overflow-y: auto;
|
||||
}
|
||||
.o_gantt_right_panel {
|
||||
width: 60%; overflow: auto; position: relative;
|
||||
}
|
||||
.o_gantt_chart_area { min-height: 100%; padding: 20px; }
|
||||
@@ -0,0 +1,96 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { Component, onMounted, useState, useRef } from "@odoo/owl";
|
||||
|
||||
class GanttViewController extends Component {
|
||||
setup() {
|
||||
this.orm = useService("orm");
|
||||
this.action = useService("action");
|
||||
this.notification = useService("notification");
|
||||
this.ganttContainer = useRef("gantt-container");
|
||||
|
||||
this.state = useState({
|
||||
tasks: [],
|
||||
criticalPathEnabled: false,
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
await this.loadTasks();
|
||||
this.renderGanttChart();
|
||||
});
|
||||
}
|
||||
|
||||
async loadTasks() {
|
||||
const tasks = await this.orm.searchRead(
|
||||
"project.task",
|
||||
[["project_id", "!=", false]],
|
||||
["name", "gantt_start_date", "gantt_end_date", "task_progress", "is_milestone", "stage_color", "constraint_type", "date_deadline"]
|
||||
);
|
||||
this.state.tasks = tasks;
|
||||
}
|
||||
|
||||
renderGanttChart() {
|
||||
const chart_area = this.ganttContainer.el.querySelector(".o_gantt_chart_area");
|
||||
chart_area.innerHTML = '<p class="text-center text-muted mt-5">Initializing Gantt Engine...<br/>(Integrate Frappe Gantt or DHTMLX JS library here)</p>';
|
||||
|
||||
/*
|
||||
INTEGRATION POINT:
|
||||
Import your JS library (e.g., Frappe Gantt) in __manifest__.py
|
||||
const gantt = new Gantt(chart_area, this.state.tasks.map(t => ({
|
||||
id: t.id, name: t.name, start: t.gantt_start_date, end: t.gantt_end_date, progress: t.task_progress
|
||||
})), {
|
||||
on_date_change: (task, start, end) => this.onDateChange(task, start, end)
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
async onDateChange(task, start, end) {
|
||||
await this.orm.write("project.task", [task.id], {
|
||||
gantt_start_date: start,
|
||||
gantt_end_date: end,
|
||||
});
|
||||
this.notification.add("Task dates updated successfully!", { type: "success" });
|
||||
}
|
||||
|
||||
zoomToFit() { this.notification.add("Zoom to Fit triggered", { type: "info" }); }
|
||||
|
||||
toggleCriticalPath() {
|
||||
this.state.criticalPathEnabled = !this.state.criticalPathEnabled;
|
||||
this.notification.add(`Critical Path ${this.state.criticalPathEnabled ? 'Enabled' : 'Disabled'}`, { type: "info" });
|
||||
}
|
||||
|
||||
goToToday() { this.notification.add("Scrolled to Today", { type: "info" }); }
|
||||
|
||||
toggleFullScreen() {
|
||||
if (!document.fullscreenElement) this.ganttContainer.el.requestFullscreen();
|
||||
else document.exitFullscreen();
|
||||
}
|
||||
|
||||
editTask(task) {
|
||||
this.action.doAction({
|
||||
type: "ir.actions.act_window",
|
||||
res_model: "project.task",
|
||||
res_id: task.id,
|
||||
views: [[false, "form"]],
|
||||
target: "current",
|
||||
});
|
||||
}
|
||||
|
||||
async deleteTask(task) {
|
||||
if (confirm("Are you sure you want to delete this task?")) {
|
||||
await this.orm.unlink("project.task", [task.id]);
|
||||
this.notification.add("Task deleted", { type: "warning" });
|
||||
await this.loadTasks();
|
||||
this.renderGanttChart();
|
||||
}
|
||||
}
|
||||
|
||||
exportData() {
|
||||
window.location.href = `/gantt_view_ck/export/data?format=excel`;
|
||||
}
|
||||
}
|
||||
|
||||
GanttViewController.template = "gantt_view_ck.GanttViewTemplate";
|
||||
registry.category("actions").add("gantt_view_ck.gantt_action", GanttViewController);
|
||||
@@ -0,0 +1 @@
|
||||
.gantt .grid-background{fill:none}.gantt .grid-header{fill:#fff;stroke:#e0e0e0;stroke-width:1.4}.gantt .grid-row{fill:#fff}.gantt .grid-row:nth-child(even){fill:#f5f5f5}.gantt .row-line{stroke:#ebeff2}.gantt .tick{stroke:#e0e0e0;stroke-width:.2}.gantt .tick.thick{stroke-width:.4}.gantt .today-highlight{fill:#fcf8e3;opacity:.5}.gantt .arrow{fill:none;stroke:#666;stroke-width:1.4}.gantt .bar{fill:#b8c2cc;stroke:#8d99a6;stroke-width:0;transition:stroke-width .3s ease;user-select:none}.gantt .bar-progress{fill:#a3a3ff}.gantt .bar-invalid{fill:rgba(0,0,0,0);stroke:#8d99a6;stroke-width:1;stroke-dasharray:5}.gantt .bar-invalid~.bar-label{fill:#555}.gantt .bar-label{fill:#fff;dominant-baseline:central;text-anchor:middle;font-size:12px;font-weight:lighter}.gantt .bar-label.big{fill:#555;text-anchor:start}.gantt .handle{fill:#ddd;cursor:ew-resize;opacity:0;visibility:hidden;transition:opacity .3s ease}.gantt .bar-wrapper{cursor:pointer;outline:none}.gantt .bar-wrapper:hover .bar{fill:#a9b5c1}.gantt .bar-wrapper:hover .bar-progress{fill:#8a8aff}.gantt .bar-wrapper:hover .handle{visibility:visible;opacity:1}.gantt .bar-wrapper.active .bar{fill:#a9b5c1}.gantt .bar-wrapper.active .bar-progress{fill:#8a8aff}.gantt .lower-text,.gantt .upper-text{font-size:12px;text-anchor:middle}.gantt .upper-text{fill:#555}.gantt .lower-text{fill:#333}.gantt .hide{display:none}.gantt-container{position:relative;overflow:auto;font-size:12px}.gantt-container .popup-wrapper{position:absolute;top:0;left:0;background:rgba(0,0,0,.8);padding:0;color:#959da5;border-radius:3px}.gantt-container .popup-wrapper .title{border-bottom:3px solid #a3a3ff;padding:10px}.gantt-container .popup-wrapper .subtitle{padding:10px;color:#dfe2e5}.gantt-container .popup-wrapper .pointer{position:absolute;height:5px;margin:0 0 0 -5px;border:5px solid rgba(0,0,0,0);border-top-color:rgba(0,0,0,.8)}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="gantt_view_ck.GanttViewTemplate">
|
||||
<div class="o_gantt_view_container">
|
||||
<div class="o_gantt_action_bar">
|
||||
<button class="btn btn-primary o_gantt_btn" t-on-click="zoomToFit">Zoom to Fit</button>
|
||||
<button class="btn btn-secondary o_gantt_btn" t-on-click="toggleCriticalPath">Critical Path</button>
|
||||
<button class="btn btn-secondary o_gantt_btn" t-on-click="goToToday">Today</button>
|
||||
<button class="btn btn-secondary o_gantt_btn" t-on-click="toggleFullScreen">Full Screen</button>
|
||||
<button class="btn btn-success o_gantt_btn" t-on-click="exportData">Export</button>
|
||||
</div>
|
||||
<div class="o_gantt_layout">
|
||||
<div class="o_gantt_left_grid">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Task Name</th>
|
||||
<th>Start</th>
|
||||
<th>End</th>
|
||||
<th>%</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-foreach="state.tasks" t-as="task" t-key="task.id">
|
||||
<tr>
|
||||
<td>
|
||||
<span t-attf-style="background-color: {{task.stage_color}}; width: 12px; height: 12px; display: inline-block; border-radius: 50%; margin-right: 8px;"></span>
|
||||
<t t-esc="task.name"/>
|
||||
<i t-if="task.is_milestone" class="fa fa-flag ms-2 text-warning"></i>
|
||||
</td>
|
||||
<td><t t-esc="task.gantt_start_date"/></td>
|
||||
<td><t t-esc="task.gantt_end_date"/></td>
|
||||
<td><t t-esc="task.task_progress"/></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-link" t-on-click="() => this.editTask(task)">Edit</button>
|
||||
<button class="btn btn-sm btn-link text-danger" t-on-click="() => this.deleteTask(task)">Del</button>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="o_gantt_right_panel" t-ref="gantt-container">
|
||||
<div class="o_gantt_chart_area"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,58 @@
|
||||
<odoo>
|
||||
<record id="project_project_form_view_gantt" model="ir.ui.view">
|
||||
<field name="name">project.project.form.gantt</field>
|
||||
<field name="model">project.project</field>
|
||||
<field name="inherit_id" ref="project.edit_project"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//page[@name='settings']" position="inside">
|
||||
<group string="CK Gantt Settings">
|
||||
<group>
|
||||
<field name="gantt_enable_dynamic_text"/>
|
||||
<field name="gantt_enable_dynamic_progress"/>
|
||||
<field name="gantt_hide_holidays"/>
|
||||
<field name="gantt_days_off"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="gantt_mail_timesheet_user" widget="many2many_tags"/>
|
||||
</group>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="project_task_form_view_gantt" model="ir.ui.view">
|
||||
<field name="name">project.task.form.gantt</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_task_form2"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//page[@name='extra_info']" position="after">
|
||||
<page string="Gantt Details" name="gantt_details">
|
||||
<group>
|
||||
<group>
|
||||
<field name="gantt_start_date"/>
|
||||
<field name="gantt_end_date"/>
|
||||
<field name="is_milestone"/>
|
||||
<field name="unschedule"/>
|
||||
<field name="schedule_mode"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="constraint_type"/>
|
||||
<field name="constraint_date" invisible="constraint_type == 'asap'"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="project_task_type_form_view_gantt" model="ir.ui.view">
|
||||
<field name="name">project.task.type.form.gantt</field>
|
||||
<field name="model">project.task.type</field>
|
||||
<field name="inherit_id" ref="project.task_type_edit"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='fold']" position="after">
|
||||
<field name="gantt_stage_color" widget="radio"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,24 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<!--
|
||||
<template id="listing">
|
||||
<ul>
|
||||
<li t-foreach="objects" t-as="object">
|
||||
<a t-attf-href="#{ root }/objects/#{ object.id }">
|
||||
<t t-esc="object.display_name"/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
<template id="object">
|
||||
<h1><t t-esc="object.display_name"/></h1>
|
||||
<dl>
|
||||
<t t-foreach="object._fields" t-as="field">
|
||||
<dt><t t-esc="field"/></dt>
|
||||
<dd><t t-esc="object[field]"/></dd>
|
||||
</t>
|
||||
</dl>
|
||||
</template>
|
||||
-->
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,12 @@
|
||||
<odoo>
|
||||
<record id="action_gantt_view_ck" model="ir.actions.client">
|
||||
<field name="name">CK Gantt Chart</field>
|
||||
<field name="tag">gantt_view_ck.gantt_action</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_gantt_view_ck"
|
||||
name="CK Gantt View"
|
||||
parent="project.menu_main_pm"
|
||||
action="action_gantt_view_ck"
|
||||
sequence="10"/>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user