Fix: estimates geo v2

This commit is contained in:
Arsen
2026-02-04 00:11:19 +05:00
commit 3f0086f88e
22567 changed files with 4348823 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
-- Добавить поле "с НДС / без НДС" в смету.
-- Выполните вручную, если миграция Prisma не применилась:
-- psql -U it -d estimate_assistant -f backend/prisma/ADD_WITH_VAT.sql
ALTER TABLE "Estimate" ADD COLUMN IF NOT EXISTS "withVat" BOOLEAN NOT NULL DEFAULT true;

315
backend/prisma/schema.prisma Executable file
View File

@@ -0,0 +1,315 @@
// Prisma schema for Estimate Assistant
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// Справочники базовых цен (СБЦ)
model PriceBook {
id String @id @default(uuid())
code String @unique // SBC-GEODESY-2004, SBC-GEOLOGY-1999
name String // Полное название справочника
baseDate DateTime // Дата базовых цен (01.01.2001, 01.01.1991)
approvedBy String? // Кем утвержден
effectiveDate DateTime? // Дата введения в действие
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tables PriceTable[]
items PriceItem[]
}
// Таблицы справочников
model PriceTable {
id String @id @default(uuid())
priceBookId String
tableNumber Int // Номер таблицы (8, 9, 13, и т.д.)
name String // Название таблицы
unit String // Единица измерения (1 пункт, 1 га, 1 км и т.д.)
notes Json? // Примечания к таблице
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
priceBook PriceBook @relation(fields: [priceBookId], references: [id], onDelete: Cascade)
items PriceItem[]
@@unique([priceBookId, tableNumber])
}
// Позиции справочников (расценки)
model PriceItem {
id String @id @default(uuid())
priceBookId String
priceTableId String
paragraph String // Номер параграфа (8-1, 9-4-1 и т.д.)
workType String // Тип работы / наименование
description String? // Дополнительное описание
// Цены (полевые/камеральные для разных категорий)
priceField1 Decimal? @db.Decimal(12, 2) // Полевые работы, категория 1
priceOffice1 Decimal? @db.Decimal(12, 2) // Камеральные работы, категория 1
priceField2 Decimal? @db.Decimal(12, 2) // Полевые работы, категория 2
priceOffice2 Decimal? @db.Decimal(12, 2) // Камеральные работы, категория 2
priceField3 Decimal? @db.Decimal(12, 2) // Полевые работы, категория 3
priceOffice3 Decimal? @db.Decimal(12, 2) // Камеральные работы, категория 3
priceSimple Decimal? @db.Decimal(12, 2) // Простая цена (без категорий)
// Дополнительные параметры
attributes Json? // Доп. атрибуты (масштаб, глубина, диаметр и т.д.)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
priceBook PriceBook @relation(fields: [priceBookId], references: [id], onDelete: Cascade)
priceTable PriceTable @relation(fields: [priceTableId], references: [id], onDelete: Cascade)
estimateItems EstimateItem[]
@@index([paragraph])
@@index([workType])
}
// Коэффициенты
model Coefficient {
id String @id @default(uuid())
type String // regional, transport_internal, transport_external, seasonal, special
code String // Уникальный код коэффициента
name String // Название
value Decimal @db.Decimal(6, 4) // Значение коэффициента
description String? // Описание
conditions Json? // Условия применения
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([type, code])
}
// Индексы инфляции (перевод в текущие цены)
model InflationIndex {
id String @id @default(uuid())
baseDate DateTime // Базовая дата цен
effectiveFrom DateTime // Дата начала действия индекса
effectiveTo DateTime? // Дата окончания (null = текущий)
indexValue Decimal @db.Decimal(10, 4) // Значение индекса
documentRef String? // Ссылка на документ (Письмо Минстроя и т.д.)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// Пользователи
model User {
id String @id @default(uuid())
email String @unique
passwordHash String
name String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
estimates Estimate[]
chatSessions ChatSession[]
ownedShares EstimateShare[] @relation("ShareOwner")
receivedShares EstimateShare[] @relation("ShareReceiver")
shareNotes EstimateShareNote[]
}
// Направления изысканий
model SurveyDirection {
id String @id @default(uuid())
code String @unique // geodesy, geology, ecology, hydrology
name String // Инженерно-геодезические изыскания
shortName String // Геодезия
isActive Boolean @default(true)
estimates Estimate[]
}
// Сметы
model Estimate {
id String @id @default(uuid())
number String // Номер сметы
directionId String
ownerId String // Владелец сметы
objectName String // Название объекта
customer String // Заказчик
executor String // Исполнитель
// Итоговые значения
totalFieldWorks Decimal? @db.Decimal(14, 2) // Итого полевые работы
totalOfficeWorks Decimal? @db.Decimal(14, 2) // Итого камеральные работы
totalLaboratory Decimal? @db.Decimal(14, 2) // Итого лабораторные
subtotal Decimal? @db.Decimal(14, 2) // Итого по изысканиям
// Коэффициенты и пересчет
regionalCoef Decimal? @db.Decimal(6, 4) // Районный коэффициент
regionalCoefDocRef String? // Описание (С районным коэффициентом и т.д.)
inflationIndex Decimal? @db.Decimal(10, 4) // Индекс перевода в текущие цены
inflationDocRef String? // Ссылка на документ (Письмо Минстроя и т.д.)
companyCoef Decimal? @db.Decimal(6, 4) // Коэффициент компании (Газпром и т.д.)
companyCoefDocRef String? // Описание (Коэффициент ОАО «Газпром» №544 и т.д.)
executorCoef Decimal? @db.Decimal(6, 4) // Коэффициент исполнителя
executorCoefDocRef String? // Описание (Коэффициент ООО «ГеоВектор» и т.д.)
// Итоги
withVat Boolean @default(true) // Смета с НДС (true) или без НДС (false)
totalWithoutVat Decimal? @db.Decimal(14, 2) // Итого без НДС
vatRate Decimal? @db.Decimal(4, 2) // Ставка НДС (18, 20, 7)
vatAmount Decimal? @db.Decimal(14, 2) // Сумма НДС
totalWithVat Decimal? @db.Decimal(14, 2) // Всего с НДС
status String @default("draft") // draft, completed, approved
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
direction SurveyDirection @relation(fields: [directionId], references: [id])
items EstimateItem[]
totals EstimateTotal[]
shares EstimateShare[]
versions EstimateVersion[]
}
// История версий сметы (снимок при сохранении/пересчёте)
model EstimateVersion {
id String @id @default(uuid())
estimateId String
versionNumber Int // Порядковый номер версии по смете
snapshot Json // Полный снимок: estimate + items + totals
createdAt DateTime @default(now())
estimate Estimate @relation(fields: [estimateId], references: [id], onDelete: Cascade)
@@index([estimateId])
@@index([estimateId, createdAt])
}
// Шаринг сметы с другим пользователем
model EstimateShare {
id String @id @default(uuid())
estimateId String
ownerId String // Кто поделился
sharedWithId String // С кем поделились
createdAt DateTime @default(now())
estimate Estimate @relation(fields: [estimateId], references: [id], onDelete: Cascade)
sharedWith User @relation("ShareReceiver", fields: [sharedWithId], references: [id], onDelete: Cascade)
owner User @relation("ShareOwner", fields: [ownerId], references: [id], onDelete: Cascade)
notes EstimateShareNote[]
@@unique([estimateId, sharedWithId])
@@index([sharedWithId])
}
// Заметки к шарингу сметы (кто что написал)
model EstimateShareNote {
id String @id @default(uuid())
shareId String
authorId String
content String @db.Text
createdAt DateTime @default(now())
share EstimateShare @relation(fields: [shareId], references: [id], onDelete: Cascade)
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
@@index([shareId])
}
// Позиции сметы
model EstimateItem {
id String @id @default(uuid())
estimateId String
orderNumber Int // Порядковый номер
sectionType String // field, office, laboratory, transport, other
priceItemId String? // Ссылка на позицию справочника
workName String // Наименование работы
justification String? // Обоснование (ссылка на СБЦ)
basePrice Decimal @db.Decimal(12, 2) // Базовая цена
quantity Decimal @db.Decimal(12, 4) // Объем
unit String? // Единица измерения
// Коэффициенты
coef1 Decimal? @db.Decimal(6, 4) // Коэффициент 1
coef1Desc String? // Описание коэффициента 1
coef2 Decimal? @db.Decimal(6, 4) // Коэффициент 2
coef2Desc String? // Описание коэффициента 2
coef3 Decimal? @db.Decimal(6, 4) // Коэффициент 3
coef3Desc String? // Описание коэффициента 3
totalPrice Decimal @db.Decimal(14, 2) // Итоговая стоимость
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
estimate Estimate @relation(fields: [estimateId], references: [id], onDelete: Cascade)
priceItem PriceItem? @relation(fields: [priceItemId], references: [id])
@@index([estimateId, orderNumber])
}
// Итоговая часть сметы (нижняя часть)
model EstimateTotal {
id String @id @default(uuid())
estimateId String
orderNumber Int // Порядок строки
label String // Название строки (Перевод в текущие цены, НДС и т.д.)
description String? // Дополнительное описание
baseValue Decimal? @db.Decimal(14, 2) // Базовое значение
coefficient Decimal? @db.Decimal(10, 4) // Коэффициент/процент
resultValue Decimal @db.Decimal(14, 2) // Результат
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
estimate Estimate @relation(fields: [estimateId], references: [id], onDelete: Cascade)
@@index([estimateId, orderNumber])
}
// Настройки приложения
model Setting {
id String @id @default(uuid())
key String @unique
value String
type String @default("string") // string, number, boolean, json
category String // general, company, ai, display
label String? // Человекочитаемое название
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// Сессии чата (у каждого пользователя свои контексты ИИ)
model ChatSession {
id String @id @default(uuid())
userId String // Владелец сессии
estimateId String? // Связанная смета (опционально)
status String @default("active") // active, completed
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
messages ChatMessage[]
@@index([userId])
@@index([userId, estimateId])
}
// Сообщения чата
model ChatMessage {
id String @id @default(uuid())
sessionId String
role String // user, assistant, system
content String
metadata Json? // Доп. данные (извлеченные сущности и т.д.)
createdAt DateTime @default(now())
session ChatSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
@@index([sessionId, createdAt])
}

2
backend/prisma/seed.d.ts vendored Executable file
View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=seed.d.ts.map

1
backend/prisma/seed.d.ts.map Executable file
View File

@@ -0,0 +1 @@
{"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["seed.ts"],"names":[],"mappings":""}

248
backend/prisma/seed.js Executable file
View File

@@ -0,0 +1,248 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const client_1 = require("@prisma/client");
const bcrypt = __importStar(require("bcrypt"));
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const prisma = new client_1.PrismaClient();
async function main() {
console.log('Starting database seed...');
// Clear existing data (order: children first)
await prisma.chatMessage.deleteMany();
await prisma.chatSession.deleteMany();
await prisma.estimateShare.deleteMany();
await prisma.estimateTotal.deleteMany();
await prisma.estimateItem.deleteMany();
await prisma.estimate.deleteMany();
await prisma.user.deleteMany();
await prisma.priceItem.deleteMany();
await prisma.priceTable.deleteMany();
await prisma.priceBook.deleteMany();
await prisma.coefficient.deleteMany();
await prisma.inflationIndex.deleteMany();
await prisma.surveyDirection.deleteMany();
await prisma.setting.deleteMany();
console.log('Cleared existing data');
// Admin user (логин: its@info.ru, пароль: Nemo348ax@)
const adminPasswordHash = await bcrypt.hash('Nemo348ax@', 10);
await prisma.user.create({
data: {
email: 'its@info.ru',
passwordHash: adminPasswordHash,
name: 'Админ',
},
});
console.log('Created admin user (its@info.ru / Nemo348ax@)');
// Demo user for development (email: demo@example.com, password: demo)
const demoPasswordHash = await bcrypt.hash('demo', 10);
await prisma.user.create({
data: {
email: 'demo@example.com',
passwordHash: demoPasswordHash,
name: 'Демо',
},
});
console.log('Created demo user (demo@example.com / demo)');
// Seed Survey Directions
const directions = await Promise.all([
prisma.surveyDirection.create({
data: {
code: 'geodesy',
name: 'Инженерно-геодезические изыскания',
shortName: 'Геодезия',
},
}),
prisma.surveyDirection.create({
data: {
code: 'geology',
name: 'Инженерно-геологические изыскания',
shortName: 'Геология',
},
}),
prisma.surveyDirection.create({
data: {
code: 'ecology',
name: 'Инженерно-экологические изыскания',
shortName: 'Экология',
},
}),
prisma.surveyDirection.create({
data: {
code: 'hydrology',
name: 'Инженерно-гидрометеорологические изыскания',
shortName: 'Гидрометеорология',
},
}),
]);
console.log(`Created ${directions.length} survey directions`);
// Seed Inflation Indices
const inflationIndices = await Promise.all([
prisma.inflationIndex.create({
data: {
baseDate: new Date('1991-01-01'),
effectiveFrom: new Date('2015-08-13'),
indexValue: new client_1.Prisma.Decimal(76.24),
documentRef: 'Письмо Минстроя и ЖКХ России № 25760-ЮР/08 от 13.08.2015г.',
isActive: true,
},
}),
prisma.inflationIndex.create({
data: {
baseDate: new Date('2001-01-01'),
effectiveFrom: new Date('2015-08-13'),
indexValue: new client_1.Prisma.Decimal(6.70),
documentRef: 'Письмо Минстроя и ЖКХ России № 25760-ЮР/08 от 13.08.2015г.',
isActive: true,
},
}),
]);
console.log(`Created ${inflationIndices.length} inflation indices`);
// Seed Coefficients
const coefficients = [
// Regional coefficients
{ type: 'regional', code: 'reg-1.08', name: 'Районный коэффициент 1.08', value: 1.08, description: 'К заработной плате 1.15' },
{ type: 'regional', code: 'reg-1.10', name: 'Районный коэффициент 1.10', value: 1.10, description: 'К заработной плате 1.20' },
{ type: 'regional', code: 'reg-1.15', name: 'Районный коэффициент 1.15', value: 1.15, description: 'К заработной плате 1.30' },
// Company coefficients
{ type: 'company', code: 'gazprom-544', name: 'Коэффициент ОАО «Газпром»', value: 1.00, description: '№544 от 26.12.2013 г.' },
{ type: 'company', code: 'geovector', name: 'Коэффициент ООО «ГеоВектор»', value: 0.2092, description: 'Коэффициент компании' },
// Special coefficients
{ type: 'special', code: 'computer-tech', name: 'Компьютерные технологии', value: 1.20, description: 'При выполнении камеральных работ с применением компьютерных технологий' },
{ type: 'special', code: 'night-work', name: 'Ночные работы', value: 1.35, description: 'При работе в ночное время (22:00-06:00)' },
{ type: 'special', code: 'special-regime', name: 'Специальный режим', value: 1.25, description: 'Территории со специальным режимом' },
];
for (const coef of coefficients) {
await prisma.coefficient.create({
data: {
type: coef.type,
code: coef.code,
name: coef.name,
value: new client_1.Prisma.Decimal(coef.value),
description: coef.description,
},
});
}
console.log(`Created ${coefficients.length} coefficients`);
// Seed Settings
const settings = [
{ key: 'default_executor', value: 'ООО "ГеоВектор"', type: 'string', category: 'company', label: 'Исполнитель по умолчанию' },
{ key: 'default_vat_rate', value: '20', type: 'number', category: 'company', label: 'Ставка НДС по умолчанию (%)' },
{ key: 'company_coefficient', value: '0.2092', type: 'number', category: 'company', label: 'Коэффициент компании' },
{ key: 'ai_provider', value: 'iieasy', type: 'string', category: 'ai', label: 'AI провайдер' },
{ key: 'iieasy_model', value: 'google/gemma-3n-e4b', type: 'string', category: 'ai', label: 'Модель iieasy' },
{ key: 'lmstudio_url', value: 'http://localhost:1234/v1', type: 'string', category: 'ai', label: 'URL LM Studio' },
];
for (const setting of settings) {
await prisma.setting.create({ data: setting });
}
console.log(`Created ${settings.length} settings`);
// Load and seed price books from JSON files
const dataDir = path.join(__dirname, '../../data/price-books');
// Load Geodesy price book
const geodesyPath = path.join(dataDir, 'geodesy-2004.json');
if (fs.existsSync(geodesyPath)) {
const geodesyData = JSON.parse(fs.readFileSync(geodesyPath, 'utf-8'));
await seedPriceBook(geodesyData);
console.log('Loaded geodesy-2004 price book');
}
// Load Geology price book
const geologyPath = path.join(dataDir, 'geology-ecology-1999.json');
if (fs.existsSync(geologyPath)) {
const geologyData = JSON.parse(fs.readFileSync(geologyPath, 'utf-8'));
await seedPriceBook(geologyData);
console.log('Loaded geology-ecology-1999 price book');
}
console.log('Database seeding completed!');
}
async function seedPriceBook(data) {
const priceBook = await prisma.priceBook.create({
data: {
code: data.priceBook.code,
name: data.priceBook.name,
baseDate: new Date(data.priceBook.baseDate),
approvedBy: data.priceBook.approvedBy,
effectiveDate: data.priceBook.effectiveDate ? new Date(data.priceBook.effectiveDate) : null,
},
});
for (const table of data.tables) {
const priceTable = await prisma.priceTable.create({
data: {
priceBookId: priceBook.id,
tableNumber: table.tableNumber,
name: table.name,
unit: table.unit,
notes: table.notes || null,
},
});
for (const item of table.items) {
await prisma.priceItem.create({
data: {
priceBookId: priceBook.id,
priceTableId: priceTable.id,
paragraph: item.paragraph,
workType: item.workType || item.networkType || item.type || `Таблица ${table.tableNumber}`,
description: item.description || null,
priceField1: item.category1Field ?? item.cat1 ?? item.undevelopedField ?? null,
priceOffice1: item.category1Office ?? item.undevelopedOffice ?? null,
priceField2: item.category2Field ?? item.cat2 ?? item.builtUpField ?? null,
priceOffice2: item.category2Office ?? item.builtUpOffice ?? null,
priceField3: item.category3Field ?? item.cat3 ?? item.industrialField ?? null,
priceOffice3: item.category3Office ?? item.industrialOffice ?? null,
priceSimple: item.price ?? item.fieldPrice ?? null,
attributes: {
scale: item.scale,
category: item.category,
reliefHeight: item.reliefHeight,
depth: item.depth,
diameter: item.diameter,
accuracy: item.accuracy,
passability: item.passability,
...item,
},
},
});
}
}
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
//# sourceMappingURL=seed.js.map

1
backend/prisma/seed.js.map Executable file

File diff suppressed because one or more lines are too long

238
backend/prisma/seed.ts Executable file
View File

@@ -0,0 +1,238 @@
import { PrismaClient, Prisma } from '@prisma/client';
import * as bcrypt from 'bcrypt';
import * as fs from 'fs';
import * as path from 'path';
const prisma = new PrismaClient();
async function main() {
console.log('Starting database seed...');
// Clear existing data (order: children first)
await prisma.chatMessage.deleteMany();
await prisma.chatSession.deleteMany();
await prisma.estimateShare.deleteMany();
await prisma.estimateTotal.deleteMany();
await prisma.estimateItem.deleteMany();
await prisma.estimate.deleteMany();
await prisma.user.deleteMany();
await prisma.priceItem.deleteMany();
await prisma.priceTable.deleteMany();
await prisma.priceBook.deleteMany();
await prisma.coefficient.deleteMany();
await prisma.inflationIndex.deleteMany();
await prisma.surveyDirection.deleteMany();
await prisma.setting.deleteMany();
console.log('Cleared existing data');
// Admin user (логин: its@info.ru, пароль: Nemo348ax@)
const adminPasswordHash = await bcrypt.hash('Nemo348ax@', 10);
await prisma.user.create({
data: {
email: 'its@info.ru',
passwordHash: adminPasswordHash,
name: 'Админ',
},
});
console.log('Created admin user (its@info.ru / Nemo348ax@)');
// Demo user for development (email: demo@example.com, password: demo)
const demoPasswordHash = await bcrypt.hash('demo', 10);
await prisma.user.create({
data: {
email: 'demo@example.com',
passwordHash: demoPasswordHash,
name: 'Демо',
},
});
console.log('Created demo user (demo@example.com / demo)');
// Seed Survey Directions
const directions = await Promise.all([
prisma.surveyDirection.create({
data: {
code: 'geodesy',
name: 'Инженерно-геодезические изыскания',
shortName: 'Геодезия',
},
}),
prisma.surveyDirection.create({
data: {
code: 'geology',
name: 'Инженерно-геологические изыскания',
shortName: 'Геология',
},
}),
prisma.surveyDirection.create({
data: {
code: 'ecology',
name: 'Инженерно-экологические изыскания',
shortName: 'Экология',
},
}),
prisma.surveyDirection.create({
data: {
code: 'hydrology',
name: 'Инженерно-гидрометеорологические изыскания',
shortName: 'Гидрометеорология',
},
}),
]);
console.log(`Created ${directions.length} survey directions`);
// Seed Inflation Indices
const inflationIndices = await Promise.all([
prisma.inflationIndex.create({
data: {
baseDate: new Date('1991-01-01'),
effectiveFrom: new Date('2015-08-13'),
indexValue: new Prisma.Decimal(76.24),
documentRef: 'Письмо Минстроя и ЖКХ России № 25760-ЮР/08 от 13.08.2015г.',
isActive: true,
},
}),
prisma.inflationIndex.create({
data: {
baseDate: new Date('2001-01-01'),
effectiveFrom: new Date('2015-08-13'),
indexValue: new Prisma.Decimal(6.70),
documentRef: 'Письмо Минстроя и ЖКХ России № 25760-ЮР/08 от 13.08.2015г.',
isActive: true,
},
}),
]);
console.log(`Created ${inflationIndices.length} inflation indices`);
// Seed Coefficients
const coefficients = [
// Regional coefficients
{ type: 'regional', code: 'reg-1.08', name: 'Районный коэффициент 1.08', value: 1.08, description: 'К заработной плате 1.15' },
{ type: 'regional', code: 'reg-1.10', name: 'Районный коэффициент 1.10', value: 1.10, description: 'К заработной плате 1.20' },
{ type: 'regional', code: 'reg-1.15', name: 'Районный коэффициент 1.15', value: 1.15, description: 'К заработной плате 1.30' },
// Company coefficients
{ type: 'company', code: 'gazprom-544', name: 'Коэффициент ОАО «Газпром»', value: 1.00, description: '№544 от 26.12.2013 г.' },
{ type: 'company', code: 'geovector', name: 'Коэффициент ООО «ГеоВектор»', value: 0.2092, description: 'Коэффициент компании' },
// Special coefficients
{ type: 'special', code: 'computer-tech', name: 'Компьютерные технологии', value: 1.20, description: 'При выполнении камеральных работ с применением компьютерных технологий' },
{ type: 'special', code: 'night-work', name: 'Ночные работы', value: 1.35, description: 'При работе в ночное время (22:00-06:00)' },
{ type: 'special', code: 'special-regime', name: 'Специальный режим', value: 1.25, description: 'Территории со специальным режимом' },
];
for (const coef of coefficients) {
await prisma.coefficient.create({
data: {
type: coef.type,
code: coef.code,
name: coef.name,
value: new Prisma.Decimal(coef.value),
description: coef.description,
},
});
}
console.log(`Created ${coefficients.length} coefficients`);
// Seed Settings
const settings = [
{ key: 'default_executor', value: 'ООО "ГеоВектор"', type: 'string', category: 'company', label: 'Исполнитель по умолчанию' },
{ key: 'default_vat_rate', value: '20', type: 'number', category: 'company', label: 'Ставка НДС по умолчанию (%)' },
{ key: 'company_coefficient', value: '0.2092', type: 'number', category: 'company', label: 'Коэффициент компании' },
{ key: 'ai_provider', value: 'iieasy', type: 'string', category: 'ai', label: 'AI провайдер' },
{ key: 'iieasy_model', value: 'google/gemma-3n-e4b', type: 'string', category: 'ai', label: 'Модель iieasy' },
{ key: 'lmstudio_url', value: 'http://localhost:1234/v1', type: 'string', category: 'ai', label: 'URL LM Studio' },
];
for (const setting of settings) {
await prisma.setting.create({ data: setting });
}
console.log(`Created ${settings.length} settings`);
// Load and seed price books from JSON files
const dataDir = path.join(__dirname, '../../data/price-books');
// Load Geodesy price book
const geodesyPath = path.join(dataDir, 'geodesy-2004.json');
if (fs.existsSync(geodesyPath)) {
const geodesyData = JSON.parse(fs.readFileSync(geodesyPath, 'utf-8'));
await seedPriceBook(geodesyData);
console.log('Loaded geodesy-2004 price book');
}
// Load Geology price book
const geologyPath = path.join(dataDir, 'geology-ecology-1999.json');
if (fs.existsSync(geologyPath)) {
const geologyData = JSON.parse(fs.readFileSync(geologyPath, 'utf-8'));
await seedPriceBook(geologyData);
console.log('Loaded geology-ecology-1999 price book');
}
console.log('Database seeding completed!');
}
async function seedPriceBook(data: any) {
const priceBook = await prisma.priceBook.create({
data: {
code: data.priceBook.code,
name: data.priceBook.name,
baseDate: new Date(data.priceBook.baseDate),
approvedBy: data.priceBook.approvedBy,
effectiveDate: data.priceBook.effectiveDate ? new Date(data.priceBook.effectiveDate) : null,
},
});
for (const table of data.tables) {
const priceTable = await prisma.priceTable.create({
data: {
priceBookId: priceBook.id,
tableNumber: table.tableNumber,
name: table.name,
unit: table.unit,
notes: table.notes || null,
},
});
for (const item of table.items) {
await prisma.priceItem.create({
data: {
priceBookId: priceBook.id,
priceTableId: priceTable.id,
paragraph: item.paragraph,
workType: item.workType || item.networkType || item.type || `Таблица ${table.tableNumber}`,
description: item.description || null,
priceField1: item.category1Field ?? item.cat1 ?? item.undevelopedField ?? null,
priceOffice1: item.category1Office ?? item.undevelopedOffice ?? null,
priceField2: item.category2Field ?? item.cat2 ?? item.builtUpField ?? null,
priceOffice2: item.category2Office ?? item.builtUpOffice ?? null,
priceField3: item.category3Field ?? item.cat3 ?? item.industrialField ?? null,
priceOffice3: item.category3Office ?? item.industrialOffice ?? null,
priceSimple: item.price ?? item.fieldPrice ?? null,
attributes: {
scale: item.scale,
category: item.category,
reliefHeight: item.reliefHeight,
depth: item.depth,
diameter: item.diameter,
accuracy: item.accuracy,
passability: item.passability,
...item,
},
},
});
}
}
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});