SU2-33 | Парсер формы 1.3, фикс смещения колонок в упрощённой отчётности

This commit is contained in:
Dmitry
2026-04-21 08:33:24 +00:00
parent 136de5f238
commit aea8b7975e
3 changed files with 139 additions and 7 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ class ReportParser
sheet_name: 'Отчет о финансовых результатах'
},
'1.3' => {
parser: nil,
parser: ReportParsers::Form13Parser,
sheet_name: 'Отчет об изменениях капитала'
},
'1.4' => {
+23 -6
View File
@@ -19,10 +19,11 @@ module ReportParsers
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 читается по оригинальному col заголовка, данные — по откалиброванному
value_headers = columns[:value_columns].zip(columns[:data_value_columns]).each_with_object({}) do |(header_col, data_col), hash|
header_text = read_cell(sheet, header_row, header_col).to_s
year = extract_year_from_header(header_text)
hash[col] = year
hash[data_col] = year
end
all_rows = (header_row + 1).upto(sheet.last_row).filter_map do |row|
@@ -30,7 +31,7 @@ module ReportParsers
line_code = read_cell(sheet, row, columns[:code_column]).to_s.strip.presence
next if ignored_row?(line_name, line_code)
values = columns[:value_columns].each_with_object({}) do |col, hash|
values = columns[:data_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?
@@ -46,7 +47,7 @@ module ReportParsers
end
payloads_by_year = {}
value_headers.each do |col, year|
value_headers.each do |_data_col, year|
next unless year.present?
rows_for_year = all_rows.map do |row|
@@ -114,13 +115,29 @@ module ReportParsers
value_columns = columns.keys.select { |col| col > code_column && columns[col].present? }
end
original_cols = value_columns.sort.first(3)
{
name_column: name_column,
code_column: code_column,
value_columns: value_columns.sort.first(3)
value_columns: original_cols,
data_value_columns: calibrate_value_columns(sheet, header_row, original_cols, code_column)
}
end
def calibrate_value_columns(sheet, header_row, value_columns, code_column)
data_row = (header_row + 2).upto(sheet.last_row).find do |row|
sheet.cell(row, code_column).to_s.strip.match?(/\A\d{4}\z/)
end
return value_columns unless data_row
value_columns.map do |col|
next col unless sheet.cell(data_row, col).nil? || sheet.cell(data_row, col).to_s.strip.empty?
shifted = col + 1
shifted <= sheet.last_column && !sheet.cell(data_row, shifted).to_s.strip.empty? ? shifted : col
end
end
def extract_year(sheet)
period_text = sheet.cell(4, 1).to_s
year = period_text[/\d{4}/].to_i
@@ -0,0 +1,115 @@
module ReportParsers
class Form13Parser < BaseParser
SHEET_NAME = 'Отчет об изменениях капитала'
FORM_CODE = '1.3'
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_section1_header_row(sheet)
return {} unless header_row
report_year = extract_report_year(sheet)
return {} unless report_year
name_col = find_name_column(sheet, header_row)
code_col = find_code_column(sheet, header_row)
total_col = find_total_column(sheet, header_row)
return {} unless code_col && total_col
end_row = section1_end_row(sheet, header_row)
payloads_by_year = {}
(header_row + 2).upto(end_row) do |row|
line_code = read_cell(sheet, row, code_col).to_s.strip.presence
next unless line_code&.match?(/\A3[123]\d{2}\z/)
row_year = year_from_code(line_code, report_year)
next unless row_year
name_cell = read_cell(sheet, row, name_col).to_s.strip
name_cell = read_cell(sheet, row, 1).to_s.strip if name_cell.blank?
value = read_cell(sheet, row, total_col)
payloads_by_year[row_year] ||= { form_code: FORM_CODE, sheet_name: SHEET_NAME, rows: [] }
payloads_by_year[row_year][:rows] << {
line_name: normalize_line_name(name_cell),
line_code: line_code,
value: value
}
end
payloads_by_year
end
def year_from_code(code, report_year)
case code[1]
when '1' then report_year - 2
when '2' then report_year - 1
when '3' then report_year
end
end
def extract_report_year(sheet)
1.upto([sheet.last_row, 10].min) do |row|
1.upto(sheet.last_column) do |col|
m = sheet.cell(row, col).to_s.match(/\bза\s+(20\d{2})\s*г/i)
return m[1].to_i if m
end
end
year = yearly_file&.year.to_i
year.positive? ? year : nil
end
def find_section1_header_row(sheet)
1.upto(sheet.last_row) do |row|
cells = 1.upto(sheet.last_column).map { |col| normalize_label(sheet.cell(row, col)) }
next unless cells.any? { |c| c.include?('наименование показателя') }
next unless cells.any? { |c| c.include?('код строки') || c == 'код' }
return row
end
nil
end
def find_name_column(sheet, header_row)
1.upto(sheet.last_column) do |col|
return col if normalize_label(sheet.cell(header_row, col)).include?('наименование показателя')
end
1
end
def find_code_column(sheet, header_row)
1.upto(sheet.last_column) do |col|
text = normalize_label(sheet.cell(header_row, col))
return col if text.include?('код строки') || text == 'код'
end
nil
end
def find_total_column(sheet, header_row)
result = nil
[header_row, header_row + 1].each do |row|
1.upto(sheet.last_column) do |col|
result = col if normalize_label(sheet.cell(row, col)) == 'итого'
end
return result if result
end
nil
end
def section1_end_row(sheet, from_row)
(from_row + 1).upto(sheet.last_row) do |row|
return row - 1 if normalize_label(sheet.cell(row, 1)).match?(/\A2\.\s*корректировки/i)
end
sheet.last_row
end
end
end