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 жорсткі ліміти - порядку сотні запитів на годину, - тому збір іде з паузами і контролем залишку квоти, а назви кастомних полів мапляться через конфіг, бо в кожного акаунта вони свої.
2. Собівартість товарів
Другий крок - редагована таблиця всіх товарів періоду. Те, що заповнювали минулого місяця, підставляється саме; нові позиції підсвічені жовтим, щоб їх не можна було пропустити. Введені ціни лягають у локальний xlsx і живуть між запусками, а запис у файли атомарний: обрив на півдорозі не псує сховище.
3. Звіт
На третьому кроці програма рахує метрики, підтягує вартість доставки по поверненнях і показує точний вміст, який піде в таблицю. Рядок на кожен сайт і підсумковий «РАЗОМ»: кількість заявок і забраних, виручка, собівартість, маржа допродаж, маржа основна, кількість повернень і доставка по них, а також скільки ТТН не пораховано і скільки товарів лишилось без собівартості.
Маржа розділена на дві навмисно. Основний товар продає реклама, допродаж продає оператор - для бізнесу це різні гроші, і дивляться на них окремо. Статуси замовлень зводяться у три групи через конфіг: успішні йдуть у виручку, повернення рахуються окремо, решта свідомо не враховується. Статус, якого немає в жодній групі, нічого не ламає: програма дорахує звіт і покаже список нових статусів окремим зауваженням.
Запис у Google Таблицю йде через службовий акаунт і пакетно - кілька запитів на весь звіт, а не рядок за рядком, інакше впираєшся в квоту API. Таблицю можна щомісяця створювати нову або дописувати в наявну.
Дві задачі, яких немає в документації API
Найцінніше в цьому проєкті - не інтерфейс. Це дві речі, на які немає ні методу в API, ні рядка в документації, і які довелось розбирати з нуля.
Зникаючі замовлення: колізії order_id
Симптом: клієнт стверджував, що в звіті щомісяця бракує кількох замовлень. Розбір логів показав, що API CRM справді віддає менше записів, ніж у нього просили.
Причина виявилась у генераторі номерів на лендінгу: номер складається з часу з точністю до десятої секунди. Два замовлення, оформлені в ту саму десяту секунди, отримують однаковий номер, і API віддає під ним лише одне. Гірше того - у пачці зі ста номерів таке замовлення детерміновано «зʼїдає» ще один запис, найбільший номер у пачці, через те як у CRM стоїть обмеження вибірки після зʼєднання таблиць.
Що зроблено. «Зʼїдений» запис відловлюється повторним запитом меншими пачками. Для справжніх дублів зроблено ручний етап: програма сама визначає, скільки замовлень живе під одним номером, і показує вікно, де менеджер вписує те, чого API не віддало. Вписане зберігається назавжди і підставляється в наступних прогонах - але лише якщо колізія на місці, статус зниклого замовлення збігається і API віддає те саме замовлення, що й під час заповнення. Якщо хоч щось із цього змінилось, запис відкладається і показується менеджеру знову, замість того щоб тихо підставити стару цифру. Причину передали клієнту: на частині лендінгів генератор номера вже містить випадковий суфікс, і колізій там рівно нуль.
Вартість зворотної доставки
У CRM цієї суми немає взагалі, а перевізник не віддає її жодним окремим методом. Перевірили три місця: заявки на повернення, список власних накладних і калькулятор ціни. У перших двох поле вартості порожнє, третій не приймає номер накладної.
Робоча схема виявилась такою: вартість лежить у картці накладної, а повна сума повернення - це пряме плече плюс зворотне плюс платне зберігання на відділенні, яке нараховується після семи безкоштовних днів. Зберігання в кабінеті перевізника показується на обох накладних, тому рахувати його треба один раз на посилку, інакше сума подвоюється. Схему звірили по контрольній вибірці накладних з точністю до копійки.
Програма не вдає, що дані ідеальні
Перед записом звіту програма показує те, що може зробити цифри неточними: товари без собівартості, замовлення без товарів, повернення без ТТН або з ТТН, якої немає в перевізника, один і той самий ТТН у двох замовленнях, нові статуси, незаповнені колізії. Розрахунок при цьому не зупиняється - можна подивитись приклади, прийняти як є або повернутись і виправити.
Сенс простий: звіт на кілька тисяч замовлень або правильний, або нічого не вартий. Тому користувач має бачити, де саме цифра може бути кривою, а не отримувати мовчазне «порахувалось».
Ключова логіка
- → Ручні записи по колізіях перевіряються щоразу заново: змінився статус або відповідь API - запис відкладається і показується людині, а не підставляється тихо
- → Платне зберігання на відділенні рахується один раз на посилку, хоча перевізник показує його на обох накладних - інакше сума повернення подвоюється
- → Маржа розділена на основну й допродажну: їх створюють різні люди, тож і дивляться на них окремо
- → Невідомий статус замовлення не ламає розрахунок - звіт дораховується, а список нових статусів іде в зауваження
- → Запис у Google Таблицю пакетний: кілька запитів на весь звіт замість рядка за рядком, інакше квота API
- → Атомарний запис локальних файлів: обрив посеред збереження не псує сховище собівартості й ручних записів
- → Помилки API доходять до користувача людською мовою, а не трейсбеком, і запит повторюється з паузою
Що вміє програма
- → Збір замовлень за період з двох CRM-систем - двох акаунтів LP-CRM і SalesDrive - в одному проході
- → Дотримання лімітів запитів з паузами й контролем залишку квоти
- → Мапінг кастомних полів замовлення через конфіг - під назви конкретного акаунта
- → Редагована таблиця собівартості з пошуком, фільтром незаповнених і підсвіткою нових товарів
- → Перенесення собівартості між місяцями: заповнене раніше підставляється автоматично
- → Ручний етап для колізій order_id з перевіркою записів при кожному наступному прогоні
- → Розрахунок вартості доставки по кожному поверненню з накладних Нової Пошти
- → Фінансовий звіт по кожному сайту й підсумковий, з розділеною маржею та повну статистику повернень
- → Зауваження до даних перед записом - з прикладами й вибором «прийняти як є» чи виправити
- → Запис у Google Таблицю з форматуванням: нова таблиця щомісяця або дозапис у наявну
- → Демо-режим: усі три кроки проходяться на згенерованих даних без жодного ключа API
- → Власний набір самоперевірок на 354 перевірки, що проганяється однією командою і ловить регресії в розрахунках, мапінгу статусів і логіці ручних записів