Вопрос

У нас в энтерпрайз есть в моб приложении чек-ин и чек-аут. Снимает время и координаты.

После попадания в основную систему, где можно их увидеть?

Ответ

К сожалению, в базовой версии продукта эти данные не отображаются.

Найти эти данные можно в таблице БД, которая называется CheckInOutResult.

Для решения Вашего вопроса необходимы знания разработчика, который прошел обучение по конфигурированию от нашей компании.

Информацию о визите на  мы храним локально на устройстве и затем при синхронизации передаем ее.

Получение данных при чекине (схема «FieldForceMobileUtilitiesV2»):

checkInOut: function(activityId, isCheckIn) {
    Terrasoft.Geolocation.getCurrentCoordinates({
        success: function(latitude, longitude) {
            var checkinResultModelName = "CheckInOutResult";
            var saveQueryConfig = Ext.create("Terrasoft.QueryConfig", {
                modelName: checkinResultModelName,
                columns: ["GpsX", "GpsY", "Activity", "IsCheckIn", "ActionTime"]
            });
            var checkInOutRecord = Ext.create("CheckInOutResult", {
                Activity: activityId,
                GpsX: String(latitude),
                GpsY: String(longitude),
                IsCheckIn: isCheckIn,
                ActionTime: new Date()
            });
            checkInOutRecord.save({
                queryConfig: saveQueryConfig,
                success: function() {
                    this.changeActivityStatusByCheckInOut(isCheckIn);
                },
                failure: this.failureHandler
            }, this);
        },
        failure: this.failureHandler,
        scope: this
    });
},
 

Получение текущих координат описано в схеме «FieldForceMapsModule»

Также, координаты, которые получает bpm'online, можно управлять. Приведу пример текущей реализации метода «Terrasoft.Geolocation.getCurrentCoordinates»:

getCurrentCoordinates: function(config) {
   var enableHighAccuracy = !Terrasoft.Connection.isOnline() ||
      Terrasoft.Connection.getType() !== Terrasoft.ConnectionTypes.WiFi;
   var geo = Ext.create("Ext.util.Geolocation", {
      autoUpdate: false,
      allowHighAccuracy: enableHighAccuracy,
      timeout: 60000,
      listeners: {
         scope: this,
         locationupdate: function(geo) {
            Ext.callback(config.success, config.scope, [geo.getLatitude(), geo.getLongitude()]);
         },
         locationerror: function(geo, timeout, permissionDenied, locationUnavailable, message) {
            …
         }
      }
   });
   geo.updateLocation();
}

Сейчас, если нет интернет-соединения или это соединение – не WiFi (типа, EDGE, 3G), то будет использоваться «точное» позиционирование (с использованием GPS), иначе данные будут получены с использованием WiFi, кеширования и т.д., т.е. способами дающими неточные представления о местоположении устройства, но вполне достаточные для выполнения бизнес-задач.

Если нужно получать данные с использованием GPS, можно создать свою реализацию действия, по аналогии с тем, как это реализовано в bpm'online:

getCurrentCoordinates: function(config) {
   var geo = Ext.create("Ext.util.Geolocation", {
      autoUpdate: false,
      allowHighAccuracy: true,
      timeout: 60000,
      listeners: {
         scope: this,
         locationupdate: function(geo) {
            Ext.callback(config.success, config.scope, [geo.getLatitude(), geo.getLongitude()]);
         },
         locationerror: function(geo, timeout, permissionDenied, locationUnavailable, message) {
            …
         }
      }
   });
   geo.updateLocation();
}

При реализации нужно учитывать, что у такого способа есть ряд проблем:

- несмотря, что метод GPS более точен, время получения таких данных может быть достаточно больших (до 10-15 минут);

- соответсвенно время ожидания получения ответа нужно увеличить, т.е. параметр timeout выше в коде нужно будет устанавливать больше, чем 1 минуту (60000 мс);

- любые перекрытия, помехи в виде любых объектов могут не позволить получить координаты (например, у нас в офисе это нереально);

- быстрее садится батарея, что для Android-устройств весьма критично.

Нравится

Поделиться

0 комментариев
Показать все комментарии

Вопрос

Пропал доступ к разделу, у всех пользователей кроме Supervisor`а. Стандартный скрипт раздачи прав, отрабатывает но не добавляет записи.

Ответ

У пользователя в таблице БД не заполнено поле CreatedById, для решение проблемы поле было дозаполнено, создателем был выставлен Supervisor:

Update Account --необходимо изменить объект
SET CreatedById = (SELECT id FROM Contact c WHERE c.Name = 'Supervisor')
where CreatedById is null;

 

Нравится

Поделиться

0 комментариев
Показать все комментарии

Симптомы

В мобильном приложении добавляется файл/фото на деталь "файлы и ссылки" в одном из разделов.

После запуска синхронизации возникает ошибка:

Bpm'online mobile bug report

Type: Terrasoft.ODataException 

Message: An error occurred while processing this request. 

Сообщение содержит: Невозможно получить значение колонки Data, так как она не была загружена

Причина

В процессе схемы File в скрипте ScriptFileSaving есть код:

var data = Entity.GetColumnValue("Data") as byte[];

И если обновлять данные в этой схеме (через OData) и не указывать колонку Data, то появляется сообщение, что она не загружена.

Такая ситуация может происходить если в таблице файлов (ActivityFile, OpportunityFile и т.п.) есть циклическая связь.

Например в таблице ActivityFile есть колонка Activity, которая ссылается на таблицу Activity, в которой, в свою очередь, есть справочная колонка UsrActivityFile, которая ссылается на таблицу ActivityFile.

Мобильное приложение это отслеживает и разбивает один запрос

 

INSERT INTO ActivityFile (Id, Name, Data, Activity) VALUES (1, 2, 3, 4)

На два запроса:

INSERT INTO ActivityFile (Id, Name, Data) VALUES (1, 2, 3)
UPDATE ActivityFile SET Activity = 4 WHERE Id = 1

Вот этот второй запрос и не срабатывает.

Решение

Есть обходное решение. Для требуемой таблицы в манифесте установить признак IgnoreSplitLogActions

{
   "SyncOptions": {
      "ModelDataExportConfig": [
         {
            "Name": "ActivityFile",
            "IgnoreSplitLogActions": true
         }
      ],

Тогда вставка файла не будет разбиваться на два запроса.

Необходимые условия и возможные ограничения

Для UIV1 необходимо дополнительно "заоверрайдить" схему:

if (!Terrasoft.SysSettingsValue.getBooleanValue("UseMobileUIV2")) {
    /*
     * Moved from UIV2
     */
    Ext.define("Terrasoft.Sync.LogManager.override", {
        override: "Terrasoft.Sync.LogManager",
        /**
         * @private
         */
        ignoreSplitLogActions: function(logAction) {
            var modelName = logAction.get("ModelName");
            var manifest = Terrasoft.ApplicationConfig.manifest;
            var ignore = false;
            if (manifest.SyncOptions) {
                var modelDataExportConfig = manifest.SyncOptions.ModelDataExportConfig || [];
                for (var i = 0, ln = modelDataExportConfig.length; i < ln; i++) {
                    var modelConfig = modelDataExportConfig[i];
                    if (Ext.isObject(modelConfig) && modelConfig.Name === modelName) {
                        ignore = modelConfig.IgnoreSplitLogActions;
                        break;
                    }
                }
            }
            return ignore === true;
        },
        /**
         * @private
         */
        splitLogActions: function() {
            var firstTierActions = this.firstTierActions = [];
            var secondTierActions = this.secondTierActions = [];
            for (var i = 0, ln = this.mergedLogActions.length; i < ln; i++) {
                var logAction = this.mergedLogActions[i];
                if (this.isCreateAction(logAction) && !this.ignoreSplitLogActions(logAction)) {
                    firstTierActions.push(logAction);
                    this.splitLogActionWithLoopColumns(logAction);
                    this.splitLogActionWithBinaryColumns(logAction);
                } else {
                    secondTierActions.push(logAction);
                    if (this.isUpdateAction(logAction)) {
                        this.splitLogActionWithBinaryColumns(logAction);
                    }
                }
            }
        }
    });
}

 

Нравится

Поделиться

0 комментариев
Показать все комментарии
// пример настройки заполнения значений полей детали значениями основной записи
define("ContractPageV2", ["ProcessModuleUtilities"], function(ProcessModuleUtilities) {
    return {
        entitySchemaName: "Contract",
        details: /**SCHEMA_DETAILS*/{
    "CaseDetailf1b0196f": {
        "schemaName": "CaseDetail",
        "entitySchemaName": "Case",
        "filter": {
            "detailColumn": "UsrContract",
            "masterColumn": "Id"
        },// поставить запятую добавить код ниже:
        "defaultValues": {
            "UsrContract": {//колонка детали
                "masterColumn": "Id"//колонка основной записи (Важно! добавление id тоже нужно прописать (если связь не по id, а иначе - ориентироваться на блок "filter":))
            },
            "Account": {//колонка детали
                "masterColumn": "Account"//колонка основной записи
            },
            "UsrProject": {//колонка детали
                "masterColumn": "UsrProject"//колонка основной записи
            }
        }
    },
    "UsrSchema17Detail0e700a70": {
        "schemaName": "UsrSchema17Detail",
        "entitySchemaName": "UsrPaySchedule",
        "filter": {
            "detailColumn": "UsrContract",
            "masterColumn": "Id"
        },
        "defaultValues": {
            "UsrContract": {
                "masterColumn": "Id"
            },
            "UsrProject": {
                "masterColumn": "UsrProject"
            }
        }
    },
    "UsrSchema19Detailde623a55": {
        "schemaName": "UsrSchema19Detail",
        "entitySchemaName": "Contract",
        "filter": {
            "detailColumn": "Parent",
            "masterColumn": "Id"
        },
        "defaultValues": {
            "Parent": {
                "masterColumn": "Id"
            },
            "Account": {
                "masterColumn": "Account"
            },
            "UsrProject": {
                "masterColumn": "UsrProject"
            },
            "CurrencyRate": {
                "masterColumn": "CurrencyRate"
            }
        }
    },
    "UsrSchema13Detail4970b370": {
        "schemaName": "UsrSchema13Detail",
        "entitySchemaName": "UsrDepartmet",
        "filter": {
            "detailColumn": "UsrCredit",
            "masterColumn": "Id"
        }
    }
}/**SCHEMA_DETAILS*/,
//...

 

Нравится

Поделиться

0 комментариев
Показать все комментарии

Вопрос

Веб-сервис без авторизации для вызова БП (Бизнесс процесса) с параметрами.

Ответ

Инструкция по написанию сервиса, вызывающего БП, доступного по HTTP\GET с передачей параметров в БП:  https://community.terrasoft.ru/articles/veb-servis-dostupnyi-bez-avtorizacii-cors

Сервис:

namespace Terrasoft.Configuration
{
    using System;
    using System.Collections.Generic;
    using System.Runtime.Serialization;
    using System.Linq;
    using System.ServiceModel;
    using System.ServiceModel.Web;
    using System.ServiceModel.Activation;
    using System.Web;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Drawing;
    using System.Globalization;
    using System.Net;
    using System.Text;
    using System.IO;
 
    using Terrasoft.Core;
    using Terrasoft.Core.Configuration;
    using Terrasoft.Core.DB;
    using Terrasoft.Core.Entities;
    using Terrasoft.Core.Process;
    using Terrasoft.Web.Common;
    using Terrasoft.Common;
    using Terrasoft.Common.Json;
    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
    public class UsrLaunchProccService : BaseService
    {
        private UserConnection SystemUserConnection {
            get {
                return AppConnection.SystemUserConnection;
            }
        }
 
        public ProcessSchemaManager ProcessSchemaManager {
            get {
                return (ProcessSchemaManager)SystemUserConnection.GetSchemaManager("ProcessSchemaManager");
            }
        }
 
        [OperationContract]
        [WebInvoke(Method = "GET", RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped,
        ResponseFormat = WebMessageFormat.Json, UriTemplate = "runUsrTestProcc/{ustTestPhone}/")]
        public string runUsrTestProcc(string ustTestPhone)
        {
            ///////
            var currentWebOperationContext = WebOperationContext.Current;
            var outgoingResponseHeaders = currentWebOperationContext.OutgoingResponse.Headers;
            var incomingRequestOrigin = currentWebOperationContext.IncomingRequest.Headers["Origin"];
            outgoingResponseHeaders.Add("Access-Control-Allow-Origin", incomingRequestOrigin);
            ///////
            var result = "";
            try {
                var UserConnection = this.SystemUserConnection;
 
                var manager = UserConnection.ProcessSchemaManager;
                var processSchema = manager.GetInstanceByName("UsrTestProcc");
                var process = processSchema.CreateProcess(UserConnection);
                if (processSchema.Parameters.ExistsByName("UstTestPhone"))
                {
                process.SetPropertyValue("UstTestPhone", ustTestPhone);
                }
                process.Execute(UserConnection);
                result = "OK for ustTestPhone = " + ustTestPhone;
            } catch (Exception ex) {
                result = ex.Message;
            };
            return result;
        }
 
 
        [OperationContract]
        [WebInvoke(Method = "OPTIONS", UriTemplate = "*")]
        public void GetWebFormLeadDataRequestOptions() {
            var outgoingResponseHeaders = WebOperationContext.Current.OutgoingResponse.Headers;
            outgoingResponseHeaders.Add("Access-Control-Allow-Origin", "*");
            outgoingResponseHeaders.Add("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
            outgoingResponseHeaders.Add("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, X-Requested-With, X-Requested-With, x-request-source");
            outgoingResponseHeaders.Add("Access-Control-Request-Headers", "X-Requested-With, x-request-source, accept, content-type");
        }
    }
}

 

Нравится

Поделиться

0 комментариев
Показать все комментарии

Вопрос

Мы обнаружили, что пользователей системы можно создать только для контактов, у которых в поле контрагент указана "Наша компания". Однако ввиду специфики оргструктуры организации клиента нам необходимо, чтобы была возможность создавать пользователей для контактов, контрагент которого является, допустим, дочерним для "Нашей компании". Есть ли возможность включить таких контактов в список сотрудников, который выдается при создании пользователя?

Ответ

В штатной логике системы не должно быть такого ограничения, только у контакта должен быть заполнен контрагент.

Логика фильтра в поле «Сотрудник» карточки пользователя задаётся в функции «PrepareEmployeeEditFilter» страницы «UserEditPage».

Вы можете модифицировать (или вообще отключить) фильтры, ограничивающие выбор контакта.

Нравится

Поделиться

0 комментариев
Показать все комментарии

Вопрос

Не отображаются активности типа "Звонок" в разделе история

Ответ

Начиная с версии 7.7.0, на детали "Активности" в разделе "История" добавлена фильтрация отображаемых записей по типам, в результате которой на данной детали не отображаются активности с типом "E-mail" и "Звонок", которые могли быть созданы в предыдущих версиях. Данная особенность связана с выделением для этих сущностей отдельных разделов и деталей.

Для того, чтобы на детали «Активности» вкладки «История» отображались ранее созданные активности с типом «Звонок», необходимо создать замещающий клиентский модуль для схемы ActivityDetailV2 и переопределить в нём метод getFilters() следующим образом, удалив фильтрацию по звонкам:

getFilters: function() {
    var filters = this.callParent(arguments);
    filters.removeByKey("NotCallFilter");
    return filters;
}

 

Нравится

Поделиться

0 комментариев
Показать все комментарии

Вопрос

Не устанавливается Silverlight в браузере на mac os

Ответ

Для решения задачи рекомендуем использовать последнюю версию браузера Safari (для mac os). Также хотим обратить внимание, что Google Chrome с версии 45.x не поддерживает Silverlight. Вы можете использовать Firefox Mozilla, Internet Explorer (для Windows).

Нравится

Поделиться

0 комментариев
Показать все комментарии

Симптомы

Испытываю сложности с настройкой клиента на IOS, он просто не проходит процедуру синхронизации, говорит что сервер недоступен и т.п. При этом, на андроиде все работает

Причина

Проблема связана с особенностью мобильно приложения, точнее с аунтефикацией. Если метод Basic Authentication включен, при синхронизации с мобильным приложением происходит свал в системе.

Решение

1. Убедиться что метод отключен в IIS. В структуре IIS выбрать сайт, перейти в меню Authentication. Включены только Anonymous, Forms Authentications.

Изображение удалено.

2. Убедиться, что версия .Net Framework соответствует значениям из таблицы. Определить версию можно в регистре, ознакомившись со статьей https://msdn.microsoft.com/en-us/library/hh925568%28v=vs.110%29.aspx

Изображение удалено.

3. Перезапустить сайт.

Необходимые условия и возможные ограничения

Доступ к IIS, доступ к регистру.

Нравится

Поделиться

0 комментариев
Показать все комментарии

Вопрос

Как добавить поле типа web (как в детали средства связи), чтобы при клике в другой вкладке открывалась эта ссылка.

Ответ

Для превращения обычного текстового поля в ссылку, необходимо доработать diff этого поля следующим кодом:

{
    "operation": "insert",
    "name": "AdditionalExpenses",
    "values": {
 
        // -->
        "showValueAsLink": true,
        "controlConfig": {
            "enabled": true,
            "href": {
                "bindTo": "getAdditionalExpensesLink"
            },
            "linkclick": {
                "bindTo": "onExternalLinkClick"
            }
        },
        // <--
 
        "layout": {
            "colSpan": 12,
            "rowSpan": 1,
            "column": 0,
            "row": 3,
            "layoutName": "Tab07720f4eTabLabelGridLayout691629ea"
        },
        "labelConfig": {},
        "enabled": true,
        "bindTo": "AdditionalExpenses"
    },
    "parentName": "Tab07720f4eTabLabelGridLayout691629ea",
    "propertyName": "items",
    "index": 5
},

А в секцию methods страницы добавить:

methods: {
 
    getAdditionalExpensesLink: function() {
        return this.getLink(this.get("AdditionalExpenses"));
    },
    onExternalLinkClick: function() {
        return;
    },
    getLink: function(value) {
        if (Terrasoft.isUrl(value)) {
            return {
                url: value,
                caption: value
            };
        }
    }
},

После этого поле будет со ссылкой, если указать её в правильном ссылочном формате, или скопировать из браузера. Клик из контекстного меню "Открыть в новой вкладке" откроет ссылку в новом окне, по клику первой кнопкой мыши, откроет в текущем:

Изображение удалено.

Чтоб ссылка открывалась в новом окне, нужно дополнить метод onExternalLinkClick():

onExternalLinkClick: function() {
    var value = this.get("AdditionalExpenses");
    if (!Ext.isEmpty(value)) {
        window.open(value, "_blank");
    }
    return false;
},

 

Нравится

Поделиться

0 комментариев
Показать все комментарии