SU2-33 | Парсер формы 1.3, фикс смещения колонок в упрощённой отчётности
This commit is contained in:
@@ -11,7 +11,7 @@ class ReportParser
|
||||
sheet_name: 'Отчет о финансовых результатах'
|
||||
},
|
||||
'1.3' => {
|
||||
parser: nil,
|
||||
parser: ReportParsers::Form13Parser,
|
||||
sheet_name: 'Отчет об изменениях капитала'
|
||||
},
|
||||
'1.4' => {
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user