Как мы дорабатываем продукт под конкретного клиента

    image

    Итак, мы продали клиенту программный B2B продукт.

    На презентации ему все нравилось, но в ходе внедрения выяснилось, что кое-что все-таки не подходит. Можно конечно сказать что нужно следовать “best practice”, и изменить себя под продукт, а не наоборот. Это может сработать, если у вас есть сильный бренд (например, из трех больших букв, и вы можете послать всех на три маленькие буквы). В противном случае, вам быстро объяснят, что заказчик добился всего благодаря своим уникальным бизнес-процессам, и давайте-ка, лучше меняйте свой продукт, или ничего не получится. Есть вариант отказаться и сослаться на то, что лицензии уже куплены, и с подводной лодки деваться уже некуда. Но на относительно узких рынках такая стратегия долго работать не будет.

    Приходится дорабатывать.

    Подходы


    Существует несколько основных подходов к адаптации продуктов.

    Монолит


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

    Копия


    Клиенту передается весь исходный код продукта с лицензией на его изменение. Часто такие вендоры говорят заказчику, что они не будут сами адаптировать продукт, так как это будет слишком дорого (вендору гораздо выгоднее продавать лицензии, чем связываться с услугами). Но у них есть знакомые аутсорсеры, которые нанимают относительно недорогих и качественных сотрудников где-нибудь в третьих странах, готовые им помочь. Также бывают ситуации, когда доработки будут проводиться непосредственно специалистами заказчика (если у них есть штатные единицы). В таких случаях исходный код берется как отправная точка, а измененный код не будет иметь никакой связи с тем, что было изначально, и будет жить своей жизнью. В этом случае можно спокойно удалить хоть половину исходного продукта и заменить на полностью свою логику.

    Слияние


    Это смесь первых двух подходов. Но в нем разработчику, который правит код, всегда нужно помнить: “merge is coming”. Когда выйдет новая версия исходного продукта, то ему придется в большинстве случаев вручную сливать изменения в исходном и измененном коде. Проблема в том, что при любом конфликте нужно будет вспоминать, зачем были внесены определенные изменения, а это могло быть очень давно. А если в исходном продукте был проведен рефакторинг по коду (например, просто переставили местами блоки кода), то слияние будет очень трудоемким.

    Модульность


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

    Описание


    Дальше на примерах я покажу, как мы расширяем продукты, разработанные на базе открытой и бесплатной платформы lsFusion.

    Ключевым элементом системы является модуль. Модуль — это текстовый файл с расширением lsf, в котором находится код на языке lsFusion. В каждом модуле объявляется как доменная логика (функции, классы, действия), так и логика представления (формы, навигатор). Модули, как правило, находятся в директориях, разбитых по логическому принципу. Продукт представляет собой совокупность модулей, которые реализуют его функционал, и хранящиеся в отдельном репозитории.

    Модули имеют зависимость между собой. Один модуль зависит от другого, если он использует его логику (например, обращается к свойствам или формам).

    Когда появляется новый клиент, то под него заводится отдельный репозиторий (Git или Subversion), в котором будут создаваться модули с необходимыми ему доработками. В этом репозитории задается так называемый верхний модуль. При запуске сервера будут подключены только те модули, от которых он зависит напрямую или транзитивно через другие модули. Это позволяет использовать у клиента не весь функционал продукта, а только нужную ему часть.
    На Jenkins создается задание, которое объединяет модули продукта и клиента в единый jar-файл, который затем устанавливается на рабочий или тестовый сервер.

    Рассмотрим несколько основных случаев доработок, возникающих на практике:

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

    Модуль Order
    MODULE Order;

    CLASS Book 'Книга';
    name 'Наименование' = DATA ISTRING[100] (Book) IN id;

    CLASS Order 'Заказ';
    date 'Дата' = DATA DATE (Order) IN id;
    number 'Номер' = DATA STRING[10] (Order) IN id;

    CLASS OrderDetail 'Строка заказа';
    order 'Заказ' = DATA Order (OrderDetail) NONULL DELETE;

    book 'Книга' = DATA Book (OrderDetail) NONULL;
    nameBook 'Книга' (OrderDetail d) = name(book(d));

    quantity 'Количество' = DATA INTEGER (OrderDetail);
    price 'Цена' = DATA NUMERIC[14,2] (OrderDetail);
    sum 'Сумма' (OrderDetail d) = quantity(d) * price(d);

    FORM order 'Заказ'
        OBJECTS o = Order PANEL
        PROPERTIES(o) date, number

        OBJECTS d = OrderDetail
        PROPERTIES(d) nameBook, quantity, price, NEWDELETE
        FILTERS order(d) = o

        EDIT Order OBJECT o
    ;

    FORM orders 'Заказы'
        OBJECTS o = Order
        PROPERTIES(o) READONLY date, number
        PROPERTIES(o) NEWSESSION NEWEDITDELETE
    ;

    NAVIGATOR {
        NEW orders;
    }

    Клиент X хочет добавить для строки заказа также процент скидки и цену со скидкой.
    Сначала в репозитории заказчика создается новый модуль OrderX. В его заголовке ставится зависимость на исходный модуль Order:
    REQUIRE Order;

    В этом модуле объявляем новые свойства, под которые будут созданы дополнительные поля в таблицах, и добавляем их на форму:
    discount 'Скидка, %' = DATA NUMERIC[5,2] (OrderDetail);
    discountPrice 'Цена со скидкой' = DATA NUMERIC[14,2] (OrderDetail);

    EXTEND FORM order
        PROPERTIES(d) AFTER price(d) discount, discountPrice READONLY
    ;

    Цену со скидкой делаем недоступной для записи. Она будет рассчитываться отдельным событием при изменении либо исходной цены, либо процента скидки:
    WHEN LOCAL CHANGED(price(OrderDetail d)) OR CHANGED(discount(d)) DO
      discountPrice(d) <- price(d) * (100 (-) discount(d)) / 100;

    Теперь нужно изменить расчет суммы по строке заказа (он должен учитывать нашу вновь созданную цену со скидкой). Для этого мы, как правило, создаем определенные “точки входа”, куда другие модули могут вставлять свое поведение. Вместо исходного объявления свойства sum в модуле Order используем следующее:
    sum 'Сумма' = ABSTRACT CASE NUMERIC[16,2] (OrderDetail);
    sum (OrderDetail d) += WHEN price(d) THEN quantity(d) * price(d); 

    В этом случае, значение свойства sum будет собрано в один CASE, где WHEN могут быть разбросаны по разным модулям. Гарантируется, что если модуль A зависит от модуля B, то все WHEN модуля B сработают позже, чем WHEN модуля A. Для правильного подсчета суммы со скидкой в модуле OrderX добавляется следующее объявление:
    sum(OrderDetail d) += WHEN discount(d) THEN quantity(d) * discountPrice(d);

    В итоге, если будет задана скидка, то сумма будет с ее учетом, а иначе — исходное выражение.

    Допустим, клиент хочет добавить ограничение, что сумма заказа не должна превышать некоторую заданную величину. В том же модуле OrderX объявляем свойство, в котором будет хранится величина ограничения, и добавляем его на стандартную форму options (можно при желании создать отдельную форму с настройками):
    orderLimit 'Лимит заказа' = DATA NUMERIC[16,2] ();
    EXTEND FORM options 
        PROPERTIES() orderLimit
    ;

    Затем, в том же модуле, объявляем сумму по заказу, показываем ее на форме и добавляем ограничение на ее превышение:
    sum 'Сумма' (Order o) = GROUP SUM sum(OrderDetail d) IF order(d) = o;
    EXTEND FORM order
       PROPERTIES(o) sum
    ;
    CONSTRAINT sum(Order o) > orderLimit() MESSAGE 'Превышен лимит заказов';

    И, наконец, клиент попросил немного поменять дизайн формы редактирования заказа: сделать, чтобы шапка заказа была слева от строк с разделителем, а также, чтобы цены всегда показывались с точностью до двух знаков. Для этого в его модуль добавляется следующий код, который изменяет стандартный сгенерированный дизайн формы order:
    DESIGN order {
        OBJECTS {
            NEW pane {
                fill = 1;
                type = SPLITH;
                MOVE BOX(o);
                MOVE BOX(d) {
                    PROPERTY(price(d)) { pattern = '#,##0.00'; }
                    PROPERTY(discountPrice(d)) { pattern = '#,##0.00'; }
                }
            }
        }
    }
    В итоге мы получаем два модуля Order (в продукте), в котором реализована базовая логика заказа, и OrderX (у заказчика), в котором реализована нужная ему логика скидок:

    Order
    MODULE Order;

    CLASS Book 'Книга';
    name 'Наименование' = DATA ISTRING[100] (Book) IN id;

    CLASS Order 'Заказ';
    date 'Дата' = DATA DATE (Order) IN id;
    number 'Номер' = DATA STRING[10] (Order) IN id;

    CLASS OrderDetail 'Строка заказа';
    order 'Заказ' = DATA Order (OrderDetail) NONULL DELETE;

    book 'Книга' = DATA Book (OrderDetail) NONULL;
    nameBook 'Книга' (OrderDetail d) = name(book(d));

    quantity 'Количество' = DATA INTEGER (OrderDetail);
    price 'Цена' = DATA NUMERIC[14,2] (OrderDetail);
    sum 'Сумма' = ABSTRACT CASE NUMERIC[16,2] (OrderDetail);
    sum (OrderDetail d) += WHEN price(d) THEN quantity(d) * price(d); 

    FORM order 'Заказ'
        OBJECTS o = Order PANEL
        PROPERTIES(o) date, number

        OBJECTS d = OrderDetail
        PROPERTIES(d) nameBook, quantity, price, NEWDELETE
        FILTERS order(d) = o

        EDIT Order OBJECT o
    ;

    FORM orders 'Заказы'
        OBJECTS o = Order
        PROPERTIES(o) READONLY date, number
        PROPERTIES(o) NEWSESSION NEWEDITDELETE
    ;

    NAVIGATOR {
        NEW orders;
    }

    OrderX
    MODULE OrderX;

    REQUIRE Order;

    discount 'Скидка, %' = DATA NUMERIC[5,2] (OrderDetail);
    discountPrice 'Цена со скидкой' = DATA NUMERIC[14,2] (OrderDetail);

    EXTEND FORM order
        PROPERTIES(d) AFTER price(d) discount, discountPrice READONLY
    ;

    WHEN LOCAL CHANGED(price(OrderDetail d)) OR CHANGED(discount(d)) DO
        discountPrice(d) <- price(d) * (100 (-) discount(d)) / 100;

    sum(OrderDetail d) += WHEN discount(d) THEN quantity(d) * discountPrice(d);

    orderLimit 'Лимит заказа' = DATA NUMERIC[16,2] ();
    EXTEND FORM options 
        PROPERTIES() orderLimit
    ;

    sum 'Сумма' (Order o) = GROUP SUM sum(OrderDetail d) IF order(d) = o;
    EXTEND FORM order
       PROPERTIES(o) sum
    ;
    CONSTRAINT sum(Order o) > orderLimit() MESSAGE 'Превышен лимит заказов';

    DESIGN order {
        OBJECTS {
            NEW pane {
                fill = 1;
                type = SPLITH;
                MOVE BOX(o);
                MOVE BOX(d) {
                    PROPERTY(price(d)) { pattern = '#,##0.00'; }
                    PROPERTY(discountPrice(d)) { pattern = '#,##0.00'; }
                }
            }
        }
    }

    Следует отметить, что модуль OrderX можно назвать OrderDiscount и перенести непосредственно в продукт. Тогда каждому заказчику можно будет при необходимости легко подключать функционал со скидками.

    Это далеко не все возможности, которые предоставляет платформа для расширения функционала в отдельных модулях. Например, при помощи наследования можно модульно реализовывать логику регистров.

    Если в исходном коде продукта произойдут какие-либо изменения, которые будут противоречить коду в зависимом модуле, то при запуске сервера будет выдана ошибка. Например, если в модуле Order удалят форму order, то при старте будет ошибка, что в модуле OrderX не найдена форма order. Также ошибка будет подсвечена в IDE. Кроме того, в IDE есть функция поиска всех ошибок в проекте, которая позволяет определить все проблемы возникшие из-за обновления версии продукта.

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

    Заключение


    Такая микромодульная архитектура обеспечивает следующие преимущества:

    • Каждому заказчику подключается только нужный ему функционал. Структура его базы данных содержит только те поля, которые он использует. Интерфейс конечного решения не содержит лишних элементов. Сервер и клиент не выполняют ненужные события и проверки.
    • Гибкость в изменениях базового функционала. Непосредственно в проекте клиента можно вносить изменения в абсолютно любые формы продукта, добавлять события, новые объекты и свойства, действия, менять дизайн и многое другое.
    • Значительно ускоряется поставка новых доработок, требуемых заказчику. При каждом запросе на изменение не требуется продумывать, каким образом она отразится на других клиентах. За счет этого многие доработки могут быть выполнены и введены в эксплуатацию в кратчайшие сроки (часто в течение нескольких часов).
    • Более удобная схема расширения функционала продукта. Сначала любой функционал можно включить конкретному заказчику, который готов его попробовать, а затем, в случае успешного внедрения, модули целиком переносятся в репозиторий продукта.
    • Независимость кодовой базы. Так как многие доработки оказываются по договорам услуг клиенту, то формально весь код, разработанный в рамках этих договоров, принадлежит заказчику. При такой схеме обеспечивается полное разделение кода продукта, который принадлежит вендору от кода, собственником которого является клиент. По запросу мы переносим репозиторий на сервер клиента, где он может силами собственных разработчиков дорабатывать нужный ему функционал. Кроме того, если поставщик осуществляет лицензирование по отдельным модулям продукта, то у заказчика нет исходного кода модулей, на которые нет лицензии. Таким образом, у него отсутствует техническая возможность подключить их самостоятельно в нарушение условий лицензирования.

    Описанная выше схема модульности при помощи расширений в программировании чаще всего называется mix in. Например, в Microsoft Dynamics относительно недавно появилась концепция extension, которая также позволяет расширять базовые модули. Однако, там требуется гораздо более низкоуровневое программирование, что в свою очередь требует более высокой квалификации разработчиков. Кроме того, в отличие от lsFusion, расширение событий и ограничений требует изначально заложенных «точек входа» в продукт, чтобы можно было этим воспользоваться.

    На данный момент, по описанной выше схеме мы поддерживаем и внедряем ERP-систему для розничной торговли у более чем 30 относительно крупных клиентов, которая состоит из более чем 1000 модулей. Среди заказчиков присутствуют как FMCG-сети, так и аптеки, магазины одежды, сети магазинов дрогери, оптовики и другие. В продукте, соответственно, есть отдельные категории модулей, которые подключаются в зависимости от индустрии и используемых бизнес-процессов.
    lsFusion
    44.46
    Не очередной язык программирования
    Share post

    Comments 26

      +1
      ой нагородили…
      Вообще-то это называется отделение от среды логической модели во внешние скрипты.
      Собственно на этом принципе построены современные игры, где вся логика вынесена в Луа-скрипты.
      Разница с вашей средой лишь в том, что игры как среды отделены от языка Луа, а у вас lsfusion-язык и lsfusion-среда связаны.

      Поэтому обновления среды и обратно-совместимые изменения в язык не затрагивают логику и могут быть обновлены отдельно и безопасно. Это же позволяет отделять программистов на работающих над средой и на работающих над клиентским кодом.
        +1
        Да, по такой схеме работают практически все ERP-платформы: 1С, SAP, Microsoft Dynamics. Они в основном написаны на C/C++, но у них есть, соответственно, свои встроенные языки 1С, ABAP и X++.

        В играх язык действительно используется язык Луа, но лишь синтаксис и базовое поведение. При этом нельзя просто взять и перенести скрипты из одной игры в другую (там другая логическая модель), так что они тоже можно сказать, что связаны со средой.
        0
        Интересно. Это вроде конфигураций 1С, верно? При данном подходе каждая конфигурация клиента хранится в репозитории.
        Как происходит обновление конфигураций при обновлении платформы? Или совместимость никогда не ломается? Вы хостите «конфигурации» у себя. Есть ли гарантии для заказчиков?
          0
          Это вроде конфигураций 1С, верно? При данном подходе каждая конфигурация клиента хранится в репозитории.

          Да, что-то вроде этого. Но я не знаю, насколько в 1С можно делать несколько отдельных конфигураций как модули и подключать друг к другу. Если конфигурация просто копируется и правится исходный код, то тут немного другая схема именно сделанная по модульному принципу.

          Как происходит обновление конфигураций при обновлении платформы? Или совместимость никогда не ломается?

          Да, начиная с версии 2.0 мы держим и будем держать обратную совместимость. Можно обновлять абсолютно независимо. Обновление платформы можно делать через apt-get/yum upgrade. «Конфигурация» собирается в один jar-файл (через jenkins или в IDEA) и просто складывается в определенный каталог. Когда сервер с платформой стартует, то он считывает модули из всех jar-файлов, которые там есть и подключает их. Внутри jar-файлов (а это zip архив) в открытом виде хранятся исходный код всех модулей. Пока обфускацией исходников не занимаемся.

          Вы хостите «конфигурации» у себя. Есть ли гарантии для заказчиков?

          Извините, но не понял, о каких гарантиях идет речь.
            0
            Спасибо за ответ. По последнему пункту, поправьте меня, если я неправильно понял. Ваша платформа предоставляет API и свой язык для разработки модулей. Плюс у вас есть набор готовых модулей, условно CRM. Заказчик покупает продукт Платформа + CRM, но ему нужны доработки. Вы делаете CRM ver. 2 и размещаете в своем репозитории.

            Заказчик при использовании CRM ver.2 загружает ее себе и использует локально? В чем отличие от доработок других продуктов. Компания «A» тоже может выпустить доработку для своего софта «ERP A» в виде патча и передать заказчику.

            Мне казалось, что идея в том, что CRM ver.2 находится в облаке. Фактически заказчик использует Платформу в облаке и модуль / модули в облаке. Вам так будет удобнее дорабатывать и сопровождать. В этом случае ему нужны гарантии по доступу (SLA).

            Еще вопрос по модульности и доработкам. Если взять к примеру демо ERP. В качестве требования заказчик хочет добавлять распознанные накладные со сканера. Т.е. некий сервис распознавания возвращает извлеченные данные + картинку. Платформа позволяет делать такие доработки?
              +1
              Вы делаете CRM ver. 2 и размещаете в своем репозитории

              Не совсем — у заказчика абсолютно свои модули в своем репозитории, которые просто используют модули из CRM. То есть, грубо говоря, на выходе получается два jar-файла: один содержит модули CRM, другой содержит модули заказчика. При запуске сервера используются оба файла.
              Компания «A» тоже может выпустить доработку для своего софта «ERP A» в виде патча и передать заказчику.

              Смотря, что рассматривать под патчем. Если просто правки в исходном коде (то есть, например, .patch файл subversion'а) CRM, то это не тоже самое, что модульность. Это именно то, что я писал в блоке Слияние, со всеми проблемами, что там есть.

              Мне казалось, что идея в том, что CRM ver.2 находится в облаке. Фактически заказчик использует Платформу в облаке и модуль / модули в облаке. Вам так будет удобнее дорабатывать и сопровождать. В этом случае ему нужны гарантии по доступу (SLA).

              На практике мы не используем такую схему. Только on-premise установка продукт + доработки для заказчика. Хотя местами, конечно, ставим на облачную инфраструктуру.

              В качестве требования заказчик хочет добавлять распознанные накладные со сканера. Т.е. некий сервис распознавания возвращает извлеченные данные + картинку. Платформа позволяет делать такие доработки?

              Да, конечно позволяет. Нам доводилось туда вставлять взаимодействие практически со всеми типами устройств (COM-сканера, весы, фискальные регистраторы, платежные терминалы), так и с кучей различных сервисов (электронные накладные, отправка SMS и прочее). Файлы мы храним прямо в базе данных (в виде byte array). Соответственно, с ними удобная и прозрачная работа.
          0
          Посмотрел онлайн демо.
          Наймите дизайнера, возможно, у вас гениальная система, но выглядит она ужасно.
            0
            В первых версиях мы сделали все достаточно красиво. Но потом от всего пришлось отказаться в пользу самого минимального DOM'а по двум причинам:
            • Все работало достаточно медленно. Нагруженный DOM с кучей дополнительных элементов для дизайна очень сильно сказывался на отклике системы. Когда мы начали делать формы, в которых много таблиц с 30 колонками в каждой и кучей рядов, то все становилось совсем печально. Посмотрите, например, демо 1С. Да, там относительно красиво, но я бы в такой системе работал с трудом, так как там бывают секунды отклика системы на простые действия. И это на простых формах, если там построить формы с десятком таблиц, то это вообще будет работать очень плохо. У нас есть клиенты, которые одновременно работают с веб-мордой 1С и нашей, и по их отзывам у нас работать гораздо удобнее именно из-за скорости. Не забывайте, что мы делаем B2B приложения, где пользователями являются сотрудники. Для бизнеса важно, чтобы они быстро выполняли свои процессы, а не любовались дизайном.
            • На формы помещалось мало информации. Если взять классический material design, то 90% форм нарисованных в том же ERP-приложении просто не поместятся на стандартный монитор. Да, их можно прятать за всякими tab'ами, dialog'ами и прочими элементами. Но на практике пользователю для принятия решения часто нужно видеть всю информацию сразу, а не судорожно работать мышкой. В итоге, мы сознательно максимально скрутили все отступы на минимум и убрали все ненужные элементы, чтобы как можно было как можно больше впихнуть на одну форму. Кроме того, у нас и десктоп-клиент, который может отображать те же самые формы. Так вот выглядеть они должны более менее одинаково, что также накладывает свои ограничения на дизайн.

            Но в целом посмотрите на GUI основных игроков бизнес-приложений. Поймете, что дизайн там не главное, так как решения о выборе системы, как правило, принимают люди, которые не будут в ней работать. Им главное эффективность работы сотрудников.
              –1
              Честно говоря не понимаю, как может замедлить, если вы перерисуете иконки, ну и новые добавите. На 1С не смотрите, смотрите на, ну например, Навижин. Таблицы с 30 колонками — это само по себе плохо, это нечитабельно, просто само по себе, слишком много информации.
              Для бизнеса важно, чтобы они быстро выполняли свои процессы, а не любовались дизайном.
              Вот с таким подходом совсем все плохо, софт вы делаете для Людей, а не роботов, вы обязаны думать об эстетике.
              2. Я понял почему вы так сделали, вашу логику, но мне кажется она не верной.
              Чисто мое мнение.
                0
                Честно говоря не понимаю, как может замедлить, если вы перерисуете иконки, ну и новые добавите. На 1С не смотрите, смотрите на, ну например, Навижин. Таблицы с 30 колонками — это само по себе плохо, это нечитабельно, просто само по себе, слишком много информации.

                Вы видели когда-нибудь рабочее место торгового брокера или диспетчера в аэропорту? У них все на одном экране (более того, у них много мониторов). И все это нужно, чтобы оперативно принимать решения.
                Возьмите, например, форму ручного заказа на закупку (Закупка / Заказы / Редактировать заказ и там вкладка Подбор). Вся эта информация была добавлена по просьбам пользователей, так как им нужно видеть и динамику, остатки, продажу и резервы без каких-либо дополнительных нажатий. Может покажете в Navision как выглядит эта форма?
                Вот с таким подходом совсем все плохо, софт вы делаете для Людей, а не роботов, вы обязаны думать об эстетике.
                2. Я понял почему вы так сделали, вашу логику, но мне кажется она не верной.
                Чисто мое мнение.

                Just business. Ничего личного. Люди в любом случае привыкают и затем уже не замечают особо дизайн. А вот скорость работы остается прежней. Лично меня гораздо больше бесят тормознутые системы, чем некрасивые.
                  0
                  Да, конечно.
                  вот
                  image

                    +1

                    Как-то не похоже на идеальный интерфейс. И даже на просто хороший — тоже не похоже...

                      0
                      Дизайн конечно получше, но если честно тоже не супер. Но на вкус и цвет, как известно, все фломастеры разные.

                      Но это не веб-интерфейс. А можете скинуть, как он в браузере выглядит?
                      Что сразу мы получили бы от пользователей с таким интерфейсом, что все «слишком мелко и у меня глаза болят». Можете сделать, что-то вроде CTRL+ (как в браузере), чтобы посмотреть как будет выглядеть, если шрифты побольше?

                      Второй момент: у вас видно на скрине только 2 записи. При ручном заказе процесс немного другой. У вас есть, допустим, 50 позиций, которые можно заказать. По каждой из них я должен видеть продажи, остатки и прочее в колонки, чтобы не приходилось переходить на каждую запись и смотреть, нужно ли делать заказ. Как в этом интерфейсе это сделать, если видны только 2 записи? И собственно как посмотреть «доступные для заказа» 50 записей, чтобы потом набрать в него 10 строк, которые я буду заказывать?

                      Вот пример подбора из доступных «50 записей»:
                      Подбор
                      image

                      Вот пример подобранных 3х записей:
                      Строки
                      image
                        0
                        Сейчас сделать из веба не могу, может чуть позже.Что касается позиций — тут вы отчасти правы, я бы этой форме выделил больше места, но записи естественно скролятся. Представление столбцов настраивается прям из клиента, можно убирать ненужные и добавлять нужные столбцы. Работа с большим-кол-вом (я для себя) представляю как выгрузку из Эксель. Откуда то мы знаем что хотим добавить или выгрузить, обычно это эксель, и тут все очень просто с этим.
                          0
                          Представление столбцов настраивается прям из клиента, можно убирать ненужные и добавлять нужные столбцы

                          К слову, такая возможность есть во всех современных ERP-платформах.

                          Работа с большим-кол-вом (я для себя) представляю как выгрузку из Эксель. Откуда то мы знаем что хотим добавить или выгрузить, обычно это эксель, и тут все очень просто с этим.


                          Не очень понимаю, причем здесь Excel. Конечно, можно использовать Excel как GUI-клиент все время выгружая/загружая в/из него. Но это криво. Смотрите, вот например use case:
                          Звонит менеджеру по продажам клиент и просит оформить заказ на продажу. У вас есть набор товаров, сформированных по какому-то принципу, который менеджер по продажам может ему продать. Клиент постоянно спрашивает, что есть в наличии и цены и просит сразу же зарезервировать ему этот товар (то есть создать строки в заказе на продажу). Как это делать в Navision?
                          В нашем случае все просто: есть вкладка Подбор с доступным товаром, и там есть колонка, куда можно вводить то, что просит клиент. На основании этой колонки тут же создаются строки заказа. В обратную сторону также работает (если меняются строки, то и колонка в подборе изменяется).
                            0
                            что есть в наличии и цены и просит сразу же зарезервировать ему этот товар (то есть создать строки в заказе на продажу). Как это делать в Navision?

                            Я правильно понимаю что клиенту все равно что, лишь бы было?
                            Как то странно.
                            Если клиент что то конкретно хочет, вы начинаете вводить это название, навижин подтягивает каталог, вы выбираете — сразу показывается какой остаток на складе.

                            Наша специфика другая, клиенту нужно 100 различных товаров, артикулы и кол-во известно. Как он это сообщит, естественно пришлет список в экселе, который очень удобно сразу загнать в заказ.
                              0
                              Я правильно понимаю что клиенту все равно что, лишь бы было?
                              Как то странно.
                              Если клиент что то конкретно хочет, вы начинаете вводить это название, навижин подтягивает каталог, вы выбираете — сразу показывается какой остаток на складе.

                              Это не странно, если клиенту важно получить что-то и точно знать, что он это получит. В многих индустриях есть большое количество аналогичных товаров и клиенту, в принципе, все равно какой из них получить (точнее лучше получить хоть что-то, чем конкретные позиции). Кроме того, часто клиент не знает весь ассортимент и ему требуется помощь менеджера, чтобы он подсказал ему.

                              Наша специфика другая, клиенту нужно 100 различных товаров, артикулы и кол-во известно. Как он это сообщит, естественно пришлет список в экселе, который очень удобно сразу загнать в заказ.

                              Это совершенно другой use case, и он часто бывает если поставки регулярные, а один и тот же товар практически всегда есть в наличии.

                              Вообще логика подбора реализована в том же 1С (если зайти в их демо, там редактировать документ, а потом Заполнить и Подбор товара). Так что она востребована судя по всему. Но реализована она как-то криво. Ввести кол-во в колонку нельзя, нужно сначала добавить вниз, а потом там уже менять количество. Если закрыть подбор и открыть, то уже введенные позиции не показываются (то есть редактировать неудобно).
              –1
              молодцы, изобрели разделение утилит низкого уровня и обьектной модели, что должно быть в любом нормальном проэкте. А вы задумывались что обьекнтая модель может задаваться графически или вообще выводиться методами машинного обучения из обратной связи клиетнов?
                +1
                молодцы, изобрели разделение утилит низкого уровня и обьектной модели, что должно быть в любом нормальном проэкте

                Мы ничего не изобретали, а реализовали наиболее логичным образом.

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

                Холивару на тему графического программирования много лет. Не стоит устраивать битву снова. С графическим программированием подумайте сначала про систему контроля версий и merge коммитов.

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

                В идеальном мире, вообще программисты не нужны, и все должен делать AI. Но мы, к сожалению, живем в другом мире.
                0
                del
                  0
                  … объявляем новые свойства, под которые будут созданы дополнительные поля в таблицах, и добавляем их на форму:
                  discount 'Скидка, %' = DATA NUMERIC[5,2] (OrderDetail);
                  discountPrice 'Цена со скидкой' = DATA NUMERIC[14,2] (OrderDetail);

                  EXTEND FORM order
                  PROPERTIES(d) AFTER price(d) discount, discountPrice READONLY
                  ;


                  Цену со скидкой делаем недоступной для записи. Она будет рассчитываться отдельным событием при изменении либо исходной цены, либо процента скидки:
                  WHEN LOCAL CHANGED(price(OrderDetail d)) OR CHANGED(discount(d)) DO
                  discountPrice(d) <- price(d) * (100 (-) discount(d)) / 100;

                  Мне вот что непонятно. Если "цена со скидкой" недоступна для записи, то почему это вообще хранящееся в БД поле, которое потом сделали "только для чтения" на форме (что позволяет любому коду туда записать, минуя бизнес-правила) и поверх навесили еще и событие, которое это поле обновляет? Почему нельзя просто сделать это поле вычислимым, минуя все эти прыжки?

                    0
                    Хороший вопрос. Мне его задал один из разработчиков платформы, который непосредственно логику не пишет. Кстати, READONLY оно именно на этой форме. На другой оно может быть редактируемой.

                    Мы так обычно делаем, когда может измениться формула расчета со временем. Если сделать, его вычислимым, а потом потребуется изменить формулу, то нужно, чтобы старые заказы считали по старому. А новые, соответственно, по новому. В этом случае, мы просто изменим событие, и все будет работать. Если же делать через вычисляемое свойство, то нужно будет менять формулу типа IF date >… THEN expr1 ELSE expr2. А если изменений будет очень много, то все это превратится в огромный CASE WHEN THEN, что будет не очень удобно. Ну и опять же, заказчик может захотеть вводить цену со скидкой вручную.
                      0

                      Можно ли сделать поле, которое невозможно присвоить напрямую, минуя бизнес-правила?

                        0
                        Не очень понятно с логической точки зрения, как отличить присвоение напрямую от присвоения не напрямую.
                        Как вариант, можно просто добавить CONSTRAINT, что discountPrice не равен формуле. Тогда при применении в базу всегда будет ошибка, если значение не соответствует.
                          0
                          Не очень понятно с логической точки зрения, как отличить присвоение напрямую от присвоения не напрямую.

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


                          Интересно, кстати, а можно сделать WHEN CHANGED поверх вычисляемого поля?

                            0
                            Интересно, кстати, а можно сделать WHEN CHANGED поверх вычисляемого поля?

                            Да, конечно можно — в CHANGED может быть любое выражение. Мы такую технику и используем, когда в базовой версии есть первичное свойство, а его нужно сделать «вычисляемым»: WHEN CHANGED(expr) DO f < — expr;

                  Only users with full accounts can post comments. Log in, please.