Files
sfera/src/services/dadata-service.ts

289 lines
7.8 KiB
TypeScript
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 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
}
}