Extract company register

Closes #1079, #916, #1077
This commit is contained in:
Artur Beljajev 2019-02-09 16:35:01 +02:00
parent 8c4e6f1656
commit 83f8a9fb6a
44 changed files with 530 additions and 610 deletions

View file

@ -1,66 +0,0 @@
=begin
The portal for registrants has to offer an overview of the domains the user is related to directly or through an organisation.
Personal relation is defined by matching the personal identification code associated with a domain and the one acquired on
authentication using electronic ID. Association through a business organisation requires a query to business registry.
* when user logs in the personal identification code is sent to business registry (using XML service)
* business registry returns the list of business registry codes the user is a board member of
* the list is cached for two days (configurable)
* during that time no new queries are made to business registry for that personal identification code
and the cached organisation code listing is used
* user sees the listing of domains that are associated with him/her directly or through registered organisation
* UI of the portal displays the list of organisation codes and names used to fetch additional domains for the user
(currently by clicking on a username in top right corner of the screen).
Also time and date of the query to the business registry is displayed with the list of organisations.
* if the query to the business registry fails for any reason the list of
domains associated directly with the user is still displayed with an error message indicating a problem
with receiving current list business entities. Outdated list of organisations cannot be used.
=end
class BusinessRegistryCache < ActiveRecord::Base
def associated_domain_ids
contact_ids = Contact.where(ident_type: 'org', ident: associated_businesses, ident_country_code: ident_country_code).pluck(:id)
contact_ids += Contact.where(ident_type: 'priv', ident: ident, ident_country_code: ident_country_code).pluck(:id)
domain_ids = []
unless contact_ids.blank?
domain_ids = DomainContact.distinct.where(contact_id: contact_ids).pluck(:domain_id)
end
domain_ids += Domain.where(registrant_id: contact_ids).pluck(:id)
domain_ids
end
def associated_domains
Domain.includes(:registrar, :registrant).where(id: associated_domain_ids)
end
class << self
def fetch_associated_domains(ident_code, ident_cc)
fetch_by_ident_and_cc(ident_code, ident_cc).associated_domains
end
def fetch_by_ident_and_cc(ident_code, ident_cc)
cache = BusinessRegistryCache.where(ident: ident_code, ident_country_code: ident_cc).first_or_initialize
msg_start = "[Ariregister] #{ident_cc}-#{ident_code}:"
# fetch new data if cache is expired
if cache.retrieved_on && cache.retrieved_on > (Time.zone.now - Setting.days_to_keep_business_registry_cache.days)
Rails.logger.info("#{msg_start} Info loaded from cache")
return cache
end
cache.attributes = business_registry.associated_businesses(ident_code, ident_cc)
Rails.logger.info("#{msg_start} Info loaded from server")
cache.save
cache
end
def business_registry
Soap::Arireg.new
end
end
end

View file

@ -263,6 +263,28 @@ class Contact < ActiveRecord::Base
state
)
end
def registrant_user_contacts(registrant_user)
# In Rails 5, can be replaced with a much simpler `or` query method and the raw SQL parts can
# be removed.
from("(#{registrant_user_direct_contacts(registrant_user).to_sql} UNION " \
"#{registrant_user_indirect_contacts(registrant_user).to_sql}) AS contacts")
end
def registrant_user_direct_contacts(registrant_user)
where(ident_type: PRIV, ident: registrant_user.ident, ident_country_code: registrant_user
.country.alpha2)
end
private
def registrant_user_indirect_contacts(registrant_user)
ident = registrant_user.companies.collect(&:registration_number)
where(ident_type: ORG,
ident: ident,
ident_country_code: registrant_user.country.alpha2)
end
end
def roid
@ -431,7 +453,6 @@ class Contact < ActiveRecord::Base
# fetch domains
domains = Domain.where("domains.id IN (#{filter_sql})")
domains = domains.where("domains.id" => params[:leave_domains]) if params[:leave_domains]
domains = domains.includes(:registrar).page(page).per(per)
if sorts.first == "registrar_name".freeze

View file

@ -205,6 +205,56 @@ class Domain < ActiveRecord::Base
def nameserver_required?
Setting.nameserver_required
end
def registrant_user_domains(registrant_user)
# In Rails 5, can be replaced with a much simpler `or` query method and the raw SQL parts can
# be removed.
from(
"(#{registrant_user_domains_by_registrant(registrant_user).to_sql} UNION " \
"#{registrant_user_domains_by_contact(registrant_user).to_sql}) AS domains"
)
end
def registrant_user_direct_domains(registrant_user)
# In Rails 5, can be replaced with a much simpler `or` query method and the raw SQL parts can
# be removed.
from(
"(#{registrant_user_direct_domains_by_registrant(registrant_user).to_sql} UNION " \
"#{registrant_user_direct_domains_by_contact(registrant_user).to_sql}) AS domains"
)
end
def registrant_user_administered_domains(registrant_user)
# In Rails 5, can be replaced with a much simpler `or` query method and the raw SQL parts can
# be removed.
from(
"(#{registrant_user_domains_by_registrant(registrant_user).to_sql} UNION " \
"#{registrant_user_domains_by_admin_contact(registrant_user).to_sql}) AS domains"
)
end
private
def registrant_user_domains_by_registrant(registrant_user)
where(registrant: registrant_user.contacts)
end
def registrant_user_domains_by_contact(registrant_user)
joins(:domain_contacts).where(domain_contacts: { contact_id: registrant_user.contacts })
end
def registrant_user_domains_by_admin_contact(registrant_user)
joins(:domain_contacts).where(domain_contacts: { contact_id: registrant_user.contacts,
type: [AdminDomainContact] })
end
def registrant_user_direct_domains_by_registrant(registrant_user)
where(registrant: registrant_user.direct_contacts)
end
def registrant_user_direct_domains_by_contact(registrant_user)
joins(:domain_contacts).where(domain_contacts: { contact_id: registrant_user.direct_contacts })
end
end
def name=(value)

View file

@ -13,43 +13,34 @@ class RegistrantUser < User
registrant_ident.to_s.split('-').last
end
def country_code
registrant_ident.to_s.split('-').first
def country
alpha2_code = registrant_ident.to_s.split('-').first
Country.new(alpha2_code)
end
# In Rails 5, can be replaced with a much simpler `or` query method and the raw SQL parts can be
# removed.
# https://guides.rubyonrails.org/active_record_querying.html#or-conditions
def domains
domains_where_is_contact = begin
Domain.joins(:domain_contacts)
.where(domain_contacts: { contact_id: contacts })
end
domains_where_is_registrant = Domain.where(registrant_id: contacts)
Domain.from(
"(#{domains_where_is_registrant.to_sql} UNION " \
"#{domains_where_is_contact.to_sql}) AS domains"
)
def companies(company_register = CompanyRegister::Client.new)
company_register.representation_rights(citizen_personal_code: ident,
citizen_country_code: country.alpha3)
end
def contacts
Contact.where(ident_type: 'priv', ident: ident, ident_country_code: country_code)
Contact.registrant_user_contacts(self)
end
def direct_contacts
Contact.registrant_user_direct_contacts(self)
end
def domains
Domain.registrant_user_domains(self)
end
def direct_domains
Domain.registrant_user_direct_domains(self)
end
def administered_domains
domains_where_is_administrative_contact = begin
Domain.joins(:domain_contacts)
.where(domain_contacts: { contact_id: contacts, type: [AdminDomainContact] })
end
domains_where_is_registrant = Domain.where(registrant_id: contacts)
Domain.from(
"(#{domains_where_is_registrant.to_sql} UNION " \
"#{domains_where_is_administrative_contact.to_sql}) AS domains"
)
Domain.registrant_user_administered_domains(self)
end
def to_s
@ -122,4 +113,4 @@ class RegistrantUser < User
user
end
end
end
end

View file

@ -36,7 +36,6 @@ class Setting < RailsSettings::CachedSettings
transfer_wait_time
invoice_number_min
invoice_number_max
days_to_keep_business_registry_cache
days_to_keep_invoices_active
days_to_keep_overdue_invoices_active
days_to_renew_domain_before_expire

View file

@ -1,209 +0,0 @@
# coding: utf-8
require 'savon'
=begin
Estonian Business registry provides information about registered companies via xml (SOAP over HTTPS).
Note:
The SSL endpoint certificate is self signed.
Documentation:
http://www.rik.ee/et/e-ariregister/xml-teenus
Specifications are in Eng and Est
User contract required
Testing:
https://demo-ariregxml.rik.ee:447/testariport/?wsdl
http://demo-ariregxml.rik.ee:81
https://demo-ariregxml.rik.ee:447
Live service:
https://ariregxml.rik.ee/ariport/?wsdl
https://ariregxml.rik.ee/
Implements Soap::Arireg # associated_businesses
8. arireg.paringesindus_v4
Rights of representation of all persons related to the company (newer)
http://www2.rik.ee/schemas/xtee/arireg/live/paringesindus_v4.xsd
expects personal id code, to fetch list of registered business id codes
returning {ident: person, ident_country_code: ... associated_businesses: [...id_codes...]}
=end
# do some SSL set up?
# ssl_version
# ssl_verify_mode
# ssl_cert_key_file
# ssl_cert_key
# ssl_cert_key_password
# ssl_cert_file
# ssl_cert
# ssl_ca_cert_file
# ssl_ca_cert
module Soap
class Arireg
class NotAvailableError < StandardError
attr_accessor :json
def initialize(params)
params[:message] = "#{I18n.t(:business_registry_service_not_available)}" unless params.key? :message
@json = params
super(params)
end
end
class << self
attr_accessor :wsdl, :host, :username, :password
end
def initialize
if self.class.username.nil?
self.class.username = ENV['arireg_username']
self.class.password = ENV['arireg_password']
end
if self.class.wsdl.nil?
self.class.wsdl = ENV['arireg_wsdl']
self.class.host = ENV['arireg_host']
end
# note Savon has error if https w/non-standard port,
# use non-standard force to pre-set endpoint
@client = Savon.client(wsdl: self.class.wsdl,
host: self.class.host,
endpoint: "#{self.class.host}/cgi-bin/consumer_proxy")
@session = nil
end
# retrieve business id codes for business that a person has a legal role
def associated_businesses(ident, ident_cc = 'EST')
begin
msg = {
'fyysilise_isiku_kood' => ident,
'fyysilise_isiku_koodi_riik' => country_code_3(ident_cc)
}
Rails.logger.info "[Ariregister] Request sent with data: #{msg.inspect}"
response = @client.call :paringesindus_v4, message: body(msg)
content = extract response, :paringesindus_v4_response
Rails.logger.info "[Ariregister] Got response with data: #{content.inspect}"
if content.present? && content[:ettevotjad].key?(:item)
business_ident = items(content, :ettevotjad).map{|item| item[:ariregistri_kood]}
else
business_ident = []
end
{
ident: ident,
ident_country_code: ident_cc,
# ident_type: 'priv',
retrieved_on: Time.now,
associated_businesses: business_ident
}
rescue Savon::SOAPFault => fault
Rails.logger.error "[Ariregister] #{fault} Äriregister arireg #{self.class.username} at #{self.class.host }"
raise NotAvailableError.new(exception: fault)
rescue HTTPI::SSLError => ssl_error
Rails.logger.error "[Ariregister] #{ssl_error} at #{self.class.host}"
raise NotAvailableError.new(exception: ssl_error)
rescue SocketError => sock
Rails.logger.error "[Ariregister] #{sock}"
raise NotAvailableError.new(exception: sock)
end
end
def debug
@client.globals.log_level :debug
@client.globals.log true
@client.globals.pretty_print_xml true
@debug = true
@client
end
private
# add required elements to request
def body(args)
if @session.nil?
args['ariregister_kasutajanimi'] = self.class.username
args['ariregister_parool'] = self.class.password
else
args['ariregister_sessioon'] = @session
end
{keha: args}
end
# TLA --- three letter acronym required not two letter acronym, transform
def country_code_3(code)
if code.length == 2
code = CC2X3[code]
raise NotAvailableError.new(message: 'Unrecognized Country') if code.nil?
end
code
end
def extract(response, element)
# response envelope body has again header/body under element; header is user and password returned
response.hash[:envelope][:body][element][:keha]
end
def items(content, parent)
items = content[parent][:item]
items.is_a?(Array) ? items : [items]
end
CC2X3 = {"AF"=>"AFG", "AX"=>"ALA", "AL"=>"ALB", "DZ"=>"DZA", "AS"=>"ASM",
"AD"=>"AND", "AO"=>"AGO", "AI"=>"AIA", "AQ"=>"ATA", "AG"=>"ATG",
"AR"=>"ARG", "AM"=>"ARM", "AW"=>"ABW", "AU"=>"AUS", "AT"=>"AUT",
"AZ"=>"AZE", "BS"=>"BHS", "BH"=>"BHR", "BD"=>"BGD", "BB"=>"BRB",
"BY"=>"BLR", "BE"=>"BEL", "BZ"=>"BLZ", "BJ"=>"BEN", "BM"=>"BMU",
"BT"=>"BTN", "BO"=>"BOL", "BQ"=>"BES", "BA"=>"BIH", "BW"=>"BWA",
"BV"=>"BVT", "BR"=>"BRA", "IO"=>"IOT", "BN"=>"BRN", "BG"=>"BGR",
"BF"=>"BFA", "BI"=>"BDI", "CV"=>"CPV", "KH"=>"KHM", "CM"=>"CMR",
"CA"=>"CAN", "KY"=>"CYM", "CF"=>"CAF", "TD"=>"TCD", "CL"=>"CHL",
"CN"=>"CHN", "CX"=>"CXR", "CC"=>"CCK", "CO"=>"COL", "KM"=>"COM",
"CD"=>"COD", "CG"=>"COG", "CK"=>"COK", "CR"=>"CRI", "CI"=>"CIV",
"HR"=>"HRV", "CU"=>"CUB", "CW"=>"CUW", "CY"=>"CYP", "CZ"=>"CZE",
"DK"=>"DNK", "DJ"=>"DJI", "DM"=>"DMA", "DO"=>"DOM", "EC"=>"ECU",
"EG"=>"EGY", "SV"=>"SLV", "GQ"=>"GNQ", "ER"=>"ERI", "EE"=>"EST",
"ET"=>"ETH", "FK"=>"FLK", "FO"=>"FRO", "FJ"=>"FJI", "FI"=>"FIN",
"FR"=>"FRA", "GF"=>"GUF", "PF"=>"PYF", "TF"=>"ATF", "GA"=>"GAB",
"GM"=>"GMB", "GE"=>"GEO", "DE"=>"DEU", "GH"=>"GHA", "GI"=>"GIB",
"GR"=>"GRC", "GL"=>"GRL", "GD"=>"GRD", "GP"=>"GLP", "GU"=>"GUM",
"GT"=>"GTM", "GG"=>"GGY", "GN"=>"GIN", "GW"=>"GNB", "GY"=>"GUY",
"HT"=>"HTI", "HM"=>"HMD", "VA"=>"VAT", "HN"=>"HND", "HK"=>"HKG",
"HU"=>"HUN", "IS"=>"ISL", "IN"=>"IND", "ID"=>"IDN", "IR"=>"IRN",
"IQ"=>"IRQ", "IE"=>"IRL", "IM"=>"IMN", "IL"=>"ISR", "IT"=>"ITA",
"JM"=>"JAM", "JP"=>"JPN", "JE"=>"JEY", "JO"=>"JOR", "KZ"=>"KAZ",
"KE"=>"KEN", "KI"=>"KIR", "KP"=>"PRK", "KR"=>"KOR", "KW"=>"KWT",
"KG"=>"KGZ", "LA"=>"LAO", "LV"=>"LVA", "LB"=>"LBN", "LS"=>"LSO",
"LR"=>"LBR", "LY"=>"LBY", "LI"=>"LIE", "LT"=>"LTU", "LU"=>"LUX",
"MO"=>"MAC", "MK"=>"MKD", "MG"=>"MDG", "MW"=>"MWI", "MY"=>"MYS",
"MV"=>"MDV", "ML"=>"MLI", "MT"=>"MLT", "MH"=>"MHL", "MQ"=>"MTQ",
"MR"=>"MRT", "MU"=>"MUS", "YT"=>"MYT", "MX"=>"MEX", "FM"=>"FSM",
"MD"=>"MDA", "MC"=>"MCO", "MN"=>"MNG", "ME"=>"MNE", "MS"=>"MSR",
"MA"=>"MAR", "MZ"=>"MOZ", "MM"=>"MMR", "NA"=>"NAM", "NR"=>"NRU",
"NP"=>"NPL", "NL"=>"NLD", "NC"=>"NCL", "NZ"=>"NZL", "NI"=>"NIC",
"NE"=>"NER", "NG"=>"NGA", "NU"=>"NIU", "NF"=>"NFK", "MP"=>"MNP",
"NO"=>"NOR", "OM"=>"OMN", "PK"=>"PAK", "PW"=>"PLW", "PS"=>"PSE",
"PA"=>"PAN", "PG"=>"PNG", "PY"=>"PRY", "PE"=>"PER", "PH"=>"PHL",
"PN"=>"PCN", "PL"=>"POL", "PT"=>"PRT", "PR"=>"PRI", "QA"=>"QAT",
"RE"=>"REU", "RO"=>"ROU", "RU"=>"RUS", "RW"=>"RWA", "BL"=>"BLM",
"SH"=>"SHN", "KN"=>"KNA", "LC"=>"LCA", "MF"=>"MAF", "PM"=>"SPM",
"VC"=>"VCT", "WS"=>"WSM", "SM"=>"SMR", "ST"=>"STP", "SA"=>"SAU",
"SN"=>"SEN", "RS"=>"SRB", "SC"=>"SYC", "SL"=>"SLE", "SG"=>"SGP",
"SX"=>"SXM", "SK"=>"SVK", "SI"=>"SVN", "SB"=>"SLB", "SO"=>"SOM",
"ZA"=>"ZAF", "GS"=>"SGS", "SS"=>"SSD", "ES"=>"ESP", "LK"=>"LKA",
"SD"=>"SDN", "SR"=>"SUR", "SJ"=>"SJM", "SZ"=>"SWZ", "SE"=>"SWE",
"CH"=>"CHE", "SY"=>"SYR", "TW"=>"TWN", "TJ"=>"TJK", "TZ"=>"TZA",
"TH"=>"THA", "TL"=>"TLS", "TG"=>"TGO", "TK"=>"TKL", "TO"=>"TON",
"TT"=>"TTO", "TN"=>"TUN", "TR"=>"TUR", "TM"=>"TKM", "TC"=>"TCA",
"TV"=>"TUV", "UG"=>"UGA", "UA"=>"UKR", "AE"=>"ARE", "GB"=>"GBR",
"UM"=>"UMI", "US"=>"USA", "UY"=>"URY", "UZ"=>"UZB", "VU"=>"VUT",
"VE"=>"VEN", "VN"=>"VNM", "VG"=>"VGB", "VI"=>"VIR", "WF"=>"WLF",
"EH"=>"ESH", "YE"=>"YEM", "ZM"=>"ZMB", "ZW"=>"ZWE"}
end
end