diff --git a/app/services/report_parser.rb b/app/services/report_parser.rb index 1c5e5c6..7754bdd 100644 --- a/app/services/report_parser.rb +++ b/app/services/report_parser.rb @@ -11,7 +11,7 @@ class ReportParser sheet_name: 'Отчет о финансовых результатах' }, '1.3' => { - parser: nil, + parser: ReportParsers::Form13Parser, sheet_name: 'Отчет об изменениях капитала' }, '1.4' => { diff --git a/app/services/report_parsers/form11_parser.rb b/app/services/report_parsers/form11_parser.rb index 1e86f87..bd66e0d 100644 --- a/app/services/report_parsers/form11_parser.rb +++ b/app/services/report_parsers/form11_parser.rb @@ -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 diff --git a/app/services/report_parsers/form13_parser.rb b/app/services/report_parsers/form13_parser.rb new file mode 100644 index 0000000..0480126 --- /dev/null +++ b/app/services/report_parsers/form13_parser.rb @@ -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