Devlly

get in touch

Devlly - студія розробки. Автоматизуємо бізнес: від Telegram-бота до повноцінної CRM/ERP-системи.

Розсилка

  • Кейс
  • Фінанси та аналітика

MarginTracker - звіт по маржі з трьох CRM і Нової Пошти

MarginTracker - програма для Windows, яка раз на місяць робить те, на що раніше йшли робочі дні. Вона збирає замовлення з двох CRM-систем, у які падають заявки з кількох інтернет-магазинів, підставляє собівартість товарів, витягує з Нової Пошти вартість доставки по кожному поверненню і записує готовий звіт у Google Таблицю - рядок на кожен сайт плюс підсумковий «РАЗОМ».

Кому підходить: товарному бізнесу з кількома магазинами, у якого замовлення живуть у різних CRM, частина посилок повертається, а маржу досі зводять в Excel руками. Чим більше замовлень за місяць, тим дорожча ручна робота і тим дешевше обходиться помилка, якої не сталося.

Стек: Python 3, CustomTkinter для інтерфейсу, requests для роботи з API, openpyxl для локального сховища собівартості, gspread і google-auth для запису в таблицю. Збірка в один exe через PyInstaller - у замовника на машині нічого ставити не треба.

  • Python 3
  • CustomTkinter
  • requests
  • openpyxl
  • gspread
  • PyInstaller

Яка була задача

Замовник - товарний бізнес із кількома інтернет-магазинами. Заявки з лендінгів падають у дві різні CRM-системи, товар їде Новою Поштою, частина посилок повертається. Щоб зрозуміти, скільки реально заробили за місяць, звіт зводили руками: вивантажували замовлення з кожної CRM окремо, зліплювали в Excel, підставляли собівартість і окремо рахували, скільки зʼїли повернення. На кілька тисяч замовлень це займало робочі дні, і кожен перенос цифри був шансом на помилку.

Складність тут не в арифметиці. Вона в тому, що дані треба зібрати з трьох різних API з різними обмеженнями, звести докупи різні написання одного й того ж товару та сайту - і дістати вартість зворотної доставки, якої немає ні в CRM, ні в жодному звіті перевізника.

Як це працює

Програма веде користувача трьома кроками, і перейти далі, не закривши поточний, не можна. Збір - собівартість - звіт. Усе локально, у вікні на робочому столі: один exe, зібраний PyInstaller, нічого встановлювати не треба.

Усі цифри, сайти, товари й номери на знімках нижче - вигадані. Це демо-режим програми, у якому можна пройти всі три кроки без жодного ключа API: інтерфейс і розрахунки справжні, дані згенеровані спеціально для портфоліо і не перетинаються з даними замовника.

1. Збір замовлень

Користувач обирає період і тисне «Зібрати». Програма по черзі опитує джерела і показує, що саме зараз робить. LP-CRM віддає дані в три заходи: спочатку довідник статусів, потім по кожному статусу список номерів, потім самі замовлення пачками по сто. У SalesDrive жорсткі ліміти - порядку сотні запитів на годину, - тому збір іде з паузами і контролем залишку квоти, а назви кастомних полів мапляться через конфіг, бо в кожного акаунта вони свої.

Програма для звіту по маржі - крок збору замовлень з CRM, прогрес і журнал виконання
Крок 1 під час збору: обраний період, прогрес і журнал по кожному джерелу. Поки триває збір, навігація заблокована - на наступний крок не можна піти з напівзібраними даними.
Підсумок збору замовлень: кількість замовлень по кожному акаунту CRM, сайтів і унікальних товарів
Той самий екран після збору: скільки замовлень віддало кожне джерело окремо - два акаунти LP-CRM і SalesDrive - плюс кількість сайтів і унікальних товарів за період.

2. Собівартість товарів

Другий крок - редагована таблиця всіх товарів періоду. Те, що заповнювали минулого місяця, підставляється саме; нові позиції підсвічені жовтим, щоб їх не можна було пропустити. Введені ціни лягають у локальний xlsx і живуть між запусками, а запис у файли атомарний: обрив на півдорозі не псує сховище.

Таблиця собівартості товарів: заповнені позиції з минулих місяців і нові підсвічені жовтим
Крок 2: редагована таблиця собівартості. Товари, які вже заповнювали раніше, підтягуються самі, нові підсвічені жовтим. Є пошук, фільтр «лише без собівартості» і лічильник незаповнених у правому куті.
Кнопка «Колізії: 2 потребують уваги» на екрані собівартості
Той самий екран з кнопкою «Колізії: 2 потребують уваги». Вона зʼявляється тоді, коли CRM віддала під одним номером замовлення не повністю - про це нижче.

3. Звіт

На третьому кроці програма рахує метрики, підтягує вартість доставки по поверненнях і показує точний вміст, який піде в таблицю. Рядок на кожен сайт і підсумковий «РАЗОМ»: кількість заявок і забраних, виручка, собівартість, маржа допродаж, маржа основна, кількість повернень і доставка по них, а також скільки ТТН не пораховано і скільки товарів лишилось без собівартості.

Маржа розділена на дві навмисно. Основний товар продає реклама, допродаж продає оператор - для бізнесу це різні гроші, і дивляться на них окремо. Статуси замовлень зводяться у три групи через конфіг: успішні йдуть у виручку, повернення рахуються окремо, решта свідомо не враховується. Статус, якого немає в жодній групі, нічого не ламає: програма дорахує звіт і покаже список нових статусів окремим зауваженням.

Запис у Google Таблицю йде через службовий акаунт і пакетно - кілька запитів на весь звіт, а не рядок за рядком, інакше впираєшся в квоту API. Таблицю можна щомісяця створювати нову або дописувати в наявну.

Порахований фінансовий звіт: виручка, собівартість, розділена маржа і доставка по поверненнях
Крок 3: звіт порахований. Виручка, собівартість, маржа окремо по основному товару і по допродажах, кількість повернень і те, скільки зʼїла доставка по них. Нижче - зауваження, які програма знайшла сама.
Попередній перегляд вмісту Google Таблиці: рядок на кожен сайт і підсумковий «РАЗОМ»
Той самий екран, прокручений донизу: попередження про подвійний облік ТТН і точний вміст, який піде в Google Таблицю - рядок на кожен сайт і підсумковий «РАЗОМ» з усіма колонками звіту.

Дві задачі, яких немає в документації API

Найцінніше в цьому проєкті - не інтерфейс. Це дві речі, на які немає ні методу в API, ні рядка в документації, і які довелось розбирати з нуля.

Зникаючі замовлення: колізії order_id

Симптом: клієнт стверджував, що в звіті щомісяця бракує кількох замовлень. Розбір логів показав, що API CRM справді віддає менше записів, ніж у нього просили.

Причина виявилась у генераторі номерів на лендінгу: номер складається з часу з точністю до десятої секунди. Два замовлення, оформлені в ту саму десяту секунди, отримують однаковий номер, і API віддає під ним лише одне. Гірше того - у пачці зі ста номерів таке замовлення детерміновано «зʼїдає» ще один запис, найбільший номер у пачці, через те як у CRM стоїть обмеження вибірки після зʼєднання таблиць.

Що зроблено. «Зʼїдений» запис відловлюється повторним запитом меншими пачками. Для справжніх дублів зроблено ручний етап: програма сама визначає, скільки замовлень живе під одним номером, і показує вікно, де менеджер вписує те, чого API не віддало. Вписане зберігається назавжди і підставляється в наступних прогонах - але лише якщо колізія на місці, статус зниклого замовлення збігається і API віддає те саме замовлення, що й під час заповнення. Якщо хоч щось із цього змінилось, запис відкладається і показується менеджеру знову, замість того щоб тихо підставити стару цифру. Причину передали клієнту: на частині лендінгів генератор номера вже містить випадковий суфікс, і колізій там рівно нуль.

Ручний етап для колізій order_id: версії замовлень під одним номером і форма введення
Ручний етап для колізій. Зліва по кожному номеру видно, що саме віддало API, і чого бракує. Справа - картка вже отриманого замовлення і форма, у якій поля змінюються залежно від обраного статусу: для продажу це сайт, товар, кількість і сума.

Вартість зворотної доставки

У CRM цієї суми немає взагалі, а перевізник не віддає її жодним окремим методом. Перевірили три місця: заявки на повернення, список власних накладних і калькулятор ціни. У перших двох поле вартості порожнє, третій не приймає номер накладної.

Робоча схема виявилась такою: вартість лежить у картці накладної, а повна сума повернення - це пряме плече плюс зворотне плюс платне зберігання на відділенні, яке нараховується після семи безкоштовних днів. Зберігання в кабінеті перевізника показується на обох накладних, тому рахувати його треба один раз на посилку, інакше сума подвоюється. Схему звірили по контрольній вибірці накладних з точністю до копійки.

Програма не вдає, що дані ідеальні

Перед записом звіту програма показує те, що може зробити цифри неточними: товари без собівартості, замовлення без товарів, повернення без ТТН або з ТТН, якої немає в перевізника, один і той самий ТТН у двох замовленнях, нові статуси, незаповнені колізії. Розрахунок при цьому не зупиняється - можна подивитись приклади, прийняти як є або повернутись і виправити.

Сенс простий: звіт на кілька тисяч замовлень або правильний, або нічого не вартий. Тому користувач має бачити, де саме цифра може бути кривою, а не отримувати мовчазне «порахувалось».

Вікно зауважень до звіту: товари без собівартості й необроблені колізії з прикладами
Розбір проблем перед записом. Розрахунок не зупиняється - програма показує, що саме може зробити цифри неточними, з конкретними прикладами, і дає вибір: прийняти як є або повернутись і виправити.

Ключова логіка

  • → Ручні записи по колізіях перевіряються щоразу заново: змінився статус або відповідь API - запис відкладається і показується людині, а не підставляється тихо
  • → Платне зберігання на відділенні рахується один раз на посилку, хоча перевізник показує його на обох накладних - інакше сума повернення подвоюється
  • → Маржа розділена на основну й допродажну: їх створюють різні люди, тож і дивляться на них окремо
  • → Невідомий статус замовлення не ламає розрахунок - звіт дораховується, а список нових статусів іде в зауваження
  • → Запис у Google Таблицю пакетний: кілька запитів на весь звіт замість рядка за рядком, інакше квота API
  • → Атомарний запис локальних файлів: обрив посеред збереження не псує сховище собівартості й ручних записів
  • → Помилки API доходять до користувача людською мовою, а не трейсбеком, і запит повторюється з паузою

Що вміє програма

  • → Збір замовлень за період з двох CRM-систем - двох акаунтів LP-CRM і SalesDrive - в одному проході
  • → Дотримання лімітів запитів з паузами й контролем залишку квоти
  • → Мапінг кастомних полів замовлення через конфіг - під назви конкретного акаунта
  • → Редагована таблиця собівартості з пошуком, фільтром незаповнених і підсвіткою нових товарів
  • → Перенесення собівартості між місяцями: заповнене раніше підставляється автоматично
  • → Ручний етап для колізій order_id з перевіркою записів при кожному наступному прогоні
  • → Розрахунок вартості доставки по кожному поверненню з накладних Нової Пошти
  • → Фінансовий звіт по кожному сайту й підсумковий, з розділеною маржею та повну статистику повернень
  • → Зауваження до даних перед записом - з прикладами й вибором «прийняти як є» чи виправити
  • → Запис у Google Таблицю з форматуванням: нова таблиця щомісяця або дозапис у наявну
  • → Демо-режим: усі три кроки проходяться на згенерованих даних без жодного ключа API
  • → Власний набір самоперевірок на 354 перевірки, що проганяється однією командою і ловить регресії в розрахунках, мапінгу статусів і логіці ручних записів

Зводите звіт руками? Розкажіть про свої дані - подивимось, що можна автоматизувати