Files
crm.clientright.ru/layouts/v7/modules/Project/resources/Detail.js
Fedor ac7467f0b4 Major CRM updates: AI Assistant, Court Status API, S3 integration improvements, and extensive file storage system
- Added comprehensive AI Assistant system (aiassist/ directory):
  * Vector search and embedding capabilities
  * Typebot proxy integration
  * Elastic search functionality
  * Message classification and chat history
  * MCP proxy for external integrations

- Implemented Court Status API (GetCourtStatus.php):
  * Real-time court document status checking
  * Integration with external court systems
  * Comprehensive error handling and logging

- Enhanced S3 integration:
  * Improved file backup system with metadata
  * Batch processing capabilities
  * Enhanced error logging and recovery
  * Copy operations with URL fixing

- Added Telegram contact creation API
- Improved error logging across all modules
- Enhanced callback system for AI responses
- Extensive backup file storage with timestamps
- Updated documentation and README files

- File storage improvements:
  * Thousands of backup files with proper metadata
  * Fix operations for broken file references
  * Project-specific backup and recovery systems
  * Comprehensive file integrity checking

Total: 26,461+ files added/modified including AWS SDK, vendor dependencies, and extensive backup system.
2025-10-16 11:17:21 +03:00

598 lines
21 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*+***********************************************************************************
* The contents of this file are subject to the vtiger CRM Public License Version 1.0
* ("License"); You may not use this file except in compliance with the License
* The Original Code is: vtiger CRM Open Source
* The Initial Developer of the Original Code is vtiger.
* Portions created by vtiger are Copyright (C) vtiger.
* All Rights Reserved.
*************************************************************************************/
Vtiger_Detail_Js("Project_Detail_Js",{
gantt: false,
getVideo: function (crmid) {
var settings = {
"url": "https://crm.clientright.ru/GetVideoLink.php?id="+crmid,
"method": "GET",
"timeout": 0,
};
$.ajax(settings).done(function (response) {
if (response == 'https://clientright.ktalk.ru/recordings/none') {
alert('У этого проекта нет видеовстреч с записью');
} else {
location.reload();
//window.location.href = response;
window.open(response, '_blank');
}
});
},
getText: function (crmid) {
var settings = {
"url": "https://crm.clientright.ru/GetMeetingText.php?id="+crmid,
"method": "POST",
"timeout": 0,
};
$.ajax(settings).done(function (response) {
if (response != 'ok') {
alert('У этого проекта нет видеовстреч с записью');
} else {
alert('Создан связанный текстовый Документ с транскрибацией диалога');
location.reload();
}
});
},
showEditColorModel: function (url, e) {
var element = jQuery(e);
app.helper.showProgress();
app.request.post({url: url}).then(function(error, data) {
if (data) {
app.helper.hideProgress();
var callback = function (data) {
Project_Detail_Js.registerEditColorPreSaveEvent(data, element);
var form = jQuery('#editColor');
var params = {
submitHandler: function(form) {
Project_Detail_Js.saveColor(jQuery(form));
}
};
form.vtValidate(params);
}
app.helper.showModal(data, {cb: callback});
}
});
},
registerEditColorPreSaveEvent: function (data, element) {
var selectedColorField = data.find('.selectedColor');
var color = element.data('color');
if (color) {
selectedColorField.val(color);
var customParams = {
color: color
};
} else {
//if color is not present select random color
var randomColor = '#' + (0x1000000 + (Math.random()) * 0xffffff).toString(16).substr(1, 6);
selectedColorField.val(randomColor);
//color picker params for add calendar view
var customParams = {
color: randomColor
};
}
//register color picker
var params = {
flat: true,
onChange: function (hsb, hex, rgb) {
var selectedColor = '#' + hex;
selectedColorField.val(selectedColor);
}
};
if (typeof customParams != 'undefined') {
params = jQuery.extend(params, customParams);
}
data.find('.colorPicker').ColorPicker(params);
//on change of status, update color picker with the status color
var selectElement = data.find('[name=taskstatus]');
selectElement.on('change', function () {
var selectedOption = selectElement.find('option:selected');
var color = selectedOption.data('color');
selectedColorField.val(color);
data.find('.colorPicker').ColorPickerSetColor(color);
});
},
saveColor: function (form) {
var color = form.find('.selectedColor').val();
var status = form.find('[name=taskstatus]').val();
app.helper.showProgress();
var params = {
'module': app.getModuleName(),
'action': 'SaveAjax',
'mode': 'saveColor',
'color': color,
'status': status
}
app.request.post({data: params}).then(
function(error, data) {
app.helper.hideProgress();
app.helper.hideModal();
if (!error) {
app.helper.showSuccessNotification({message: app.vtranslate('JS_COLOR_SAVED_SUCESSFULLY')});
// to reload chart
jQuery('[data-label-key=Chart]').click();
} else {
app.helper.showErrorNotification({message: error});
}
}
);
}
}, {
detailViewRecentTicketsTabLabel : 'HelpDesk',
detailViewRecentTasksTabLabel : 'Project Tasks',
detailViewRecentMileStonesLabel : 'Project Milestones',
/**
* Function to register event for create related record
* in summary view widgets
*/
registerSummaryViewContainerEvents : function(summaryViewContainer){
this._super(summaryViewContainer);
this.registerStatusChangeEventForWidget();
this.registerEventsForTasksWidget(summaryViewContainer);
},
/**
* Function to get records according to ticket status
*/
registerStatusChangeEventForWidget : function(){
var thisInstance = this;
jQuery('[name="ticketstatus"],[name="projecttaskstatus"],[name="projecttaskprogress"]').on('change',function(e){
var picklistName = this.name;
var statusCondition = {};
var params = {};
var currentElement = jQuery(e.currentTarget);
var summaryWidgetContainer = currentElement.closest('.summaryWidgetContainer');
var widgetDataContainer = summaryWidgetContainer.find('.widget_contents');
var referenceModuleName = widgetDataContainer.find('[name="relatedModule"]').val();
var recordId = thisInstance.getRecordId();
var module = app.getModuleName();
var selectedStatus = currentElement.find('option:selected').val();
if(selectedStatus.length > 0 && referenceModuleName == "HelpDesk"){
var searchInfo = new Array();
searchInfo.push('ticketstatus');
searchInfo.push('e');
searchInfo.push(selectedStatus);
statusCondition['ticketstatus'] = searchInfo;
params['whereCondition'] = JSON.stringify(statusCondition);
} else if(referenceModuleName == "ProjectTask" && picklistName == 'projecttaskstatus'){
if(selectedStatus.length > 0) {
var searchInfo = new Array();
searchInfo.push('projecttaskstatus');
searchInfo.push('e');
searchInfo.push(selectedStatus);
statusCondition['projecttaskstatus'] = searchInfo;
params['whereCondition'] = JSON.stringify(statusCondition);
}
jQuery('[name="projecttaskprogress"]').val('').select2("val", '');
}
else if(referenceModuleName == "ProjectTask" && picklistName == 'projecttaskprogress'){
if(selectedStatus.length > 0) {
var searchInfo = new Array();
searchInfo.push('projecttaskprogress');
searchInfo.push('e');
searchInfo.push(selectedStatus);
statusCondition['projecttaskprogress'] = searchInfo;
params['whereCondition'] = JSON.stringify(statusCondition);
}
jQuery('[name="projecttaskstatus"]').val('').select2("val", '');
}
params['record'] = recordId;
params['view'] = 'Detail';
params['module'] = module;
params['page'] = widgetDataContainer.find('[name="page"]').val();
params['limit'] = widgetDataContainer.find('[name="pageLimit"]').val();
params['relatedModule'] = referenceModuleName;
params['mode'] = 'showRelatedRecords';
app.helper.showProgress();
app.request.post({data: params}).then(
function(error, data) {
app.helper.hideProgress();
widgetDataContainer.html(data);
}
);
})
},
/**
* Function to load module summary of Projects
*/
loadModuleSummary : function(){
var summaryParams = {};
summaryParams['module'] = app.getModuleName();
summaryParams['view'] = "Detail";
summaryParams['mode'] = "showModuleSummaryView";
summaryParams['record'] = jQuery('#recordId').val();
app.request.post({data: summaryParams}).then(
function(error, data) {
jQuery('.summaryView').html(data);
}
);
},
/**
* Function to load the gantt chart
*/
loadGanttChart : function(container) {
var gantt;
//load templates
jQuery("#ganttemplates").loadTemplates();
// here starts gantt initialization
gantt = new GanttMaster();
var workSpace = $("#workSpace");
workSpace.css({height:$("#workSpace").parent().height() - 20});
gantt.init(workSpace);
var ret;
ret = JSON.parse($("#projecttasks").val());
gantt.loadProject(ret);
gantt.checkpoint(); //empty the undo stack
$(window).resize(function(){
workSpace.trigger("resize.gantt");
})
jQuery('.toggleButton').click(function() {
workSpace.trigger("resize.gantt");
});
Project_Detail_Js.gantt = gantt;
// Added to make default sortorder of startdate to be ascending
var element = jQuery('.gdfTable.fixHead').find('.gdfColHeader[data-name=startdate]');
element.data('nextorder', 'asc');
element.trigger('click');
},
loadContents : function(url,data) {
var detailContentsHolder = this.getContentHolder();
var thisInstance = this;
var aDeferred = jQuery.Deferred();
if(url.indexOf('index.php') < 0) {
url = 'index.php?' + url;
}
var params = [];
params.url = url;
if(typeof data != 'undefined'){
params.data = data;
}
app.helper.showProgress();
app.request.pjax(params).then(function(error,response){
detailContentsHolder.html(response);
aDeferred.resolve(response);
app.helper.hideProgress();
if(detailContentsHolder.find('#workSpace').length !=0) {
thisInstance.loadGanttChart(detailContentsHolder);
}
});
return aDeferred.promise();
},
/**
* Function to register events for project tasks widget
*/
registerEventsForTasksWidget : function(summaryViewContainer) {
var thisInstance = this;
var tasksWidget = summaryViewContainer.find('.widgetContainer_tasks');
tasksWidget.on('click', '.editTaskDetails', function(e) {
var currentTarget = jQuery(e.currentTarget);
var newValue = currentTarget.text();
var element = currentTarget.closest('ul.dropdown-menu');
var editElement = element.closest('.dropdown');
var oldValue = element.data('oldValue');
if(currentTarget.hasClass('emptyOption')) {
newValue = '';
}
vtUtils.hideValidationMessage(editElement);
if(element.data('mandatory') && newValue.length <= 0) {
var result = app.vtranslate('JS_REQUIRED_FIELD');
vtUtils.showValidationMessage(editElement, result);
return false;
}
if(oldValue != newValue) {
var params = {
action : 'SaveAjax',
record : element.data('recordid'),
field : element.data('fieldname'),
value : newValue,
module : 'ProjectTask'
};
app.helper.showProgress();
app.request.post({data: params}).then(
function(error, data) {
app.helper.hideProgress();
thisInstance.showRelatedRecords(tasksWidget);
}
);
}
})
},
/**
* Function to get the related records list
* summary view widget
*/
showRelatedRecords : function(summaryWidgetContainer) {
var widgetHeaderContainer = summaryWidgetContainer.find('.widget_header');
var widgetDataContainer = summaryWidgetContainer.find('.widget_contents');
var referenceModuleName = widgetHeaderContainer.find('[name="relatedModule"]').val();
var module = app.getModuleName();
var params = {};
if(referenceModuleName == 'ProjectTask') {
var statusCondition = {};
var selectedStatus = jQuery('[name="projecttaskstatus"]', widgetHeaderContainer).val();
if(typeof selectedStatus != "undefined" && selectedStatus.length > 0) {
statusCondition['vtiger_projecttask.projecttaskstatus'] = selectedStatus;
params['whereCondition'] = statusCondition;
}
var selectedProgress = jQuery('[name="projecttaskprogress"]', widgetHeaderContainer).val();
if(typeof selectedProgress != "undefined" && selectedProgress.length > 0) {
statusCondition['vtiger_projecttask.projecttaskprogress'] = selectedProgress;
params['whereCondition'] = statusCondition;
}
}
params['record'] = this.getRecordId();
params['view'] = 'Detail';
params['module'] = module;
params['page'] = widgetDataContainer.find('[name="page"]').val();
params['limit'] = widgetDataContainer.find('[name="pageLimit"]').val();
params['relatedModule'] = referenceModuleName;
params['mode'] = 'showRelatedRecords';
app.helper.showProgress();
app.request.post({data: params}).then(
function(error, data) {
app.helper.hideProgress();
widgetDataContainer.html(data);
}
);
},
registerGanttChartEvents : function(container) {
this.registerZoomButtons(container);
this.registerTaskEdit(container);
this.registerRecordUpdateEvent(container);
this.registerGanttSorting(container);
},
registerZoomButtons : function(container) {
container.on('click', '.zoomIn', function(e){
e.preventDefault();
jQuery("#workSpace").trigger('zoomPlus.gantt');
});
container.on('click', '.zoomOut', function(e){
e.preventDefault();
jQuery("#workSpace").trigger('zoomMinus.gantt');
});
},
registerTaskEdit : function (container) {
var thisInstance = this;
container.on('click', '.editTask', function(e) {
var element = jQuery(e.currentTarget);
var params = {
'module' : 'ProjectTask',
'view' : 'QuickEditAjax',
'returnview' : 'Detail',
'returnmode' : 'showChart',
'returnmodule' : app.getModuleName(),
'returnrecord' : thisInstance.getRecordId(),
'parentid' : thisInstance.getRecordId(),
'record' : element.data('recordid')
}
app.helper.showProgress();
app.request.post({data: params}).then(
function(error, data) {
app.helper.hideProgress();
var callBackFunction = function(data) {
var form = data.find('.recordEditView');
var params = {
submitHandler: function(form) {
form = jQuery(form);
if(form.attr('id') == 'projectTaskQuickEditForm') {
app.helper.showProgress();
thisInstance.saveTask(form).then(function(err, data) {
app.helper.hideProgress();
if (err === null) {
jQuery('.vt-notification').remove();
app.helper.hideModal();
// to reload chart
jQuery('[data-label-key=Chart]').click();
} else {
app.event.trigger('post.save.failed', err);
}
});
}
},
validationMeta: quickcreate_uimeta
};
form.vtValidate(params);
}
var modalWindowParams = {
cb : callBackFunction
}
app.helper.showModal(data, modalWindowParams);
}
);
});
},
registerRecordUpdateEvent : function(container) {
container.on('updateTaskRecord.gantt','#workSpace', function(e,task) {
var dateFormat = vtUtils.getMomentDateFormat();
var startDate = moment(task.start).format(dateFormat);
var endDate = moment(task.end).format(dateFormat);
if((task.oldstart != '' && task.oldend != '') && (task.oldstart != startDate || task.oldend != endDate)) {
var params = {
'module' : 'ProjectTask',
'action' : 'SaveTask',
'record' : task.recordid,
'startdate' : startDate,
'enddate' : endDate
}
app.helper.showProgress();
app.request.post({data: params}).then(
function(error, data) {
app.helper.hideProgress();
if (error === null) {
jQuery('.vt-notification').remove();
} else {
app.event.trigger('post.save.failed', error);
}
}
);
}
});
},
sortResults: function(arr, prop, asc) {
var thisInstance = this;
arr = arr.sort(function(a, b) {
if (asc) {
if (a[prop] === parseInt(a[prop], 10) && b[prop] === parseInt(b[prop], 10)) {
return a[prop]-b[prop];
} else if( thisInstance.isDate(a[prop]) && thisInstance.isDate(b[prop])) {
return new Date(a[prop]).getTime() - new Date(b[prop]).getTime();
} else {
return thisInstance.sortAlphabetically(a[prop], b[prop]);
}
} else {
if (a[prop] === parseInt(a[prop], 10) && b[prop] === parseInt(b[prop], 10)) {
return b[prop]-a[prop];
} else if( thisInstance.isDate(a[prop]) && thisInstance.isDate(b[prop])) {
return new Date(b[prop]).getTime() - new Date(a[prop]).getTime();
} else {
return thisInstance.sortAlphabetically(b[prop], a[prop]);
}
}
});
return arr;
},
isDate: function(date) {
return (new Date(date) !== "Invalid Date" && !isNaN(new Date(date)) ) ? true : false;
},
sortAlphabetically : function(a, b) {
var nameA = a.toLowerCase();
var nameB = b.toLowerCase()
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
return 0;
},
registerGanttSorting : function(container) {
var thisInstance = this;
container.on('click', '.gdfColHeader', function(e) {
var element = jQuery(e.currentTarget);
var text = element.data('text');
var name = element.data('name');
var order = element.data('nextorder');
if(name) {
container.find('.gdfColHeader .fa.fa-chevron-down').remove();
container.find('.gdfColHeader .fa.fa-chevron-up').remove();
var descTemplate = '<i class="fa fa-chevron-down"></i> ' + text;
var ascTemplate = '<i class="fa fa-chevron-up"></i>' + text ;
if(!order) {
order = false;
element.html(descTemplate);
} else if(order == 'asc') {
order = true;
element.html(ascTemplate);
} else if(order == 'desc') {
order = false;
element.html(descTemplate);
}
var data = JSON.parse($("#projecttasks").val());
data.tasks = thisInstance.sortResults(data.tasks, name, order);
if(order == false) {
order = 'asc';
} else {
order = 'desc';
}
element.data('nextorder', order);
var gantt = Project_Detail_Js.gantt;
gantt.loadProject(data);
gantt.checkpoint(); //empty the undo stack
}
});
},
saveTask : function(form) {
var aDeferred = jQuery.Deferred();
var formData = form.serializeFormData();
app.request.post({data: formData}).then(
function(error, data) {
//TODO: App Message should be shown
aDeferred.resolve(error, data);
},
function(textStatus, errorThrown) {
aDeferred.reject(textStatus, errorThrown);
}
);
return aDeferred.promise();
},
registerEvents : function(){
var detailContentsHolder = this.getContentHolder();
var thisInstance = this;
this._super();
detailContentsHolder.on('click','.moreRecentMilestones', function(){
var recentMilestonesTab = thisInstance.getTabByLabel(thisInstance.detailViewRecentMileStonesLabel);
recentMilestonesTab.trigger('click');
});
detailContentsHolder.on('click','.moreRecentTickets', function(){
var recentTicketsTab = thisInstance.getTabByLabel(thisInstance.detailViewRecentTicketsTabLabel);
recentTicketsTab.trigger('click');
});
detailContentsHolder.on('click','.moreRecentTasks', function(){
var recentTasksTab = thisInstance.getTabByLabel(thisInstance.detailViewRecentTasksTabLabel);
recentTasksTab.trigger('click');
});
var detailViewContainer = jQuery('.detailViewContainer');
thisInstance.registerGanttChartEvents(detailViewContainer);
if(detailViewContainer.find('#workSpace').length != 0) {
this.loadGanttChart(detailViewContainer);
}
}
})