Добавлены новые зависимости, обновлены стили и улучшена структура проекта. Обновлен README с описанием функционала и технологий. Реализована анимация и адаптивный дизайн. Настроена авторизация с использованием Apollo Client.
This commit is contained in:
289
src/services/dadata-service.ts
Normal file
289
src/services/dadata-service.ts
Normal 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
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user