mirror of
https://github.com/we-promise/sure.git
synced 2026-04-07 14:31:25 +00:00
PDF ai import (#1006)
Add support to review transactions for AI pdf import
This commit is contained in:
@@ -4,7 +4,10 @@ class Import::CleansController < ApplicationController
|
|||||||
before_action :set_import
|
before_action :set_import
|
||||||
|
|
||||||
def show
|
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
|
rows = @import.rows.ordered
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ class ProcessPdfJob < ApplicationJob
|
|||||||
return unless pdf_import.is_a?(PdfImport)
|
return unless pdf_import.is_a?(PdfImport)
|
||||||
return unless pdf_import.pdf_uploaded?
|
return unless pdf_import.pdf_uploaded?
|
||||||
return if pdf_import.status == "complete"
|
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)
|
pdf_import.update!(status: :importing)
|
||||||
|
|
||||||
@@ -14,9 +14,9 @@ class ProcessPdfJob < ApplicationJob
|
|||||||
document_type = resolve_document_type(pdf_import, process_result)
|
document_type = resolve_document_type(pdf_import, process_result)
|
||||||
upload_to_vector_store(pdf_import, document_type: document_type)
|
upload_to_vector_store(pdf_import, document_type: document_type)
|
||||||
|
|
||||||
# For bank statements, extract transactions and generate import rows
|
# For statements with transactions (bank/credit card), extract and generate import rows
|
||||||
if bank_statement_document?(document_type)
|
if statement_with_transactions?(document_type)
|
||||||
Rails.logger.info("ProcessPdfJob: Extracting transactions for bank statement import #{pdf_import.id}")
|
Rails.logger.info("ProcessPdfJob: Extracting transactions for #{document_type} import #{pdf_import.id}")
|
||||||
pdf_import.extract_transactions
|
pdf_import.extract_transactions
|
||||||
Rails.logger.info("ProcessPdfJob: Extracted #{pdf_import.extracted_transactions.size} 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)
|
pdf_import.send_next_steps_email(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Bank statements with rows go to pending for user review/publish
|
# Statements with extracted rows go to pending for user review/publish
|
||||||
# Non-bank statements are marked complete (no further action needed)
|
# Other document types are marked complete (no further action needed)
|
||||||
final_status = bank_statement_document?(document_type) && pdf_import.rows_count > 0 ? :pending : :complete
|
final_status = statement_with_transactions?(document_type) && pdf_import.rows_count > 0 ? :pending : :complete
|
||||||
pdf_import.update!(status: final_status)
|
pdf_import.update!(status: final_status)
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
sanitized_error = sanitize_error_message(e)
|
sanitized_error = sanitize_error_message(e)
|
||||||
@@ -82,7 +82,7 @@ class ProcessPdfJob < ApplicationJob
|
|||||||
pdf_import.reload.document_type
|
pdf_import.reload.document_type
|
||||||
end
|
end
|
||||||
|
|
||||||
def bank_statement_document?(document_type)
|
def statement_with_transactions?(document_type)
|
||||||
document_type == "bank_statement"
|
document_type.in?(%w[bank_statement credit_card_statement])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class PdfImport < Import
|
|||||||
end
|
end
|
||||||
|
|
||||||
def extract_transactions
|
def extract_transactions
|
||||||
return unless bank_statement?
|
return unless statement_with_transactions?
|
||||||
|
|
||||||
provider = Provider::Registry.get_provider(:openai)
|
provider = Provider::Registry.get_provider(:openai)
|
||||||
raise "AI provider not configured" unless provider
|
raise "AI provider not configured" unless provider
|
||||||
@@ -91,6 +91,10 @@ class PdfImport < Import
|
|||||||
document_type == "bank_statement"
|
document_type == "bank_statement"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def statement_with_transactions?
|
||||||
|
document_type.in?(%w[bank_statement credit_card_statement])
|
||||||
|
end
|
||||||
|
|
||||||
def has_extracted_transactions?
|
def has_extracted_transactions?
|
||||||
extracted_data.present? && extracted_data["transactions"].present?
|
extracted_data.present? && extracted_data["transactions"].present?
|
||||||
end
|
end
|
||||||
@@ -147,7 +151,7 @@ class PdfImport < Import
|
|||||||
end
|
end
|
||||||
|
|
||||||
def publishable?
|
def publishable?
|
||||||
account.present? && bank_statement? && cleaned? && mappings.all?(&:valid?)
|
account.present? && statement_with_transactions? && cleaned? && mappings.all?(&:valid?)
|
||||||
end
|
end
|
||||||
|
|
||||||
def column_keys
|
def column_keys
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<%= render "imports/nav", import: @import %>
|
<%= render "imports/nav", import: @import %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= content_for :previous_path, import_configuration_path(@import) %>
|
<%= content_for :previous_path, @import.is_a?(PdfImport) ? import_path(@import) : import_configuration_path(@import) %>
|
||||||
|
|
||||||
<div class="space-y-4 mx-auto max-w-5xl">
|
<div class="space-y-4 mx-auto max-w-5xl">
|
||||||
<div class="text-center space-y-2 max-w-[400px] mx-auto mb-4">
|
<div class="text-center space-y-2 max-w-[400px] mx-auto mb-4">
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<%= render DS::Link.new(
|
<%= render DS::Link.new(
|
||||||
text: "Next step",
|
text: "Next step",
|
||||||
variant: "primary",
|
variant: "primary",
|
||||||
href: import_confirm_path(@import),
|
href: @import.is_a?(PdfImport) ? import_path(@import) : import_confirm_path(@import),
|
||||||
frame: :_top,
|
frame: :_top,
|
||||||
class: "w-full md:w-auto"
|
class: "w-full md:w-auto"
|
||||||
) %>
|
) %>
|
||||||
|
|||||||
@@ -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.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.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 }
|
{ name: t("imports.steps.confirm", default: "Confirm"), path: import_path(import), is_complete: import.complete?, step_number: 4 }
|
||||||
]
|
]
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
<div class="h-full flex flex-col justify-center items-center">
|
<div class="h-full flex flex-col justify-center items-center">
|
||||||
<div class="space-y-6 max-w-lg w-full">
|
<div class="space-y-6 max-w-lg w-full">
|
||||||
<% if import.pending? && import.rows_count > 0 %>
|
<% if import.pending? && import.rows_count > 0 %>
|
||||||
<%# Bank statement with rows ready for review %>
|
<%# Statement with rows ready for review %>
|
||||||
<div class="mx-auto bg-success/10 h-8 w-8 rounded-full flex items-center justify-center">
|
<div class="mx-auto bg-success/10 h-8 w-8 rounded-full flex items-center justify-center">
|
||||||
<%= icon "check", class: "text-success" %>
|
<%= icon "check", class: "text-success" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center space-y-2">
|
<div class="text-center space-y-2">
|
||||||
<h1 class="font-medium text-primary text-center text-3xl"><%= t("imports.pdf_import.ready_for_review_title", default: "Ready for Review") %></h1>
|
<h1 class="font-medium text-primary text-center text-3xl"><%= t("imports.pdf_import.ready_for_review_title", default: "Ready for Review") %></h1>
|
||||||
<p class="text-sm text-secondary"><%= 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) %></p>
|
<p class="text-sm text-secondary"><%= 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) %></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-container border border-primary rounded-xl p-4 space-y-4">
|
<div class="bg-container border border-primary rounded-xl p-4 space-y-4">
|
||||||
@@ -53,8 +53,9 @@
|
|||||||
<div class="space-y-2 flex flex-col">
|
<div class="space-y-2 flex flex-col">
|
||||||
<% if import.publishable? %>
|
<% 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" %>
|
<%= 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? %>
|
<% 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 %>
|
<% else %>
|
||||||
<p class="text-center text-sm text-secondary"><%= t("imports.pdf_import.select_account_to_continue", default: "Please select an account above to continue.") %></p>
|
<p class="text-center text-sm text-secondary"><%= t("imports.pdf_import.select_account_to_continue", default: "Please select an account above to continue.") %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
Reference in New Issue
Block a user