Модуль складського обліку

Повноцінний облік товарів для сервісних компаній (тенанти)

КОНЦЕПЦІЯ v1.2

1 Огляд

Модуль замінює базовий облік витратних матеріалів на повноцінну систему складського обліку рівня "1С для малого бізнесу":

Приходи

Прихідні накладні від постачальників. Серійний облік кожної одиниці. Мультивалютність (UAH, USD, EUR).

Розходи

Витрати на заявки, продажі клієнтам, списання, переміщення між складами. Автоматичне зменшення залишків.

Постачальники

Каталог постачальників з контактами, умовами оплати, історією закупівель, рейтингом.

Склад

Кілька складів (головний, мобільний, у техніка). Залишки в реальному часі. Мінімальний запас.

Гарантії

Термін гарантії на кожну одиницю. Алерти при закінченні. Зв'язок з постачальником.

Серійний облік

Кожна одиниця товару з унікальним серійним номером. Повна історія: від приходу до списання.

Ключова відмінність від існуючого: зараз є ConsumableCatalog (каталог) + CompanyConsumable (залишки) + WorkerConsumable (у техніка). Новий модуль додає: документи (приход/розхід), постачальники, склади, серійні номери, гарантії, мультивалютність.

Єдиний каталог

Один каталог номенклатури з категоріями що налаштовуються. Повноцінна картка товару з фото, артикулом, штрихкодом.

Товари на об'єкті

Товари встановлені на об'єкті клієнта (Equipment) автоматично створюються з Product при видатковій. Відокремлені від складських залишків.

Документи продажу

Рахунок-фактура, видаткова накладна, акт виконаних робіт. PDF з реквізитами ФОП/ТОВ. Українські шаблони.

Мульти-ФОП

Кілька юридичних осіб (ФОП, ТОВ) з налаштуванням які товари/послуги дозволено продавати від кожної.

Технічне рішення: свій модуль

Чому не open-source: Жоден з існуючих (StockFlow, nodejs-inventory, UA Invoice Pro) не інтегрується в наш стек (Next.js + Prisma + multi-tenant). Українських документів для Node.js не існує. Пишемо свій, використовуючи бібліотеки:

1.6 Картка номенклатури

Повноцінна картка товару з усією необхідною інформацією:

Основне

Назва, артикул (SKU), штрихкод, категорія, одиниця виміру, опис.

Фото

До 5 фото товару. Головне фото в каталозі. Завантаження drag&drop.

Ціни

Ціна закупівлі (остання, середньозважена), ціна продажу, валюта. Маржа.

Залишки

По кожному складу. Мінімальний запас. Зарезервовано. Графік руху.

Серійні номери

Якщо увімкнено — список усіх одиниць зі статусом. Гарантія кожної.

Історія руху

Всі приходи, розходи, переміщення. Хронологічна стрічка.

Категорії (налаштовуються)

Адмін тенанта створює власні категорії товарів:

Категорії (приклад для IT):
├── Картриджі
│   ├── HP
│   └── Kyocera
├── Запчастини
│   ├── HDD/SSD
│   └── RAM
├── Мережеве обладнання
├── Кабелі та аксесуари
└── Витратники

Категорії (приклад для Solar):
├── Інвертори
├── Батареї
├── Панелі
├── Кабелі та конектори
└── Кріплення
model ProductCategory {
  id        Int      @id @default(autoincrement())
  tenantId  Int?
  name      String
  parentId  Int?                // вкладені категорії
  sortOrder Int      @default(0)
  parent    ProductCategory? @relation("CategoryTree", fields: [parentId], references: [id])
  children  ProductCategory[] @relation("CategoryTree")
  products  Product[]
}

2 Моделі даних (Prisma)

2.1 Постачальник

model Supplier {
  id            Int       @id @default(autoincrement())
  tenantId      Int?
  name          String                    // "Тайстра", "ERC"
  code          String?                   // внутрішній код
  contactName   String?                   // контактна особа
  phone         String?
  email         String?
  address       String?
  notes         String?
  paymentTerms  String?                   // "передоплата", "30 днів", "по факту"
  currency      String    @default("UAH") // основна валюта
  isActive      Boolean   @default(true)
  createdAt     DateTime  @default(now())
  supplies      SupplyDocument[]
}

2.2 Склад

model Warehouse {
  id         Int      @id @default(autoincrement())
  tenantId   Int?
  name       String                // "Головний склад", "Мобільний", "Склад Петренко"
  type       String   @default("main")  // main | mobile | technician
  address    String?
  userId     Int?                  // якщо type=technician — прив'язка до юзера
  isActive   Boolean  @default(true)
  createdAt  DateTime @default(now())
  user       User?    @relation(fields: [userId], references: [id])
  stocks     WarehouseStock[]
  incomeDocs SupplyDocument[]     @relation("SupplyToWarehouse")
}

2.3 Товар (розширення каталогу)

model Product {
  id             Int       @id @default(autoincrement())
  tenantId       Int?
  sku            String?   @unique         // артикул
  name           String                    // "Картридж HP CF259A"
  category       String?                   // "Картриджі", "Кабелі", "Запчастини"
  unit           String    @default("шт")  // шт, м, кг, л
  barcode        String?                   // штрихкод EAN-13
  minStock       Float     @default(0)     // мінімальний залишок (алерт)
  warrantyMonths Int?                      // гарантія за замовчуванням (міс)
  purchasePrice  Float?                    // остання ціна закупівлі
  sellPrice      Float?                    // ціна продажу
  currency       String    @default("UAH")
  notes          String?
  isActive       Boolean   @default(true)
  createdAt      DateTime  @default(now())
  stocks         WarehouseStock[]
  serialItems    SerialItem[]
  supplyLines    SupplyLine[]
  expenseLines   ExpenseLine[]
}

2.4 Залишки на складі

model WarehouseStock {
  id           Int       @id @default(autoincrement())
  warehouseId  Int
  productId    Int
  quantity     Float     @default(0)       // поточний залишок
  reservedQty  Float     @default(0)       // зарезервовано (в заявках)
  updatedAt    DateTime  @updatedAt
  warehouse    Warehouse @relation(fields: [warehouseId], references: [id])
  product      Product   @relation(fields: [productId], references: [id])

  @@unique([warehouseId, productId])
}

2.5 Серійний облік

model SerialItem {
  id             Int       @id @default(autoincrement())
  productId      Int
  serialNumber   String    @unique
  warehouseId    Int?                      // де зараз (null = списано/продано)
  status         String    @default("in_stock") // in_stock | reserved | sold | written_off | warranty_return
  purchasePrice  Float?
  currency       String    @default("UAH")
  supplierId     Int?
  supplyDocId    Int?                      // в якому приході прийшов
  warrantyUntil  DateTime?                 // дата закінчення гарантії
  soldAt         DateTime?
  writtenOffAt   DateTime?
  notes          String?
  createdAt      DateTime  @default(now())
  product        Product   @relation(fields: [productId], references: [id])
  supplier       Supplier? @relation(fields: [supplierId], references: [id])
  supplyDoc      SupplyDocument? @relation(fields: [supplyDocId], references: [id])
}

2.6 Прихідна накладна

model SupplyDocument {
  id             Int       @id @default(autoincrement())
  tenantId       Int?
  number         String?                   // "ПН-001", автонумерація
  type           String    @default("income") // income | return_to_supplier
  supplierId     Int
  warehouseId    Int                       // на який склад
  date           DateTime  @default(now())
  currency       String    @default("UAH")
  exchangeRate   Float     @default(1)     // курс до UAH
  totalAmount    Float     @default(0)     // сума документа
  status         String    @default("draft") // draft | confirmed | cancelled
  notes          String?
  createdAt      DateTime  @default(now())
  confirmedAt    DateTime?
  confirmedById  Int?
  supplier       Supplier  @relation(fields: [supplierId], references: [id])
  warehouse      Warehouse @relation("SupplyToWarehouse", fields: [warehouseId], references: [id])
  lines          SupplyLine[]
  serialItems    SerialItem[]
}

model SupplyLine {
  id           Int            @id @default(autoincrement())
  documentId   Int
  productId    Int
  quantity     Float
  price        Float                       // ціна за одиницю в валюті документа
  amount       Float                       // quantity * price
  document     SupplyDocument @relation(fields: [documentId], references: [id], onDelete: Cascade)
  product      Product        @relation(fields: [productId], references: [id])
}

2.7 Видаткова накладна

model ExpenseDocument {
  id             Int       @id @default(autoincrement())
  tenantId       Int?
  number         String?                   // "ВН-001"
  type           String    @default("expense") // expense | transfer | writeoff | sale
  fromWarehouseId Int                      // з якого складу
  toWarehouseId  Int?                      // на який (для transfer)
  ticketId       Int?                      // прив'язка до заявки
  clientName     String?                   // для sale
  date           DateTime  @default(now())
  currency       String    @default("UAH")
  totalAmount    Float     @default(0)
  status         String    @default("draft") // draft | confirmed | cancelled
  notes          String?
  createdAt      DateTime  @default(now())
  confirmedAt    DateTime?
  confirmedById  Int?
  lines          ExpenseLine[]
}

model ExpenseLine {
  id           Int             @id @default(autoincrement())
  documentId   Int
  productId    Int
  quantity     Float
  price        Float
  amount       Float
  serialNumber String?                    // якщо серійний облік
  document     ExpenseDocument @relation(fields: [documentId], references: [id], onDelete: Cascade)
  product      Product         @relation(fields: [productId], references: [id])
}

Зв'язок з існуючим: ConsumableCatalog → мігрує в Product. CompanyConsumable → WarehouseStock. WorkerConsumable → WarehouseStock (type=technician). Міграція даних при активації модуля.

3 Функціонал

3.1 Приходи

3.2 Розходи

3.3 Склад

3.4 Гарантії

3.5 Мультивалютність

3.6 Зв'язок з заявками

Заявка створена Технік бере матеріали ExpenseDocument (auto) Залишки зменшуються Вартість → в заявку

4 Інтерфейс (сторінки)

СторінкаURLОпис
Товари/warehouse/productsКаталог товарів з пошуком, фільтрами, залишками
Товар (деталі)/warehouse/products/:idКартка товару: залишки по складах, серійники, історія руху
Склади/warehouse/stocksСписок складів, залишки, мінімальний запас
Приходи/warehouse/incomeСписок прихідних накладних, створення нових
Прихід (деталі)/warehouse/income/:idПозиції, серійники, проведення/скасування
Розходи/warehouse/expenseСписок видаткових, фільтр по типу
Розхід (деталі)/warehouse/expense/:idПозиції, зв'язок з заявкою
Постачальники/warehouse/suppliersКаталог постачальників, історія закупівель
Гарантії/warehouse/warrantyОдиниці з активною гарантією, алерти
Звіт руху/warehouse/reportРух товарів за період, оборотна відомість

Sidebar меню

Склад (нова група)
  ├── Товари
  ├── Залишки
  ├── Приходи
  ├── Розходи
  ├── Постачальники
  ├── Гарантії
  └── Звіт руху

RBAC модуль: warehouse

ДіяADMINMANAGERTECHNICIAN
warehouse:viewyesyesyes (свій склад)
warehouse:createyesyesno
warehouse:confirmyesnono
warehouse:pricesyesyesno
warehouse:exportyesyesno

5 Імпорт / Експорт

5.1 Імпорт

ЩоФорматПоля
ТовариCSV / XLSXSKU, Назва, Категорія, Одиниця, Ціна закупівлі, Ціна продажу, Мін.залишок, Гарантія (міс)
ПостачальникиCSVНазва, Контакт, Телефон, Email, Умови оплати
ЗалишкиCSVSKU, Склад, Кількість, Серійний номер (опц.)
ПриходиCSVНомер, Дата, Постачальник, SKU, Кількість, Ціна, Валюта

5.2 Експорт

ЩоФормати
ЗалишкиCSV, XLSX, PDF
Оборотна відомістьCSV, XLSX, PDF
Прихідні накладніPDF (друк)
Видаткові накладніPDF (друк)
Акт списанняPDF
Гарантійний реєстрCSV, XLSX

5.3 Сумісність з 1С

6 План реалізації

Етап 1: Моделі + API

Prisma schema, API endpoints для CRUD всіх моделей. Міграція ConsumableCatalog → Product.

~3-4 години

Етап 2: Товари + Постачальники (UI)

Сторінки каталогу товарів та постачальників. Пошук, фільтри, CRUD.

~2-3 години

Етап 3: Склади + Залишки

Управління складами, перегляд залишків, мінімальний запас.

~2 години

Етап 4: Приходи

Прихідні накладні: створення, позиції, серійники, проведення, мультивалютність.

~3-4 години

Етап 5: Розходи

Видаткові: витрата на заявку, продаж, переміщення, списання. Автоматичний розхід.

~3-4 години

Етап 6: Серійний облік + Гарантії

Повна історія серійного номера, гарантійні алерти, повернення.

~2-3 години

Етап 7: Імпорт/Експорт

CSV/XLSX імпорт товарів, постачальників, залишків. Експорт звітів. 1С сумісність.

~2-3 години

Етап 8: Звіти + Інтеграція

Оборотна відомість, рух товарів, зв'язок з заявками та фінансами.

~2 години

Етап 9: Замовлення клієнту + постачальнику

CustomerOrder, PurchaseOrder. Резервування, автоматичне створення прихідних/видаткових.

~3-4 години

Етап 10: Партійний облік + FIFO

ProductBatch. Автоматичне списання по FIFO. Точна собівартість по партіях.

~3-4 години

Етап 11: Каси + курсові різниці

CashRegister, CashOperation, ExchangeRate. Журнал курсів, переоцінка, курсові різниці.

~3 години

Етап 12: Закриття місяця

MonthClosing. Перевірки, FIFO розрахунки, P&L, блокування документів.

~2-3 години

Етап 13: Юр. особи + PDF документи

LegalEntity, Invoice. Рахунок, накладна, акт — PDF з pdfmake. Мульти-ФОП.

~4-5 годин

Етап 14: Інтеграція ПриватБанк

BankAccount, BankTransaction. API виписка, баланс, автозіставлення платежів.

~4-5 годин

Етап 15: Інтеграція Вчасно (ЕДО)

Підписання КЕП, відправка/отримання електронних документів. Статуси.

~4-5 годин

Загальна оцінка: 45-55 годин роботи, 15 етапів. MVP (товари + приходи + розходи) — після етапу 5. Повноцінний облік — після етапу 12.

7 Додаткові вимоги (v1.1)

7.1 Ціни постачальників

Один товар може закуповуватись у різних постачальників за різними цінами. Потрібно бачити історію і порівнювати.

Матриця цін

Для кожного товару — таблиця постачальників з актуальною ціною, валютою, мінімальним замовленням, терміном доставки.

Історія цін

Графік зміни ціни у кожного постачальника. При створенні приходу — ціна підтягується автоматично з останньої закупівлі.

Найкраща пропозиція

При замовленні — автоматична підказка: у кого дешевше (з урахуванням курсу валют).

model SupplierPrice {
  id            Int       @id @default(autoincrement())
  productId     Int
  supplierId    Int
  price         Float
  currency      String    @default("UAH")
  minOrderQty   Float?                    // мін. замовлення
  deliveryDays  Int?                      // термін доставки
  validUntil    DateTime?                 // дійсна до
  updatedAt     DateTime  @updatedAt
  createdAt     DateTime  @default(now())
  product       Product   @relation(fields: [productId], references: [id])
  supplier      Supplier  @relation(fields: [supplierId], references: [id])

  @@unique([productId, supplierId])
  @@index([productId])
}

7.2 Інтеграція ПриватБанк

API Приват24 для бізнесу: автоматичне завантаження виписок, перегляд балансу, створення платіжних доручень.

Баланс рахунків

Відображення поточного балансу всіх рахунків ФОП/ТОВ в реальному часі. Автооновлення кожні 15 хв.

Виписка

Завантаження банківських виписок за період. Автоматичне зіставлення з рахунками (по призначенню платежу або номеру рахунку).

Платежі

Створення платіжного доручення з CRM: постачальнику за накладну, зарплата, податки. Підпис через Приват24.

Авторозпізнавання

Вхідний платіж → автоматично знаходить рахунок-фактуру → позначає "Оплачено".

// Налаштування в SystemSettings або LegalEntity
model BankAccount {
  id              Int      @id @default(autoincrement())
  legalEntityId   Int
  bankName        String             // "ПриватБанк"
  iban            String
  currency        String   @default("UAH")
  apiToken        String?            // encrypted, Приват24 API токен
  merchantId      String?            // merchant ID
  autoSync        Boolean  @default(false)
  lastSyncAt      DateTime?
  balance         Float?
  isActive        Boolean  @default(true)
  legalEntity     LegalEntity @relation(fields: [legalEntityId], references: [id])
  transactions    BankTransaction[]
}

model BankTransaction {
  id              Int      @id @default(autoincrement())
  bankAccountId   Int
  externalId      String?  @unique      // ID з банку
  date            DateTime
  amount          Float
  currency        String
  counterparty    String?               // назва контрагента
  purpose         String?               // призначення платежу
  type            String                // credit | debit
  invoiceId       Int?                  // зіставлений рахунок
  isReconciled    Boolean  @default(false)
  createdAt       DateTime @default(now())
  bankAccount     BankAccount @relation(fields: [bankAccountId], references: [id])
}

API Приват24: потрібен бізнес-акаунт + токен доступу. Документація: api.privatbank.ua. Обмеження: виписка за останні 90 днів, платежі потребують 2FA підтвердження в додатку.

7.3 Інтеграція "Вчасно" (електронний документообіг)

Відправка документів

Рахунок, накладна, акт → підписуються КЕП → надсилаються контрагенту через Вчасно. Юридично значимий обмін.

Отримання документів

Вхідні накладні від постачальників → автоматичне створення прихідної в CRM. Зіставлення з замовленням.

Статуси

Відстеження: надіслано → переглянуто → підписано контрагентом → завершено.

// Інтеграція через API Вчасно (vchasno.ua/api)
// Потрібно: API ключ + КЕП (ключ електронного підпису)
//
// Потік:
// 1. Генеруємо PDF документ (pdfmake)
// 2. Підписуємо КЕП (через API Вчасно або Дія.Підпис)
// 3. Відправляємо через API Вчасно контрагенту
// 4. Отримуємо callback зі статусом
// 5. Зберігаємо підписаний документ

Вчасно API: потрібен платний тариф (від 500 грн/міс). Альтернатива: M.E.Doc, Paperless. Реалізація — на етапі 9+.

7.4 Каси (прив'язка до організації)

Кілька кас (готівкових та безготівкових) з прив'язкою до юридичних осіб:

model CashRegister {
  id              Int      @id @default(autoincrement())
  tenantId        Int?
  name            String              // "Каса ФОП Петренко", "Безготівковий ТОВ"
  type            String              // cash | bank | card
  legalEntityId   Int
  currency        String   @default("UAH")
  balance         Float    @default(0)
  isActive        Boolean  @default(true)
  createdAt       DateTime @default(now())
  legalEntity     LegalEntity @relation(fields: [legalEntityId], references: [id])
  operations      CashOperation[]
}

model CashOperation {
  id              Int      @id @default(autoincrement())
  cashRegisterId  Int
  type            String              // income | expense | transfer
  amount          Float
  description     String
  counterparty    String?             // від кого / кому
  documentType    String?             // invoice | supply | salary | tax | other
  documentId      Int?                // ID рахунку або накладної
  date            DateTime @default(now())
  userId          Int?                // хто провів
  cashRegister    CashRegister @relation(fields: [cashRegisterId], references: [id])
}

7.5 Курсові різниці

Ситуація: Прихід 10.04 по курсу 44.00 UAH/EUR. Оплата постачальнику 15.04 по курсу 44.50. Різниця = курсова різниця яка впливає на фінансовий результат.

model ExchangeRate {
  id        Int      @id @default(autoincrement())
  date      DateTime                   // дата курсу
  currency  String                     // USD, EUR
  rate      Float                      // курс до UAH
  source    String   @default("nbu")   // nbu | manual
  createdAt DateTime @default(now())

  @@unique([date, currency])
}

// В кожному документі:
// SupplyDocument.exchangeRate = курс на дату приходу
// При оплаті: paymentRate = курс на дату оплати
// Різниця = (paymentRate - exchangeRate) * amountInCurrency

7.6 Закриття місяця

Щомісячна процедура фіксації фінансових показників:

Перевірки

Всі документи проведені? Залишки збігаються? Каси закриті? Валютні переоцінки зроблені?

Розрахунки

FIFO собівартість. Курсові різниці. Маржа по кожному продажу. P&L за місяць.

Звіт

Оборотна відомість. Відомість залишків. P&L. Дебіторка/кредиторка.

Блокування

Після закриття — документи за цей місяць не можна змінювати (тільки сторно).

model MonthClosing {
  id          Int      @id @default(autoincrement())
  tenantId    Int?
  period      String              // "2026-04"
  status      String   @default("open") // open | closing | closed
  closedAt    DateTime?
  closedById  Int?
  totalIncome    Float?           // виручка
  totalExpense   Float?           // собівартість
  totalProfit    Float?           // прибуток
  exchangeDiff   Float?           // курсові різниці
  report      Json?               // повний звіт (snapshot)
  createdAt   DateTime @default(now())

  @@unique([tenantId, period])
}

7.7 Замовлення клієнту

Клієнт замовляє товар/послугу → формується замовлення → резервується на складі → рахунок → оплата → видаткова.

Замовлення Резерв на складі Рахунок Оплата Видаткова Акт (якщо послуга)
model CustomerOrder {
  id              Int      @id @default(autoincrement())
  tenantId        Int?
  number          String?
  clientName      String
  clientPhone     String?
  organizationId  Int?               // організація-клієнт
  legalEntityId   Int?               // від якого ФОП/ТОВ
  status          String   @default("new") // new | confirmed | reserved | invoiced | paid | shipped | completed | cancelled
  currency        String   @default("UAH")
  totalAmount     Float    @default(0)
  notes           String?
  ticketId        Int?               // зв'язок з заявкою
  invoiceId       Int?               // згенерований рахунок
  expenseDocId    Int?               // видаткова накладна
  createdAt       DateTime @default(now())
  completedAt     DateTime?
  lines           CustomerOrderLine[]
}

model CustomerOrderLine {
  id          Int    @id @default(autoincrement())
  orderId     Int
  productId   Int?
  description String
  unit        String @default("шт")
  quantity    Float
  price       Float
  amount      Float
  order       CustomerOrder @relation(fields: [orderId], references: [id], onDelete: Cascade)
}

7.8 Замовлення постачальнику

Формується на основі дефіциту (залишок < мінімум) або вручну. Прив'язується до постачальника з найкращою ціною.

Дефіцит / Ручне Замовлення Відправлено Отримано Прихідна (auto)
model PurchaseOrder {
  id              Int      @id @default(autoincrement())
  tenantId        Int?
  number          String?
  supplierId      Int
  warehouseId     Int                // на який склад
  status          String   @default("draft") // draft | sent | confirmed | received | cancelled
  currency        String   @default("UAH")
  exchangeRate    Float    @default(1)
  totalAmount     Float    @default(0)
  expectedDate    DateTime?          // очікувана дата доставки
  notes           String?
  supplyDocId     Int?               // створена прихідна
  createdAt       DateTime @default(now())
  supplier        Supplier @relation(fields: [supplierId], references: [id])
  lines           PurchaseOrderLine[]
}

model PurchaseOrderLine {
  id          Int    @id @default(autoincrement())
  orderId     Int
  productId   Int
  quantity    Float
  price       Float
  amount      Float
  order       PurchaseOrder @relation(fields: [orderId], references: [id], onDelete: Cascade)
}

7.9 Партійний облік

Партія = конкретний прихід конкретного товару від конкретного постачальника за конкретною ціною. FIFO: при розході списуються спочатку найстаріші партії.

model ProductBatch {
  id              Int       @id @default(autoincrement())
  productId       Int
  warehouseId     Int
  supplyDocId     Int?                // прихідна накладна
  supplierId      Int?
  batchNumber     String?             // номер партії (від постачальника)
  quantity        Float               // початкова кількість
  remainingQty    Float               // поточний залишок партії
  purchasePrice   Float               // ціна закупівлі
  currency        String   @default("UAH")
  exchangeRate    Float    @default(1)
  priceUah        Float               // ціна в UAH (purchasePrice * exchangeRate)
  expiresAt       DateTime?           // термін придатності
  receivedAt      DateTime @default(now())
  product         Product  @relation(fields: [productId], references: [id])

  @@index([productId, warehouseId])
  @@index([productId, remainingQty])  // для FIFO
}

FIFO алгоритм розходу:
1. Знайти партії товару на складі де remainingQty > 0, ORDER BY receivedAt ASC
2. Списувати по черзі: якщо потрібно 5 шт, а в першій партії 3 — списати 3, решту 2 з наступної
3. Собівартість = сума (кількість з партії * ціна партії) для кожної використаної партії

8 Ідеї з OneBox + AI (v1.2)

Додаткові ідеї на основі аналізу OneB та OneBox OS:

8.1 Розширена картка товару

Галерея фото

До 10 фото товару. Головне фото в каталозі. Drag&drop завантаження. Zoom при кліку. Мініатюри в таблиці.

Декілька рівнів цін

Закупівельна, роздрібна, оптова, VIP. Кожен клієнт/організація → свій рівень цін. Автоматична націнка %.

Аналоги та сумісність

Зв'язок товарів: "Картридж HP 59A → сумісний з HP M404, M428". При пошуку показувати аналоги якщо немає оригіналу.

Кастомні характеристики

Адмін створює додаткові поля: Вага, Розмір, Колір, Потужність. Фільтрація по будь-якому полю.

QR-код / Штрихкод

Генерація QR на етикетку з назвою + ціною. Сканування з мобільного для швидкого пошуку. Друк етикеток.

Одиниці з конвертацією

Купуємо в упаковках (10 шт), продаємо поштучно. Автоматичний перерахунок. Базова одиниця + похідні.

8.2 AI для документів (як OneB Invoice Price AI)

AI-асистент для складського обліку: розпізнавання документів, розумне ціноутворення, прогнозування.

Розпізнавання накладних (OCR)

Фото/скан накладної від постачальника → AI розпізнає: назву товару, кількість, ціну, серійники. Автоматичне створення прихідної.

Claude Vision / GPT-4o

Розумне ціноутворення

AI аналізує: ціни постачальників, маржу, сезонність, конкурентів. Рекомендує оптимальну ціну продажу. Алерт при зміні ціни постачальника.

AI рекомендація

Прогнозування дефіциту

AI аналізує історію витрат і прогнозує коли закінчиться товар. Автоматичне замовлення постачальнику за N днів до дефіциту.

ML прогноз

Розпізнавання чеків

Фото касового чеку → AI витягує: дату, суму, товари, ФОП. Автоматичне створення витратного документа.

Claude Vision

Класифікація товарів

При додаванні нового товару AI автоматично пропонує: категорію, одиницю виміру, мінімальний запас на основі назви.

AI підказка

Аналіз ABC/XYZ

AI класифікує товари: A — високий оборот, B — середній, C — низький. Рекомендації по оптимізації складу.

Аналітика

8.3 Workflow замовлень (як OneBox)

Клієнт
звернення
Кошторис
розрахунок
Рахунок
на оплату
Оплата
LiqPay/банк
Видаткова
передача
Акт
підписання

8.4 Прайс-листи та знижки

model PriceLevel {
  id        Int      @id @default(autoincrement())
  tenantId  Int?
  name      String              // "Роздріб", "Опт", "VIP"
  markup    Float    @default(0) // націнка % від закупівельної
  isDefault Boolean  @default(false)
  createdAt DateTime @default(now())
}

model CustomerPriceLevel {
  id              Int    @id @default(autoincrement())
  organizationId  Int?             // організація-клієнт
  priceLevelId    Int
  discount        Float  @default(0) // додаткова знижка %
}

model PriceHistory {
  id          Int      @id @default(autoincrement())
  productId   Int
  supplierId  Int?
  price       Float
  currency    String   @default("UAH")
  source      String   @default("manual") // manual | import | ai
  recordedAt  DateTime @default(now())
}

8.5 Кошторис (Estimate)

model Estimate {
  id              Int      @id @default(autoincrement())
  tenantId        Int?
  number          String?
  clientName      String
  organizationId  Int?
  ticketId        Int?              // зв'язок з заявкою
  legalEntityId   Int?              // від якого ФОП/ТОВ
  status          String   @default("draft") // draft | sent | approved | rejected
  currency        String   @default("UAH")
  totalAmount     Float    @default(0)
  validUntil      DateTime?         // дійсний до
  notes           String?
  pdfUrl          String?
  invoiceId       Int?              // згенерований рахунок
  createdAt       DateTime @default(now())
  lines           EstimateLine[]
}

model EstimateLine {
  id          Int    @id @default(autoincrement())
  estimateId  Int
  type        String @default("product") // product | service | work
  description String
  productId   Int?
  unit        String @default("шт")
  quantity    Float
  price       Float
  amount      Float
  estimate    Estimate @relation(fields: [estimateId], references: [id], onDelete: Cascade)
}

8.6 Етикетки та друк

8.7 Мобільний склад

1. Міграція існуючих даних

ConsumableCatalog → Product, CompanyConsumable → WarehouseStock. Автоматично чи вручну? Чи зберігати старі моделі для backward compatibility?

Потрібне рішення

2. Серійний облік — для всіх?

Чи потрібен серійний облік для всіх товарів (кабелі по метрах?) чи тільки для дорогих (інвертори, батареї)?

Потрібне рішення

3. Зв'язок з Equipment

Equipment (обладнання на об'єкті) та Product (товар на складі) — це різні сутності? Чи Equipment створюється з Product при встановленні?

Потрібне рішення

4. Ціноутворення

FIFO (перший прийшов — перший пішов) чи середньозважена ціна? Для розрахунку собівартості розходу.

Рекомендую: середньозважена

5. PDF генерація

Для накладних потрібні PDF? Який шаблон — стандартна українська накладна чи спрощений?

Потрібне рішення

6. Модуль як окремий

Новий модуль "warehouse" в sidebar та RBAC? Чи розширення існуючого "consumables"?

Рекомендую: окремий "warehouse"

7. API НБУ для курсів

Автоматичне оновлення курсів з API НБУ щоденно? Чи тільки вручну?

Рекомендую: API НБУ + ручне

8. Для яких галузей

Модуль warehouse для всіх тенантів чи тільки для певних галузей (IT, solar, electrical)?

Рекомендую: для всіх хто включив