Добавлено парсирование формы 14 и сохранение финансовых отчетов в базу данных; добавлена миграция для начальных данных типов документов

This commit is contained in:
Dmitry
2026-03-25 09:48:22 +03:00
parent 207e4b45a7
commit 7ee21d5e5d
5 changed files with 151 additions and 37 deletions
+4 -9
View File
@@ -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