315 lines
11 KiB
JavaScript
315 lines
11 KiB
JavaScript
import 'dotenv/config';
|
||
|
||
import { prisma } from '../src/prisma-client.js';
|
||
|
||
const managerEmail = 'manager@fregat.local';
|
||
const clientEmail = 'client@fregat.local';
|
||
|
||
const PRODUCT_TYPES = {
|
||
STANDARD: 'стандартная лента',
|
||
PAINTING: 'малярный скотч',
|
||
};
|
||
|
||
const PRODUCT_ROWS = [
|
||
{ sku: '480200', productType: PRODUCT_TYPES.STANDARD, widthMm: 48, lengthM: 40, thicknessMicron: 38, sleeveBrand: 'фрегат', quantityPerBox: '72' },
|
||
{ sku: '481200', productType: PRODUCT_TYPES.STANDARD, widthMm: 48, lengthM: 55, thicknessMicron: 38, sleeveBrand: 'фрегат', quantityPerBox: '72/36' },
|
||
{ sku: '482200', productType: PRODUCT_TYPES.STANDARD, widthMm: 48, lengthM: 66, thicknessMicron: 38, sleeveBrand: 'фрегат', quantityPerBox: '36' },
|
||
{ sku: '483200', productType: PRODUCT_TYPES.STANDARD, widthMm: 48, lengthM: 120, thicknessMicron: 38, sleeveBrand: 'фрегат', quantityPerBox: '36' },
|
||
{ sku: '751200', productType: PRODUCT_TYPES.STANDARD, widthMm: 75, lengthM: 55, thicknessMicron: 38, sleeveBrand: 'фрегат', quantityPerBox: '48' },
|
||
{ sku: '752200', productType: PRODUCT_TYPES.STANDARD, widthMm: 75, lengthM: 66, thicknessMicron: 38, sleeveBrand: 'фрегат', quantityPerBox: '24' },
|
||
{ sku: '753200', productType: PRODUCT_TYPES.STANDARD, widthMm: 75, lengthM: 120, thicknessMicron: 38, sleeveBrand: 'фрегат', quantityPerBox: '24' },
|
||
{ sku: '481400', productType: PRODUCT_TYPES.STANDARD, widthMm: 48, lengthM: 55, thicknessMicron: 43, sleeveBrand: 'фрегат', quantityPerBox: '72/36' },
|
||
{ sku: '482400', productType: PRODUCT_TYPES.STANDARD, widthMm: 48, lengthM: 66, thicknessMicron: 43, sleeveBrand: 'фрегат', quantityPerBox: '36' },
|
||
{ sku: '483400', productType: PRODUCT_TYPES.STANDARD, widthMm: 48, lengthM: 120, thicknessMicron: 43, sleeveBrand: 'фрегат', quantityPerBox: '36' },
|
||
{ sku: '751400', productType: PRODUCT_TYPES.STANDARD, widthMm: 75, lengthM: 55, thicknessMicron: 43, sleeveBrand: 'фрегат', quantityPerBox: '48' },
|
||
{ sku: '752400', productType: PRODUCT_TYPES.STANDARD, widthMm: 75, lengthM: 66, thicknessMicron: 43, sleeveBrand: 'фрегат', quantityPerBox: '24' },
|
||
{ sku: '753400', productType: PRODUCT_TYPES.STANDARD, widthMm: 75, lengthM: 120, thicknessMicron: 43, sleeveBrand: 'фрегат', quantityPerBox: '24' },
|
||
{ sku: '481500', productType: PRODUCT_TYPES.STANDARD, widthMm: 48, lengthM: 55, thicknessMicron: 45, sleeveBrand: 'фрегат', quantityPerBox: '72/36' },
|
||
{ sku: '482500', productType: PRODUCT_TYPES.STANDARD, widthMm: 48, lengthM: 66, thicknessMicron: 45, sleeveBrand: 'фрегат', quantityPerBox: '36' },
|
||
{ sku: '483500', productType: PRODUCT_TYPES.STANDARD, widthMm: 48, lengthM: 120, thicknessMicron: 45, sleeveBrand: 'фрегат', quantityPerBox: '36' },
|
||
{ sku: '487500', productType: PRODUCT_TYPES.STANDARD, widthMm: 48, lengthM: 150, thicknessMicron: 45, sleeveBrand: 'фрегат', quantityPerBox: '36' },
|
||
{ sku: '751500', productType: PRODUCT_TYPES.STANDARD, widthMm: 75, lengthM: 55, thicknessMicron: 45, sleeveBrand: 'фрегат', quantityPerBox: '48' },
|
||
{ sku: '752500', productType: PRODUCT_TYPES.STANDARD, widthMm: 75, lengthM: 66, thicknessMicron: 45, sleeveBrand: 'фрегат', quantityPerBox: '24' },
|
||
{ sku: '743500', productType: PRODUCT_TYPES.STANDARD, widthMm: 75, lengthM: 120, thicknessMicron: 45, sleeveBrand: 'фрегат', quantityPerBox: '24' },
|
||
{ sku: '482600', productType: PRODUCT_TYPES.STANDARD, widthMm: 48, lengthM: 66, thicknessMicron: 47, sleeveBrand: 'фрегат', quantityPerBox: '36' },
|
||
{ sku: '483600', productType: PRODUCT_TYPES.STANDARD, widthMm: 48, lengthM: 120, thicknessMicron: 47, sleeveBrand: 'фрегат', quantityPerBox: '36' },
|
||
{ sku: '752600', productType: PRODUCT_TYPES.STANDARD, widthMm: 75, lengthM: 66, thicknessMicron: 47, sleeveBrand: 'фрегат', quantityPerBox: '24' },
|
||
{ sku: '753600', productType: PRODUCT_TYPES.STANDARD, widthMm: 75, lengthM: 120, thicknessMicron: 47, sleeveBrand: 'фрегат', quantityPerBox: '24' },
|
||
];
|
||
|
||
function assertUniqueProductRows(rows) {
|
||
const seen = new Map();
|
||
|
||
for (const row of rows) {
|
||
const signature = [
|
||
row.productType,
|
||
row.widthMm,
|
||
row.lengthM,
|
||
row.thicknessMicron,
|
||
row.sleeveBrand,
|
||
].join('|');
|
||
|
||
const duplicate = seen.get(signature);
|
||
if (duplicate) {
|
||
throw new Error(
|
||
`Duplicate product signature detected for ${row.sku} and ${duplicate.sku}: ${signature}`,
|
||
);
|
||
}
|
||
|
||
seen.set(signature, row);
|
||
}
|
||
}
|
||
|
||
function sentenceCase(value) {
|
||
if (!value) {
|
||
return value;
|
||
}
|
||
return value[0].toUpperCase() + value.slice(1);
|
||
}
|
||
|
||
function buildName(product) {
|
||
return [
|
||
sentenceCase(product.productType),
|
||
`${product.widthMm}x${product.lengthM} м`,
|
||
`${product.thicknessMicron} мкм`,
|
||
`втулка ${sentenceCase(product.sleeveBrand)}`,
|
||
`короб ${product.quantityPerBox}`,
|
||
].join(', ');
|
||
}
|
||
|
||
function baseQuantity(quantityPerBox) {
|
||
const firstValue = quantityPerBox.split('/')[0];
|
||
const parsed = Number.parseInt(firstValue, 10);
|
||
return Number.isFinite(parsed) && parsed > 0 ? parsed : 24;
|
||
}
|
||
|
||
const company = await prisma.company.upsert({
|
||
where: { inn: '7701000000' },
|
||
update: {},
|
||
create: {
|
||
name: 'Fregat Client LLC',
|
||
inn: '7701000000',
|
||
},
|
||
});
|
||
|
||
const manager = await prisma.user.upsert({
|
||
where: { email: managerEmail },
|
||
update: { fullName: 'Default Manager' },
|
||
create: {
|
||
email: managerEmail,
|
||
fullName: 'Default Manager',
|
||
role: 'MANAGER',
|
||
},
|
||
});
|
||
|
||
await prisma.user.upsert({
|
||
where: { email: clientEmail },
|
||
update: {
|
||
fullName: 'Demo Client',
|
||
companyId: company.id,
|
||
bonusProgramEnabled: true,
|
||
},
|
||
create: {
|
||
email: clientEmail,
|
||
fullName: 'Demo Client',
|
||
role: 'CLIENT',
|
||
bonusProgramEnabled: true,
|
||
companyId: company.id,
|
||
},
|
||
});
|
||
|
||
const warehouseMain = await prisma.warehouse.upsert({
|
||
where: { code: 'MSK-01' },
|
||
update: { name: 'Main warehouse' },
|
||
create: { code: 'MSK-01', name: 'Main warehouse' },
|
||
});
|
||
|
||
const warehouseReserve = await prisma.warehouse.upsert({
|
||
where: { code: 'SPB-01' },
|
||
update: { name: 'Reserve warehouse' },
|
||
create: { code: 'SPB-01', name: 'Reserve warehouse' },
|
||
});
|
||
|
||
const activeSkus = PRODUCT_ROWS.map((item) => item.sku);
|
||
assertUniqueProductRows(PRODUCT_ROWS);
|
||
|
||
await prisma.product.updateMany({
|
||
where: { sku: { notIn: activeSkus } },
|
||
data: { isActive: false },
|
||
});
|
||
|
||
for (const product of PRODUCT_ROWS) {
|
||
const qtyBase = baseQuantity(product.quantityPerBox);
|
||
const qtyMain = qtyBase * 10;
|
||
const qtyReserve = qtyBase * 5;
|
||
|
||
const dbProduct = await prisma.product.upsert({
|
||
where: { sku: product.sku },
|
||
update: {
|
||
name: buildName(product),
|
||
description: `Артикул: ${product.sku}`,
|
||
productType: product.productType,
|
||
widthMm: product.widthMm,
|
||
lengthM: product.lengthM,
|
||
thicknessMicron: product.thicknessMicron,
|
||
sleeveBrand: product.sleeveBrand,
|
||
quantityPerBox: product.quantityPerBox,
|
||
isCustomizable: false,
|
||
isActive: true,
|
||
},
|
||
create: {
|
||
sku: product.sku,
|
||
name: buildName(product),
|
||
description: `Артикул: ${product.sku}`,
|
||
productType: product.productType,
|
||
widthMm: product.widthMm,
|
||
lengthM: product.lengthM,
|
||
thicknessMicron: product.thicknessMicron,
|
||
sleeveBrand: product.sleeveBrand,
|
||
quantityPerBox: product.quantityPerBox,
|
||
isCustomizable: false,
|
||
isActive: true,
|
||
},
|
||
});
|
||
|
||
await prisma.productStock.upsert({
|
||
where: {
|
||
productId_warehouseId: {
|
||
productId: dbProduct.id,
|
||
warehouseId: warehouseMain.id,
|
||
},
|
||
},
|
||
update: { availableQty: qtyMain },
|
||
create: {
|
||
productId: dbProduct.id,
|
||
warehouseId: warehouseMain.id,
|
||
availableQty: qtyMain,
|
||
},
|
||
});
|
||
|
||
await prisma.productStock.upsert({
|
||
where: {
|
||
productId_warehouseId: {
|
||
productId: dbProduct.id,
|
||
warehouseId: warehouseReserve.id,
|
||
},
|
||
},
|
||
update: { availableQty: qtyReserve },
|
||
create: {
|
||
productId: dbProduct.id,
|
||
warehouseId: warehouseReserve.id,
|
||
availableQty: qtyReserve,
|
||
},
|
||
});
|
||
}
|
||
|
||
const client = await prisma.user.findUnique({
|
||
where: { email: clientEmail },
|
||
});
|
||
|
||
if (!client) {
|
||
throw new Error('Demo client was not created.');
|
||
}
|
||
|
||
const seededProducts = await prisma.product.findMany({
|
||
where: {
|
||
sku: {
|
||
in: ['480200', '482500', '753400', '752600'],
|
||
},
|
||
},
|
||
orderBy: { sku: 'asc' },
|
||
});
|
||
|
||
const existingOrderCodes = new Set(
|
||
(await prisma.order.findMany({
|
||
where: {
|
||
code: {
|
||
in: ['FRG-1001', 'FRG-1002', 'FRG-1003'],
|
||
},
|
||
},
|
||
select: { code: true },
|
||
})).map((order) => order.code),
|
||
);
|
||
|
||
const demoOrders = [
|
||
{
|
||
code: 'FRG-1001',
|
||
status: 'WAITING_DOUBLE_CONFIRM',
|
||
deliveryAddress: 'Москва, Ленинградский проспект, 37',
|
||
deliveryTerms: 'Самовывоз со склада после подтверждения',
|
||
totalPrice: '125000.00',
|
||
items: [
|
||
{ sku: '480200', quantity: '24.000' },
|
||
{ sku: '752600', quantity: '12.000' },
|
||
],
|
||
},
|
||
{
|
||
code: 'FRG-1002',
|
||
status: 'IN_PROGRESS',
|
||
deliveryAddress: 'Санкт-Петербург, Кубинская улица, 75к1',
|
||
deliveryTerms: 'Доставка транспортной компанией',
|
||
totalPrice: '84200.00',
|
||
items: [
|
||
{ sku: '482500', quantity: '18.000' },
|
||
],
|
||
},
|
||
{
|
||
code: 'FRG-1003',
|
||
status: 'COMPLETED',
|
||
deliveryAddress: 'Казань, улица Родины, 43',
|
||
deliveryTerms: 'Доставка до адреса клиента',
|
||
totalPrice: '156400.00',
|
||
items: [
|
||
{ sku: '753400', quantity: '30.000' },
|
||
{ sku: '482500', quantity: '10.000' },
|
||
],
|
||
},
|
||
];
|
||
|
||
for (const demoOrder of demoOrders) {
|
||
if (existingOrderCodes.has(demoOrder.code)) {
|
||
continue;
|
||
}
|
||
|
||
const order = await prisma.order.create({
|
||
data: {
|
||
code: demoOrder.code,
|
||
kind: 'READY',
|
||
status: demoOrder.status,
|
||
customerId: client.id,
|
||
managerId: manager.id,
|
||
deliveryAddress: demoOrder.deliveryAddress,
|
||
deliveryTerms: demoOrder.deliveryTerms,
|
||
totalPrice: demoOrder.totalPrice,
|
||
},
|
||
});
|
||
|
||
for (const item of demoOrder.items) {
|
||
const product = seededProducts.find((entry) => entry.sku === item.sku);
|
||
if (!product) {
|
||
throw new Error(`Product ${item.sku} not found for demo order ${demoOrder.code}.`);
|
||
}
|
||
|
||
await prisma.orderItem.create({
|
||
data: {
|
||
orderId: order.id,
|
||
productId: product.id,
|
||
productName: product.name,
|
||
quantity: item.quantity,
|
||
},
|
||
});
|
||
}
|
||
|
||
await prisma.orderStatusEvent.create({
|
||
data: {
|
||
orderId: order.id,
|
||
status: demoOrder.status,
|
||
actorUserId: manager.id,
|
||
note: 'Демо-заказ для отображения клиентского кабинета.',
|
||
},
|
||
});
|
||
}
|
||
|
||
console.log(`Seed complete with ${PRODUCT_ROWS.length} products. Use manager header x-user-id: ${manager.id}`);
|
||
await prisma.$disconnect();
|