Добавлены новые зависимости, обновлены стили и улучшена структура проекта. Обновлен README с описанием функционала и технологий. Реализована анимация и адаптивный дизайн. Настроена авторизация с использованием Apollo Client.

This commit is contained in:
Bivekich
2025-07-16 18:00:41 +03:00
parent d260749bc9
commit 823ef9a28c
69 changed files with 15539 additions and 210 deletions

View File

@ -0,0 +1,289 @@
import axios from 'axios'
export interface DaDataCompany {
value: string
unrestricted_value: string
data: {
kpp?: string
management?: {
name?: string
post?: string
}
hid: string
type: string
state?: {
status?: string
actuality_date?: number
registration_date?: number
liquidation_date?: number
}
opf?: {
code?: string
full?: string
short?: string
}
name: {
full_with_opf?: string
short_with_opf?: string
full?: string
short?: string
}
inn: string
ogrn?: string
ogrn_date?: number
okpo?: string
okato?: string
oktmo?: string
okved?: string
employee_count?: number
phones?: object[]
emails?: object[]
finance?: {
revenue?: number
tax_system?: string
}
address?: {
value?: string
unrestricted_value?: string
data?: {
region_with_type?: string
city_with_type?: string
}
}
}
}
interface DaDataResponse {
suggestions: DaDataCompany[]
}
export interface OrganizationData {
inn: string
kpp?: string
name: string
fullName: string
address: string
addressFull?: string
ogrn?: string
ogrnDate?: Date
isActive: boolean
type: 'FULFILLMENT' | 'SELLER'
// Статус организации
status?: string
actualityDate?: Date
registrationDate?: Date
liquidationDate?: Date
// Руководитель
managementName?: string
managementPost?: string
// ОПФ
opfCode?: string
opfFull?: string
opfShort?: string
// Коды статистики
okato?: string
oktmo?: string
okpo?: string
okved?: string
// Контакты
phones?: object[]
emails?: object[]
// Финансовые данные
employeeCount?: number
revenue?: bigint
taxSystem?: string
rawData: DaDataCompany
}
export class DaDataService {
private apiKey: string
private apiUrl: string
constructor() {
this.apiKey = process.env.DADATA_API_KEY!
this.apiUrl = process.env.DADATA_API_URL!
if (!this.apiKey || !this.apiUrl) {
throw new Error('DaData API credentials not configured')
}
}
/**
* Получает информацию об организации по ИНН
*/
async getOrganizationByInn(inn: string): Promise<OrganizationData | null> {
try {
const response = await axios.post<DaDataResponse>(
`${this.apiUrl}/findById/party`,
{
query: inn,
count: 1
},
{
headers: {
'Authorization': `Token ${this.apiKey}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}
)
if (!response.data?.suggestions?.length) {
return null
}
const company = response.data.suggestions[0]
// Определяем тип организации на основе ОПФ
const organizationType = this.determineOrganizationType(company)
return {
inn: company.data.inn,
kpp: company.data.kpp || undefined,
name: company.data.name.short || company.data.name.full || 'Название не указано',
fullName: company.data.name.full_with_opf || '',
address: company.data.address?.value || '',
addressFull: company.data.address?.unrestricted_value || undefined,
ogrn: company.data.ogrn || undefined,
ogrnDate: this.parseDate(company.data.ogrn_date),
// Статус организации
status: company.data.state?.status,
actualityDate: this.parseDate(company.data.state?.actuality_date),
registrationDate: this.parseDate(company.data.state?.registration_date),
liquidationDate: this.parseDate(company.data.state?.liquidation_date),
// Руководитель
managementName: company.data.management?.name,
managementPost: company.data.management?.post,
// ОПФ
opfCode: company.data.opf?.code,
opfFull: company.data.opf?.full,
opfShort: company.data.opf?.short,
// Коды статистики
okato: company.data.okato,
oktmo: company.data.oktmo,
okpo: company.data.okpo,
okved: company.data.okved,
// Контакты
phones: company.data.phones || undefined,
emails: company.data.emails || undefined,
// Финансовые данные
employeeCount: company.data.employee_count || undefined,
revenue: company.data.finance?.revenue ? BigInt(company.data.finance.revenue) : undefined,
taxSystem: company.data.finance?.tax_system || undefined,
isActive: company.data.state?.status === 'ACTIVE',
type: organizationType,
rawData: company
}
} catch (error) {
console.error('Error fetching organization data from DaData:', error)
return null
}
}
/**
* Безопасно парсит дату из timestamp, возвращает undefined для некорректных дат
*/
private parseDate(timestamp?: number): Date | undefined {
if (!timestamp) return undefined
try {
const date = new Date(timestamp * 1000)
// Проверяем, что дата валидна и разумна (между 1900 и 2100 годами)
if (isNaN(date.getTime()) || date.getFullYear() < 1900 || date.getFullYear() > 2100) {
return undefined
}
return date
} catch {
return undefined
}
}
/**
* Определяет тип организации на основе ОПФ (организационно-правовая форма)
*/
private determineOrganizationType(company: DaDataCompany): 'FULFILLMENT' | 'SELLER' {
const opfCode = company.data.opf?.code
// Индивидуальные предприниматели чаще работают как селлеры
if (company.data.type === 'INDIVIDUAL' || opfCode === '50102') {
return 'SELLER'
}
// ООО, АО и другие юридические лица чаще работают с фулфилментом
return 'FULFILLMENT'
}
/**
* Валидирует ИНН по контрольной сумме
*/
validateInn(inn: string): boolean {
const digits = inn.replace(/\D/g, '')
if (digits.length !== 10 && digits.length !== 12) {
return false
}
// Проверяем контрольную сумму для 10-значного ИНН (юридические лица)
if (digits.length === 10) {
const checksum = this.calculateInn10Checksum(digits)
return checksum === parseInt(digits[9])
}
// Проверяем контрольную сумму для 12-значного ИНН (ИП)
if (digits.length === 12) {
const checksum1 = this.calculateInn12Checksum1(digits)
const checksum2 = this.calculateInn12Checksum2(digits)
return checksum1 === parseInt(digits[10]) && checksum2 === parseInt(digits[11])
}
return false
}
private calculateInn10Checksum(inn: string): number {
const weights = [2, 4, 10, 3, 5, 9, 4, 6, 8]
let sum = 0
for (let i = 0; i < 9; i++) {
sum += parseInt(inn[i]) * weights[i]
}
return sum % 11 % 10
}
private calculateInn12Checksum1(inn: string): number {
const weights = [7, 2, 4, 10, 3, 5, 9, 4, 6, 8]
let sum = 0
for (let i = 0; i < 10; i++) {
sum += parseInt(inn[i]) * weights[i]
}
return sum % 11 % 10
}
private calculateInn12Checksum2(inn: string): number {
const weights = [3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8]
let sum = 0
for (let i = 0; i < 11; i++) {
sum += parseInt(inn[i]) * weights[i]
}
return sum % 11 % 10
}
}