Добавление работ в 1С:Документооборот с использованием inline-бота telegram

Управление - Интеграция

Заполнение ежедневных отчетов 1С:Документооборот используя бота telegram, реализованного на python.

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

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

Плюс периодически и заказчик может пойти в отказ. Типа не делалось это, а если и делалось, то не два часа, а пятнадцать минут.

Пробовал вести записи на каждый день, пока едешь в метро. Но и это если и помогало, то недолго. И не решало проблемы с заказчиками, которые отказывались подписывать эти работы.

Для решения вышеперечисленных проблем решил сделать инлайн бота в telegram.

Выглядеть это должно было примерно следующим образом:
1. Пишешь в чатике с заказчиком, что сделано
2. Ставишь часы
3. Указываешь проект
4. Просишь заказчика подписать сразу же
5. По нажатию "провести" запись попадает в мой ежедневный отчет

Исходя из задачи от ДО мне надо:
1. Получить список проектов по пользователю
2. Записать проект часы в документ ежедневный отчёт

Эту задачу решил подняв http-сервис. Вдохновением была статья с курсов-по-1с.

Бот должен делать следующее:
1. Из инлайн команды создавать сообщение
2. Читать список проектов из http-сервиса ДО
3. Позволять редактировать затраченное время
4. Иметь возможность фиксировать подпись заказчика
5. Постить результат в ДО используя http-сервис

Бота решил писать на python

Для решения решил задействовать поднятый у нас разработческий сервер на CentOS 6.

Для теста создал простейшую базу на 1С и опубликовал ее через Apache

Общение с api telegram происходит с использованием telegram-bot-api. В момент написания первого бота использовал эту статью.

#!/usr/bin/python
import gstdobotconfig #файл с настройками
import telegram
from imp import reload # модуль для перезагрузки (обновления) других модулей
from telegram import (ReplyKeyboardMarkup, ReplyKeyboardRemove, InlineQueryResultArticle, ChatAction, InputTextMessageContent, InlineKeyboardMarkup, InlineKeyboardButton)
from telegram import (InlineKeyboardMarkup, InlineKeyboardButton)
from telegram.ext import Updater, ConversationHandler, CommandHandler, MessageHandler, Filters, InlineQueryHandler
from telegram.ext import CallbackQueryHandler, ChosenInlineResultHandler
from uuid import uuid4
import json, requests

import logging
# Включение логирования 
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
logger = logging.getLogger(__name__)
messagedata = {}
timebutton = [u"+1ч", u"+30м", u"+15м", u"-15м", u"-30м", u"-1ч"]
timevalues = [60*60,    60*30,   60*15,  -60*15,  -60*30, -60*60]

def getmessagetext(data):
    return '*{0}*\n{1}\n*{2}*\n{3}\n*{4}*\n{5} ч\n*{6}*\n{7}\n*{8}*\n{9}'.format(
		u"Описание работы:", data['WorkDescription'],
		u"Сотрудник:", data['UserID'],
		u"Затрачено времени:", data['Duration']/60/60,
		u"Проект:", data['Project']['Name'],
		u"Подписано:", data['Signed'])	

def getkeyboard(dataid, userid, start, stop):
    getTimeIKB = lambda name: InlineKeyboardButton(name, callback_data='t:'+str(timebutton.index(name))+':'+dataid)
    projects = requests.get(gstdobotconfig.urlProjects + userid,auth=(gstdobotconfig.login, gstdobotconfig.password)).json()
    getProjectIKB = lambda project: InlineKeyboardButton(project['Name'], callback_data='p:'+str(projects.index(project))+':'+dataid)
    back    = InlineKeyboardButton('U92;', callback_data='g:0:'+dataid)
    forward = InlineKeyboardButton('U94;', callback_data='g:1:'+dataid)
    btns = []
    if start == 1:
        btns = list(map(getProjectIKB, projects))[:5] + [forward]
    elif stop == len(projects)-1:
        btns = [back] + list(map(getProjectIKB, projects))[-5:] 
    else :
        btns = [back] + list(map(getProjectIKB, projects))[start:stop] + [forward] 
    return InlineKeyboardMarkup(
        [
         btns,
         list(map(getTimeIKB, timebutton)),
         [InlineKeyboardButton(u"Подписать", callback_data='s:0:'+dataid),
         InlineKeyboardButton(u"Провести", callback_data='w:0:'+dataid)]
        ]
    )

def inlinequery(bot, update):
    query = update.inline_query.query
    data = {'Project': {'Name':'','Code':''},
            'UserID': str(update.inline_query.from_user.id),
            'WorkType': u"ПУСТО",
            'Duration': 0,
            'Start': 1,
            'Stop': 5,
            'Signed': [],
            'WorkDescription': query}
    results = list()
    dataid = update.inline_query.id
    results.append(
      InlineQueryResultArticle(
        id=uuid4(),
        title=query,
        description=u"Описание работы",
        input_message_content = InputTextMessageContent(
          getmessagetext(data),
          parse_mode="Markdown"),
          reply_markup=getkeyboard(dataid, data['UserID'], data['Start'], data['Stop'])
        )
      )
    bot.answerInlineQuery(update.inline_query.id, results=results, cache_time=10)
    messagedata[dataid] = data

def mh(bot, update):
    error(bot, update, 'sss5')
	
def error(bot, update, error):
    logger.warn('Update "%s" caused error "%s"' % (update, error))

def callbackquery(bot, update):
    query = update.callback_query
    buttontype, buttonid, messageid = query.data.split(':')
    data = messagedata[messageid]
    projects = requests.get(gstdobotconfig.urlProjects + data['UserID'],auth=(gstdobotconfig.login, gstdobotconfig.password)).json()
    if buttontype == 't':
        data['Duration'] = data['Duration'] + timevalues[int(buttonid)]
    elif buttontype == 'p':
        data['Project'] = projects[int(buttonid)]
    elif buttontype == 'g' and buttonid == '0':
        data['Start'] = max(1, data['Start']-4)
        data['Stop'] = max(5,data['Start']+4)
    elif buttontype == 'g' and buttonid == '1':
        data['Stop'] = min(len(projects)-1, data['Stop']+4)
        data['Start'] = min(len(projects)-5, data['Stop']-4)
    elif buttontype == 'w':
        writedata = {'WorkType': 'общие'}
        writedata['UserID'] = data['UserID']
        writedata['WorkDescription'] = data['WorkDescription']
        writedata['ProjectCode'] = data['Project']['Code']
        writedata['Duration'] = data['Duration']
        r = requests.post(gstdobotconfig.urlPost, json=writedata,auth=(gstdobotconfig.login, gstdobotconfig.password))
    elif buttontype == 's':
        voter = str(query.from_user.first_name) + ' ' + str(query.from_user.last_name);
        if not voter in set(data['Signed']):
            data['Signed'].append(voter)
    content = getmessagetext(data)
    bot.edit_message_text(
        inline_message_id=query.inline_message_id,
        text=content, parse_mode="Markdown"
    )
    bot.edit_message_reply_markup(
        inline_message_id=query.inline_message_id,
        reply_markup=getkeyboard(messageid, data['UserID'], data['Start'], data['Stop'])
    )

def main():
    updater = Updater(token=gstdobotconfig.token)
    dispatcher = updater.dispatcher
    dispatcher.add_handler(CallbackQueryHandler(callbackquery))
    dispatcher.add_handler(InlineQueryHandler(inlinequery))
    dispatcher.add_handler(ChosenInlineResultHandler(mh))
    dispatcher.add_error_handler(error)
    updater.start_polling()
    updater.idle()

if __name__ == '__main__':
    main()

Еще надо было создать службу что бы она запускалась.

#!/bin/sh
### BEGIN INIT INFO
# Provides:          gst-do-telegram-bot
# Required-Start:    $local_fs $network $named $time $syslog
# Required-Stop:     $local_fs $network $named $time $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Description:       gst do telegram bot
### END INIT INFO

SCRIPT='/usr/bin/python3 /usr/local/bin/gstdobot.py'
RUNAS=root

PIDFILE=/var/run/gst-do-telegram-bot.pid
LOGFILE=/var/log/gst-do-telegram-bot.log

start() {
  if [ -f /var/run/$PIDNAME ] && kill -0 $(cat /var/run/$PIDNAME); then
    echo 'Service already running' >&2
    return 1
  fi
  echo 'Starting service…' >&2
  local CMD="$SCRIPT &> \"$LOGFILE\" & echo \$!"
  su -c "$CMD" $RUNAS > "$PIDFILE"
  echo 'Service started' >&2
}

stop() {
  if [ ! -f "$PIDFILE" ] || ! kill -0 $(cat "$PIDFILE"); then
    echo 'Service not running' >&2
    return 1
  fi
  echo 'Stopping service…' >&2
  kill -15 $(cat "$PIDFILE") && rm -f "$PIDFILE"
  echo 'Service stopped' >&2
}

uninstall() {
  echo -n "Are you really sure you want to uninstall this service? That cannot be undone. [yes|No] "
  local SURE
  read SURE
  if [ "$SURE" = "yes" ]; then
    stop
    rm -f "$PIDFILE"
    echo "Notice: log file is not be removed: '$LOGFILE'" >&2
    update-rc.d -f gst-do-telegram-bot remove
    rm -fv "$0"
  fi
}

case "$1" in
  start)
    start
    ;;
  stop)
    stop
    ;;
  uninstall)
    uninstall
    ;;
  restart)
    stop
    start
    ;;
  *)
    echo "Usage: $0 {start|stop|restart|uninstall}"
esac

Из моментов, которые реализованы откровенно плохо
1. При печати сообщения в инлайн запоминаются лишние итерации, побороть можно фиксируя нужную итерацию при посте этого сообщения, но как это сделать, пока не разобрался.
2. Нет сортировки проектов, хочу сделать на стороне ДО сортировать по убыванию количества запощенных часов
3. Не убираются кнопки после проведения записи в ежедневный отчет

Использую бота уже неделю, пока заполнять не так обломно, как лезть в ДО или восстанавливать свои сокращения перед выставлением счетов.

Посмотрим, что будет с заказчиками которые любили резать часы.

Содержание архива: 

gst-do-telegram-bot - файл для запуска сервиса разместил на Centos 6 в каталоге /etc/init.d
gstdobot.py - бот на питоне разместил на Centos 6 в каталоге /usr/local/bin/
gstdobotconfig.py - конфиг файл где, кто, куда разместил на Centos 6 в каталоге /usr/local/bin/
telegram-http-service.dt - пример реализации со стороны 1С:Предприятия - для работы с ботом должна быть опубликована и видна из инета - (логин:пароль - X:1qaz2wsx)

Скачать файлы

Наименование Файл Версия Размер
Добавление работ в 1С:Документооборот используя inline-бота telegram
.zip 52,34Kb
29.09.17
0
.zip 52,34Kb Скачать

См. также

Комментарии
1. Дмитрий Котов (rpgshnik) 90 30.09.17 06:45 Сейчас в теме
Скоро телеграмм прикроют...
2. Павел Городилов (bxz) 379 30.09.17 10:19 Сейчас в теме
(1) рановато хороните
sdwggg; CyberCerber; rpgshnik; +3 Ответить
3. Armando Armando (Armando) 1382 30.09.17 11:36 Сейчас в теме
Прикрепленные файлы:
bxz; sdwggg; CyberCerber; rpgshnik; +4 Ответить
4. sdwggg (sdwggg) 02.10.17 08:42 Сейчас в теме
(1) Дуров ни ВК не слил, и Телеграм не сдаст!
Оставьте свое сообщение