Skip to content

Instantly share code, notes, and snippets.

@kryhtin
Created December 8, 2022 06:44
Show Gist options
  • Save kryhtin/43ebceedb46cb6ca95b147871304fffe to your computer and use it in GitHub Desktop.
Save kryhtin/43ebceedb46cb6ca95b147871304fffe to your computer and use it in GitHub Desktop.
class Person::Car < ActiveRecord::Base
include ParserServiceSeparable
has_paper_trail
acts_as_paranoid
before_validation :set_ownership
belongs_to :person
has_many :vehicle_ownerships, foreign_key: :vin, primary_key: :vin
has_many :road_accidents, foreign_key: :vin, primary_key: :vin
has_many :taxis, foreign_key: :gosnumber, primary_key: :number
has_many :leasing_deals, foreign_key: :vin, primary_key: :vin
accepts_nested_attributes_for :vehicle_ownerships, allow_destroy: true
accepts_nested_attributes_for :road_accidents, allow_destroy: true
accepts_nested_attributes_for :taxis, allow_destroy: false
accepts_nested_attributes_for :leasing_deals, allow_destroy: false
validates :name, presence: true
def set_ownership
vehicle_ownerships&.each do |item|
if item.vin.nil?
item.vin = self.vin
item.person_id = self.person_id
end
end
road_accidents&.each do |item|
if item.vin.nil?
item.vin = self.vin
item.person_id = self.person_id
end
end
end
def restrictions
person.vehicle_restrictions.where(vin: vin)
end
end
class Person::CarSerializer < Person::BaseSerializer
attributes :id,
:name,
:car_model,
:distance,
:price,
:number,
:owner,
:vin,
:sts,
:pts,
:phone,
:car_year,
:parser_service
# TODO: check performance
attribute :sts do |record|
r = record.sts&.gsub(/\sот.+|\s/i, "")
r ? "#{r[0..1]} #{r[2..3]} #{r[4, 11]}".strip : nil
end
attribute :vehicle_ownerships_attributes do |record|
Person::VehicleOwnershipSerializer.new(record.vehicle_ownerships).call
end
attribute :restrictions do |record|
Person::VehicleRestrictionSerializer.new(record.restrictions).call
end
attribute :road_accidents_attributes do |record|
Person::RoadAccidentSerializer.new(record.road_accidents).call
end
attribute :reg_date do |record|
record.reg_date&.to_date.try(:strftime, "%d.%m.%Y")
end
end
# frozen_string_literal: true
class CreateParserRequests < ActiveRecord::Migration[5.2]
class ParserRequest < ActiveRecord::Base
enum status: {
pending: 0,
processing: 1,
complete: 2,
abort: 3
}
end
def change
create_table :parser_requests, id: :uuid do |t|
t.belongs_to :user, type: :uuid, index: true, foreign_key: true
t.integer :status, index: true, default: ParserRequest.statuses[:pending]
t.string :email
t.jsonb :parsed_records
t.jsonb :parser_ids, null: false, default: "{}"
t.datetime :start_updated_at
t.datetime :end_updated_at
t.string :profile_id, index: true
t.belongs_to :person, type: :uuid, index: true, foreign_key: true
t.timestamps
end
end
end
class CreatePeople < ActiveRecord::Migration[5.2]
class Person < ActiveRecord::Base
enum sex: %i[undefined male female]
end
def change
create_table :people, id: :uuid do |t|
t.string :first_name, null: false
t.string :last_name, null: false
t.date :birthday, null: false
t.string :snils, null: false
t.string :middle_name
t.integer :sex, default: Person.sexes[:undefined]
t.string :citizenship
t.string :inn
t.belongs_to :user, type: :uuid, index: true
t.integer :update_count, default: 0
t.datetime :deleted_at, index: true
t.timestamps
t.index %i[first_name last_name birthday snils],
name: "index_people_uniqueness",
unique: true
end
end
end
# frozen_string_literal: true
module Api
module V1
class DraftsController < AuthenticatedController
include PersonParams
load_and_authorize_resource :person
def all
people = Person.drafts.order(created_at: :desc)
pagy = Paginator.new(
query: people,
arguments: pagination_params
).execute
render json: {
results: PeopleSerializer.new(pagy.results).call,
meta: {
total: pagy.total,
pages: pagy.pages
}
}, status: :ok
end
def index
people = current_user.people.drafts.order(created_at: :desc)
pagy = Paginator.new(
query: people,
arguments: pagination_params
).execute
render json: {
results: PeopleSerializer.new(pagy.results).call,
meta: {
total: pagy.total,
pages: pagy.pages
}
}, status: :ok
end
def show
@person = Person.drafts.find(params[:id])
render json: {
results: PersonSerializer.new(@person).call,
meta: {
status: ParserRequest.person_request_status(params[:id]),
draft: true
}
}, status: :ok
end
def create
if draft_params.permitted?
person_params = Dossier::Draft.reassign_params(draft_params)
result = Creator::Person.new(
person_params: person_params,
creator: current_user,
draft: true
).call
services = available_services(result.person)
render json: { id: result.person.id, services: UserParserServiceSerializer.new(services).call },
status: :ok
else
render json: { error: "error" }, status: 422
end
end
def destroy
person = Person.drafts.find_by!(id: params[:id])
person.really_destroy!
render json: { id: person.id }, status: :ok
end
private
def draft_params
params.permit(
:snils,
:first_name,
:last_name,
:middle_name,
:birthday,
:passport_seria,
:passport_number,
:phone,
:city,
:vin,
:inn,
:gosnumber,
cars_attributes: [
:gosnumber,
:vin
],
inns_attributes: [
:id,
:inn,
:_destroy
],
phones_attributes: [
:id,
:phone
]
)
end
def available_services(person)
parser_services = current_user.user_parser_services.includes(:parser_service)
parser_services.map do |item|
item if item.active? && Validator::Person.valid?(person, item.parser_service)
end.compact
end
def pagination_params
params.permit(:page, :per_page, :sort, :order)
end
end
end
end
# frozen_string_literal: true
module Converter
class Fssp
def self.call(data, person)
return nil unless data&.data
data_array = data&.data
result = []
data_array.each do |item|
regexed_result = JSON.parse(item["result"].to_json).each do |i|
i["name"] = i["name"].gsub!(/\s,/, "")
i["department"] = i["department"].gsub!(/\s,/, "")
i["bailiff"] = i["bailiff"].gsub!("<br>", " ")
end
result << {
region: item["query"]["params"]["region"],
result: regexed_result
}
end
result.any? ? result : nil
end
end
end
class Person < ActiveRecord::Base
include PersonSearchable
has_paper_trail on: [:update]
acts_as_paranoid
enum sex: %i[undefined male female]
enum marital_status: %i[single married divorced widow]
enum source_db: { manual: 0, another_db: 1 }
belongs_to :user, optional: true
has_one :egrul_receipt, dependent: :destroy
has_many :addresses, as: :addressable, dependent: :destroy
has_many :affiliations, dependent: :destroy
has_many :armies, dependent: :destroy
has_many :auto_wanteds, dependent: :destroy
has_many :avinfo_images, dependent: :destroy
has_many :avito_ads, dependent: :destroy
has_many :bankruptcies, dependent: :destroy
has_many :business_trips, dependent: :destroy
has_many :cars, dependent: :destroy
has_many :car_numbers, dependent: :destroy
has_many :car_registration_certificates, dependent: :destroy
has_many :car_vehicle_passports, dependent: :destroy
has_many :car_vins, dependent: :destroy
has_many :citizenships, dependent: :destroy
has_many :driving_licences, dependent: :destroy
has_many :drom_ads, dependent: :destroy
has_many :educations, dependent: :destroy
has_many :emails, dependent: :destroy
has_many :enforcements, dependent: :destroy
has_many :facebook_ids, dependent: :destroy
has_many :fines, dependent: :destroy
has_many :foreign_addresses, dependent: :destroy
has_many :foreign_languages, dependent: :destroy
has_many :full_name_changes, dependent: :destroy
has_many :full_name_histories, dependent: :destroy
has_many :identity_documents, dependent: :destroy
has_many :income_sources, dependent: :destroy
has_many :instagram_ids, dependent: :destroy
has_many :international_passports, dependent: :destroy
has_many :jobs, dependent: :destroy
has_many :legal_entities, dependent: :destroy
has_many :loans, dependent: :destroy
has_many :marital_statuses, dependent: :destroy
has_many :mortgages, dependent: :destroy
has_many :native_cities, dependent: :destroy
has_many :nicknames, dependent: :destroy
has_many :ok_ids, dependent: :destroy
has_many :other_educations, dependent: :destroy
has_many :other_inns, dependent: :destroy
has_many :parking_sessions, dependent: :destroy
has_many :parser_requests, dependent: :destroy
has_many :passports, dependent: :destroy
has_many :personal_sites, dependent: :destroy
has_many :phones, dependent: :destroy
has_many :real_estates, dependent: :destroy
has_many :recommendations, dependent: :destroy
has_many :relationships, dependent: :destroy
has_many :road_accidents, dependent: :destroy
has_many :schools, dependent: :destroy
has_many :science_achievements, dependent: :destroy
has_many :sole_proprietorships, dependent: :destroy
has_many :vacations, dependent: :destroy
has_many :vehicle_operations, dependent: :destroy
has_many :vehicle_ownerships, dependent: :destroy
has_many :vehicle_restrictions, dependent: :destroy
has_many :vkontakte_ids, dependent: :destroy
has_many :wanteds, dependent: :destroy
has_many :inns, dependent: :destroy
has_many :stop_lists, dependent: :destroy
has_many :skypes, dependent: :destroy
has_many_attached :dossier_files
has_one_attached :avatar
has_many_attached :gallery_files
accepts_nested_attributes_for :phones,
:inns,
:emails,
:passports,
:native_cities,
:addresses,
:avinfo_images,
:foreign_addresses,
:driving_licences,
:jobs,
:vacations,
:educations,
:enforcements,
:other_educations,
:other_inns,
:schools,
:science_achievements,
:sole_proprietorships,
:foreign_languages,
:full_name_changes,
:full_name_histories,
:armies,
:vkontakte_ids,
:ok_ids,
:identity_documents,
:instagram_ids,
:facebook_ids,
:skypes,
:personal_sites,
:car_numbers,
:car_vins,
:car_registration_certificates,
:car_vehicle_passports,
:nicknames,
:international_passports,
:auto_wanteds,
:road_accidents,
:cars,
:citizenships,
:parking_sessions,
:bankruptcies,
:marital_statuses,
:mortgages,
:recommendations,
:affiliations,
:loans,
:stop_lists,
:legal_entities,
:drom_ads,
:avito_ads,
:real_estates,
:relationships,
:business_trips,
:income_sources,
:fines,
:vehicle_restrictions,
:vehicle_ownerships,
:wanteds, allow_destroy: true
validates :first_name, uniqueness: { scope: %i[last_name birthday snils] }, if: Proc.new { |p| p.snils.present? && p.birthday.present? && !draft }
validates :snils, uniqueness: true, length: {minimum: 9, maximum: 11}, if: Proc.new { |p| p.snils.present? && !draft }
before_save :fill_empty_name
class << self
def published
where(draft: [false, nil])
end
def drafts
where(draft: true)
end
end
def fill_empty_name
rand_chars = ((0..9).to_a + ("a".."z").to_a).shuffle[0,5].join
self.first_name = "Без_имени_#{rand_chars}" unless first_name.present?
self.last_name = "Без_фамилии_#{rand_chars}" unless last_name.present?
end
def full_name
[last_name, first_name, middle_name].keep_if(&:present?).join(" ")
end
def last_updated
last_updated_at.try(:strftime, "%d.%m.%Y в %H:%M")
end
def current_jobs
jobs.where(is_current: true)
end
def previous_jobs
jobs.where(is_current: false)
end
def current_addresses
addresses.where(is_current: true, is_registration: false)
end
def registration_addresses
addresses.where(is_registration: true)
end
def additional_data
{
first_name: first_name,
last_name: last_name,
middle_name: middle_name,
current_jobs: current_jobs,
phones: phones,
passports: passports,
snils: snils,
inn: inn,
birthday: birthday,
addresses: addresses,
registration_addresses: registration_addresses
}
end
def age
birthday.present? ? ((Time.zone.now - birthday.to_time) / 1.year.seconds).floor : nil
end
def current_addresses_attributes=(params)
addresses = []
params.each do |address|
addresses.push(address[:addresses_attributes])
end
self.addresses_attributes = addresses.flatten!
end
def registration_addresses_attributes=(params)
addresses = []
params.each do |address|
addresses.push(address[:addresses_attributes])
end
self.addresses_attributes = addresses.flatten!
end
def current_jobs_attributes=(params)
self.jobs_attributes = params
end
def previous_jobs_attributes=(params)
self.jobs_attributes = params
end
end
module Creator
class Person
Result = Struct.new(:success, :person) do
def success?
success
end
end
def initialize(person_params:, creator:, draft: false, source_db: 0)
@person_params = person_params
@creator = creator
@draft = draft
@source_db = source_db
end
def call
ActiveRecord::Base.transaction do
PaperTrail.request.whodunnit = @creator.id
@person = ::Person.new(@person_params)
@person.source_db = @source_db
@person.user = @creator
@person.draft = @draft
@person.save! unless @draft
@person.save(validate: false) if @draft
end
Result.new(@person.persisted?, @person)
end
end
end
require 'rails_helper'
RSpec.describe Person, type: :model do
let!(:person_let) do
Person.create!(
first_name: "Николай",
last_name: "Постоянный",
birthday: 22.years.ago,
snils: "12332145677"
)
end
context "create person" do
it "success" do
person = Person.new(
first_name: "Иван",
last_name: "Иванов",
birthday: 35.years.ago,
snils: "12332145655"
)
expect(person).to be_valid
end
it "validation error" do
person = Person.new(
first_name: "Иван"
)
expect(person).to_not be_valid
end
it "count" do
expect(Person.all.count).to eq(1)
end
end
context "create person with nested data" do
it "success full" do
person = Person.create!(
first_name: "Петр",
last_name: "Петров",
birthday: 22.years.ago,
snils: "12332145655"
)
create(:full_person)
expect(person.errors).to be_empty
expect(Person.all.count).to eq(3)
end
it "fail job validation" do
person = build(:person) do |item|
item.jobs << build(:job, company: nil)
end
expect { person.save! }.to raise_error(ActiveRecord::RecordInvalid)
expect(Person.all.count).to eq(1)
end
it "fail passport validation" do
person = build(:person) do |item|
item.passports << build(:passport, seria: nil)
end
expect { person.save! }.to raise_error(ActiveRecord::RecordInvalid)
expect(Person.all.count).to eq(1)
end
end
context "destroy person" do
it "record not found" do
expect { Person.find(535343).destroy! }.to raise_error(ActiveRecord::RecordNotFound)
expect(Person.all.count).to eq(1)
end
it "success" do
person = person_let
expect { person.destroy! }.not_to raise_error
expect(Person.all.count).to eq(0)
end
end
end
module Generator
class PersonVersion
Result = Struct.new(:person, :versions, :current_version_id)
def self.call(person_id:, version_id:)
@person = get_person(person_id, version_id)
raise ActiveRecord::RecordNotFound if @person.nil?
raise ActiveRecord::RecordNotFound if @person.id != person_id
transaction_ids = @person.versions.group_by(&:transaction_id).keys
raise ActiveRecord::RecordNotFound if version_id.present? && !transaction_ids.include?(version_id.to_i)
serialized_versions = get_versions(transaction_ids)
serialized_person = PersonSerializer.new(@person).call
Result.new(
person: serialized_person,
versions: serialized_versions,
current_version_id: version_id || nil
)
end
private
def self.get_person(person_id, version_id)
if version_id.present?
version = PaperTrail::Version.find(version_id)
person = version.next&.reify(has_many: true) || Person.find(person_id)
person
else
Person.find(person_id)
end
end
def self.get_versions(transaction_ids)
versions = PaperTrail::Version.where(transaction_id: transaction_ids, item_type: "Person").order(created_at: :desc)
Person::VersionSerializer.new(versions).call
end
end
end
# frozen_string_literal: true
module Api
module V1
class ServiceSettingsController < AuthenticatedController
load_and_authorize_resource :service_settings
def show
render json: ServiceSettingsSerializer.new(ServiceSettings.instance).call, status: :ok
end
def update
ServiceSettings.instance.update_attributes(settings_params)
render json: ServiceSettingsSerializer.new(ServiceSettings.instance).call, status: :ok
end
private
def settings_params
params.require(:service_settings).permit(
:croinform_api_url,
:croinform_login,
:croinform_password,
:croinform_use_production,
:xneo_api_url,
:xneo_api_token,
:avinfo_api_url,
:avinfo_api_token,
:fssp_api_url,
:fssp_api_token,
:kontur_focus_api_url,
:kontur_focus_api_token,
:kladr_api_url,
:kladr_api_token,
:anti_captcha_token,
:two_captcha_token,
:telegram_phone,
:telegram_proxy_host,
:telegram_proxy_port
)
end
end
end
end
require "rails_helper"
RSpec.describe Api::V1::UserGroupsController, type: :controller do
login_user
let(:authenticated_user) { subject.instance_eval { current_user } }
let!(:initial_group) do
FactoryBot.create(
:user_group,
person_ids: create_list(:person, 15).pluck(:id),
user_id: authenticated_user.id
)
end
after(:all) do
User.destroy_all
end
context "GET index" do
it "success" do
get :index
expect(response.content_type).to eq "application/json"
expect(response.status).to eq(200)
expect(JSON.parse(response.body)["results"]).to be_an_instance_of(Array)
expect(JSON.parse(response.body)["results"]).not_to be_empty
end
it "another user cannot see the initial group" do
user = FactoryBot.create(
:user,
email: "moderator@mail.com",
first_name: "Moder",
last_name: "Moder"
)
allow(controller).to receive(:current_user).and_return(user)
get :index
expect(response.status).to eq(200)
expect(response.content_type).to eq "application/json"
expect(JSON.parse(response.body)["results"]).to be_empty
end
end
context "POST create" do
it "success" do
params = FactoryBot.attributes_for(
:user_group,
person_ids: create_list(:person, 7).pluck(:id)
)
post :create, params: params
expect(response.content_type).to eq "application/json"
expect(response.status).to eq(200)
expect(JSON.parse(response.body)).to have_key("id")
end
end
context "DELETE destroy" do
it "success" do
delete :destroy, params: { id: initial_group }
expect(response.content_type).to eq "application/json"
expect(JSON.parse(response.body)["id"]).to eq(initial_group.id)
expect(UserGroup.all.count).to eq 0
end
it "another user cannot delete the group" do
user = FactoryBot.create(
:user,
email: "moderator@mail.com",
first_name: "Moder",
last_name: "Moder"
)
allow(controller).to receive(:current_user).and_return(user)
expect { delete :destroy, params: { id: initial_group.id } }
.to raise_error(ActiveRecord::RecordNotFound)
end
end
context "PATCH add" do
it "success" do
new_person = create(:person)
patch :add, params: { group_id: initial_group.id, person_ids: [new_person.id] }
group = UserGroup.find(initial_group.id)
expect(response.content_type).to eq "application/json"
expect(response.status).to eq(200)
expect(group.person_ids).to include(new_person.id)
end
end
end
class Parser::VK::Profiles::ParserService
FIELDS = %w[
photo_id
verified
sex
bdate
city
country
home_town
has_photo
photo_50
photo_100
photo_200_orig
photo_200
photo_400_orig
photo_max
photo_max_orig
online
lists
domain
has_mobile
contacts
site
education
universities
schools
status
last_seen
followers_count
occupation
nickname
relatives
relation
personal
connections
exports
wall_comments
activities
interests
music
movies
tv
books
games
about
quotes
can_post
can_see_all_posts
can_see_audio
can_write_private_message
can_send_friend_request
is_favorite
is_hidden_from_feed
timezone
screen_name
maiden_name
crop_photo
is_friend
friend_status
career
military
blacklisted
blacklisted_by_me
].freeze
def initialize(service = true)
@api_service = VkontakteApi::Client.new(service ? ENV.fetch("VK_SERVICE_TOKEN") : ENV.fetch("VK_USER_TOKEN"))
end
def users_get(ids)
@api_service.users.get(user_ids: ids, fields: FIELDS, lang: 0)
end
def users_search(query:, birthday: nil)
params = {
q: query,
fields: FIELDS,
lang: 0
}
if birthday
date = birthday.is_a?(Date) ? birthday : Date.parse(birthday)
params.merge!(birth_day: date.day, birth_month: date.month, birth_year: date.year)
end
@api_service.users.search(params)
end
end
# frozen_string_literal: true
require 'progress_bar'
class XlsxDownloader
def initialize(file_path)
@file_path = file_path
end
def call
bar = ProgressBar.new
XlsxInputParser.new(@file_path).parse do |data|
if data["имя"].present? & data["фамилия"].present?
params = create_person_params(data)
logger(params)
user = User.first
people_creator = Creator::Person.new(
person_params: params,
creator: user
)
people_creator.call
bar.increment!
end
rescue StandardError
next
end
end
private
def create_person_params(data)
params = {
first_name: data["имя"],
last_name: data["фамилия"],
middle_name: data["отчество"],
sex: parse_sex_value(data["пол"]),
birthday: data["дата рождения"],
snils: data["снилс"],
inn: data["инн"],
emails_attributes: [{field: data["почта"]}],
phones_attributes: [{phone: data["телефон сотовый"]}],
vkontakte_ids_attributes: [{field: data["вконтакте"]}],
native_cities_attributes: [{field: data["место рождения"]}],
driving_licences_attributes: [
{
seria: data["серия прав"],
number: data["номер прав"],
start_date: data["дата регистрации прав"]
}
],
passports_attributes: [
{
seria: data["серия паспорта"],
number: data["номер паспорта"],
issue_date_at: data["когда выдан"],
issued_by: data["кем выдан"]
}
],
current_jobs_attributes: [{
company: data["компания"],
start_date: data["дата приема"],
end_date: data["дата увольнения"],
department: data["департамент"],
administration: data["управление"],
office: data["отдел"],
position: data["должность"],
is_current: true
}],
educations_attributes: [{
field: data["образование первое"],
start_date: data["образование первое начало"],
end_date: data["образование первое окончание"]
}],
car_numbers_attributes: [{field: data["госномер"]}],
car_vins_attributes: [{field: data["вин автомобиля"]}],
car_registration_certificates_attributes: [{number: data["свидетельство тс"]}],
current_addresses_attributes: [
addresses_attributes: [
{
field: data["адрес проживания"]
}
]
],
registration_addresses_attributes: [
addresses_attributes: [
{
field: data["адрес регистрации"]
}
]
]
}
::RecursiveCompact.instance.call(params)
end
def parse_sex_value(value)
if value&.downcase == "м"
"male"
elsif value&.downcase == "ж"
"female"
else
"undefined"
end
end
end
# frozen_string_literal: true
module Builder
class XneoSocial
def self.call(person:, data:)
if data.present?
if data[:vk].present?
data[:vk].flatten.uniq.each do |id|
next unless person.vkontakte_ids.find_by(field: id).nil?
person.vkontakte_ids.build(field: id, parser_service: "xneo_social")
end
end
if data[:ok].present?
data[:ok].flatten.uniq.each do |id|
next unless person.ok_ids.find_by(field: id).nil?
person.ok_ids.build(field: id, parser_service: "xneo_social")
end
end
end
person
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment