Добавлено парсирование формы 14 и сохранение финансовых отчетов в базу данных; добавлена миграция для начальных данных типов документов
This commit is contained in:
@@ -11,8 +11,11 @@ class ReportParser
|
||||
|
||||
form_11_data = ReportParsers::Form11Parser.new(@yearly_file, spreadsheet: spreadsheet).call
|
||||
form_12_data = ReportParsers::Form12Parser.new(@yearly_file, spreadsheet: spreadsheet).call
|
||||
form_14_data = ReportParsers::Form14Parser.new(@yearly_file, spreadsheet: spreadsheet).call
|
||||
|
||||
merge_financial_statements(form_11_data, form_12_data)
|
||||
save_form_statements(form_11_data, 1) # 1 - Бух. баланс
|
||||
save_form_statements(form_12_data, 2) # 2 - Отчет о фин. результатах
|
||||
save_form_statements(form_14_data, 4) # 4 - Отчет о движении денежных средств
|
||||
end
|
||||
end
|
||||
|
||||
@@ -28,35 +31,21 @@ class ReportParser
|
||||
end
|
||||
end
|
||||
|
||||
def merge_financial_statements(form_11_data, form_12_data)
|
||||
document_type = DocumentType.find_or_create_by!(name: 'Финансовые данные')
|
||||
def save_form_statements(form_data, form_id)
|
||||
document_type = DocumentType.find_by(id: form_id)
|
||||
raise "DocumentType with id=#{form_id} not found!" unless document_type
|
||||
|
||||
all_years = (form_11_data.keys + form_12_data.keys).uniq
|
||||
form_data.each do |year, data|
|
||||
rows = data[:rows] || data['rows'] || []
|
||||
next if rows.blank?
|
||||
|
||||
all_years.each do |year|
|
||||
statement = find_or_build_statement(document_type, year)
|
||||
|
||||
all_rows = []
|
||||
|
||||
if form_11_data[year].present?
|
||||
form_11_rows = form_11_data[year][:rows] || form_11_data[year]['rows'] || []
|
||||
all_rows.concat(form_11_rows)
|
||||
end
|
||||
|
||||
if form_12_data[year].present?
|
||||
form_12_rows = form_12_data[year][:rows] || form_12_data[year]['rows'] || []
|
||||
all_rows.concat(form_12_rows)
|
||||
end
|
||||
|
||||
if all_rows.present?
|
||||
payload = {
|
||||
data_year: year,
|
||||
rows: all_rows
|
||||
}
|
||||
|
||||
statement.assign_attributes(data: payload)
|
||||
statement.save!
|
||||
end
|
||||
payload = {
|
||||
data_year: year,
|
||||
rows: rows
|
||||
}
|
||||
statement.assign_attributes(data: payload)
|
||||
statement.save!
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -98,21 +98,16 @@ module ReportParsers
|
||||
|
||||
value_columns = columns.keys.select do |col|
|
||||
text = columns[col]
|
||||
text.include?('за ') ||
|
||||
text.include?('на ') ||
|
||||
text.match?(/\b20\d{2}\b/) ||
|
||||
(col > code_column && text.include?('года')) ||
|
||||
(col > code_column && text.match?(/\d+/))
|
||||
next false if text.include?('поясн')
|
||||
text.match?(/20\d{2}/) || text.include?('за ') || text.include?('на ')
|
||||
end
|
||||
|
||||
if value_columns.empty?
|
||||
value_columns = columns.keys.select { |col| col > code_column && columns[col].present? }
|
||||
end
|
||||
value_columns = value_columns.sort.last(2)
|
||||
|
||||
{
|
||||
name_column: name_column,
|
||||
code_column: code_column,
|
||||
value_columns: value_columns.sort.first(3)
|
||||
value_columns: value_columns
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
def extract_year_from_header(header_text)
|
||||
year_match = header_text[/\b(20\d{2})\b/]
|
||||
year_match.to_i if year_match
|
||||
end
|
||||
def find_header_row(sheet)
|
||||
1.upto(sheet.last_row) do |row|
|
||||
normalized_cells = 1.upto(sheet.last_column).map { |col| normalize_label(sheet.cell(row, col)) }
|
||||
has_name = normalized_cells.any? { |text| text.include?("наименование показателя") }
|
||||
has_code = normalized_cells.any? { |text| text == "код" || text.start_with?("код ") || text.start_with?("код:") }
|
||||
return row if has_name && has_code
|
||||
end
|
||||
|
||||
1
|
||||
end
|
||||
|
||||
def detect_columns(sheet, header_row)
|
||||
columns = 1.upto(sheet.last_column).each_with_object({}) do |col, hash|
|
||||
hash[col] = normalize_label(sheet.cell(header_row, col))
|
||||
end
|
||||
|
||||
name_column = columns.key(columns.values.find { |text| text.include?("наименование показателя") }) || 1
|
||||
|
||||
code_column = columns.key(columns.values.find { |text|
|
||||
text == "код строки" || text.start_with?("код строки ") ||
|
||||
(text == "код" && columns[name_column + 1] == text)
|
||||
}) || columns.key(columns.values.find { |text| text == "код" || text.start_with?("код ") }) || [name_column + 1, sheet.last_column].min
|
||||
|
||||
value_columns = columns.keys.select do |col|
|
||||
text = columns[col]
|
||||
next false if text.include?("поясн")
|
||||
text.match?(/20\d{2}/) || text.include?("за ") || text.include?("на ")
|
||||
end
|
||||
value_columns = value_columns.sort.last(2)
|
||||
|
||||
{
|
||||
name_column: name_column,
|
||||
code_column: code_column,
|
||||
value_columns: value_columns
|
||||
}
|
||||
end
|
||||
|
||||
def normalize_label(text)
|
||||
text.to_s.downcase.gsub(/[[:space:]]+/, ' ').strip
|
||||
end
|
||||
module ReportParsers
|
||||
class Form14Parser < BaseParser
|
||||
SHEET_NAME = 'Отчет о движении денежных средс'
|
||||
FORM_CODE = '1.4'
|
||||
|
||||
def call
|
||||
return {} unless sheet_exists?(SHEET_NAME)
|
||||
|
||||
sheet = spreadsheet.sheet(SHEET_NAME)
|
||||
build_payloads(sheet)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_payloads(sheet)
|
||||
header_row = find_header_row(sheet)
|
||||
columns = detect_columns(sheet, header_row)
|
||||
|
||||
value_headers = columns[:value_columns].each_with_object({}) do |col, hash|
|
||||
header_text = read_cell(sheet, header_row, col).to_s
|
||||
year = extract_year_from_header(header_text)
|
||||
hash[col] = year
|
||||
end
|
||||
|
||||
all_rows = (header_row + 1).upto(sheet.last_row).filter_map do |row|
|
||||
line_name = read_cell(sheet, row, columns[:name_column])
|
||||
line_code = read_cell(sheet, row, columns[:code_column]).to_s.strip.presence
|
||||
|
||||
values = columns[:value_columns].each_with_object({}) do |col, hash|
|
||||
value = read_cell(sheet, row, col)
|
||||
year = value_headers[col]
|
||||
hash[year.to_s] = value if value.present? && year.present?
|
||||
end.compact_blank
|
||||
|
||||
next if line_name.blank? && line_code.blank? && values.blank?
|
||||
|
||||
{
|
||||
line_name: line_name,
|
||||
line_code: line_code,
|
||||
values: values
|
||||
}
|
||||
end
|
||||
|
||||
payloads_by_year = {}
|
||||
value_headers.each do |col, year|
|
||||
next unless year.present?
|
||||
|
||||
rows_for_year = all_rows.map do |row|
|
||||
value = row[:values][year.to_s]
|
||||
next unless value.present?
|
||||
|
||||
{
|
||||
line_name: row[:line_name],
|
||||
line_code: row[:line_code],
|
||||
value: value
|
||||
}
|
||||
end.compact
|
||||
|
||||
payloads_by_year[year] = {
|
||||
form_code: FORM_CODE,
|
||||
rows: rows_for_year
|
||||
}
|
||||
end
|
||||
|
||||
payloads_by_year
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
class SeedDocumentTypes < ActiveRecord::Migration[7.0]
|
||||
def up
|
||||
execute <<-SQL
|
||||
INSERT INTO document_types (id, name, created_at, updated_at) VALUES
|
||||
(1, 'Бухгалтерский баланс', NOW(), NOW()),
|
||||
(2, 'Отчет о финансовых результатах', NOW(), NOW()),
|
||||
(3, 'Отчет об изменениях капитала', NOW(), NOW()),
|
||||
(4, 'Отчет о движении денежных средств', NOW(), NOW())
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
SQL
|
||||
end
|
||||
|
||||
def down
|
||||
execute <<-SQL
|
||||
DELETE FROM document_types WHERE id IN (1,2,3,4);
|
||||
SQL
|
||||
end
|
||||
end
|
||||
Generated
+1
-1
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.1].define(version: 2026_03_21_000006) do
|
||||
ActiveRecord::Schema[7.1].define(version: 2026_03_25_063000) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user