SU2-33 | Фикс критических и серьёзных ошибок, удаление/перезагрузка организации, пагинация, favicon
This commit is contained in:
@@ -0,0 +1,10 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
|
||||||
|
<rect width="32" height="32" rx="6" fill="#1a1a1a"/>
|
||||||
|
<rect x="5" y="22" width="5" height="6" rx="1" fill="white"/>
|
||||||
|
<rect x="13" y="16" width="5" height="12" rx="1" fill="white"/>
|
||||||
|
<rect x="21" y="10" width="5" height="18" rx="1" fill="white"/>
|
||||||
|
<polyline points="7.5,18 15.5,12 23.5,6" stroke="#4ade80" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<circle cx="7.5" cy="18" r="1.5" fill="#4ade80"/>
|
||||||
|
<circle cx="15.5" cy="12" r="1.5" fill="#4ade80"/>
|
||||||
|
<circle cx="23.5" cy="6" r="1.5" fill="#4ade80"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 614 B |
@@ -104,3 +104,55 @@ a.link:hover {
|
|||||||
a.link:has(.active__button) {
|
a.link:has(.active__button) {
|
||||||
background-color: gray;
|
background-color: gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nav.pagy {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-block: 20px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
|
||||||
|
a {
|
||||||
|
padding: 4px 10px;
|
||||||
|
border: 1px solid black;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
|
||||||
|
&.current { font-weight: 700; }
|
||||||
|
&[aria-disabled="true"] { opacity: 0.4; }
|
||||||
|
&:not([aria-disabled="true"]):hover { background-color: black; color: white; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.organisation-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-block: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.link-action {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: black;
|
||||||
|
color: var(--white-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.link-action--danger {
|
||||||
|
border-color: #c0392b;
|
||||||
|
color: #c0392b;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #c0392b;
|
||||||
|
color: var(--white-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,18 +28,13 @@ module Authorization
|
|||||||
session['cas'] && session['cas']['user']
|
session['cas'] && session['cas']['user']
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_current_user(options = {})
|
|
||||||
return nil if current_login.blank?
|
|
||||||
load_current_user
|
|
||||||
end
|
|
||||||
|
|
||||||
def load_current_user
|
def load_current_user
|
||||||
@current_user ||= User.where('lower(login) = lower(?)', current_login).first
|
@current_user ||= User.where('lower(login) = lower(?)', current_login).first
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_authentication
|
def check_authentication
|
||||||
if session.blank? || session['cas'].blank? || session['cas']['user'].blank? ||
|
if session.blank? || session['cas'].blank? || session['cas']['user'].blank? ||
|
||||||
(request.get? && !request.xhr? && request.xhr? != 0 && (session['cas']['last_validated_at'].blank? || session['cas']['last_validated_at'] < 15.minutes.ago))
|
(request.get? && !request.xhr? && (session['cas']['last_validated_at'].blank? || session['cas']['last_validated_at'] < 15.minutes.ago))
|
||||||
render plain: 'Требуется авторизация', status: 401
|
render plain: 'Требуется авторизация', status: 401
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
class OrganisationsController < ApplicationController
|
class OrganisationsController < ApplicationController
|
||||||
before_action :set_organisation, only: [:show]
|
before_action :set_organisation, only: [:show, :destroy, :reload]
|
||||||
|
|
||||||
DOC_TYPES = {
|
DOC_TYPES = {
|
||||||
0 => { title: 'Бухгалтерский баланс', form_code: '1.1' },
|
0 => { title: 'Бухгалтерский баланс', form_code: '1.1' },
|
||||||
@@ -9,7 +9,7 @@ class OrganisationsController < ApplicationController
|
|||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
def index
|
def index
|
||||||
organizations = Organization.all
|
@pagy, organizations = pagy(Organization.order(:created_at))
|
||||||
|
|
||||||
@info_about_organisations = organizations.each_with_object({}) do |org, hash|
|
@info_about_organisations = organizations.each_with_object({}) do |org, hash|
|
||||||
hash[org.id] = {
|
hash[org.id] = {
|
||||||
@@ -42,6 +42,17 @@ class OrganisationsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@org.destroy!
|
||||||
|
redirect_to '/organisations/index', notice: "Организация #{@org.inn} удалена"
|
||||||
|
end
|
||||||
|
|
||||||
|
def reload
|
||||||
|
report = Report.create!(inn: @org.inn, status: 'processing')
|
||||||
|
FileProcessorJob.perform_async(@org.inn, report.id)
|
||||||
|
redirect_to "/organisations/#{@org.id}", notice: 'Данные обновляются в фоне'
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_organisation
|
def set_organisation
|
||||||
|
|||||||
@@ -4,9 +4,13 @@ class ReportsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
inn = params[:inn]
|
inn = params[:inn].to_s.strip
|
||||||
report = Report.create!(inn: inn, status: 'processing')
|
unless inn.match?(/\A\d{10}(\d{2})?\z/)
|
||||||
|
redirect_to reports_path, alert: 'Некорректный ИНН (должен содержать 10 или 12 цифр)'
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
report = Report.create!(inn: inn, status: 'processing')
|
||||||
FileProcessorJob.perform_async(inn, report.id)
|
FileProcessorJob.perform_async(inn, report.id)
|
||||||
|
|
||||||
redirect_to reports_path, notice: 'Отчёт загружается в фоне'
|
redirect_to reports_path, notice: 'Отчёт загружается в фоне'
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ module OrganisationsHelper
|
|||||||
end
|
end
|
||||||
|
|
||||||
def parse_number(value)
|
def parse_number(value)
|
||||||
return 0 if value.empty? || ['-', '(-)', 'X'].include?(value)
|
return 0 if value.nil? || value.empty? || ['-', '(-)', 'X'].include?(value)
|
||||||
negative = value.start_with?('(') && value.end_with?(')')
|
negative = value.start_with?('(') && value.end_with?(')')
|
||||||
value = value.tr('()', '') if negative
|
value = value.tr('()', '') if negative
|
||||||
value = value.delete(' ')
|
value = value.delete(' ')
|
||||||
|
|||||||
@@ -33,17 +33,19 @@ class ReportParser
|
|||||||
parsed_forms = parse_forms
|
parsed_forms = parse_forms
|
||||||
save_aggregated_statements(parsed_forms)
|
save_aggregated_statements(parsed_forms)
|
||||||
end
|
end
|
||||||
|
ensure
|
||||||
|
@tempfile&.close!
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def spreadsheet
|
def spreadsheet
|
||||||
@spreadsheet ||= begin
|
@spreadsheet ||= begin
|
||||||
tempfile = Tempfile.new(['report', '.xlsx'])
|
@tempfile = Tempfile.new(['report', '.xlsx'])
|
||||||
tempfile.binmode
|
@tempfile.binmode
|
||||||
@yearly_file.file.download { |chunk| tempfile.write(chunk) }
|
@yearly_file.file.download { |chunk| @tempfile.write(chunk) }
|
||||||
tempfile.rewind
|
@tempfile.rewind
|
||||||
Roo::Excelx.new(tempfile.path)
|
Roo::Excelx.new(@tempfile.path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -202,7 +204,7 @@ class ReportParser
|
|||||||
end
|
end
|
||||||
|
|
||||||
def report_codes
|
def report_codes
|
||||||
@report_codes ||= ReportCode.select(:code, :name).to_a
|
@report_codes ||= ReportCode.select(:code, :name).order(:id).to_a
|
||||||
end
|
end
|
||||||
|
|
||||||
def report_codes_for_form(form_code, codes)
|
def report_codes_for_form(form_code, codes)
|
||||||
@@ -229,7 +231,7 @@ class ReportParser
|
|||||||
.where(sheet_number: year)
|
.where(sheet_number: year)
|
||||||
.where(yearly_files: { organization_id: @yearly_file.organization_id })
|
.where(yearly_files: { organization_id: @yearly_file.organization_id })
|
||||||
.where.not(id: keeper_id)
|
.where.not(id: keeper_id)
|
||||||
.delete_all
|
.destroy_all
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ class FileProcessorJob
|
|||||||
include Sidekiq::Job
|
include Sidekiq::Job
|
||||||
|
|
||||||
def perform(inn, report_id)
|
def perform(inn, report_id)
|
||||||
|
raise ArgumentError, "Некорректный ИНН: #{inn}" unless inn.to_s.match?(/\A\d{10}(\d{2})?\z/)
|
||||||
|
|
||||||
report = Report.find(report_id)
|
report = Report.find(report_id)
|
||||||
Rails.logger.info "=== Начало загрузки для ИНН: #{inn} ==="
|
Rails.logger.info "=== Начало загрузки для ИНН: #{inn} ==="
|
||||||
|
|
||||||
# Временная папка
|
temp_dir = Rails.root.join('tmp', 'reports', inn.to_s.gsub(/[^0-9]/, ''))
|
||||||
temp_dir = Rails.root.join('tmp', 'reports', inn)
|
|
||||||
FileUtils.mkdir_p(temp_dir)
|
FileUtils.mkdir_p(temp_dir)
|
||||||
|
|
||||||
download_inn(inn, temp_dir.to_s)
|
download_inn(inn, temp_dir.to_s)
|
||||||
@@ -26,7 +27,9 @@ class FileProcessorJob
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Создаём YearlyFile и прикрепляем xlsx
|
# Создаём YearlyFile и прикрепляем xlsx
|
||||||
yearly_file = organization.yearly_files.build(year: 0)
|
year_from_filename = File.basename(file_path, '.*').split('_')[2].to_i
|
||||||
|
year_from_filename = 0 unless (2000..2099).cover?(year_from_filename)
|
||||||
|
yearly_file = organization.yearly_files.build(year: year_from_filename)
|
||||||
yearly_file.file.attach(
|
yearly_file.file.attach(
|
||||||
io: File.open(file_path),
|
io: File.open(file_path),
|
||||||
filename: File.basename(file_path)
|
filename: File.basename(file_path)
|
||||||
@@ -57,18 +60,28 @@ class FileProcessorJob
|
|||||||
'Accept-Language' => 'en-US,en;q=0.9',
|
'Accept-Language' => 'en-US,en;q=0.9',
|
||||||
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
|
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
|
||||||
}
|
}
|
||||||
content = HTTParty.get(url, headers: headers).body
|
response = HTTParty.get(url, headers: headers, timeout: 30)
|
||||||
json_need ? JSON.parse(content) : content
|
raise "HTTP #{response.code} при запросе #{url}" unless response.success?
|
||||||
|
|
||||||
|
json_need ? JSON.parse(response.body) : response.body
|
||||||
end
|
end
|
||||||
|
|
||||||
def download_inn(inn, output)
|
def download_inn(inn, output)
|
||||||
url = "https://bo.nalog.gov.ru/advanced-search/organizations/search?query=#{inn}&page=0&size=20"
|
url = "https://bo.nalog.gov.ru/advanced-search/organizations/search?query=#{inn}&page=0&size=20"
|
||||||
content = get_content(url, true)
|
content = get_content(url, true)
|
||||||
id = content['content'][0]['id']
|
org_entry = content.dig('content', 0) or raise "Организация с ИНН #{inn} не найдена"
|
||||||
|
id = org_entry['id']
|
||||||
|
|
||||||
url = "https://bo.nalog.gov.ru/nbo/organizations/#{id}/bfo/"
|
url = "https://bo.nalog.gov.ru/nbo/organizations/#{id}/bfo/"
|
||||||
content = get_content(url, true)
|
content = get_content(url, true)
|
||||||
periods_and_details_ids = content.map { |item| [item['period'], item['typeCorrections'][0]['correction']['id']] }
|
raise "Нет отчётности для организации #{id}" if content.blank?
|
||||||
|
|
||||||
|
periods_and_details_ids = content.filter_map do |item|
|
||||||
|
details_id = item.dig('typeCorrections', 0, 'correction', 'id')
|
||||||
|
next if details_id.nil?
|
||||||
|
|
||||||
|
[item['period'], details_id]
|
||||||
|
end
|
||||||
|
|
||||||
periods_and_details_ids.each do |period, details_id|
|
periods_and_details_ids.each do |period, details_id|
|
||||||
url = "https://bo.nalog.gov.ru/download/bfo/#{id}?auditReport=false&balance=true"\
|
url = "https://bo.nalog.gov.ru/download/bfo/#{id}?auditReport=false&balance=true"\
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ html
|
|||||||
meta name="turbo-refresh-method" content="morph"
|
meta name="turbo-refresh-method" content="morph"
|
||||||
= csrf_meta_tags
|
= csrf_meta_tags
|
||||||
= csp_meta_tag
|
= csp_meta_tag
|
||||||
/ = favicon_link_tag 'favicon.ico'
|
= favicon_link_tag 'favicon.svg', type: 'image/svg+xml'
|
||||||
/ = favicon_link_tag 'favicon-32x32.png', sizes: '32x32'
|
|
||||||
/ = favicon_link_tag 'apple-icon.png', rel: 'apple-touch-icon'
|
|
||||||
= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload"
|
= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload"
|
||||||
= stylesheet_link_tag "application", media: "all", "data-turbo-track": "reload"
|
= stylesheet_link_tag "application", media: "all", "data-turbo-track": "reload"
|
||||||
script[async src="https://ga.jspm.io/npm:es-module-shims@1.8.2/dist/es-module-shims.js" data-turbo-track="reload"]
|
script[async src="https://ga.jspm.io/npm:es-module-shims@1.8.2/dist/es-module-shims.js" data-turbo-track="reload"]
|
||||||
|
|||||||
@@ -17,3 +17,4 @@
|
|||||||
.information-button
|
.information-button
|
||||||
a.link[href="/organisations/#{organisation}"]
|
a.link[href="/organisations/#{organisation}"]
|
||||||
= "Подробнее"
|
= "Подробнее"
|
||||||
|
== pagy_nav(@pagy)
|
||||||
|
|||||||
@@ -14,6 +14,17 @@
|
|||||||
- @info_about_organisation["Сведения об организации"].each do |key, value|
|
- @info_about_organisation["Сведения об организации"].each do |key, value|
|
||||||
.information-elem
|
.information-elem
|
||||||
= "#{key}: #{value}"
|
= "#{key}: #{value}"
|
||||||
|
.organisation-actions
|
||||||
|
= link_to "/organisations/#{@org.id}/reload",
|
||||||
|
class: 'link-action',
|
||||||
|
data: { turbo_method: :post, turbo_confirm: 'Перекачать данные с nalog.gov.ru?' } do
|
||||||
|
i.bi.bi-arrow-clockwise
|
||||||
|
| Обновить данные
|
||||||
|
= link_to "/organisations/#{@org.id}",
|
||||||
|
class: 'link-action link-action--danger',
|
||||||
|
data: { turbo_method: :delete, turbo_confirm: 'Удалить организацию и все её данные? Это действие необратимо.' } do
|
||||||
|
i.bi.bi-trash
|
||||||
|
| Удалить
|
||||||
= render 'organisations/templates/type_doc', target: "type_doc_#{@type_doc}", org_id: "#{@org.id}"
|
= render 'organisations/templates/type_doc', target: "type_doc_#{@type_doc}", org_id: "#{@org.id}"
|
||||||
h2
|
h2
|
||||||
= "#{@doc_name}"
|
= "#{@doc_name}"
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ Rails.application.routes.draw do
|
|||||||
get '/auth/login', to: 'auth#login', as: :login
|
get '/auth/login', to: 'auth#login', as: :login
|
||||||
get '/organisations/index'
|
get '/organisations/index'
|
||||||
get '/organisations/:id', to: 'organisations#show'
|
get '/organisations/:id', to: 'organisations#show'
|
||||||
|
delete '/organisations/:id', to: 'organisations#destroy'
|
||||||
|
post '/organisations/:id/reload', to: 'organisations#reload'
|
||||||
get '/auditors/index'
|
get '/auditors/index'
|
||||||
get '/reports/create'
|
get '/reports/create'
|
||||||
get '/reports', to: 'reports#index'
|
get '/reports', to: 'reports#index'
|
||||||
|
|||||||
Reference in New Issue
Block a user