diff --git a/data_providers/avtocod.js b/data_providers/avtocod.js index 0805a11..b599e58 100644 --- a/data_providers/avtocod.js +++ b/data_providers/avtocod.js @@ -47,7 +47,7 @@ function waitForReport(pubnubConfig, channel) { } else { reject(new Error('Request to avtocod timed out')); } - }, 20000); + }, 10000); let pubnub = new PubNub(pubnubConfig); pubnub.addListener({ @@ -93,7 +93,9 @@ class AvtocodProvider { if(report == null) { report = await waitForReport(mainData.pubnub, mainData.report_container.channel); } - return Vehicle.fromAvtocod(report); + let vehicle = Vehicle.fromAvtocod(report); + console.log('Avtocod found vehicle: ', vehicle?.brand?.name?.original); + return vehicle; } } diff --git a/data_providers/constants.js b/data_providers/constants.js index c8b70d0..1b22a8f 100644 --- a/data_providers/constants.js +++ b/data_providers/constants.js @@ -159,7 +159,7 @@ const typeOperation = { '91':'по наследству с заменой государственных регистрационных знаков', '92':'по наследству с сохранением государственных регистрационных знаков за новым собственником (наследником)', '93':'по сделкам, произведенным в любой форме (купля-продажа, дарение, др.) с заменой государственных регистрационных знаков', - '94':'по сделкам, произведенным в любой форме (купля-продажа, дарение, др.) с сохранением государственных регистрационных', + '94':'по сделкам, произведенным в любой форме (купля-продажа, дарение, др.) с сохранением государственных регистрационных знаков', '95':'учет прекращения действия водительского удостоверения', '96':'учет восстановления действия водительского удостоверения', '97':'учет приостановления действия права управления ТС по постановлению судебного пристава-исполнителя о временном', diff --git a/data_providers/vin01.js b/data_providers/vin01.js index 9bb8f91..9145506 100644 --- a/data_providers/vin01.js +++ b/data_providers/vin01.js @@ -2,7 +2,7 @@ const fetch = require('node-fetch'); const Vehicle = require('../models/vehicle'); const baseUrl = 'https://vin01.ru/v2'; -const reportBaseUrl = 'https://vin01.ru/api/gibddApp.php'; +const reportBaseUrl = `${baseUrl}/gibddApp.php`; class Vin01Provider { static async getVin(number, token) { @@ -16,35 +16,99 @@ class Vin01Provider { } } - static async getReport(number, token) { - let vin = await Vin01Provider.getVin(number, token); - console.log('vin01 found VIN: ', vin); + static async runCheck(type, vin, token) { let result = await fetch(reportBaseUrl, { method: 'POST', body: new URLSearchParams({ - typeCheckValue: 'history', + typeCheckValue: type, vinValue: vin, - key: token + key: token, + token: null }) }); - let json = await result.json(); + return await result.json(); + } - if(json.status == 200) { - if(json.data.status != 200) { - console.log('Vin01 failed to get GIBDD report with status: ', json.data.status, ', and error: ', json.data.message); - let vehicle = new Vehicle(); - vehicle.vin1 = vin; - return vehicle; + static checkErrors(result) { + if(result.status == 'fulfilled') { + let json = result.value; + if(json.status == 200) { + if('status' in json.data) { + if(json.data.status == 200) { + result.value = json.data; + return result; + } else { + return { status: 'rejected', reason: `[${json.data.status}] ${json.data.message}` }; + } + } else { + result.value = json.data; + return result; + } + } else { + return { status: 'rejected', reason: `[${json.status}] ${json.code}` }; } - console.log('vin01 found history'); - let vehicle = Vehicle.fromVin01(json.data); - vehicle.number = number; - return vehicle; } else { + return result; + } + } + + static async getReport(number, token) { + let vin = await Vin01Provider.getVin(number, token); + console.log('vin01 found VIN: ', vin); + + let checks = [Vin01Provider.runCheck('base', vin, token), Vin01Provider.runCheck('history', vin, token)]; + let [base, history] = (await Promise.allSettled(checks)).map(Vin01Provider.checkErrors); + if(base.status == 'rejected' && history.status == 'rejected') { + console.log('Vin01 base error: ', base.reason); + console.log('Vin01 history error: ', history.reason); let vehicle = new Vehicle(); vehicle.vin1 = vin; return vehicle; - //throw Error('Vin01 provider failed to get gibdd info'); + } else if(base.status == 'rejected') { + console.log('vin01 found history'); + let vehicle = Vehicle.fromVin01History(history.value); + vehicle.number = number; + return vehicle; + } else if(history.status == 'rejected') { + console.log('vin01 found base info'); + let vehicle = Vehicle.fromVin01Base(base.value); + vehicle.number = number; + return vehicle; + } else { + // Both history and base reports were successfully received, merge them in one report + let baseVehicle = Vehicle.fromVin01Base(base.value); + let historyVehicle = Vehicle.fromVin01History(history.value); + + historyVehicle.brand.name.normalized = baseVehicle.brand.name.normalized; + historyVehicle.model = baseVehicle.model; + + for(let period of historyVehicle.ownershipPeriods) { + let basePeriod = baseVehicle.ownershipPeriods.find(p => p.from == period.from); + if(basePeriod) { + period.region = basePeriod.region; + period.registrationRegion = basePeriod.registrationRegion; + period.locality = basePeriod.locality; + + if(basePeriod.street) { + period.street = basePeriod.street; + } + + if(basePeriod.building) { + period.building = basePeriod.building; + } + + if(basePeriod.inn) { + period.inn = basePeriod.inn; + } + + if(basePeriod.code) { + period.code = basePeriod.code; + } + } + } + + historyVehicle.number = number; + return historyVehicle; } } } diff --git a/models/vehicle.js b/models/vehicle.js index 3a85f7d..de185cc 100644 --- a/models/vehicle.js +++ b/models/vehicle.js @@ -63,7 +63,7 @@ class Vehicle { return v; } - static fromVin01(report) { + static fromVin01History(report) { let main = report.RequestResult.vehicle; let v = new Vehicle(); @@ -71,11 +71,11 @@ class Vehicle { v.category = main.category; v.engine = { number: main.engineNumber, - powerHp: main.powerHp, - powerKw: main.powerKwt, - volume: main.engineVolume + powerHp: parseFloat(main.powerHp), + powerKw: parseFloat(main.powerKwt), + volume: parseInt(main.engineVolume) }; - v.year = main.year; + v.year = parseInt(main.year, 10); v.vin1 = main.vin; v.vin2 = main.bodyNumber; v.color = main.color; @@ -98,6 +98,60 @@ class Vehicle { return v; } + + static fromVin01Base(report) { + let first = report[0]; + + let v = new Vehicle(); + v.brand = { name: { original: first.brandmark, normalized: first.brand } }; + v.model = { normalized: first.model }; + v.category = first.ts_category; + v.year = parseInt(first.year); + v.vin1 = first.vin; + v.vin2 = first.body; + + v.engine = { + powerHp: parseFloat(first.engine_power), + volume: parseInt(first.engine_volume), + number: first.engine + }; + + v.ownershipPeriods = report.map(p => { + let dateParams = p.last_operation_date.split('.').reverse().map(n => parseInt(n, 10)); + dateParams[1] -= 1; + let date = (new Date(...dateParams)).getTime(); + + let period = { + from: date, + to: 0, + region: p.region, + registrationRegion: p.ts_registration_region, + locality: p.ts_locality, + lastOperation: Constants.typeOperation[p.tech_operation_code] ?? p.tech_operation_code, + ownerType: p.owner_category == '2' ? 'individual' : 'legal', + code: p.code + }; + + if(p.street && p.street != '-') { + period.street = p.street; + } + + if(p.building && p.building != '-') { + period.building = p.building; + } + + if(p.entity_inn && p.entity_inn != '-') { + period.inn = p.entity_inn; + } + + return period; + }); + + v.addedDate = Date.now(); + v.events = []; + + return v; + } } module.exports = Vehicle; diff --git a/routes/vehicles.js b/routes/vehicles.js index 8620b07..ccadf49 100644 --- a/routes/vehicles.js +++ b/routes/vehicles.js @@ -32,29 +32,36 @@ router.post('/check', async (req, res) => { all.push(vin01Promise); } let [autocod, vin01] = await Promise.allSettled(all); - if(autocod.status == 'fulfilled') { - let vehicle = autocod.value; - console.log('autocod found vehicle: ', vehicle?.brand?.name?.original); - vehicle.addedBy = email; - if(vin01?.status == 'fulfilled') { - vehicle.vin1 = vin01.value.vin1; - if(vin01.value?.brand?.name?.original == vehicle?.brand?.name?.original) { - vehicle.vin2 = vin01.value.vin2; - vehicle.color = vin01.value.color; - vehicle.ownershipPeriods = vin01.value.ownershipPeriods; - } - } - // In case of force update of existing vehicle, transfer all events to the new DB record - if (vehicles.length > 0) { - vehicle.events = vehicles[0].events; - } - - await collection.replaceOne({ number }, vehicle, { upsert: true }); - res.status(201).send({ success: true, data: vehicle }); - } else { + let vehicle = null; + if(autocod.status == 'rejected' && vin01.status == 'rejected') { throw Error(autocod.reason); + } else if(vin01.status == 'rejected') { + vehicle = autocod.value; + } else if(autocod.status == 'rejected') { + vehicle = vin01.value; + if(!vehicle.brand.name.normalized) { + throw Error(autocod.reason); + } + } else { + vehicle = autocod.value; + if(vin01.value.vin1.match(RegExp(vehicle.vin1.replace(/\*/g, '.')))) { + vehicle.vin1 = vin01.value.vin1; + vehicle.vin2 = vin01.value.vin2; + vehicle.color = vin01.value.color; + vehicle.ownershipPeriods = vin01.value.ownershipPeriods; + } } + + vehicle.addedBy = email; + + // In case of force update of existing vehicle, transfer all events to the new DB record + if (vehicles.length > 0) { + vehicle.events = vehicles[0].events; + } + + await collection.replaceOne({ number }, vehicle, { upsert: true }); + res.status(201).send({ success: true, data: vehicle }); } catch(ex) { res.send(makeError(ex.message)); console.error(ex);