Files
clientsflow/Frontend/prisma/seed.mjs
2026-02-18 13:56:35 +07:00

354 lines
12 KiB
JavaScript
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 { PrismaClient } from "@prisma/client";
import fs from "node:fs";
import path from "node:path";
function loadEnvFromDotEnv() {
const p = path.resolve(process.cwd(), ".env");
if (!fs.existsSync(p)) return;
const raw = fs.readFileSync(p, "utf8");
for (const line of raw.split("\n")) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith("#")) continue;
const idx = trimmed.indexOf("=");
if (idx === -1) continue;
const key = trimmed.slice(0, idx).trim();
let val = trimmed.slice(idx + 1).trim();
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
val = val.slice(1, -1);
}
if (!key) continue;
// Force DATABASE_URL from local .env for scripts, to avoid inheriting a stale shell env.
if (key === "DATABASE_URL") {
process.env[key] = val;
continue;
}
if (!process.env[key]) process.env[key] = val;
}
}
loadEnvFromDotEnv();
const prisma = new PrismaClient();
function atOffset(days, hour, minute) {
const d = new Date();
d.setDate(d.getDate() + days);
d.setHours(hour, minute, 0, 0);
return d;
}
async function main() {
// Create default team/user for dev.
const user = await prisma.user.upsert({
where: { id: "demo-user" },
update: { email: "demo@clientsflow.local", name: "Demo User" },
create: { id: "demo-user", email: "demo@clientsflow.local", name: "Demo User" },
});
const team = await prisma.team.upsert({
where: { id: "demo-team" },
update: { name: "Demo Team" },
create: { id: "demo-team", name: "Demo Team" },
});
await prisma.teamMember.upsert({
where: { teamId_userId: { teamId: team.id, userId: user.id } },
update: {},
create: { teamId: team.id, userId: user.id, role: "OWNER" },
});
// Idempotent-ish seed per team: if we already have contacts in this team, do nothing.
const existing = await prisma.contact.count({ where: { teamId: team.id } });
if (existing > 0) return;
const contacts = await prisma.contact.createManyAndReturn({
data: [
{
teamId: team.id,
name: "Anna Meyer",
company: "Nordline GmbH",
country: "Germany",
location: "Berlin",
avatarUrl: "https://randomuser.me/api/portraits/women/44.jpg",
email: "anna@nordline.example",
phone: "+49 30 123 45 67",
},
{
teamId: team.id,
name: "Murat Ali",
company: "Connect FZCO",
country: "UAE",
location: "Dubai",
avatarUrl: "https://randomuser.me/api/portraits/men/32.jpg",
email: "murat@connect.example",
phone: "+971 50 123 4567",
},
{
teamId: team.id,
name: "Ilya Petroff",
company: "Volta Tech",
country: "Armenia",
location: "Yerevan",
avatarUrl: "https://randomuser.me/api/portraits/men/18.jpg",
email: "ilya@volta.example",
phone: "+374 10 123 456",
},
{
teamId: team.id,
name: "Carlos Rivera",
company: "BluePort",
country: "Spain",
location: "Barcelona",
avatarUrl: "https://randomuser.me/api/portraits/men/65.jpg",
email: "carlos@blueport.example",
phone: "+34 600 123 456",
},
{
teamId: team.id,
name: "Daria Ivanova",
company: "Skyline Trade",
country: "Kazakhstan",
location: "Almaty",
avatarUrl: "https://randomuser.me/api/portraits/women/22.jpg",
email: "daria@skyline.example",
phone: "+7 777 123 45 67",
},
],
});
const byName = Object.fromEntries(contacts.map((c) => [c.name, c]));
await prisma.contactNote.createMany({
data: [
{
contactId: byName["Anna Meyer"].id,
content:
"Decision owner. Prefers short, concrete updates with a clear next step.\nRisk: decision date slips if we don't lock timeline.",
},
{
contactId: byName["Murat Ali"].id,
content:
"High activity. Needs legal path clarity and an explicit owner on their side.\nBest move: lock legal owner + target signature date.",
},
{
contactId: byName["Ilya Petroff"].id,
content:
"Early-stage. Wants structured onboarding before commercial details.\nBest move: onboarding plan + 2 time slots.",
},
],
});
await prisma.contactMessage.createMany({
data: [
{
contactId: byName["Anna Meyer"].id,
kind: "MESSAGE",
direction: "IN",
channel: "TELEGRAM",
content: "Thanks for the demo. Can you send 2 pricing options?",
occurredAt: atOffset(0, 10, 20),
},
{
contactId: byName["Anna Meyer"].id,
kind: "MESSAGE",
direction: "OUT",
channel: "EMAIL",
content: "Sure. Option A/B attached. Can you confirm decision date for this cycle?",
occurredAt: atOffset(0, 10, 35),
},
{
contactId: byName["Murat Ali"].id,
kind: "MESSAGE",
direction: "IN",
channel: "WHATSAPP",
content: "Let's do a quick call. Need to clarify legal owner.",
occurredAt: atOffset(-1, 18, 10),
},
{
contactId: byName["Ilya Petroff"].id,
kind: "MESSAGE",
direction: "OUT",
channel: "EMAIL",
content: "Draft: onboarding plan + two slots for tomorrow.",
occurredAt: atOffset(-1, 11, 12),
},
{
contactId: byName["Murat Ali"].id,
kind: "CALL",
direction: "OUT",
channel: "PHONE",
content: "Call started from CRM",
durationSec: 180,
occurredAt: atOffset(-1, 18, 30),
},
],
});
await prisma.calendarEvent.createMany({
data: [
{
teamId: team.id,
contactId: byName["Anna Meyer"].id,
title: "Follow-up: Anna",
startsAt: atOffset(0, 12, 30),
endsAt: atOffset(0, 13, 0),
note: "Lock decision date + confirm option A/B.",
status: "planned",
},
{
teamId: team.id,
contactId: byName["Murat Ali"].id,
title: "Call: Murat (legal owner)",
startsAt: atOffset(0, 15, 0),
endsAt: atOffset(0, 15, 20),
note: "Confirm legal owner + target signature date.",
status: "planned",
},
],
});
await prisma.deal.createMany({
data: [
{
teamId: team.id,
contactId: byName["Anna Meyer"].id,
title: "Nordline onboarding",
stage: "Proposal",
amount: 25000,
nextStep: "Lock decision date",
summary: "After demo: pricing options sent; waiting for decision date.",
},
{
teamId: team.id,
contactId: byName["Murat Ali"].id,
title: "Connect legal alignment",
stage: "Qualification",
amount: 18000,
nextStep: "Confirm legal owner",
summary: "High engagement; needs legal owner on their side.",
},
],
});
await prisma.chatConversation.upsert({
where: { id: `pilot-${team.id}` },
update: {},
create: {
id: `pilot-${team.id}`,
teamId: team.id,
createdByUserId: user.id,
title: "Pilot",
},
});
await prisma.chatMessage.createMany({
data: [
{
teamId: team.id,
conversationId: `pilot-${team.id}`,
authorUserId: null,
role: "ASSISTANT",
text:
"Я смотрю календарь, контакты и переписки как один поток. Спроси: \"чем заняться сегодня\" или \"покажи 10 лучших клиентов\".",
planJson: { steps: ["Скажи задачу", соберу срез данных", "Предложу план и действия"], tools: ["read index/contacts.json"] },
},
],
});
await prisma.contactPin.createMany({
data: [
{ teamId: team.id, contactId: byName["Anna Meyer"].id, text: "First lock the decision date, then send the final offer." },
{ teamId: team.id, contactId: byName["Anna Meyer"].id, text: "A short follow-up is needed no later than 30 minutes after the demo." },
{ teamId: team.id, contactId: byName["Murat Ali"].id, text: "In every update, confirm the legal owner on the client side." },
{ teamId: team.id, contactId: byName["Ilya Petroff"].id, text: "Work through a structured onboarding plan, not pricing first." },
],
});
await prisma.workspaceDocument.createMany({
data: [
{
teamId: team.id,
title: "Outbound cadence v1",
type: "Regulation",
owner: "Revenue Ops",
scope: "All B2B accounts",
summary: "Unified sequence for first touch, follow-up, and qualification.",
body:
"## Goal\nMove a new contact to the first qualified call within 5 business days.\n\n## Base sequence\n- Day 0: first message in the primary channel.\n- Day 1: short follow-up with one clear ask.\n- Day 3: second follow-up + alternate channel.\n- Day 5: final ping and move to \"later\".\n\n## Rules\n- Always keep one explicit next step in each message.\n- Avoid long text walls.\n- After each reply, update context in the contact card.",
updatedAt: atOffset(-1, 10, 0),
},
{
teamId: team.id,
title: "Discovery call playbook",
type: "Playbook",
owner: "Sales Lead",
scope: "Discovery calls",
summary: "Call structure, mandatory questions, and outcome logging format.",
body:
"## Structure\n1. Context check (2 min)\n2. Current pain (8 min)\n3. Success criteria (6 min)\n4. Next step lock (4 min)\n\n## Mandatory outcomes\n- Confirmed business owner\n- Confirmed decision timeline\n- Confirmed next meeting date\n\n## Notes format\nAlways log: pain, impact, owner, ETA, risks.",
updatedAt: atOffset(-2, 12, 15),
},
{
teamId: team.id,
title: "AI assistant operating policy",
type: "Policy",
owner: "Founders",
scope: "AI recommendations and automations",
summary: "What actions AI can suggest and what requires explicit approval.",
body:
"## Allowed without approval\n- Draft message suggestions\n- Calendar proposal suggestions\n- Conversation summaries\n\n## Requires explicit approval\n- Sending a message to an external contact\n- Creating a calendar event\n- Changing deal stage\n\n## Logging\nEvery AI action must leave a short trace in the feed.",
updatedAt: atOffset(-3, 9, 40),
},
{
teamId: team.id,
title: "Post-call follow-up template",
type: "Template",
owner: "Enablement",
scope: "Any completed client call",
summary: "Template for short post-call follow-up with aligned actions.",
body:
"## Message template\nThanks for the call. Summary below:\n- What we aligned on\n- What remains open\n- Owner per action\n- Exact date for next sync\n\n## Quality bar\nThe client should understand the next step within 10 seconds.",
updatedAt: atOffset(-4, 16, 20),
},
],
});
await prisma.feedCard.createMany({
data: [
{
teamId: team.id,
contactId: byName["Anna Meyer"].id,
happenedAt: atOffset(0, 9, 35),
text:
"I analyzed Anna Meyer's latest activity: after a demo, the decision window is usually open for 1-2 hours. I suggest scheduling a follow-up immediately to keep momentum.",
proposalJson: {
title: "Add event to calendar",
details: ["Contact: Anna Meyer", "Start: 30 minutes from now", "Duration: 30 minutes"],
key: "create_followup",
},
},
{
teamId: team.id,
contactId: byName["Murat Ali"].id,
happenedAt: atOffset(0, 10, 8),
text:
"I found that Murat Ali gave 3 quick replies in a row over the last hour. I suggest moving to a short call now while engagement is high.",
proposalJson: {
title: "Start a call and open chat",
details: ["Contact: Murat Ali", "Channel: Phone", "After action: open the communication thread for this contact"],
key: "call",
},
},
],
});
}
main()
.catch((e) => {
console.error(e);
process.exitCode = 1;
})
.finally(async () => {
await prisma.$disconnect();
});