mirror of
https://github.com/we-promise/sure.git
synced 2026-08-04 08:02:15 +00:00
Add display step for reconciliation report
This commit is contained in:
@@ -15,7 +15,8 @@ class ProcessPdfJob < ApplicationJob
|
||||
upload_to_vector_store(pdf_import, document_type: document_type)
|
||||
|
||||
# For statements with transactions (bank/credit card), extract and generate import rows
|
||||
if statement_with_transactions?(document_type)
|
||||
# unless reconciliation already confirmed balances match
|
||||
if statement_with_transactions?(document_type) && !pdf_import.reconciliation_matched?
|
||||
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")
|
||||
@@ -23,6 +24,8 @@ class ProcessPdfJob < ApplicationJob
|
||||
pdf_import.generate_rows_from_extracted_data
|
||||
pdf_import.sync_mappings
|
||||
Rails.logger.info("ProcessPdfJob: Generated #{pdf_import.rows_count} import rows")
|
||||
elsif pdf_import.reconciliation_matched?
|
||||
Rails.logger.info("ProcessPdfJob: Reconciliation matched for import #{pdf_import.id}, skipping transaction extraction")
|
||||
end
|
||||
|
||||
# Find the user who created this import (first admin or any user in the family)
|
||||
@@ -32,9 +35,14 @@ class ProcessPdfJob < ApplicationJob
|
||||
pdf_import.send_next_steps_email(user)
|
||||
end
|
||||
|
||||
# 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
|
||||
final_status = if pdf_import.reconciliation_matched?
|
||||
:complete
|
||||
elsif statement_with_transactions?(document_type) && pdf_import.rows_count > 0
|
||||
:pending
|
||||
else
|
||||
:complete
|
||||
end
|
||||
|
||||
pdf_import.update!(status: final_status)
|
||||
rescue StandardError => e
|
||||
sanitized_error = sanitize_error_message(e)
|
||||
|
||||
@@ -177,10 +177,16 @@ class PdfImport < Import
|
||||
end
|
||||
|
||||
result = response.data
|
||||
update!(
|
||||
|
||||
attrs = {
|
||||
ai_summary: result.summary,
|
||||
document_type: result.document_type
|
||||
)
|
||||
}
|
||||
if result.reconciliation.present?
|
||||
attrs[:extracted_data] = (result.extracted_data || {}).merge("reconciliation" => result.reconciliation)
|
||||
end
|
||||
|
||||
update!(attrs)
|
||||
|
||||
result
|
||||
end
|
||||
@@ -215,6 +221,16 @@ class PdfImport < Import
|
||||
document_type.in?(%w[bank_statement credit_card_statement])
|
||||
end
|
||||
|
||||
def reconciliation_data
|
||||
extracted_data&.dig("reconciliation")
|
||||
end
|
||||
|
||||
def reconciliation_matched?
|
||||
reconciliation_data.present? &&
|
||||
reconciliation_data["performed"] == true &&
|
||||
reconciliation_data["balance_match"] == true
|
||||
end
|
||||
|
||||
def has_extracted_transactions?
|
||||
extracted_data.present? && extracted_data["transactions"].present?
|
||||
end
|
||||
|
||||
@@ -97,6 +97,8 @@ class Provider::Openai::PdfProcessor
|
||||
"performed": true|false,
|
||||
"account_id": "account_id or null",
|
||||
"balance_match": true|false|null,
|
||||
"statement_closing_balance": number|null,
|
||||
"synced_closing_balance": number|null,
|
||||
"statement_transaction_count": number|null,
|
||||
"synced_transaction_count": number|null,
|
||||
"matched_count": number|null,
|
||||
|
||||
@@ -8,10 +8,11 @@
|
||||
elsif import.is_a?(PdfImport)
|
||||
# PDF imports have a simplified flow: Upload -> Confirm
|
||||
# Upload/Configure/Clean are always complete for processed PDF imports
|
||||
finalized = import.complete?
|
||||
[
|
||||
{ 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: import.configured? ? import_clean_path(import) : nil, is_complete: import.cleaned?, step_number: 3 },
|
||||
{ name: t("imports.steps.configure", default: "Configure"), path: nil, is_complete: finalized || import.configured?, step_number: 2 },
|
||||
{ name: t("imports.steps.clean", default: "Clean"), path: (!finalized && import.configured?) ? import_clean_path(import) : nil, is_complete: finalized || import.cleaned?, step_number: 3 },
|
||||
{ name: t("imports.steps.confirm", default: "Confirm"), path: import_path(import), is_complete: import.complete?, step_number: 4 }
|
||||
]
|
||||
elsif import.is_a?(QifImport)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<%# locals: (import:) %>
|
||||
|
||||
<div class="h-full flex flex-col justify-center items-center">
|
||||
<div class="min-h-full flex flex-col justify-center items-center py-8">
|
||||
<div class="space-y-6 max-w-lg w-full">
|
||||
<% if import.pending? && import.rows_count > 0 %>
|
||||
<%# Statement with rows ready for review %>
|
||||
@@ -104,6 +104,77 @@
|
||||
<%= button_to t("imports.pdf_import.delete_import"), import_path(import), method: :delete, class: "w-full font-medium text-sm px-3 py-2 rounded-lg text-primary bg-surface-inset hover:bg-surface-inset-hover" %>
|
||||
</div>
|
||||
|
||||
<% elsif import.complete? && import.respond_to?(:reconciliation_matched?) && import.reconciliation_matched? %>
|
||||
<% recon = import.reconciliation_data %>
|
||||
|
||||
<div class="mx-auto bg-success/10 h-8 w-8 rounded-full flex items-center justify-center">
|
||||
<%= icon "check", class: "text-success" %>
|
||||
</div>
|
||||
|
||||
<div class="text-center space-y-2">
|
||||
<h1 class="font-medium text-primary text-center text-3xl"><%= t("imports.pdf_import.reconciliation_title") %></h1>
|
||||
<p class="text-sm text-secondary"><%= t("imports.pdf_import.reconciliation_description") %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-container border border-primary rounded-xl p-4 space-y-4">
|
||||
<div class="space-y-2">
|
||||
<h2 class="font-medium text-primary"><%= t("imports.pdf_import.document_type_label") %></h2>
|
||||
<p class="text-sm text-secondary px-3 py-2 bg-gray-500/5 rounded-lg">
|
||||
<%= t("imports.document_types.#{import.document_type}", default: import.document_type&.humanize || t("imports.pdf_import.unknown_document_type", default: "Unknown")) %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<h2 class="font-medium text-primary"><%= t("imports.pdf_import.reconciliation_details_label") %></h2>
|
||||
<div class="space-y-1 px-3 py-2 bg-gray-500/5 rounded-lg">
|
||||
<% if recon["statement_closing_balance"].present? %>
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-secondary"><%= t("imports.pdf_import.reconciliation_statement_balance") %></span>
|
||||
<span class="text-primary font-medium privacy-sensitive"><%= number_to_currency(recon["statement_closing_balance"], unit: import.extracted_data&.dig("currency") || "") %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if recon["synced_closing_balance"].present? %>
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-secondary"><%= t("imports.pdf_import.reconciliation_synced_balance") %></span>
|
||||
<span class="text-primary font-medium privacy-sensitive"><%= number_to_currency(recon["synced_closing_balance"], unit: import.extracted_data&.dig("currency") || "") %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-secondary"><%= t("imports.pdf_import.reconciliation_balance_match") %></span>
|
||||
<span class="text-success font-medium"><%= t("imports.pdf_import.reconciliation_balance_yes") %></span>
|
||||
</div>
|
||||
<% if recon["statement_transaction_count"].present? %>
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-secondary"><%= t("imports.pdf_import.reconciliation_statement_txns") %></span>
|
||||
<span class="text-primary font-medium"><%= recon["statement_transaction_count"] %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if recon["synced_transaction_count"].present? %>
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-secondary"><%= t("imports.pdf_import.reconciliation_synced_txns") %></span>
|
||||
<span class="text-primary font-medium"><%= recon["synced_transaction_count"] %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if recon["matched_count"].present? %>
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-secondary"><%= t("imports.pdf_import.reconciliation_matched_txns") %></span>
|
||||
<span class="text-primary font-medium"><%= recon["matched_count"] %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<h2 class="font-medium text-primary"><%= t("imports.pdf_import.summary_label") %></h2>
|
||||
<p class="text-sm text-secondary px-3 py-2 bg-gray-500/5 rounded-lg whitespace-pre-wrap privacy-sensitive"><%= import.ai_summary %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2 flex flex-col">
|
||||
<%= render DS::Link.new(text: t("imports.pdf_import.back_to_imports"), href: imports_path, variant: "primary", full_width: true) %>
|
||||
<%= button_to t("imports.pdf_import.delete_import"), import_path(import), method: :delete, class: "w-full font-medium text-sm px-3 py-2 rounded-lg text-primary bg-gray-200 hover:bg-gray-300" %>
|
||||
</div>
|
||||
|
||||
<% elsif import.complete? && import.ai_processed? %>
|
||||
<div class="mx-auto bg-success/10 h-8 w-8 rounded-full flex items-center justify-center">
|
||||
<%= icon "check", class: "text-success" %>
|
||||
|
||||
@@ -460,6 +460,16 @@ en:
|
||||
summary_label: Summary
|
||||
email_sent_notice: An email has been sent to you with next steps.
|
||||
back_to_imports: Back to imports
|
||||
reconciliation_title: Reconciliation report
|
||||
reconciliation_description: We've compared your statement against synced data and everything matches.
|
||||
reconciliation_details_label: Reconciliation Details
|
||||
reconciliation_statement_balance: "Statement closing balance"
|
||||
reconciliation_synced_balance: "Synced closing balance"
|
||||
reconciliation_balance_match: Balance match
|
||||
reconciliation_balance_yes: Yes
|
||||
reconciliation_statement_txns: Statement transactions
|
||||
reconciliation_synced_txns: Synced transactions
|
||||
reconciliation_matched_txns: Matched transactions
|
||||
unknown_state_title: Unknown state
|
||||
unknown_state_description: This import is in an unexpected state. Please return to imports.
|
||||
processing_failed_with_message: "%{message}"
|
||||
|
||||
Reference in New Issue
Block a user