"Вам СМС от Террасофт" - часто приходится видеть такое у клиентов:smile:? Для нас это достаточно распространённая задача, поэтому решил описать, как мы ее решаем в реальных проектах. Вообще для отправки смс есть 2 технических подхода: использовать интернет-сервисы рассылок или установить локальный GSM-шлюз (таким шлюзом может выступать 3G-модем с соответствующим ПО). Сегодня мы рассмотрим первый вариант...

С точки зрения пользователя все просто – в разделы Контрагенты и Контакты добавляется кнопка «SMS», при нажатии на которую открывается окно для ввода текста сообщения:

Телефон Контрагента/Клиента подтягивается автоматически из средства связи с типом «Мобильный телефон». Естественно можно выбрать несколько Контрагентов/Контактов для отправки смс.
Чтобы вести учет отправленных смс, создан отдельный раздел, а для Контрагента/Контакта добавлена соответствующая деталь-запрос:

Теперь к тому, как это работает. Грузим сервисы с FTP, создаем раздел, кнопки, пишем обработчик нажатия кнопки:

function ShowSMSDialog(AccountOrContact) {

    var Attributes = GetNewDictionary();
    var DefaultValues = GetNewDictionary();

    DefaultValues.Add('SelectedIDs', grdData.SelectedIDs);
    DefaultValues.Add('Account_or_Contact', AccountOrContact);
    Attributes('NotifyObject') = Self;
    ShowEditWindowEx('wnd_SMS', Attributes, DefaultValues);
}

Непосредственно отправка выглядит так:
function SendAllSMS(Self, Text) {
    var SelectedIDs = GetArrayByCollection(GetDefaultValue(Self, 'SelectedIDs'));
    var AccountOrContact = GetArrayByCollection(GetDefaultValue(Self, 'Account_or_Contact'));
    var Phone = '';
    var SmsText;
    var count = SelectedIDs.length;
    var countOfFailedSMS = 0;
    var countOfSuccessSMS = 0;

    for (var i = 0; i SelectedIDs.length; i++) {
        Phone = GetMobilePhoneNumberFromAccount(AccountOrContact, SelectedIDs[i]);
        SmsText = Text;

        if (Phone != null)
            SendSmsSinglePhone(SmsText, Phone, SelectedIDs[i], AccountOrContact);
        else {
            SaveSMS(AccountOrContact, SelectedIDs[i], SmsText, false);
            countOfFailedSMS++;
        }

    }

    countOfSuccessSMS = (count - countOfFailedSMS);
    ShowInformationDialog("Отправлено SMS: " + countOfSuccessSMS + ' из ' + count);
}

Теперь нужно добавить параметр и фильтр Name в сервис sq_CommunicationType:

Также для настроек создается отдельный справочник:

В нем заполняются следующие поля:

Ну и наконец для отображения результатов рассылки нужно добавить справочник «Результаты: SMS» и деталь «SMS» с нужными колонками. Подробная инструкция также доступна на FTP.
Примерно также мы реализуем отправку смс в BPMonline, так что не вижу смысла отдельно писать пост по отправке смс оттуда.

Нравится

Поделиться

1 комментарий

Спасибо Александру за очередное расширение 3.Х. Очень нужная и востребованная вещь в жизни и в программе.

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

Выкладываю очередной интересный модуль для тройки - сервис автоматизированной ежедневной рассылки отчетов. Плюс покажу способ создания раздела из ярлыка Террасофт.

Создание раздела

Многие знают, что в ярлык Террасофт можно приписывать различные параметры, в том числе открываемое по умолчанию окно. Если таким параметром написать wnd_CreateNewWorkspace, то мы попадем сразу в окно создания нового раздела:


Не забываем добавить иконки для раздела:smile::

В итоге получаем раздел в системе:

Добавление сервисов

Сервисы добавляются стандартно: открывается Администратор и вперед, добавляем:smile: Скачать все можно здесь.

Задача в шедулере

Необходимо создать задачу в шедулере, которая будет запускать Террасофт, проверять, нужно ли что-то сейчас формировать и отправлять на email.
Особо не буду расписывать, результат должен быть такой:

Единственное, что отмечу, это список необходимых параметров для запуска:
• \wnd=wnd_SendReport – название сервиса, который выполняет отправку отчета
• \cfg=Configuration1 – название Вашей конфигурации
• \usr=User1 – имя отправителя отчета
• \pwd=Password1 – пароль данного пользователя

Итог

Собственно зачем это все нужно? Теперь мы можем в интерфейсе Террасофт выбирать необходимый отчет (по стандартному шаблону), он будет формироваться и отправялться на email в определенное время.


Самое главное, это то, что:

  • Используются стандартные шаблоны отчетов Террасофт, следовательно мы можем рассылать отчеты любой сложности, при этом не нужно дублировать алгоритм их создания
  • Можно указывать время и производить все настройки из интерфейса Террасофт без привлечения программистов

Нравится

Поделиться

11 комментариев

День добрый Александр!!! подскажите пожалуйста ваше творение формирует только стандартные отчеты без входящих параметров или возможно автоматическое формирование и рассылка отчетов с входящими параметрами. В своей статье вы данный вопрос не осветили. Если это возможно пожалуйста расскажите как. Требуется ли для этого кодить в скриптах или это все можно достичь с помощью пользовательского интерфейса. спасибо!!!

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

"Власов Михаил Викторович" написал:Требуется ли для этого кодить в скриптах

В скриптах, которые я предоставил, кодить ничего не нужно. Если Вам нужно настроить какие то параметры/фильтры, то это делается в скриптах отчета FastReport в администраторе (например, выводить данные по контрагентам определенной группы).
"Зверев Александр" написал:контакт логичнее было бы делать не полем, а деталью, с возможностью добавления контактов и их групп

С одной стороны отличная идея, но с другой стороны из практики вспомнил только один случай, когда один и тот же ежедневный отчет нужно было присылать на группу, а не руководителю - это был сервисдеск и отчет нужно было отправлять всем сотрудникам группы, отвечающей за определенный сервис; хотя мы предлагали отправлять этот отчет руководителю группы.

"Александр Свистунов" написал: из практики вспомнил только один случай, когда один и тот же ежедневный отчет нужно было присылать на группу, а не руководителю

У меня — на группу руководителей (или инвесторов), хардкодом прописан ID группы контактов.

"Александр Свистунов" написал:

В скриптах, которые я предоставил, кодить ничего не нужно. Если Вам нужно настроить какие то параметры/фильтры, то это делается в скриптах отчета FastReport в администраторе (например, выводить данные по контрагентам определенной группы).

Спасибо огромное Александр за Ответ. Все что предложил Зверев Александр идея хорошая. Но в жизни не всегда и не всем она требуется.

Добрый день.
Сделала все как описано. Получилось что запускается на сервере Террасофт, открывается wnd_SendReport, которое выглядит просто как пустое базовое окно редактирования (в администраторе оно тоже такое же). И все, оно не закрывается, можно его закрыть вручную, письмо не отправляется.
В следующий раз через 5 минут открывается еще одно такое же окно. И так далее.

Какая используется версия бинарников?

Добрый день, еще раз.
Интересно, пробовал ли кто-то еще произвести изменения по этой инструкции? И получился ли правильный результат ?

"Тихенко Виктория" написал:

Добрый день, еще раз.

Интересно, пробовал ли кто-то еще произвести изменения по этой инструкции? И получился ли правильный результат ?


Я пробовал, но у меня всё остановилось при сохранении раздела в системе :(
окно создания после нажатия "OK"не закрылось, раздел не появился, хотя сервисы создались, в журнал вывалилось сообщение (Е), и к сожалению желаемый результат не получил. Версия 3.4.1.162

По поводу создания раздела.
Насколько помню, мастер может некорректно создавать поля некоторых типов. Поэтому лучше создать раздал с одним-двумя полями, сразу сохранить и новые поля добавлять посредством FieldMaker. Так же само и с деталью: зашли в готовый раздел, добавили деталь с одним полем и сохранили.

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

Всех с трудовыми праздниками!
На майские ленивые программисты не едут на дачу, а ездят на велосипеде, а еще более ленивые программисты изобретают свой велосипед...
По случаю праздников решил немного отвлечься и выложить что-нибудь просто интересное. Часто в проектах нужно работать с картами, лучше всего конечно использовать внешние карты с API. Проекты бывают разной сложности, но для простых подойдет вот такое решение...

Начнем с конца:smile:
Скриншот карточки:

Скриншот с открытым адресом:

Скриншот с открытым маршрутом:

Для работы нужно сделать следующие действия:

  1. Добавить скрипт с методами работы с картой
  2. Добавить ссылку на скрипт
  3. Создать 2 кнопки в карточке Контрагента
  4. Добавить обработчики кнопок

Итак, к коду. Для открытия адреса используются 2 основных метода.

Открытие ссылки в IE:

function OpenPageInIE(URL) {
    var browser = new ActiveXObject("InternetExplorer.Application");
    browser.Navigate(URL);
    browser.Visible = true;
}

Формирование url:

function LoadYandexMap(Dataset, RouteOrNot) {
    if ((Dataset('CountryID') == null) && (Dataset('StateID') == null) && (Dataset('CityID') == null) && (Dataset('Address') == null))
        ShowInformationDialog('Укажите адрес, пожалуйста')
    else {
        AddressString = GetAddressFromDataset(Dataset)

        if (RouteOrNot == 'Маршрут') {
            OurCompanyAddressString = FormAddressString(GetOurCompanyAddressString())
            OpenPageInIE(FormURL(OurCompanyAddressString, FormAddressString(AddressString)));
        }
        else
            OpenPageInIE(FormURL(FormAddressString(AddressString)));
    }
}

function FormAddressString(AddressString) {
    var space_symbol = ' '
    var space_code = '%20'

    try {
        while (AddressString.indexOf(space_symbol) != -1)
            AddressString = AddressString.substring(0, AddressString.indexOf(space_symbol)) + space_code + AddressString.substring(AddressString.indexOf(space_symbol) + 1, AddressString.length)

        return AddressString
    }
    catch (ex) { return ''; }
}

function FormURL(AddressString, AddressString2) {

    if (AddressString2 == undefined)
        URL = 'http://maps.yandex.ru/?text=' + DecodeURL(AddressString) + '&l=map'
    else
        URL = 'http://maps.yandex.ru/?rtext=' + DecodeURL(AddressString) + '~' + DecodeURL(AddressString2) + '&l=map'

    return URL
}

function DecodeURL(text) {
    var text_result = text;
    var source_symbols = "№ / , й ц у к е н г ш щ з х ъ ф ы в а п р о л д ж э я ч с м и т ь б ю Й Ц У К Е Н Г Ш Щ З Х Ъ Ф Ы В А П Р О Л Д Ж Э Я Ч С М И Т Ь Б Ю".split(' ');
    var result_symbols = "%E2%84%96+%2F+%2C+%D0%B9+%D1%86+%D1%83+%D0%BA+%D0%B5+%D0%BD+%D0%B3+%D1%88+%D1%89+%D0%B7+%D1%85+%D1%8A+%D1%84+%D1%8B+%D0%B2+%D0%B0+%D0%BF+%D1%80+%D0%BE+%D0%BB+%D0%B4+%D0%B6+%D1%8D+%D1%8F+%D1%87+%D1%81+%D0%BC+%D0%B8+%D1%82+%D1%8C+%D0%B1+%D1%8E+%D0%99+%D0%A6+%D0%A3+%D0%9A+%D0%95+%D0%9D+%D0%93+%D0%A8+%D0%A9+%D0%97+%D0%A5+%D0%AA+%D0%A4+%D0%AB+%D0%92+%D0%90+%D0%9F+%D0%A0+%D0%9E+%D0%9B+%D0%94+%D0%96+%D0%AD+%D0%AF+%D0%A7+%D0%A1+%D0%9C+%D0%98+%D0%A2+%D0%AC+%D0%91+%D0%AE".split('+');

    for (var i = 0; i source_symbols.length; i++)
        while (text_result.indexOf(source_symbols[i]) != -1)
            text_result = text_result.replace(source_symbols[i], result_symbols[i]);

    text_result = text_result.replace(' ', '+');

    return text_result;
}

function GetAddressFromDataset(Dataset) {
    var separator = ', '
    var CountryName = String(GetDatasetFieldValueFromDatasetByUSI('ds_Country', 'ID', Dataset('CountryID'), 'Name'))
    var StateName = String(GetDatasetFieldValueFromDatasetByUSI('ds_State', 'ID', Dataset('StateID'), 'Name'))
    var CityName = String(GetDatasetFieldValueFromDatasetByUSI('ds_City', 'ID', Dataset('CityID'), 'Name'))

    var address_result = separator + CountryName + separator + StateName + separator + CityName + separator + Dataset('Address');

    while (address_result.indexOf('null') != -1)
        address_result = address_result.substring(0, address_result.indexOf('null')) + address_result.substring(address_result.indexOf('null') + 4, address_result.length)

    while (address_result.indexOf(separator) == 0)
        address_result = address_result.substring(separator.length, address_result.length)

    if (address_result.lastIndexOf(separator) == address_result.length - separator.length)
        address_result = address_result.substring(0, address_result.lastIndexOf(separator))

    return address_result
}

Для построения маршрута нужна начальная точка - это тот адрес, который указан в Контрагенте "Наша компания":

function GetOurCompanyAddressString() {
    var ContactID = Connector.CurrentUser.ContactID
    var AccountID = GetDatasetFieldValueFromDatasetByUSI('ds_Contact', 'ID', ContactID, 'AccountID')

    var SelectQueryOurCompany = Services.GetNewItemByUSI('sq_Account');
    ApplySelectQueryFilter(SelectQueryOurCompany, 'ID', AccountID, true);
    var DatasetOurCompany = SelectQueryOurCompany.Open();
    DatasetOurCompany.Open()

    return GetAddressFromDataset(DatasetOurCompany)
}

Ну и конечно код сервиса и инструкция по установке всегда в вашем распоряжении.
Вопросы, замечания и предложения приветствуются!

Нравится

Поделиться

5 комментариев

Интересно! А еще как бы открыть по адресу (или без адреса), указать точное расположение, и сохранить координаты в базе :)

"Владимир Соколов" написал:

Интересно! А еще как бы открыть по адресу (или без адреса), указать точное расположение, и сохранить координаты в базе :)


И такой проект у нас как раз недавно был, правда на 7ке и для e-commerce - нужно было определять ближайший склад и маршрутизировать туда заказ.
P.S.: В понедельник, 5го мая выложу:)

Александр а у вас есть страница в фейсбук. есть вопрос хочу написать в личку. или в одноклассниках? подскажите

Ммм, мои контакты конечно не секрет:smile:
Почта svistunov@samarasoft.ru
Телефон +79023218417
Скайп aleksander.svistunov
Вконтакт https://vk.com/tom_sv
Фейсбук https://www.facebook.com/a.svistunov (бываю там очень редко, не пользуюсь)
Одноклассники - по-моему заводил там аккаунт лет 6 назад и вроде даже не удалял, но не пользуюсь.

"Владимир Соколов" написал:

Интересно! А еще как бы открыть по адресу (или без адреса), указать точное расположение, и сохранить координаты в базе :)


Владимир, как и обещал, выкладываю пост о там, как это сделать - http://www.community.terrasoft.ru/blogs/10538

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

Всем привет!
Сегодня представляю вашему вниманию еще одну полезную утилиту для тройки – модуль копирования прав. Все мы знаем, как удобно выдать права на отдельные записи в 3.X, но когда нужно дать все права на все разделы, нужно изобретать что то другое. Данная утилита предназначена для копирования прав от одного пользователя другому. Копирование происходит для всех записей выбранных разделов. Выглядит это вот так:

Код, как всегда полностью доступен на нашем FTP. Берите и используйте в Ваших проектах, будем только рады.
Подробная инструкция по установке в комплекте.

Всегда рад вопросам и предложениям.

Нравится

Поделиться

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

Всем доброго времени суток!

У нас в компании за годы работы с прекрасной тройкой скопилась масса полезных утилит, упрощающих рутину и не только. Сегодня хочу представить сообществу первую ласточку (из опубликованных здесь) - CopyProfile.
Вкратце - это решение для автоматизации копирования и переноса профиля Террасофт между сотрудниками (да, я знаю, что это можно делать руками, но так приятнее и комфортнее:))
Изображение во вложении для тех, кто может гадать по картинкам.

И FAQ для всех остальных:

Для чего это нужно?

Мы у себя используем это решение для новых сотрудников. Кейс: приходит новый сотрудник в отдел продаж, открываем Террасофт, 3 клика и профиль скопирован. А это значит, что теперь у него все реестры настроены также, как у остальных менеджеров, при этом нет необходимости заморачиваться с копированием файлов и т.д.

Как это работает?

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

Где скачать?

Чтобы не мучать сообщество архивами, отдельными файлами, выложил все на наш FTP

А инструкция есть?

Да, подробная инструкция на FTP. Вкратце нужно:

  1. Загрузить сервисы
  2. В wnd_Main добавить пункт меню
  3. В wnd_Main добавить обработчик события OnExecute
  4. Добавить в sq_Contact фильтр типа Exists “IsContactUser”

Какие есть ограничения и подводные камни?

  • Работает только для профиля, хранящегося в БД
  • Администратор может менять профиль любого пользователя, а не администратор – только свой

Я ничего не понял в коде, можно пояснить зачем нужно столько сервисов?

*_Copy_Profile – карточка с настройками (диалог)
*_ProfileChange – информация об изменениях профиля пользователя
*_ProfileData – сервис доступа к данным профиля

А можно код отдельно?

Не вопрос - выложил там же на FTP файл ТОЛЬКО с измененным кодом

По любым вопросом пишите тут, либо мне напрямую.
P.S.: В ближайшее время буду публиковать и другие утилиты/модули.

Нравится

Поделиться

1 комментарий

Ура! Наконец-то мы делимся своими наработками с остальным миром! :)
Как говорится, ждите следующих выпусков, там все интереснее и интереснее!

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