Цей матеріал зіштовхує ERPNext і MyCompany (lsFusion) на реальній задачі роздрібного реплениша — з кодом і чесною ціною зміни. Від першої публікації розробка за допомогою AI лише загострила суть: платформа, чиї правила компактні й декларативні, — це та, яку AI (і ваша власна команда) реально зможе підтримувати.
ERPNext vs MyCompany — коротко:
| Вимір | ERPNext | MyCompany (lsFusion) |
|---|---|---|
| Найкраще пасує | Процеси, близькі до стандартної моделі | Унікальні, змінювані процеси |
| Ціна зміни правила | Зростає, коли відходиш від моделі | Описано один раз, в одному місці |
| Коду на ту саму задачу | Більше «клею» між моделями й хуками | Менше — декларативно, один формалізм |
| Широта «з коробки» | Висока — готовий ERP | Нижча — будуєш на платформі |
| Виграє, коли… | Готовий стандартизувати | Важливі відповідність процесу й дешева зміна |
Решта — чесна довга версія: бізнес-специфікація, обидві реалізації з кодом, порівняння пліч-о-пліч і git-diff, який зміряєш сам. Пов’язане: ландшафт open-source ERP ↗ та чому архітектура визначає ціну зміни ↗.
1. Чому роздрібний реплениш — хороший стрес-тест для ERP
ERP-демо красиві. Реальні операції — ні.
Поповнення — це місце, де краса закінчується. Це один із перших процесів, де система перестає бути «формами і документами» і стає рушієм рішень — їй треба думати, а не просто зберігати. Щоб робити це по-справжньому, платформі доводиться жонглювати:
- продажами як часовим рядом, а не одним числом
- прогнозами, що гнуться під сезонність і акції
- реальністю постачальника: термін постачання, MOQ, кратність пакування
- кількома складами
- документами, що формуються самі
- людиною, яка все ще може сказати «не в цього постачальника цього тижня»
- і відповіддю на питання «чому система замовила саме це?»
Саме цей набір робить порівняння чесним. Рахунок-фактуру змоделює будь-хто. Поповнення змушує обидві платформи показати, як вони дають раду правилам, що постійно змінюються.
Це не маркетинговий текст. Він відповідає на єдине питання, яке CTO ставить перед тим, як зважитися:
Скільки зусиль потрібно, щоб побудувати реальний роздрібний процес — і продовжувати його змінювати?
Тому ми порахували рядки коду. В обох випадках.
2. Бізнес-специфікація (практична, не вигадана)
Сценарій
- 15 роздрібних магазинів
- 1 центральний склад
- 4 основні постачальники
- щоденні продажі через POS
- щотижневі промоакції
- змішана стратегія: трансфер / закупівля
Вимоги
Щоночі (02:00):
Для кожного SKU у кожному магазині:
- Розрахувати середні щоденні продажі (останні 28 днів)
- Скоригувати на промо-коефіцієнт (якщо активний)
- Помножити на lead time постачальника
- Відняти:
- поточний залишок
- вхідні замовлення постачальнику
- вхідні трансфери
- Застосувати:
- MOQ (мінімальна кількість замовлення)
- округлення до кратності пакування
- За наявності дефіциту:
- обрати трансфер із центрального складу (якщо є)
- інакше запропонувати замовлення постачальнику
- Створити чернетки документів
- Записати пояснення (чому саме така кількість)
- Надати 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 (приблизно) |
|---|---|
| Основна логіка Python | 250–400 |
| Звіти | 70–120 |
| Клієнтський JS | 40–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. Порівняння поруч
| Метрика | ERPNext | MyCompany |
|---|---|---|
| Парадигма логіки | Процедурна (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. Висновок
Рядки коду — не вся правда. Але на такому зубатому процесі вони відображають когнітивне навантаження — а за нього ти платиш місяць за місяцем, ще довго після того, як ліцензія виявилася безкоштовною.
У цьому сценарії поповнення:
- 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: