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

в 11:28, , рубрики: .net, C#, caml, knockout, Portal, sharepoint, Блог компании EastBanc Technologies

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

С нашей точки зрения этот материал будет интересен прежде всего тем, кто занимается разработкой и развитием внутренних порталов, и думает что-же полезного можно сделать кроме всеми любимых справочников сотрудников, новостей, дней рождений и мать их курсов валют.
Портал на службе бухгалтерии или автоматизация авансовых отчетов - 1
Итак, представим средних размеров организацию, которая разбросана по территории нашей большой страны, а именно такие компании выбирают SharePoint в качестве корпоративного портала. Самый обычный сотрудник собирается поехать в командировку и вот тут начинается самое интересное: заполни заявку, согласуй у руководителя, передай в службу персонала, получи аванс на расходы, съезди в командировку, заполни отчет, сдай его в бухгалтерию.

Если честно, пока писали, уже сами запутались, что и в какой последовательности нужно делать, а ведь еще нужно помнить, как все правильно оформить.
Если представить это графически, то выглядит вот так:

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

Цифры из реальной жизни – сотрудники нашего заказчика совершают 20 000 командировок в год, вы скажете не может быть такого? это не реально? Но посмотрим на эту цифру по-другому – эти 20 000 командировок совершают 4 000 человек. То есть в среднем по 5 командировок на человека за год.

Вполне реальная цифра.

НО если посчитать время, которое траться на поддержку данного процесса, то выходит 200 000 часов… Двести тысяч часов, КАРЛ!

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

И как это можно автоматизировать?

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

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

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

Срок оформления заявок и согласования отчетов сократился до 1 дня. Не нужно физического присутствия специалистов, не нужно оформлять тонны бумажных документов. Все онлайн и в короткие сроки. И даже с мобильного.

Звучит неплохо, правда? Сейчас расскажем, как мы этого добились:

1) UI/UX-концепция

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

Вот так выглядят пользовательские экраны:
Список командировок и добавление новой заявки
Портал на службе бухгалтерии или автоматизация авансовых отчетов - 4

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

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

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

А в таком виде авансовый отчет видит руководитель и бухгалтерия
Портал на службе бухгалтерии или автоматизация авансовых отчетов - 8

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

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

Мы научились обходить ее так:
1. Например, для хранения списка городов, целей и дат добавили в список SharePoint поле типа Note:

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

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

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

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

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

Сим-салабим и порчу с Шарепоинта сняли.

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

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

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

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

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

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

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

1. Создали канал устройств:
Портал на службе бухгалтерии или автоматизация авансовых отчетов - 10

2. Сделали переключение MasterPage в зависимости от канала.

3. На 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>

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

1. Основная:

            <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> 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> 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>

2. Мобильная:

<div class="list-block last" data-bind="with: requests">
                <div class="list-header">
                    <h1 class="color-blue text-uppercase inline-block">Твои командировки</h1>
                    <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> 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> 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>

Вуаля!

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

При этом мы не изобретали велосипед, а мягко встроились в существующую ИТ-экосистему заказчика. Компания давно и надолго выбрала стратегию работы с корпоративным порталом как основным инструментом коммуникации с сотрудниками. И мы с радостью помогаем им делать SharePoint-портал действительно эффективным рабочим ресурсом.

Автор: EastBanc Technologies

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js