1. Чому роздрібний реплениш — хороший стрес-тест для ERP

ERP-демо красиві. Реальні операції — ні.

Роздрібне поповнення запасів — один із перших процесів, де ERP-системи перестають бути «формами і документами» і стають рушіями прийняття рішень. Він змушує платформу обробляти:

  • дані продажів у часі
  • коригування прогнозу (сезонність, промо)
  • обмеження постачальників (lead time, MOQ, кратність пакування)
  • логіку кількох складів
  • автоматичну генерацію документів
  • процеси узгодження людьми
  • пояснюваність рішень системи

Це робить його ідеальним кейсом для порівняння ERPNext і MyCompany.

Мета цієї статті — не маркетинг. Мета — дати відповідь на практичне питання рівня CTO:

Скільки інженерних зусиль потрібно для реалізації реального роздрібного процесу?

І так — ми будемо рахувати рядки коду.

Схема процесу роздрібного поповнення запасів

2. Бізнес-специфікація (практична, не вигадана)

Сценарій

  • 15 роздрібних магазинів
  • 1 центральний склад
  • 4 основні постачальники
  • щоденні продажі через POS
  • щотижневі промоакції
  • змішана стратегія: трансфер / закупівля

Вимоги

Щоночі (02:00):

Для кожного SKU у кожному магазині:

  1. Розрахувати середні щоденні продажі (останні 28 днів)
  2. Скоригувати на промо-коефіцієнт (якщо активний)
  3. Помножити на lead time постачальника
  4. Відняти:
    • поточний залишок
    • вхідні замовлення постачальнику
    • вхідні трансфери
  5. Застосувати:
    • MOQ (мінімальна кількість замовлення)
    • округлення до кратності пакування
  6. За наявності дефіциту:
    • обрати трансфер із центрального складу (якщо є)
    • інакше запропонувати замовлення постачальнику
  7. Створити чернетки документів
  8. Записати пояснення (чому саме така кількість)
  9. Надати UI-екран:
    • перегляд пропозицій
    • затвердження
    • відхилення
    • перерахунок

Приклад пояснюваності:

«Розрахункова потреба: 94 од. (ADS 4.7 × 14 днів − 32 залишок − 40 вхідних). Округлено до 96 через пакування по 12».

3. Реалізація на ERPNext

ERPNext побудований на фреймворку Frappe (Python-бекенд, JavaScript-фронтенд).

Платформа вже підтримує:

  • залишки
  • рівні перезамовлення
  • матеріальні заявки (Material Requests)
  • замовлення постачальникам (Purchase Orders)
  • трансфери

Але щойно додаються промо, правила для кількох магазинів, MOQ і кратність пакування, логіка «купувати чи перемістити» та пояснюваність — вбудований реордер швидко закінчується.

3.1 Архітектурний підхід

Типова реалізація:

  • кастомний DocType: Replenishment Rule
  • завдання за розкладом на Python
  • кастомний звіт на серверній стороні
  • клієнтські дії в UI
  • хуки для автоматизації та зв'язки модулів

3.2 Розширення моделі даних

{
              "doctype": "Replenishment Rule",
              "fields": [
                { "fieldname": "item", "fieldtype": "Link", "options": "Item" },
                { "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse" },
                { "fieldname": "lead_time_days", "fieldtype": "Int" },
                { "fieldname": "promotion_factor", "fieldtype": "Float" },
                { "fieldname": "moq", "fieldtype": "Int" },
                { "fieldname": "pack_size", "fieldtype": "Int" }
              ]
            }

Типовий обсяг: ~120–180 рядків коду разом із метаданими.

3.3 Завдання за розкладом (основна логіка)

# app/replenishment/scheduler.py

            import frappe


            def nightly_replenishment():
                rules = frappe.get_all("Replenishment Rule", fields="*")

                for rule in rules:
                    avg_sales = calculate_average_sales(rule.item, rule.warehouse)
                    adjusted_sales = avg_sales * (rule.promotion_factor or 1)

                    required_qty = adjusted_sales * rule.lead_time_days

                    current_stock = get_stock(rule.item, rule.warehouse)
                    incoming = get_incoming(rule.item, rule.warehouse)

                    deficit = required_qty - current_stock - incoming

                    if deficit > 0:
                        rounded_qty = round_to_pack(deficit, rule.pack_size, rule.moq)
                        create_suggestion(rule, rounded_qty)
            

Допоміжні функції (запити залишків, SQL-джоїни, звіти, округлення, створення документів, ідемпотентність, логування) додають ще ~180–300 рядків.

3.4 Звіт про пропозиції

SELECT
                item,
                warehouse,
                suggested_qty,
                explanation
            FROM `tabReplenishment Suggestion`
            WHERE status = 'Draft';
            

Типовий обсяг: ~70–120 рядків.

3.5 Клієнтська дія «затвердити»

frappe.ui.form.on("Replenishment Suggestion", {
                approve(frm) {
                    frappe.call({
                        method: "app.replenishment.approve",
                        args: { name: frm.doc.name },
                        callback() {
                            frm.reload_doc();
                        }
                    });
                }
            });
            

Типовий обсяг: ~40–80 рядків.

3.6 Підсумок за обсягом коду ERPNext

КомпонентLOC (приблизно)
Основна логіка Python250–400
Звіти70–120
Клієнтський JS40–80
JSON-метадані120–200
Усього (LOC у репозиторії)480–800

Лише бізнес-логіка (LOC): ~320–600


4. Реалізація на MyCompany

MyCompany побудована на lsFusion — декларативній платформі бізнес-логіки.

Замість процедурних завдань ви описуєте:

  • властивості даних
  • обчислювані вирази
  • дії
  • форми

4.1 Визначення даних

CLASS Item;
            CLASS Warehouse;
            CLASS Rule;

            item            = DATA Item (Rule);
            warehouse       = DATA Warehouse (Rule);
            leadTime        = DATA INTEGER (Rule);
            promotionFactor = DATA NUMERIC[10,2] (Rule);
            moq             = DATA INTEGER (Rule);
            packSize        = DATA INTEGER (Rule);
            

4.2 Обчислювані властивості

avgSales(Item i, Warehouse w) =
                SUM quantity(Sale s)
                WHERE s.item = i
                  AND s.warehouse = w
                  AND s.date >= currentDate() - 28
                / 28;

            requiredQty(Rule r) =
                avgSales(item(r), warehouse(r))
                * promotionFactor(r)
                * leadTime(r);

            deficit(Rule r) =
                requiredQty(r)
                - currentStock(item(r), warehouse(r))
                - incoming(item(r), warehouse(r));
            

4.3 Логіка округлення

roundedQty(Rule r) =
                MAX(
                    moq(r),
                    CEIL(deficit(r) / packSize(r)) * packSize(r)
                )
                IF deficit(r) > 0;
            

4.4 Дія

generateSuggestions() {
                FOR r IN Rule DO {
                    IF deficit(r) > 0 THEN
                        NEW Suggestion {
                            rule        = r;
                            quantity    = roundedQty(r);
                            explanation =
                                "ADS × LT – stock – incoming = " + deficit(r);
                        };
                }
            }
            

4.5 UI

FORM suggestions
                OBJECTS s = Suggestion
                PROPERTIES s.rule, s.quantity, s.explanation;
            

4.6 Підсумок за обсягом коду MyCompany

КомпонентLOC (приблизно)
Визначення даних~25
Бізнес-логіка (властивості)~60–90
Округлення~10
Дії~30–40
UI~20–30
Загалом~145–195

5. Порівняння поруч

МетрикаERPNextMyCompany
Парадигма логікиПроцедурна (Python + зв'язки)Декларативна (властивості + дії)
Планування завданьЯвне завдання за розкладом + зв'язкиМодель, орієнтована на дії (зазвичай менше зв'язок)
Збірка UIЧасто потребує клієнтських скриптівДекларативні форми
LOC (лише логіка)~320–600~100–150
LOC (увесь репозиторій)~480–800~145–200

6. Архітектурні наслідки

Розширення ERPNext часто розмазують логіку між Python, JavaScript і метаданими. MyCompany, як правило, централізує бізнес-правила як декларативну модель.

Більше рядків коду зазвичай означає:

  • вище когнітивне навантаження
  • більше інтеграційних «зв'язок»
  • більше поверхні для регресій

Це автоматично не робить одну платформу «кращою». Це говорить про те, яку ціну змін ви купуєте.


7. Економіка розробки з ШІ

Є й сучасний вимір, який не можна ігнорувати: обсяг і структура коду впливають на те, наскільки ефективно ШІ-інструменти допомагають, рефакторять і генерують розширення.

  • Вартість контексту: більше файлів і шарів-зв'язок зазвичай збільшують обсяг контексту для коректної допомоги ШІ — і піднімають вартість інференсу.
  • Вартість верифікації: фрагментована процедурна логіка часто потребує більше тестової обв'язки і перевірок під час виконання.
  • Вартість рефакторингу: більше точок зв'язку ускладнює безпечний автоматичний рефакторинг (і для людей, і для ШІ).
  • Вартість фіксації знань: розрізнена логіка збільшує обсяг документації, зусилля на промптинг і час супервізії.

Інакше кажучи, LOC — це вже не лише проксі для вартості підтримки людьми. Це дедалі більше проксі для вартості еволюції з ШІ.


8. Коли яка платформа виграє

Обирайте ERPNext, якщо:

  • потрібен широкий ERP швидко (облік + закупівлі + залишки) і хочеться велику екосистему;
  • правила поповнення відносно стандартні і рідко змінюються;
  • надаєте перевагу Python і звичному для спільноти рішенню, а не парадигмі «спочатку модель».

Обирайте MyCompany, якщо:

  • конкурентна перевага — у кастомних бізнес-правилах та швидких ітераціях;
  • правила часто змінюються, і хочеться, щоб система залишалася пояснюваною;
  • хочеться ERP як конструктор: модель, яку ви розвиваєте, а не продукт, який ви патчите.

9. Висновок

Підрахунок рядків коду — не все. Але у складних бізнес-процесах LOC корелює з когнітивним навантаженням, а воно — з вартістю довгострокової підтримки.

У цьому сценарії поповнення:

  • ERPNext зазвичай вимагає ~у 2–4 рази більше кастомного коду;
  • MyCompany часто виражає ті самі правила на меншій і більш централізованій поверхні логіки.

Додаток A. Симуляція git diff (як чесно це виміряти)

Якщо хочете, щоб порівняння «скільки рядків» було захищеним, вимірюйте його реальним diff репозиторію і LOC-інструментом (наприклад, cloc) із прозорими правилами включення. Нижче — симульований, але структурно реалістичний приклад.

ERPNext (застосунок Frappe) — симульований diff

$ git diff --stat

              app/replenishment/hooks.py                                              |  34 +++++++
              app/replenishment/scheduler.py                                          | 140 +++++++++++++++++++++
              app/replenishment/api.py                                                |  88 +++++++++++++
              app/replenishment/utils/sales.py                                        |  74 ++++++++++++
              app/replenishment/utils/stock.py                                        |  92 +++++++++++++++
              app/replenishment/utils/rounding.py                                     |  38 ++++++++
              app/replenishment/doctype/replenishment_rule/replenishment_rule.json    | 165 +++++++++++++++++++++++++
              app/replenishment/doctype/replenishment_suggestion/replenishment_suggestion.json | 142 +++++++++++++++++++++++
              app/replenishment/report/replenishment_suggestions/replenishment_suggestions.py |  76 +++++++++++++
              app/replenishment/report/replenishment_suggestions/replenishment_suggestions.js |  58 ++++++++++
              app/replenishment/public/js/replenishment_suggestion_form.js            |  62 +++++++++++
              12 files changed, 971 insertions(+)
              

Типова інтерпретація:

  • LOC лише логіки = scheduler + utils + API + Python звітів + мінімум JS (~320–600).
  • Загальний LOC репозиторію включає JSON-метадані (~480–900+ залежно від UI/фікстур звітів).

MyCompany (модуль lsFusion) — симульований diff

$ git diff --stat

              modules/replenishment/Replenishment.lsf        | 182 +++++++++++++++++++++++++++
              modules/replenishment/ReplenishmentForms.lsf   |  54 ++++++++
              modules/replenishment/ReplenishmentDocs.lsf    |  31 ++++
              3 files changed, 267 insertions(+)
              

Дисципліна, яка робить порівняння LOC осмисленими: одна й та сама специфікація, один і той самий метод вимірювання, прозорі правила включення.

Швидкий зворотний зв'язок

Було корисно?

Короткий сигнал допомагає нам обирати теми для наступних матеріалів.

Що ще почитати:

Якщо вас хвилюють практичні наслідки архітектури ERP та зменшення залежності від підрядників — більше матеріалів на DevLab Blog: