krona-front/src/demo/seedState.ts

801 lines
35 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Contact } from '../entities/crm/contact';
import { Company } from '../entities/crm/company';
import { Deal, DealStage } from '../entities/crm/deal';
import { Order, OrderStatus, PaymentStatus, FulfillmentStatus } from '../entities/commerce/order';
import { Product, ProductCategory } from '../entities/commerce/product';
import { Warehouse } from '../entities/inventory/warehouse';
import { StockItem, StockMovement } from '../services/contracts/inventory';
import { Account, CashFlowTransaction } from '../services/contracts/finance';
import { Payment, Shipment } from '../services/contracts/commerce';
import { SYSTEM_ENTITY_METADATA } from '../entities/metadata/registry';
import { AuditEntry } from '../services/contracts/audit';
import {
putRecord,
putStoreItem,
countRecords,
resetDatabase,
} from '../services/storage/indexedDb';
import { DemoClock } from './demoClock';
function relDate(daysOffset: number, hoursOffset = 0): string {
const base = DemoClock.getNow().getTime();
const target = new Date(base + daysOffset * 86400000 + hoursOffset * 3600000);
return target.toISOString();
}
function relDateStr(daysOffset: number): string {
return relDate(daysOffset).slice(0, 10);
}
// ----------------------------------------------------
// 1. COMPANIES (12)
// ----------------------------------------------------
export function generateSeedCompanies(): Company[] {
const raw = [
{ name: 'ООО «СеверТорг»', inn: '7701234567', roles: ['client', 'partner'] as const, city: 'Москва' },
{ name: 'АО «ТехноЛаб»', inn: '7812987654', roles: ['vendor'] as const, city: 'Санкт-Петербург' },
{ name: 'ГК «Айсберг Логистик»', inn: '5405112233', roles: ['partner'] as const, city: 'Новосибирск' },
{ name: 'ООО «Кофе Эксперт»', inn: '7722445566', roles: ['vendor', 'client'] as const, city: 'Москва' },
{ name: 'ИП «Михайлов Автодеталь»', inn: '6677889900', roles: ['client'] as const, city: 'Екатеринбург' },
{ name: 'ООО «Бьюти Групп»', inn: '7733557799', roles: ['client'] as const, city: 'Москва' },
{ name: 'АО «Ресторанный Синдикат»', inn: '7801334455', roles: ['client', 'partner'] as const, city: 'Санкт-Петербург' },
{ name: 'ООО «ПакТрейд Упаковка»', inn: '5001223344', roles: ['vendor'] as const, city: 'Мытищи' },
{ name: 'ООО «СтройСервисМонтаж»', inn: '7744112233', roles: ['subcontractor'] as const, city: 'Москва' },
{ name: 'ИП «Семенова Студио»', inn: '2311445566', roles: ['client'] as const, city: 'Краснодар' },
{ name: 'ООО «Гурман Продукт»', inn: '7715667788', roles: ['vendor'] as const, city: 'Москва' },
{ name: 'АО «ФармаХолдинг»', inn: '7819998877', roles: ['client'] as const, city: 'Санкт-Петербург' },
];
return raw.map((c, i) => ({
id: `comp_seed_${i + 1}`,
name: c.name,
legalName: c.name,
inn: c.inn,
kpp: '770101001',
roles: [...c.roles],
phone: `+7 (495) ${100 + i * 15}-${10 + i * 2}-${20 + i * 3}`,
email: `contact@${c.name.toLowerCase().replace(/[^a-z0-9]/g, '') || 'company'}.ru`,
website: `https://${c.name.toLowerCase().replace(/[^a-z0-9]/g, '') || 'company'}.example.com`,
address: `г. ${c.city}, ул. Деловая, д. ${i + 10}`,
status: 'active',
audit: {
createdAt: relDate(-30 + i),
createdById: 'usr_sys_01',
updatedAt: relDate(-2),
updatedById: 'usr_sys_01',
version: 1,
},
}));
}
// ----------------------------------------------------
// 2. CONTACTS (32)
// ----------------------------------------------------
export function generateSeedContacts(companies: Company[]): Contact[] {
const names = [
['Алексей', 'Морозов', 'Владимирович'],
['Елена', 'Соколова', 'Сергеевна'],
['Михаил', 'Романов', 'Николаевич'],
['Анна', 'Кузнецова', 'Игоревна'],
['Дмитрий', 'Смирнов', 'Алексеевич'],
['Ольга', 'Попова', 'Дмитриевна'],
['Сергей', 'Васильев', 'Петрович'],
['Татьяна', 'Новикова', 'Олеговна'],
['Иван', 'Федоров', 'Сергеевич'],
['Наталья', 'Морозова', 'Андреевна'],
['Артем', 'Волков', 'Михайлович'],
['Мария', 'Алексеева', 'Юрьевна'],
['Виктор', 'Лебедев', 'Борисович'],
['Юлия', 'Семенова', 'Викторовна'],
['Павел', 'Егоров', 'Анатольевич'],
['Ирина', 'Павлова', 'Константиновна'],
['Роман', 'Козлов', 'Евгеньевич'],
['Екатерина', 'Степанова', 'Валерьевна'],
['Денис', 'Николаев', 'Григорьевич'],
['Светлана', 'Орлова', 'Романовна'],
['Константин', 'Андреев', 'Сергеевич'],
['Алина', 'Макарова', 'Ильинична'],
['Максим', 'Захаров', 'Витальевич'],
['Ксения', 'Зайцева', 'Тимуровна'],
['Владимир', 'Соловьев', 'Максимович'],
['Дарья', 'Борисова', 'Денисовна'],
['Андрей', 'Яковлев', 'Станиславович'],
['Виктория', 'Григорьева', 'Аркадьевна'],
['Илья', 'Романов', 'Вадимович'],
['Полина', 'Воробьева', 'Антоновна'],
['Григорий', 'Сергеев', 'Олегович'],
['Александра', 'Кузьмина', 'Федоровна'],
];
return names.map(([first, last, middle], i) => {
const comp = companies[i % companies.length];
const role = i % 4 === 0 ? 'partner' : i % 3 === 0 ? 'vendor' : 'client';
return {
id: `cnt_seed_${i + 1}`,
firstName: first,
lastName: last,
middleName: middle,
roles: [role],
companyId: i % 2 === 0 ? comp.id : undefined,
jobTitle: i % 2 === 0 ? 'Руководитель направления' : 'Частный клиент',
email: `${last.toLowerCase()}.${first.toLowerCase()}@example.com`,
phone: `+7 (9${10 + (i % 80)}) ${200 + i * 5}-${10 + (i % 80)}-${20 + (i % 70)}`,
telegram: `@${last.toLowerCase()}_${i + 1}`,
status: 'active',
notes: `Клиентский профиль #${i + 1}. Приоритетное обслуживание.`,
audit: {
createdAt: relDate(-25 + i * 0.5),
createdById: 'usr_sys_01',
updatedAt: relDate(-1),
updatedById: 'usr_sys_01',
version: 1,
},
};
});
}
// ----------------------------------------------------
// 3. CATEGORIES & PRODUCTS (45 across 6 categories)
// ----------------------------------------------------
export function generateSeedCategories(): ProductCategory[] {
const cats = [
{ id: 'cat_beverages', name: 'Кофе и чайное сырье', code: 'BEV' },
{ id: 'cat_food', name: 'Кулинария и десерты', code: 'FOOD' },
{ id: 'cat_equip', name: 'Кофейное оборудование', code: 'EQUIP' },
{ id: 'cat_auto', name: 'Автохимия и запчасти', code: 'AUTO' },
{ id: 'cat_beauty', name: 'Косметика и уход', code: 'BEAUTY' },
{ id: 'cat_services', name: 'Сервисные работы и ТО', code: 'SRV' },
];
return cats.map((c) => ({
id: c.id,
name: c.name,
code: c.code,
audit: {
createdAt: relDate(-60),
createdById: 'usr_sys_01',
updatedAt: relDate(-10),
updatedById: 'usr_sys_01',
version: 1,
},
}));
}
export function generateSeedProducts(categories: ProductCategory[]): Product[] {
const products: Product[] = [];
const baseSkus = [
// Beverages (8)
{ cat: 'cat_beverages', name: 'Зерновой кофе Espresso Bar 1кг', price: 185000, cost: 95000, unit: 'кг' },
{ cat: 'cat_beverages', name: 'Зерновой кофе Ethiopia Yirgacheffe 1кг', price: 245000, cost: 130000, unit: 'кг' },
{ cat: 'cat_beverages', name: 'Зерновой кофе Colombia Supremo 1кг', price: 210000, cost: 110000, unit: 'кг' },
{ cat: 'cat_beverages', name: 'Чай листовой Ассам Мокалбари 500г', price: 120000, cost: 60000, unit: 'уп' },
{ cat: 'cat_beverages', name: 'Чай зеленый Сенча Премиум 500г', price: 135000, cost: 70000, unit: 'уп' },
{ cat: 'cat_beverages', name: 'Сироп Карамель Соленая 1л', price: 89000, cost: 42000, unit: 'бут' },
{ cat: 'cat_beverages', name: 'Сироп Ваниль Мадагаскар 1л', price: 89000, cost: 42000, unit: 'бут' },
{ cat: 'cat_beverages', name: 'Какао-порошок Barry Callebaut 1кг', price: 160000, cost: 90000, unit: 'кг' },
// Food (8)
{ cat: 'cat_food', name: 'Круассан классический на сливочном масле', price: 22000, cost: 9000, unit: 'шт' },
{ cat: 'cat_food', name: 'Круассан миндальный с кремом', price: 29000, cost: 12000, unit: 'шт' },
{ cat: 'cat_food', name: 'Сэндвич с лососем и авокадо', price: 42000, cost: 21000, unit: 'шт' },
{ cat: 'cat_food', name: 'Сэндвич с индейкой и песто', price: 36000, cost: 17000, unit: 'шт' },
{ cat: 'cat_food', name: 'Чизкейк Нью-Йорк классический', price: 34000, cost: 14000, unit: 'порц' },
{ cat: 'cat_food', name: 'Торт Морковный со сливочным кремом', price: 32000, cost: 13000, unit: 'порц' },
{ cat: 'cat_food', name: 'Гранола с греческим йогуртом и ягодами', price: 28000, cost: 11000, unit: 'порц' },
{ cat: 'cat_food', name: 'Печенье Кукис Шоколадное 80г', price: 15000, cost: 6000, unit: 'шт' },
// Equipment (7)
{ cat: 'cat_equip', name: 'Кофемашина La Marzocco Linea PB 2GR', price: 98000000, cost: 72000000, unit: 'шт' },
{ cat: 'cat_equip', name: 'Кофемашина Nuova Simonelli Appia Life 2GR', price: 45000000, cost: 34000000, unit: 'шт' },
{ cat: 'cat_equip', name: 'Кофемолка Mahlkonig E65S GBW', price: 23000000, cost: 17500000, unit: 'шт' },
{ cat: 'cat_equip', name: 'Водоумягчитель DVA 12л', price: 1450000, cost: 850000, unit: 'шт' },
{ cat: 'cat_equip', name: 'Темпер динамометрический 58.5мм', price: 95000, cost: 45000, unit: 'шт' },
{ cat: 'cat_equip', name: 'Питчер молочный Motta 500мл', price: 38000, cost: 18000, unit: 'шт' },
{ cat: 'cat_equip', name: 'Весы бариста Acaia Lunar', price: 280000, cost: 190000, unit: 'шт' },
// Auto (8)
{ cat: 'cat_auto', name: 'Моторное масло синтетическое 5W-40 4л', price: 450000, cost: 270000, unit: 'канистра' },
{ cat: 'cat_auto', name: 'Моторное масло 0W-20 Eco 4л', price: 520000, cost: 310000, unit: 'канистра' },
{ cat: 'cat_auto', name: 'Масляный фильтр Mann-Filter W712', price: 85000, cost: 42000, unit: 'шт' },
{ cat: 'cat_auto', name: 'Воздушный фильтр C25004', price: 95000, cost: 48000, unit: 'шт' },
{ cat: 'cat_auto', name: 'Колодки тормозные передние керамик', price: 420000, cost: 230000, unit: 'компл' },
{ cat: 'cat_auto', name: 'Тормозная жидкость DOT-4 1л', price: 75000, cost: 35000, unit: 'бут' },
{ cat: 'cat_auto', name: 'Очиститель тормозов аэрозоль 500мл', price: 45000, cost: 20000, unit: 'баллон' },
{ cat: 'cat_auto', name: 'Антифриз готовый красный G12+ 5кг', price: 160000, cost: 85000, unit: 'канистра' },
// Beauty (7)
{ cat: 'cat_beauty', name: 'Шампунь восстанавливающий кератин 1л', price: 320000, cost: 160000, unit: 'бут' },
{ cat: 'cat_beauty', name: 'Маска глубокого увлажнения волос 500мл', price: 280000, cost: 135000, unit: 'банка' },
{ cat: 'cat_beauty', name: 'Масло аргановое для кончиков волос 100мл', price: 210000, cost: 95000, unit: 'флакон' },
{ cat: 'cat_beauty', name: 'Крем питательный для рук с маслом ши 75мл', price: 85000, cost: 38000, unit: 'тюбик' },
{ cat: 'cat_beauty', name: 'Гель-лак премиум оттенок Nude 15мл', price: 65000, cost: 28000, unit: 'флакон' },
{ cat: 'cat_beauty', name: 'Сыворотка для лица с гиалуроновой кислотой 30мл', price: 390000, cost: 180000, unit: 'флакон' },
{ cat: 'cat_beauty', name: 'Набор одноразовых пилок для маникюра (50 шт)', price: 95000, cost: 40000, unit: 'уп' },
// Services (7)
{ cat: 'cat_services', name: 'Регламентное ТО кофемашины с декальцинацией', price: 650000, cost: 200000, unit: 'усл', isSrv: true },
{ cat: 'cat_services', name: 'Комплексная замена масла и фильтров автомобиля', price: 250000, cost: 80000, unit: 'усл', isSrv: true },
{ cat: 'cat_services', name: 'Диагностика ходовой части автомобиля на вибростенде', price: 180000, cost: 50000, unit: 'усл', isSrv: true },
{ cat: 'cat_services', name: 'Стрижка женская модельная и укладка', price: 350000, cost: 100000, unit: 'усл', isSrv: true },
{ cat: 'cat_services', name: 'Маникюр аппаратный с покрытием гель-лак', price: 280000, cost: 80000, unit: 'усл', isSrv: true },
{ cat: 'cat_services', name: 'Настройка и калибровка кофемолки bar-flow', price: 300000, cost: 90000, unit: 'усл', isSrv: true },
{ cat: 'cat_services', name: 'Выездной аудит торговой точки и склада', price: 1200000, cost: 400000, unit: 'усл', isSrv: true },
];
baseSkus.forEach((b, i) => {
const isService = !!b.isSrv;
const cat = categories.find((c) => c.id === b.cat);
products.push({
id: `prd_seed_${i + 1}`,
sku: `${cat?.code || 'GEN'}-${1000 + i}`,
name: b.name,
categoryId: b.cat,
categoryName: cat?.name || 'Общая',
barcode: `4600000${100000 + i}`,
price: { minor: b.price, currency: 'RUB' },
costPrice: { minor: b.cost, currency: 'RUB' },
unit: b.unit,
isService,
trackInventory: !isService,
minStockAlert: isService ? 0 : 10,
status: 'active',
audit: {
createdAt: relDate(-45 + (i % 20)),
createdById: 'usr_sys_01',
updatedAt: relDate(-2),
updatedById: 'usr_sys_01',
version: 1,
},
});
});
return products;
}
// ----------------------------------------------------
// 4. WAREHOUSES (3)
// ----------------------------------------------------
export function generateSeedWarehouses(): Warehouse[] {
return [
{
id: 'wh_main',
name: 'Центральный распределительный склад (Москва-Юг)',
code: 'WH-MAIN',
type: 'central',
address: 'г. Москва, Варшавское шоссе, д. 148',
isDefault: true,
status: 'active',
responsibleUserId: 'usr_wh_01',
audit: {
createdAt: relDate(-100),
createdById: 'usr_sys_01',
updatedAt: relDate(-5),
updatedById: 'usr_sys_01',
version: 1,
},
},
{
id: 'wh_northwest',
name: 'Региональный склад (Санкт-Петербург СЗ)',
code: 'WH-NW',
type: 'store',
address: 'г. Санкт-Петербург, Выборгское шоссе, д. 32',
isDefault: false,
status: 'active',
responsibleUserId: 'usr_wh_02',
audit: {
createdAt: relDate(-80),
createdById: 'usr_sys_01',
updatedAt: relDate(-5),
updatedById: 'usr_sys_01',
version: 1,
},
},
{
id: 'wh_showroom',
name: 'Склад-шоурум (Флагманский магазин)',
code: 'WH-SHOWROOM',
type: 'store',
address: 'г. Москва, ул. Большая Дмитровка, д. 7',
isDefault: false,
status: 'active',
responsibleUserId: 'usr_wh_03',
audit: {
createdAt: relDate(-60),
createdById: 'usr_sys_01',
updatedAt: relDate(-5),
updatedById: 'usr_sys_01',
version: 1,
},
},
];
}
// ----------------------------------------------------
// 5. DEALS (25 across pipeline stages)
// ----------------------------------------------------
export function generateSeedDeals(contacts: Contact[], companies: Company[]): Deal[] {
const stages: DealStage[] = ['discovery', 'proposal', 'negotiation', 'contract', 'won', 'lost'];
const titles = [
'Поставка кофемашин для сети ресторанов',
'Годовой сервисный контракт на автопарк',
'Оптовая партия премиального зернового кофе',
'Оснащение спа-салона уходовой косметикой',
'Комплекс ТО и замена масел для таксопарка',
'Поставка оборудования для 3 новых кофеен',
'Партия сиропов и чайных смесей для бара',
'Договор на поставку автохимии и фильтров',
'Корпоративные подарочные наборы кофе и чая',
'Обновление кухонного и барного инвентаря',
'Запуск франшизной точки: полное оснащение',
'Поставка косметики для сети барбершопов',
'Договор планового технического обслуживания',
'Спецзаказ на партию динамических темперов',
'Поставка расходных материалов для шиномонтажа',
'Кейтеринг и мобильная кофейня на выставку',
'Автоматизация рабочих мест и кассовой зоны',
'Поставка профессиональных весов и посуды',
'Партия синтетических масел 0W-20 для СТО',
'Поставка десертов и выпечки для кофе-поинтов',
'Сертификация и аудит оборудования фабрики',
'Поставка средств для бережного ухода за кожей',
'Замена группы прокладок и клапанов 12 машин',
'Снабжение автосервиса тормозными колодками',
'Партия крафтового какао для кондитерской',
];
return titles.map((title, i) => {
const stage = stages[i % stages.length];
const amountMinor = 15000000 + ((i * 37) % 85) * 1000000; // between 150,000 and 1,000,000 RUB
const contact = contacts[i % contacts.length];
const company = companies[i % companies.length];
const prob = stage === 'won' ? 100 : stage === 'lost' ? 0 : stage === 'contract' ? 85 : stage === 'negotiation' ? 65 : 40;
return {
id: `deal_seed_${i + 1}`,
title,
pipelineId: 'pipe_b2b',
stage,
amount: { minor: amountMinor, currency: 'RUB' },
probability: prob,
expectedCloseDate: relDateStr(5 + (i % 25)),
contactId: contact.id,
companyId: company.id,
lostReason: stage === 'lost' ? 'Бюджет проекта перенесен на следующий квартал' : undefined,
audit: {
createdAt: relDate(-20 + (i % 15)),
createdById: 'usr_sys_01',
updatedAt: relDate(-1),
updatedById: 'usr_sys_01',
version: 1,
},
};
});
}
// ----------------------------------------------------
// 6. ORDERS (28 with complete items, payments, shipments)
// ----------------------------------------------------
export function generateSeedOrders(
contacts: Contact[],
products: Product[],
warehouses: Warehouse[]
): { orders: Order[]; payments: Payment[]; shipments: Shipment[] } {
const statuses: OrderStatus[] = [
'delivered',
'delivered',
'completed',
'completed',
'shipped',
'ready',
'confirmed',
'processing',
'draft',
'cancelled',
];
const orders: Order[] = [];
const payments: Payment[] = [];
const shipments: Shipment[] = [];
for (let i = 0; i < 28; i++) {
const status = statuses[i % statuses.length];
const contact = contacts[i % contacts.length];
const warehouse = warehouses[i % warehouses.length];
const orderNumber = `ORD-2026-${String(1001 + i).padStart(4, '0')}`;
const orderId = `ord_seed_${i + 1}`;
// Select 2-4 products for order items
const itemCount = 2 + (i % 3);
const orderItems = [];
let subtotalMinor = 0;
for (let j = 0; j < itemCount; j++) {
const p = products[(i * 3 + j) % products.length];
const qty = 1 + ((i + j) % 4);
const totalItemMinor = p.price.minor * qty;
subtotalMinor += totalItemMinor;
orderItems.push({
id: `oi_seed_${i + 1}_${j + 1}`,
productId: p.id,
sku: p.sku,
name: p.name,
quantity: qty,
unitPrice: p.price,
discountMinor: 0,
totalPrice: { minor: totalItemMinor, currency: 'RUB' },
});
}
const discountMinor = i % 5 === 0 ? Math.round(subtotalMinor * 0.05) : 0;
const totalMinor = subtotalMinor - discountMinor;
const isPaid = status === 'delivered' || status === 'completed' || status === 'shipped' || i % 3 === 0;
const isShipped = status === 'delivered' || status === 'completed' || status === 'shipped';
const payStatus: PaymentStatus = isPaid ? 'paid' : status === 'draft' ? 'pending' : 'pending';
const fulfillStatus: FulfillmentStatus = isShipped ? 'fulfilled' : status === 'ready' ? 'partially_fulfilled' : 'unfulfilled';
const order: Order = {
id: orderId,
orderNumber,
customerType: 'contact',
customerId: contact.id,
customerName: `${contact.firstName} ${contact.lastName}`,
status,
paymentStatus: payStatus,
fulfillmentStatus: fulfillStatus,
items: orderItems,
subtotal: { minor: subtotalMinor, currency: 'RUB' },
discountMinor,
taxMinor: Math.round(totalMinor * 0.2), // 20% VAT included
shippingMinor: 0,
total: { minor: totalMinor, currency: 'RUB' },
warehouseId: warehouse.id,
channel: i % 4 === 0 ? 'pos' : i % 3 === 0 ? 'web' : 'b2b',
shippingAddress: `г. Москва, ул. Клиентская, д. ${i + 1}`,
audit: {
createdAt: relDate(-18 + i * 0.6),
createdById: 'usr_sys_01',
updatedAt: relDate(-1),
updatedById: 'usr_sys_01',
version: 1,
},
};
orders.push(order);
// Linked Payment
if (isPaid) {
payments.push({
id: `pmt_seed_${i + 1}`,
orderId,
orderNumber,
amount: { minor: totalMinor, currency: 'RUB' },
method: i % 2 === 0 ? 'bank_transfer' : 'card',
status: 'completed',
transactionId: `TXN-${100000 + i * 17}`,
paidAt: relDate(-17 + i * 0.6),
audit: {
createdAt: relDate(-17 + i * 0.6),
createdById: 'usr_sys_01',
updatedAt: relDate(-17 + i * 0.6),
updatedById: 'usr_sys_01',
version: 1,
},
});
}
// Linked Shipment
if (isShipped) {
shipments.push({
id: `shp_seed_${i + 1}`,
orderId,
orderNumber,
warehouseId: warehouse.id,
status: status === 'delivered' || status === 'completed' ? 'delivered' : 'shipped',
trackingNumber: `KRONA-TRK-${70000 + i * 11}`,
carrier: 'Krona Экспресс Доставка',
dispatchedAt: relDate(-15 + i * 0.6),
deliveredAt: status === 'delivered' || status === 'completed' ? relDate(-14 + i * 0.6) : undefined,
items: orderItems.map((oi) => ({
productId: oi.productId,
sku: oi.sku,
name: oi.name,
quantity: oi.quantity,
})),
audit: {
createdAt: relDate(-15 + i * 0.6),
createdById: 'usr_sys_01',
updatedAt: relDate(-1),
updatedById: 'usr_sys_01',
version: 1,
},
});
}
}
return { orders, payments, shipments };
}
// ----------------------------------------------------
// 7. STOCK LEDGER & MOVEMENTS
// ----------------------------------------------------
export function generateSeedStock(products: Product[], warehouses: Warehouse[]): {
stockItems: StockItem[];
movements: StockMovement[];
} {
const stockItems: StockItem[] = [];
const movements: StockMovement[] = [];
warehouses.forEach((wh) => {
products.filter((p) => !p.isService).forEach((p, pIdx) => {
const onHand = 30 + ((pIdx * 7) % 150);
const reserved = (pIdx % 5) * 3;
const available = onHand - reserved;
stockItems.push({
id: `stk_${wh.id}_${p.id}`,
warehouseId: wh.id,
warehouseName: wh.name,
productId: p.id,
sku: p.sku,
productName: p.name,
onHand,
reserved,
available,
unit: p.unit,
minThreshold: p.minStockAlert,
audit: {
createdAt: relDate(-40),
createdById: 'usr_sys_01',
updatedAt: relDate(-2),
updatedById: 'usr_sys_01',
version: 1,
},
});
// Add a couple recent movements
if (pIdx % 3 === 0) {
movements.push({
id: `mov_${wh.id}_${p.id}_1`,
type: 'receipt',
productId: p.id,
productName: p.name,
sku: p.sku,
targetWarehouseId: wh.id,
quantity: 50,
referenceNumber: `GR-${1000 + pIdx}`,
notes: 'Плановое поступление от поставщика',
timestamp: relDate(-10 + (pIdx % 5)),
audit: {
createdAt: relDate(-10 + (pIdx % 5)),
createdById: 'usr_sys_01',
updatedAt: relDate(-10 + (pIdx % 5)),
updatedById: 'usr_sys_01',
version: 1,
},
});
}
});
});
return { stockItems, movements };
}
// ----------------------------------------------------
// 8. FINANCE ACCOUNTS & CASH-FLOW
// ----------------------------------------------------
export function generateSeedFinance(payments: Payment[]): {
accounts: Account[];
transactions: CashFlowTransaction[];
} {
const accounts: Account[] = [
{
id: 'acc_main_bank',
name: 'Основной расчетный счет (ПАО Сбербанк)',
type: 'bank',
balance: { minor: 458000000, currency: 'RUB' }, // 4,580,000.00 RUB
currency: 'RUB',
accountNumber: '40702810938000012345',
bankName: 'ПАО СБЕРБАНК',
isDefault: true,
status: 'active',
audit: {
createdAt: relDate(-100),
createdById: 'usr_sys_01',
updatedAt: relDate(-1),
updatedById: 'usr_sys_01',
version: 1,
},
},
{
id: 'acc_cash_desk',
name: 'Касса наличных (Главный офис)',
type: 'cash_desk',
balance: { minor: 34500000, currency: 'RUB' }, // 345,000.00 RUB
currency: 'RUB',
isDefault: false,
status: 'active',
audit: {
createdAt: relDate(-100),
createdById: 'usr_sys_01',
updatedAt: relDate(-1),
updatedById: 'usr_sys_01',
version: 1,
},
},
{
id: 'acc_acquiring',
name: 'Эквайринг Т-Банк',
type: 'acquiring',
balance: { minor: 189000000, currency: 'RUB' }, // 1,890,000.00 RUB
currency: 'RUB',
isDefault: false,
status: 'active',
audit: {
createdAt: relDate(-100),
createdById: 'usr_sys_01',
updatedAt: relDate(-1),
updatedById: 'usr_sys_01',
version: 1,
},
},
];
const transactions: CashFlowTransaction[] = [];
// Inflow transactions matching order payments
payments.forEach((pmt, i) => {
transactions.push({
id: `tx_seed_${i + 1}`,
transactionNumber: `CFT-2026-${String(1001 + i).padStart(4, '0')}`,
type: 'inflow',
category: 'sales_revenue',
amount: pmt.amount,
accountId: pmt.method === 'cash' ? 'acc_cash_desk' : 'acc_main_bank',
accountName: pmt.method === 'cash' ? 'Касса наличных' : 'Основной расчетный счет',
referenceType: 'order_payment',
referenceId: pmt.id,
description: `Поступление оплаты по заказу №${pmt.orderNumber}`,
timestamp: pmt.paidAt,
audit: {
createdAt: pmt.paidAt,
createdById: 'usr_sys_01',
updatedAt: pmt.paidAt,
updatedById: 'usr_sys_01',
version: 1,
},
});
});
// A few regular operating outflows (rent, payroll, supplier payments)
const outflows = [
{ cat: 'rent' as const, name: 'Аренда помещений склада и офиса за текущий месяц', amt: 38000000, daysAgo: 10 },
{ cat: 'payroll' as const, name: 'Выплата аванса сотрудникам', amt: 65000000, daysAgo: 15 },
{ cat: 'supplier_payment' as const, name: 'Оплата партии сырья поставщику ООО «Кофе Эксперт»', amt: 48500000, daysAgo: 7 },
{ cat: 'taxes' as const, name: 'Уплата квартальных налоговых платежей (УСН/НДС)', amt: 29000000, daysAgo: 12 },
{ cat: 'utilities' as const, name: 'Оплата электроэнергии и коммунальных услуг', amt: 8500000, daysAgo: 4 },
];
outflows.forEach((o, idx) => {
const timestamp = relDate(-o.daysAgo);
transactions.push({
id: `tx_outflow_${idx + 1}`,
transactionNumber: `CFT-OUT-${2001 + idx}`,
type: 'outflow',
category: o.cat,
amount: { minor: o.amt, currency: 'RUB' },
accountId: 'acc_main_bank',
accountName: 'Основной расчетный счет',
referenceType: 'manual',
referenceId: `ref_outflow_${idx + 1}`,
description: o.name,
timestamp,
audit: {
createdAt: timestamp,
createdById: 'usr_sys_01',
updatedAt: timestamp,
updatedById: 'usr_sys_01',
version: 1,
},
});
});
return { accounts, transactions };
}
// ----------------------------------------------------
// 9. AUDIT LOG INITIAL SEED
// ----------------------------------------------------
export function generateSeedAuditLogs(): AuditEntry[] {
return [
{
id: `aud_seed_1`,
entityType: 'system',
entityId: 'seed_init',
action: 'create',
userId: 'usr_sys_01',
userName: 'Системный администратор',
timestamp: relDate(-30),
summary: 'Инициализация демонстрационного контура Krona Universal Business OS',
},
{
id: `aud_seed_2`,
entityType: 'warehouse',
entityId: 'wh_main',
action: 'create',
userId: 'usr_sys_01',
userName: 'Администратор склада',
timestamp: relDate(-25),
summary: 'Создан центральный склад «Москва-Юг» с адресным хранением',
},
{
id: `aud_seed_3`,
entityType: 'company',
entityId: 'comp_seed_1',
action: 'create',
userId: 'usr_sales_01',
userName: 'Менеджер по продажам',
timestamp: relDate(-20),
summary: 'Зарегистрирован ключевой корпоративный клиент ООО «СеверТорг»',
},
];
}
// ----------------------------------------------------
// SEEDING AND RESET
// ----------------------------------------------------
export async function seedInitialData(force = false): Promise<boolean> {
const count = await countRecords('contact');
if (count > 0 && !force) {
return false;
}
if (force) {
await resetDatabase();
}
const companies = generateSeedCompanies();
const contacts = generateSeedContacts(companies);
const categories = generateSeedCategories();
const products = generateSeedProducts(categories);
const warehouses = generateSeedWarehouses();
const deals = generateSeedDeals(contacts, companies);
const { orders, payments, shipments } = generateSeedOrders(contacts, products, warehouses);
const { stockItems, movements } = generateSeedStock(products, warehouses);
const { accounts, transactions } = generateSeedFinance(payments);
const auditLogs = generateSeedAuditLogs();
// Save into unified records_data store with entityType
for (const item of companies) await putRecord({ ...item, entityType: 'company' });
for (const item of contacts) await putRecord({ ...item, entityType: 'contact' });
for (const item of categories) await putRecord({ ...item, entityType: 'product_category' });
for (const item of products) await putRecord({ ...item, entityType: 'product' });
for (const item of warehouses) await putRecord({ ...item, entityType: 'warehouse' });
for (const item of deals) await putRecord({ ...item, entityType: 'deal' });
for (const item of orders) await putRecord({ ...item, entityType: 'order' });
for (const item of payments) await putRecord({ ...item, entityType: 'payment' });
for (const item of shipments) await putRecord({ ...item, entityType: 'shipment' });
for (const item of stockItems) await putRecord({ ...item, entityType: 'stock_item' });
for (const item of movements) await putRecord({ ...item, entityType: 'stock_movement' });
for (const item of accounts) await putRecord({ ...item, entityType: 'account' });
for (const item of transactions) await putRecord({ ...item, entityType: 'cash_flow_transaction' });
// Save audit logs
for (const item of auditLogs) await putStoreItem('audit_log', item);
// Save metadata
for (const [, meta] of Object.entries(SYSTEM_ENTITY_METADATA)) {
await putStoreItem('entities_meta', meta);
for (const field of meta.fields) {
await putStoreItem('fields_meta', { ...field, entityCode: meta.code });
}
}
return true;
}
export async function resetToInitialSeed(): Promise<void> {
await seedInitialData(true);
}