From d79d86d8487dcc99e2e813543b2e99f916c97306 Mon Sep 17 00:00:00 2001 From: soky srm Date: Mon, 16 Feb 2026 21:11:41 +0100 Subject: [PATCH] PDF ai import (#1006) Add support to review transactions for AI pdf import --- app/controllers/import/cleans_controller.rb | 5 ++++- app/jobs/process_pdf_job.rb | 18 +++++++++--------- app/models/pdf_import.rb | 8 ++++++-- app/views/import/cleans/show.html.erb | 4 ++-- app/views/imports/_nav.html.erb | 2 +- app/views/imports/_pdf_import.html.erb | 7 ++++--- 6 files changed, 26 insertions(+), 18 deletions(-) diff --git a/app/controllers/import/cleans_controller.rb b/app/controllers/import/cleans_controller.rb index a8bda133c..2c502f0a1 100644 --- a/app/controllers/import/cleans_controller.rb +++ b/app/controllers/import/cleans_controller.rb @@ -4,7 +4,10 @@ class Import::CleansController < ApplicationController before_action :set_import def show - redirect_to import_configuration_path(@import), alert: "Please configure your import before proceeding." unless @import.configured? + unless @import.configured? + redirect_path = @import.is_a?(PdfImport) ? import_path(@import) : import_configuration_path(@import) + return redirect_to redirect_path, alert: "Please configure your import before proceeding." + end rows = @import.rows.ordered diff --git a/app/jobs/process_pdf_job.rb b/app/jobs/process_pdf_job.rb index e5d0e2480..1ed48cf49 100644 --- a/app/jobs/process_pdf_job.rb +++ b/app/jobs/process_pdf_job.rb @@ -5,7 +5,7 @@ class ProcessPdfJob < ApplicationJob return unless pdf_import.is_a?(PdfImport) return unless pdf_import.pdf_uploaded? return if pdf_import.status == "complete" - return if pdf_import.ai_processed? && (!pdf_import.bank_statement? || pdf_import.rows_count > 0) + return if pdf_import.ai_processed? && (!pdf_import.statement_with_transactions? || pdf_import.rows_count > 0) pdf_import.update!(status: :importing) @@ -14,9 +14,9 @@ class ProcessPdfJob < ApplicationJob document_type = resolve_document_type(pdf_import, process_result) upload_to_vector_store(pdf_import, document_type: document_type) - # For bank statements, extract transactions and generate import rows - if bank_statement_document?(document_type) - Rails.logger.info("ProcessPdfJob: Extracting transactions for bank statement import #{pdf_import.id}") + # For statements with transactions (bank/credit card), extract and generate import rows + if statement_with_transactions?(document_type) + Rails.logger.info("ProcessPdfJob: Extracting transactions for #{document_type} import #{pdf_import.id}") pdf_import.extract_transactions Rails.logger.info("ProcessPdfJob: Extracted #{pdf_import.extracted_transactions.size} transactions") @@ -32,9 +32,9 @@ class ProcessPdfJob < ApplicationJob pdf_import.send_next_steps_email(user) end - # Bank statements with rows go to pending for user review/publish - # Non-bank statements are marked complete (no further action needed) - final_status = bank_statement_document?(document_type) && pdf_import.rows_count > 0 ? :pending : :complete + # Statements with extracted rows go to pending for user review/publish + # Other document types are marked complete (no further action needed) + final_status = statement_with_transactions?(document_type) && pdf_import.rows_count > 0 ? :pending : :complete pdf_import.update!(status: final_status) rescue StandardError => e sanitized_error = sanitize_error_message(e) @@ -82,7 +82,7 @@ class ProcessPdfJob < ApplicationJob pdf_import.reload.document_type end - def bank_statement_document?(document_type) - document_type == "bank_statement" + def statement_with_transactions?(document_type) + document_type.in?(%w[bank_statement credit_card_statement]) end end diff --git a/app/models/pdf_import.rb b/app/models/pdf_import.rb index 90cb3379d..38091bf2c 100644 --- a/app/models/pdf_import.rb +++ b/app/models/pdf_import.rb @@ -68,7 +68,7 @@ class PdfImport < Import end def extract_transactions - return unless bank_statement? + return unless statement_with_transactions? provider = Provider::Registry.get_provider(:openai) raise "AI provider not configured" unless provider @@ -91,6 +91,10 @@ class PdfImport < Import document_type == "bank_statement" end + def statement_with_transactions? + document_type.in?(%w[bank_statement credit_card_statement]) + end + def has_extracted_transactions? extracted_data.present? && extracted_data["transactions"].present? end @@ -147,7 +151,7 @@ class PdfImport < Import end def publishable? - account.present? && bank_statement? && cleaned? && mappings.all?(&:valid?) + account.present? && statement_with_transactions? && cleaned? && mappings.all?(&:valid?) end def column_keys diff --git a/app/views/import/cleans/show.html.erb b/app/views/import/cleans/show.html.erb index c0bce7d7c..3fcbf8143 100644 --- a/app/views/import/cleans/show.html.erb +++ b/app/views/import/cleans/show.html.erb @@ -2,7 +2,7 @@ <%= render "imports/nav", import: @import %> <% end %> -<%= content_for :previous_path, import_configuration_path(@import) %> +<%= content_for :previous_path, @import.is_a?(PdfImport) ? import_path(@import) : import_configuration_path(@import) %>
@@ -20,7 +20,7 @@ <%= render DS::Link.new( text: "Next step", variant: "primary", - href: import_confirm_path(@import), + href: @import.is_a?(PdfImport) ? import_path(@import) : import_confirm_path(@import), frame: :_top, class: "w-full md:w-auto" ) %> diff --git a/app/views/imports/_nav.html.erb b/app/views/imports/_nav.html.erb index 26435d8b6..43282c5d3 100644 --- a/app/views/imports/_nav.html.erb +++ b/app/views/imports/_nav.html.erb @@ -6,7 +6,7 @@ [ { name: t("imports.steps.upload", default: "Upload"), path: nil, is_complete: import.pdf_uploaded?, step_number: 1 }, { name: t("imports.steps.configure", default: "Configure"), path: nil, is_complete: import.configured?, step_number: 2 }, - { name: t("imports.steps.clean", default: "Clean"), path: nil, is_complete: import.cleaned?, step_number: 3 }, + { name: t("imports.steps.clean", default: "Clean"), path: import.configured? ? import_clean_path(import) : nil, is_complete: import.cleaned?, step_number: 3 }, { name: t("imports.steps.confirm", default: "Confirm"), path: import_path(import), is_complete: import.complete?, step_number: 4 } ] else diff --git a/app/views/imports/_pdf_import.html.erb b/app/views/imports/_pdf_import.html.erb index 834dbd894..f565e8274 100644 --- a/app/views/imports/_pdf_import.html.erb +++ b/app/views/imports/_pdf_import.html.erb @@ -3,14 +3,14 @@
<% if import.pending? && import.rows_count > 0 %> - <%# Bank statement with rows ready for review %> + <%# Statement with rows ready for review %>
<%= icon "check", class: "text-success" %>

<%= t("imports.pdf_import.ready_for_review_title", default: "Ready for Review") %>

-

<%= t("imports.pdf_import.ready_for_review_description", default: "We extracted %{count} transactions from your bank statement. Review and publish them to add to your account.", count: import.rows_count) %>

+

<%= t("imports.pdf_import.ready_for_review_description", default: "We extracted %{count} transactions from your statement. Review and publish them to add to your account.", count: import.rows_count) %>

@@ -53,8 +53,9 @@
<% if import.publishable? %> <%= button_to t("imports.pdf_import.publish_transactions", default: "Publish %{count} Transactions", count: import.rows_count), publish_import_path(import), method: :post, class: "w-full font-medium text-sm px-3 py-2 rounded-lg text-inverse bg-inverse hover:bg-inverse-hover" %> + <%= render DS::Link.new(text: t("imports.pdf_import.review_transactions", default: "Review Transactions"), href: import_clean_path(import), variant: "secondary", full_width: true) %> <% elsif import.account.present? %> - <%= render DS::Link.new(text: t("imports.pdf_import.review_transactions", default: "Review Transactions"), href: import_confirm_path(import), variant: "primary", full_width: true) %> + <%= render DS::Link.new(text: t("imports.pdf_import.review_transactions", default: "Review Transactions"), href: import_clean_path(import), variant: "primary", full_width: true) %> <% else %>

<%= t("imports.pdf_import.select_account_to_continue", default: "Please select an account above to continue.") %>

<% end %>