11 августа 2017

Кейсы

Лемана Про

Разработка

Портал на службе бухгалтерии или автоматизация авансовых отчетов

Мы придумали модуль на корпоративный портал заказчика, интегрированный с используемыми в компании системами 1С и БОСС-кадровик, который позволяет сотрудникам осуществлять всю процедуру согласования командировок и отчетности по ним в электронном виде. 

Наш заказчик — компания «Леруа Мерлен», её российское подразделение объединяет 90 магазинов в 44 городах, в которых работают свыше 30 000 сотрудников.

В год сотрудники «Леруа Мерлен Восток» совершают свыше 20 000 командировок. Долгое время все заявления и отчеты по командировкам согласовывались в бумажном виде. Сотрудник заполнял заявку, подписывал ее у руководителя, передавал в службу персонала для заказа билетов и гостиницы, далее заявка передавалась в бухгалтерию для перечисления аванса. После возвращения путь повторялся. Авансовый отчет подписывался у руководителя, передавался в бухгалтерию, бухгалтер оформлял отчет и делал необходимые проводки в учетной системе.

Естественно, человеческий фактор часто приводил к ошибкам и необходимости повторять весь путь с начала. На поддержку этого процесса в ручном режиме тратилось, по самым грубым подсчетам, 200 000 часов в год.

С учетом активного роста компании эти трудозатраты могли со временем вырасти в разы. Чтобы этого не произошло, компания решила изменить подход.

«Леруа Мерлен» давно выбрали стратегию работы с корпоративным порталом на Microsoft SharePoint.  

Компания Тrue Engineering разработала модуль, интегрированный с 1С и БОСС-кадровик, который позволяет сотрудникам осуществлять всю процедуру согласования командировок и отчетности по ним в электронном виде. 

 

Процедура согласования командировок стала выглядеть вот так:

Решение включает: 

  • унифицированную форму заявки на командировку на портале; 

  • общий график отпусков и командировок сотрудников отдела для взвешенного планирования; 

  • электронное согласование заявки руководителем; 

  • автоматическое перечисление аванса на командировку; 

  • заполнение авансового отчета на портале с прикреплением отчетных документов. 

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

 

Подробности реализации проекта
1) UI/UX-концепция

Первым шагом мы проработали UI/UX – прорисовали простые, последовательные, логичные и удобные экраны решения, а затем перешли к реализации.

Вот так выглядят пользовательские экраны:

Список командировок и добавление новой заявки

Заявка на командировку

Форма согласования заявки руководителем

Форма авансового отчета о командировке

А в таком виде авансовый отчет видит руководитель и бухгалтерия

2) Прикрутка к SharePoint

Далее начали прикручивать все к порталу на SharePoint. Портал на шарике всем хорош, кроме того, что в нем нет транзакционности. Но на то мы и спецы, чтобы решать нестандартные задачи.

Мы научились обходить ее так:

Например, для хранения списка городов, целей и дат добавили в список SharePoint поле типа Note:

<Field Type="Note" DisplayName="Destinations" ID="{0aa6522b-ce74-4148-9b54-b9b7cd218098}" Name="Destinations" NumLines="6" RichText="FALSE" />

При сохранении айтема в это поле сохранили сериализованный JSON:

requestListItem[Constants.Lists.Requests.Destinations] = JsonHelper.JsonSerializer(destinations);

Для получения данных о городах, целях и датах при просмотре десериализовываем обратно:

Destinations = JsonHelper.JsonDeserialize<RequestDestination[]>((string)item[Constants.Lists.Requests.Destinations])

3) Интеграция с БОСС-кадровик и 1С

С системами БОСС-кадровик и «1С Бухгалтерия» проинтегрировались через базы SQL с помощью EntityFramework. Но это задача более-менее стандартная и зависит от конкретных конфигураций. Уверены, вы и без нас знаете, что делать.

4) Мобильная версия модуля

И, наконец, сделали мобильную версию для оформления заявок на командировки прямо на лету.

Вы же понимаете, в среднем 5 командировок на человека в год это значит один ездит раз в год, а другой 50. Есть еще нюанс – в данной конкретной компании очень многие не имеют стационарных компьютеров и тем более ноутов. Вот для тех, кто без ноута из одной командировки летит сразу в другую, мобильная форма штука просто незаменимая.

Итак, как мы обеспечили мобильность.

JavaScript решили использовать единый, а переключение между мобильным представлением и основным сделали с помощью средств SharePoint:

А. Создали канал устройств:

В. Сделали переключение MasterPage в зависимости от канала. С. На aspx странице сделали отображение и подключение скриптов и css в зависимости от канала:

<asp:Content ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server"> <PublishingWebControls:DeviceChannelPanel ID="DefaultPanel" runat="server" IncludedChannels="Default"> <defaultMain:Header runat="server"></defaultMain:Header> </PublishingWebControls:DeviceChannelPanel> <PublishingWebControls:DeviceChannelPanel runat="server" IncludedChannels="Mobile"> <mobileMain:Header runat="server"></mobileMain:Header> </PublishingWebControls:DeviceChannelPanel> <meta name="CollaborationServer" content="SharePoint Team Web Site" /> </asp:Content> <asp:Content ID="Content8" ContentPlaceHolderID="PlaceHolderMain" runat="server"> <PublishingWebControls:DeviceChannelPanel ID="DeviceChannelPanel1" runat="server" IncludedChannels="Default"> <defaultMain:Page runat="server"></defaultMain:Page> </PublishingWebControls:DeviceChannelPanel> <PublishingWebControls:DeviceChannelPanel runat="server" IncludedChannels="Mobile"> <mobileMain:Page runat="server"></mobileMain:Page> </PublishingWebControls:DeviceChannelPanel> </asp:Content>

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

Основная:

<div class="list-block">

  <div class="list-header">

    <h2 class="text-uppercase color-blue inline-block">Твои командировки</h2>

    <button type="button" class="new-trip-button" data-bind="click: editRequest.newRequest">Новая командировка</button>

    <!-- ko if: $root.user().hasDebt -->

      <div class="alert alert-danger text-center">

        <span class="glyphicon glyphicon-alert pull-left"></span> <span>У тебя есть незакрытый аванс в размере <span data-bind="text: $root.user().debt"></span>&nbsp;RUB. Ты не можешь взять новый аванс, пока не закроешь старый.</span>

      </div>

    <!-- /ko -->

    <!-- ko if: $root.user().debt < 0 -->

      <div class="alert alert-danger text-center cred">

        <span class="glyphicon glyphicon-alert pull-left"></span> <span>У тебя есть сумма к возмещению в размере <span data-bind="text: -$root.user().debt"></span>&nbsp;RUB.</span>

      </div>

    <!-- /ko -->

  </div>

  <div class="list-body" data-bind="with: requests">

    <table class="table">

      <thead>

        <tr class="text-muted">

          <th></th>

          <th></th>

          <th></th>

          <th></th>

          <th>Город</th>

          <th>Статус</th>

          <th>Цель</th>

          <th class="text-right">Сумма</th>

        </tr>

      </thead>

      <tbody>

        <!-- ko foreach: items -->

          <tr data-bind="css: { 'travel-request': needAnswer }">

            <td class="status-block">

              <small class="status travel-request">заявка на командировку</small><strong class="link-blue-lg" data-bind="text: period, click: viewTripRequest"></strong>

            </td>

            <td>

              <!-- ko if: questions -->

                <i class="travel-request-icon" data-bind="click: $parent.conversation"></i>

              <!-- /ko -->

              <!-- ko if: statusID == 14 -->

                <i class="glyphicon icon-print" data-bind="click: $parent.print"></i>

             <!-- /ko -->

            </td>

            <td>

              <!-- ko if: forManager -->

                <i class="for-manager-icon"></i>

              <!-- /ko -->

            </td>

            <td>

              <!-- ko if: advanceReportStatus -->

                <i class="pink-doc-icon" data-bind="click: viewAdvanceReport"></i>

              <!-- /ko -->

            </td>

            <td>

              <span data-bind="text: city"></span>

            </td>

            <td>

              <div data-bind="text: statusDescription"></div>

            </td>

            <td>

              <div data-bind="text: goal"></div>

            </td>

            <td class="text-right">

              <!-- ko if: advance.length > 0 -->

                <strong class="color-red" data-bind="text: advance, css: { 'color-red': statusID != 15 }"></strong>

              <!-- /ko -->

              <!-- ko if: advance.length == 0 -->

                <span class="text-muted">Без аванса</span>

              <!-- /ko -->

            </td>

          </tr>

        <!-- /ko -->

      </tbody>

    </table>

  </div>

</div>

Мобильная:

<div class="list-block last" data-bind="with: requests">

  <div class="list-header">

    <h3 class="color-blue text-uppercase inline-block">Твои командировки</h3>

    <button type="button" class="new-trip-button" data-bind="click: $parent.editRequest.newRequest"></button>

    <!-- ko if: $root.user().hasDebt -->

      <div class="alert alert-danger">

        <span class="glyphicon glyphicon-alert"></span><span>У тебя есть незакрытый аванс в размере <span data-bind="text: $root.user().debt"></span>&nbsp;RUB. Ты не можешь взять новый аванс, пока не закроешь старый.</span>

      </div>

    <!-- /ko -->

    <!-- ko if: $root.user().debt < 0 -->

        <div class="alert alert-danger cred">

        <span class="glyphicon glyphicon-alert"></span><span>У тебя есть сумма к возмещению в размере <span data-bind="text: -$root.user().debt"></span>&nbsp;RUB.</span>

      </div>

    <!-- /ko -->

  </div>

  <!-- ko foreach: items -->

    <div class="list-content">

      <div class="row">

        <div class="state-block">

          <div class="form-group">

            <small class="state bg-green color-white text-lowercase">Заявка на командировку</small>

          </div>

        </div>

        </div>

      <div class="row">

        <div class="col-xs-8">

          <div class="form-group">

            <span class="text-large" data-bind="text: statusDescription"></span>

          </div>

        </div>

        <div class="col-xs-4">

          <div class="form-group text-right">

            <!-- ko if: advance.length > 0 -->

              <span class="color-red text-large" data-bind="text: advance"></span>

            <!-- /ko -->

            <!-- ko if: advance.length == 0 -->

              <span class="text-muted text-large">Без аванса</span>

            <!-- /ko -->

          </div>

        </div>

         </div>

      <div class="row list-foot">

        <div class="col-xs-4">

          <h4><a href="javascript://" class="link link-blue" data-bind="text: period, click: viewTripRequest"></a></h4>

        </div>

        <div class="col-xs-4">

          <h4 class="icon" data-bind="text: city, css: { 'icon-rus': tripType[0] == 'По России', 'icon-non-rus': tripType[0] == 'Заграничная' }"></h4>

        </div>

        <div class="col-xs-4">

          <!-- ko if: advanceReportStatus -->

            <span class="icon-attach" data-bind="click: viewAdvanceReport"></span>

          <!-- /ko -->

           <!-- ko if: questions -->

            <i class="travel-request-icon" data-bind="click: $parent.conversation"></i>

          <!-- /ko -->

          <!-- ko if: advanceReportStatus == 'Авансовый отчет утвержден' -->

            <i class="icon-print" data-bind="click: $parent.print"></i>

          <!-- /ko -->

        </div>

      </div>

    </div>

  <!-- /ko -->

</  div>

Заключение

Традиционные процессы, которые работали годами, можно поменять и сделать быстрыми и удобными, а самое главное — менее ресурсо-затратными.

В результате внедрения: 

  • сократились трудозатраты руководителей на согласование командировок; 

  • значительно снизилась нагрузка на специалистов отдела персонала и бухгалтерии; 

  • появилась возможность оформлять командировки в регионах без участия специалистов; 

  • можно оформить документы даже в пути, для этого есть мобильная версия модуля. 

Модуль выполнен в корпоративном стиле портала «Леруа Мерлен». Продуманный UI на каждом этапе отображает только ту информацию, которая необходима и достаточна для принятия решения на данном этапе.