Реализация реферальной системы и улучшение системы авторизации

- Добавлена полная реферальная система с GraphQL резолверами и UI компонентами
- Улучшена система регистрации с поддержкой ВКонтакте и реферальных ссылок
- Обновлена схема Prisma для поддержки реферальной системы
- Добавлены новые файлы документации правил системы
- Улучшена система партнерства и контрагентов
- Обновлены компоненты авторизации для поддержки новых функций
- Удален устаревший server.log

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Veronika Smirnova
2025-08-11 09:47:00 +03:00
parent af16402f22
commit 8f7ec70fe6
28 changed files with 5827 additions and 4313 deletions

View File

@ -9,13 +9,41 @@ import { MarketplaceService } from '@/services/marketplace-service'
import { SmsService } from '@/services/sms-service'
import { WildberriesService } from '@/services/wildberries-service'
import '@/lib/seed-init'; // Автоматическая инициализация БД
import '@/lib/seed-init' // Автоматическая инициализация БД
// Сервисы
const smsService = new SmsService()
const dadataService = new DaDataService()
const marketplaceService = new MarketplaceService()
// Функция генерации уникального реферального кода
const generateReferralCode = async (): Promise<string> => {
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'
let attempts = 0
const maxAttempts = 10
while (attempts < maxAttempts) {
let code = ''
for (let i = 0; i < 10; i++) {
code += chars.charAt(Math.floor(Math.random() * chars.length))
}
// Проверяем уникальность
const existing = await prisma.organization.findUnique({
where: { referralCode: code }
})
if (!existing) {
return code
}
attempts++
}
// Если не удалось сгенерировать уникальный код, используем cuid как fallback
return `REF${Date.now()}${Math.random().toString(36).substr(2, 5).toUpperCase()}`
}
// Интерфейсы для типизации
interface Context {
user?: {
@ -2023,6 +2051,53 @@ export const resolvers = {
return scheduleRecords
},
// Получить партнерскую ссылку текущего пользователя
myPartnerLink: async (_: unknown, __: unknown, context: Context) => {
if (!context.user?.organizationId) {
throw new GraphQLError('Требуется авторизация и организация', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
const organization = await prisma.organization.findUnique({
where: { id: context.user.organizationId },
select: { referralCode: true }
})
if (!organization?.referralCode) {
throw new GraphQLError('Реферальный код не найден')
}
return `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/register?partner=${organization.referralCode}`
},
// ВРЕМЕННЫЙ myReferralLink для отладки
myReferralLink: async (_: unknown, __: unknown, context: Context) => {
console.log('🔥 OLD RESOLVER - myReferralLink called!')
if (!context.user?.organizationId) {
console.log('❌ OLD RESOLVER - NO organizationId!')
throw new GraphQLError('Требуется авторизация и организация', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
const organization = await prisma.organization.findUnique({
where: { id: context.user.organizationId },
select: { referralCode: true }
})
if (!organization?.referralCode) {
console.log('❌ OLD RESOLVER - NO referralCode!')
throw new GraphQLError('Реферальный код не найден')
}
const link = `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/register?ref=${organization.referralCode}`
console.log('✅ OLD RESOLVER - Generated link:', link)
return link
},
},
Mutation: {
@ -2139,17 +2214,27 @@ export const resolvers = {
phone: string
inn: string
type: 'FULFILLMENT' | 'LOGIST' | 'WHOLESALE'
referralCode?: string
partnerCode?: string
}
},
context: Context,
) => {
console.log('🚀 registerFulfillmentOrganization called with:', {
inn: args.input.inn,
type: args.input.type,
referralCode: args.input.referralCode,
partnerCode: args.input.partnerCode,
userId: context.user?.id
})
if (!context.user) {
throw new GraphQLError('Требуется авторизация', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
const { inn, type } = args.input
const { inn, type, referralCode, partnerCode } = args.input
// Валидируем ИНН
if (!dadataService.validateInn(inn)) {
@ -2181,6 +2266,9 @@ export const resolvers = {
}
}
// Генерируем уникальный реферальный код
const generatedReferralCode = await generateReferralCode()
// Создаем организацию со всеми данными из DaData
const organization = await prisma.organization.create({
data: {
@ -2225,6 +2313,9 @@ export const resolvers = {
type: type,
dadataData: JSON.parse(JSON.stringify(organizationData.rawData)),
// Реферальная система - генерируем код автоматически
referralCode: generatedReferralCode,
},
})
@ -2241,6 +2332,106 @@ export const resolvers = {
},
})
// Обрабатываем реферальные коды
if (referralCode) {
try {
// Находим реферера по реферальному коду
const referrer = await prisma.organization.findUnique({
where: { referralCode: referralCode }
})
if (referrer) {
// Создаем реферальную транзакцию (100 сфер)
await prisma.referralTransaction.create({
data: {
referrerId: referrer.id,
referralId: organization.id,
points: 100,
type: 'REGISTRATION',
description: `Регистрация ${type.toLowerCase()} организации по реферальной ссылке`
}
})
// Увеличиваем счетчик сфер у реферера
await prisma.organization.update({
where: { id: referrer.id },
data: { referralPoints: { increment: 100 } }
})
// Устанавливаем связь реферала
await prisma.organization.update({
where: { id: organization.id },
data: { referredById: referrer.id }
})
}
} catch (error) {
console.warn('Error processing referral code:', error)
// Не прерываем регистрацию из-за ошибки реферальной системы
}
}
if (partnerCode) {
try {
console.log(`🔍 Processing partner code: ${partnerCode}`)
// Находим партнера по партнерскому коду
const partner = await prisma.organization.findUnique({
where: { referralCode: partnerCode }
})
console.log(`🏢 Partner found:`, partner ? `${partner.name} (${partner.id})` : 'NOT FOUND')
if (partner) {
// Создаем реферальную транзакцию (100 сфер)
await prisma.referralTransaction.create({
data: {
referrerId: partner.id,
referralId: organization.id,
points: 100,
type: 'AUTO_PARTNERSHIP',
description: `Регистрация ${type.toLowerCase()} организации по партнерской ссылке`
}
})
// Увеличиваем счетчик сфер у партнера
await prisma.organization.update({
where: { id: partner.id },
data: { referralPoints: { increment: 100 } }
})
// Устанавливаем связь реферала
await prisma.organization.update({
where: { id: organization.id },
data: { referredById: partner.id }
})
// Создаем партнерскую связь (автоматическое добавление в контрагенты)
await prisma.counterparty.create({
data: {
organizationId: partner.id,
counterpartyId: organization.id,
type: 'AUTO',
triggeredBy: 'PARTNER_LINK'
}
})
await prisma.counterparty.create({
data: {
organizationId: organization.id,
counterpartyId: partner.id,
type: 'AUTO',
triggeredBy: 'PARTNER_LINK'
}
})
console.log(`✅ Partnership created: ${organization.name} <-> ${partner.name}`)
}
} catch (error) {
console.warn('Error processing partner code:', error)
// Не прерываем регистрацию из-за ошибки партнерской системы
}
}
return {
success: true,
message: 'Организация успешно зарегистрирована',
@ -2263,17 +2454,28 @@ export const resolvers = {
wbApiKey?: string
ozonApiKey?: string
ozonClientId?: string
referralCode?: string
partnerCode?: string
}
},
context: Context,
) => {
console.log('🚀 registerSellerOrganization called with:', {
phone: args.input.phone,
hasWbApiKey: !!args.input.wbApiKey,
hasOzonApiKey: !!args.input.ozonApiKey,
referralCode: args.input.referralCode,
partnerCode: args.input.partnerCode,
userId: context.user?.id
})
if (!context.user) {
throw new GraphQLError('Требуется авторизация', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
const { wbApiKey, ozonApiKey, ozonClientId } = args.input
const { wbApiKey, ozonApiKey, ozonClientId, referralCode, partnerCode } = args.input
if (!wbApiKey && !ozonApiKey) {
return {
@ -2320,6 +2522,9 @@ export const resolvers = {
const tradeMark = validationResults[0]?.data?.tradeMark
const sellerName = validationResults[0]?.data?.sellerName
const shopName = tradeMark || sellerName || 'Магазин'
// Генерируем уникальный реферальный код
const generatedReferralCode = await generateReferralCode()
const organization = await prisma.organization.create({
data: {
@ -2327,6 +2532,9 @@ export const resolvers = {
name: shopName, // Используем tradeMark как основное название
fullName: sellerName ? `${sellerName} (${shopName})` : `Интернет-магазин "${shopName}"`,
type: 'SELLER',
// Реферальная система - генерируем код автоматически
referralCode: generatedReferralCode,
},
})
@ -2355,6 +2563,106 @@ export const resolvers = {
},
})
// Обрабатываем реферальные коды
if (referralCode) {
try {
// Находим реферера по реферальному коду
const referrer = await prisma.organization.findUnique({
where: { referralCode: referralCode }
})
if (referrer) {
// Создаем реферальную транзакцию (100 сфер)
await prisma.referralTransaction.create({
data: {
referrerId: referrer.id,
referralId: organization.id,
points: 100,
type: 'REGISTRATION',
description: 'Регистрация селлер организации по реферальной ссылке'
}
})
// Увеличиваем счетчик сфер у реферера
await prisma.organization.update({
where: { id: referrer.id },
data: { referralPoints: { increment: 100 } }
})
// Устанавливаем связь реферала
await prisma.organization.update({
where: { id: organization.id },
data: { referredById: referrer.id }
})
}
} catch (error) {
console.warn('Error processing referral code:', error)
// Не прерываем регистрацию из-за ошибки реферальной системы
}
}
if (partnerCode) {
try {
console.log(`🔍 Processing partner code: ${partnerCode}`)
// Находим партнера по партнерскому коду
const partner = await prisma.organization.findUnique({
where: { referralCode: partnerCode }
})
console.log(`🏢 Partner found:`, partner ? `${partner.name} (${partner.id})` : 'NOT FOUND')
if (partner) {
// Создаем реферальную транзакцию (100 сфер)
await prisma.referralTransaction.create({
data: {
referrerId: partner.id,
referralId: organization.id,
points: 100,
type: 'AUTO_PARTNERSHIP',
description: 'Регистрация селлер организации по партнерской ссылке'
}
})
// Увеличиваем счетчик сфер у партнера
await prisma.organization.update({
where: { id: partner.id },
data: { referralPoints: { increment: 100 } }
})
// Устанавливаем связь реферала
await prisma.organization.update({
where: { id: organization.id },
data: { referredById: partner.id }
})
// Создаем партнерскую связь (автоматическое добавление в контрагенты)
await prisma.counterparty.create({
data: {
organizationId: partner.id,
counterpartyId: organization.id,
type: 'AUTO',
triggeredBy: 'PARTNER_LINK'
}
})
await prisma.counterparty.create({
data: {
organizationId: organization.id,
counterpartyId: partner.id,
type: 'AUTO',
triggeredBy: 'PARTNER_LINK'
}
})
console.log(`✅ Partnership created: ${organization.name} <-> ${partner.name}`)
}
} catch (error) {
console.warn('Error processing partner code:', error)
// Не прерываем регистрацию из-за ошибки партнерской системы
}
}
return {
success: true,
message: 'Селлер организация успешно зарегистрирована',