Повноцінний облік товарів для сервісних компаній (тенанти)
КОНЦЕПЦІЯ v1.2Модуль замінює базовий облік витратних матеріалів на повноцінну систему складського обліку рівня "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 не існує. Пишемо свій, використовуючи бібліотеки:
Система підтримує кілька юридичних осіб від яких компанія може продавати товари та послуги:
| Поле | Опис | Приклад |
|---|---|---|
| Назва | Повна назва | ФОП Петренко О.М. |
| Тип | fop | tov | pp | fop |
| ЄДРПОУ / ІПН | Код | 3245678901 |
| Адреса | Юридична | м. Тернопіль, вул. Руська 2 |
| Банк | Назва банку | ПриватБанк |
| IBAN | Рахунок | UA21 3052 9900 0002 6006 0338 1784 9 |
| Телефон / Email | Контакти | +380352520050 |
| Система оподаткування | Група ФОП | 3 група, без ПДВ |
| Дозволені категорії | Які товари/послуги | ["Обладнання", "ТО"] |
| Печатка / Підпис | Зображення (опц.) | stamp.png, sign.png |
| isDefault | За замовчуванням | true |
Виставляється клієнту на оплату. Реквізити ФОП/ТОВ, позиції, QR-код для оплати. PDF для друку/email.
Підтверджує передачу товару. Підпис постачальника та отримувача. Серійні номери.
Для послуг (ТО, ремонт, монтаж). Зв'язок з заявкою. Опис робіт + матеріали.
Внутрішній документ при списанні обладнання. Причина, комісія.
model LegalEntity {
id Int @id @default(autoincrement())
tenantId Int?
name String // "ФОП Петренко О.М."
shortName String? // "Петренко"
type String // fop | tov | pp
taxCode String? // ЄДРПОУ або ІПН
address String?
bankName String?
iban String?
phone String?
email String?
taxSystem String? // "3 група, без ПДВ"
allowedCategories Json? // ["Обладнання", "Послуги ТО"]
stampUrl String? // URL печатки
signatureUrl String? // URL підпису
isDefault Boolean @default(false)
isActive Boolean @default(true)
createdAt DateTime @default(now())
invoices Invoice[]
}
model Invoice {
id Int @id @default(autoincrement())
tenantId Int?
number String? // "РФ-001"
type String // invoice | nakladna | act | writeoff_act
legalEntityId Int
clientName String // назва клієнта/організації
clientCode String? // ЄДРПОУ клієнта
clientAddress String?
ticketId Int? // зв'язок з заявкою
expenseDocId Int? // зв'язок з видатковою
date DateTime @default(now())
dueDate DateTime? // термін оплати
currency String @default("UAH")
totalAmount Float @default(0)
status String @default("draft") // draft | sent | paid | cancelled
notes String?
pdfUrl String? // згенерований PDF
createdAt DateTime @default(now())
legalEntity LegalEntity @relation(fields: [legalEntityId], references: [id])
lines InvoiceLine[]
}
model InvoiceLine {
id Int @id @default(autoincrement())
invoiceId Int
description String // назва товару/послуги
unit String @default("шт")
quantity Float
price Float
amount Float
invoice Invoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
}
Повноцінна картка товару з усією необхідною інформацією:
Назва, артикул (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[]
}
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[]
}
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")
}
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[]
}
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])
}
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])
}
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])
}
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). Міграція даних при активації модуля.
| Сторінка | URL | Опис |
|---|---|---|
| Товари | /warehouse/products | Каталог товарів з пошуком, фільтрами, залишками |
| Товар (деталі) | /warehouse/products/:id | Картка товару: залишки по складах, серійники, історія руху |
| Склади | /warehouse/stocks | Список складів, залишки, мінімальний запас |
| Приходи | /warehouse/income | Список прихідних накладних, створення нових |
| Прихід (деталі) | /warehouse/income/:id | Позиції, серійники, проведення/скасування |
| Розходи | /warehouse/expense | Список видаткових, фільтр по типу |
| Розхід (деталі) | /warehouse/expense/:id | Позиції, зв'язок з заявкою |
| Постачальники | /warehouse/suppliers | Каталог постачальників, історія закупівель |
| Гарантії | /warehouse/warranty | Одиниці з активною гарантією, алерти |
| Звіт руху | /warehouse/report | Рух товарів за період, оборотна відомість |
Склад (нова група) ├── Товари ├── Залишки ├── Приходи ├── Розходи ├── Постачальники ├── Гарантії └── Звіт руху
| Дія | ADMIN | MANAGER | TECHNICIAN |
|---|---|---|---|
| warehouse:view | yes | yes | yes (свій склад) |
| warehouse:create | yes | yes | no |
| warehouse:confirm | yes | no | no |
| warehouse:prices | yes | yes | no |
| warehouse:export | yes | yes | no |
| Що | Формат | Поля |
|---|---|---|
| Товари | CSV / XLSX | SKU, Назва, Категорія, Одиниця, Ціна закупівлі, Ціна продажу, Мін.залишок, Гарантія (міс) |
| Постачальники | CSV | Назва, Контакт, Телефон, Email, Умови оплати |
| Залишки | CSV | SKU, Склад, Кількість, Серійний номер (опц.) |
| Приходи | CSV | Номер, Дата, Постачальник, SKU, Кількість, Ціна, Валюта |
| Що | Формати |
|---|---|
| Залишки | CSV, XLSX, PDF |
| Оборотна відомість | CSV, XLSX, PDF |
| Прихідні накладні | PDF (друк) |
| Видаткові накладні | PDF (друк) |
| Акт списання | |
| Гарантійний реєстр | CSV, XLSX |
Prisma schema, API endpoints для CRUD всіх моделей. Міграція ConsumableCatalog → Product.
~3-4 години
Сторінки каталогу товарів та постачальників. Пошук, фільтри, CRUD.
~2-3 години
Управління складами, перегляд залишків, мінімальний запас.
~2 години
Прихідні накладні: створення, позиції, серійники, проведення, мультивалютність.
~3-4 години
Видаткові: витрата на заявку, продаж, переміщення, списання. Автоматичний розхід.
~3-4 години
Повна історія серійного номера, гарантійні алерти, повернення.
~2-3 години
CSV/XLSX імпорт товарів, постачальників, залишків. Експорт звітів. 1С сумісність.
~2-3 години
Оборотна відомість, рух товарів, зв'язок з заявками та фінансами.
~2 години
CustomerOrder, PurchaseOrder. Резервування, автоматичне створення прихідних/видаткових.
~3-4 години
ProductBatch. Автоматичне списання по FIFO. Точна собівартість по партіях.
~3-4 години
CashRegister, CashOperation, ExchangeRate. Журнал курсів, переоцінка, курсові різниці.
~3 години
MonthClosing. Перевірки, FIFO розрахунки, P&L, блокування документів.
~2-3 години
LegalEntity, Invoice. Рахунок, накладна, акт — PDF з pdfmake. Мульти-ФОП.
~4-5 годин
BankAccount, BankTransaction. API виписка, баланс, автозіставлення платежів.
~4-5 годин
Підписання КЕП, відправка/отримання електронних документів. Статуси.
~4-5 годин
Загальна оцінка: 45-55 годин роботи, 15 етапів. MVP (товари + приходи + розходи) — після етапу 5. Повноцінний облік — після етапу 12.
Один товар може закуповуватись у різних постачальників за різними цінами. Потрібно бачити історію і порівнювати.
Для кожного товару — таблиця постачальників з актуальною ціною, валютою, мінімальним замовленням, терміном доставки.
Графік зміни ціни у кожного постачальника. При створенні приходу — ціна підтягується автоматично з останньої закупівлі.
При замовленні — автоматична підказка: у кого дешевше (з урахуванням курсу валют).
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])
}
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 підтвердження в додатку.
Рахунок, накладна, акт → підписуються КЕП → надсилаються контрагенту через Вчасно. Юридично значимий обмін.
Вхідні накладні від постачальників → автоматичне створення прихідної в CRM. Зіставлення з замовленням.
Відстеження: надіслано → переглянуто → підписано контрагентом → завершено.
// Інтеграція через API Вчасно (vchasno.ua/api) // Потрібно: API ключ + КЕП (ключ електронного підпису) // // Потік: // 1. Генеруємо PDF документ (pdfmake) // 2. Підписуємо КЕП (через API Вчасно або Дія.Підпис) // 3. Відправляємо через API Вчасно контрагенту // 4. Отримуємо callback зі статусом // 5. Зберігаємо підписаний документ
Вчасно API: потрібен платний тариф (від 500 грн/міс). Альтернатива: M.E.Doc, Paperless. Реалізація — на етапі 9+.
Кілька кас (готівкових та безготівкових) з прив'язкою до юридичних осіб:
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])
}
Ситуація: Прихід 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
Щомісячна процедура фіксації фінансових показників:
Всі документи проведені? Залишки збігаються? Каси закриті? Валютні переоцінки зроблені?
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])
}
Клієнт замовляє товар/послугу → формується замовлення → резервується на складі → рахунок → оплата → видаткова.
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)
}
Формується на основі дефіциту (залишок < мінімум) або вручну. Прив'язується до постачальника з найкращою ціною.
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)
}
Партія = конкретний прихід конкретного товару від конкретного постачальника за конкретною ціною. 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. Собівартість = сума (кількість з партії * ціна партії) для кожної використаної партії
Додаткові ідеї на основі аналізу OneB та OneBox OS:
До 10 фото товару. Головне фото в каталозі. Drag&drop завантаження. Zoom при кліку. Мініатюри в таблиці.
Закупівельна, роздрібна, оптова, VIP. Кожен клієнт/організація → свій рівень цін. Автоматична націнка %.
Зв'язок товарів: "Картридж HP 59A → сумісний з HP M404, M428". При пошуку показувати аналоги якщо немає оригіналу.
Адмін створює додаткові поля: Вага, Розмір, Колір, Потужність. Фільтрація по будь-якому полю.
Генерація QR на етикетку з назвою + ціною. Сканування з мобільного для швидкого пошуку. Друк етикеток.
Купуємо в упаковках (10 шт), продаємо поштучно. Автоматичний перерахунок. Базова одиниця + похідні.
AI-асистент для складського обліку: розпізнавання документів, розумне ціноутворення, прогнозування.
Фото/скан накладної від постачальника → AI розпізнає: назву товару, кількість, ціну, серійники. Автоматичне створення прихідної.
AI аналізує: ціни постачальників, маржу, сезонність, конкурентів. Рекомендує оптимальну ціну продажу. Алерт при зміні ціни постачальника.
AI аналізує історію витрат і прогнозує коли закінчиться товар. Автоматичне замовлення постачальнику за N днів до дефіциту.
Фото касового чеку → AI витягує: дату, суму, товари, ФОП. Автоматичне створення витратного документа.
При додаванні нового товару AI автоматично пропонує: категорію, одиницю виміру, мінімальний запас на основі назви.
AI класифікує товари: A — високий оборот, B — середній, C — низький. Рекомендації по оптимізації складу.
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())
}
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)
}
ConsumableCatalog → Product, CompanyConsumable → WarehouseStock. Автоматично чи вручну? Чи зберігати старі моделі для backward compatibility?
Чи потрібен серійний облік для всіх товарів (кабелі по метрах?) чи тільки для дорогих (інвертори, батареї)?
Equipment (обладнання на об'єкті) та Product (товар на складі) — це різні сутності? Чи Equipment створюється з Product при встановленні?
FIFO (перший прийшов — перший пішов) чи середньозважена ціна? Для розрахунку собівартості розходу.
Для накладних потрібні PDF? Який шаблон — стандартна українська накладна чи спрощений?
Новий модуль "warehouse" в sidebar та RBAC? Чи розширення існуючого "consumables"?
Автоматичне оновлення курсів з API НБУ щоденно? Чи тільки вручну?
Модуль warehouse для всіх тенантів чи тільки для певних галузей (IT, solar, electrical)?