diff --git a/app/controllers/column/webhooks_controller.rb b/app/controllers/column/webhooks_controller.rb index 986b882ed1..bf1539eaca 100644 --- a/app/controllers/column/webhooks_controller.rb +++ b/app/controllers/column/webhooks_controller.rb @@ -11,6 +11,12 @@ def webhook type = params[:type] if type == "ach.incoming_transfer.scheduled" handle_ach_incoming_transfer_scheduled + elsif type == "ach.incoming_transfer.settled" + handle_as_raw_pending_column_transaction + elsif type == "wire.incoming_transfer.completed" + handle_as_raw_pending_column_transaction + elsif type == "swift.incoming_transfer.completed" + handle_as_raw_pending_column_transaction elsif type == "ach.outgoing_transfer.returned" handle_ach_outgoing_transfer_returned elsif type == "check.outgoing_debit.settled" @@ -50,6 +56,20 @@ def handle_ach_incoming_transfer_scheduled # at this point, the ACH is approved! end + def handle_as_raw_pending_column_transaction(column_event_type:) + account_number = AccountNumber.find_by(column_id: @object[:account_number_id]) + # we only create RawPendingColumnTransaction for transactions to + # event-specific account numbers. + return if account_number.nil? + + RawPendingColumnTransaction.create!( + amount_cents: @object["available_amount"], + date_posted: @object["effective_at_utc"], + column_transaction: @object, + column_event_type: + ) + end + def handle_ach_outgoing_transfer_returned AchTransfer.find_by(column_id: @object[:id])&.mark_failed!(reason: @object[:return_details].pick(:description)&.gsub(/\(trace #: \d+\)\Z/, "")&.strip) end diff --git a/app/models/canonical_pending_transaction.rb b/app/models/canonical_pending_transaction.rb index 53da13f795..6614814e93 100644 --- a/app/models/canonical_pending_transaction.rb +++ b/app/models/canonical_pending_transaction.rb @@ -18,6 +18,7 @@ # increase_check_id :bigint # paypal_transfer_id :bigint # raw_pending_bank_fee_transaction_id :bigint +# raw_pending_column_transaction_id :bigint # raw_pending_donation_transaction_id :bigint # raw_pending_incoming_disbursement_transaction_id :bigint # raw_pending_invoice_transaction_id :bigint @@ -31,6 +32,7 @@ # # Indexes # +# idx_on_raw_pending_column_transaction_id_ceea9a99e1 (raw_pending_column_transaction_id) UNIQUE # index_canonical_pending_transactions_on_check_deposit_id (check_deposit_id) # index_canonical_pending_transactions_on_hcb_code (hcb_code) # index_canonical_pending_transactions_on_increase_check_id (increase_check_id) @@ -44,6 +46,7 @@ # index_canonical_pending_txs_on_raw_pending_stripe_tx_id (raw_pending_stripe_transaction_id) # index_canonical_pending_txs_on_reimbursement_expense_payout_id (reimbursement_expense_payout_id) # index_canonical_pending_txs_on_reimbursement_payout_holding_id (reimbursement_payout_holding_id) +# index_canonical_pending_txs_on_rpct_id (raw_pending_column_transaction_id) # index_cpts_on_raw_pending_incoming_disbursement_transaction_id (raw_pending_incoming_disbursement_transaction_id) # index_cpts_on_raw_pending_outgoing_disbursement_transaction_id (raw_pending_outgoing_disbursement_transaction_id) # @@ -66,6 +69,7 @@ class CanonicalPendingTransaction < ApplicationRecord belongs_to :raw_pending_donation_transaction, optional: true belongs_to :raw_pending_invoice_transaction, optional: true belongs_to :raw_pending_bank_fee_transaction, optional: true + belongs_to :raw_pending_column_transaction, optional: true belongs_to :raw_pending_incoming_disbursement_transaction, optional: true belongs_to :raw_pending_outgoing_disbursement_transaction, optional: true belongs_to :increase_check, optional: true @@ -363,6 +367,10 @@ def stripe_card end end + def column_transaction_id + raw_pending_column_transaction&.column_id + end + private def write_hcb_code diff --git a/app/models/raw_column_transaction.rb b/app/models/raw_column_transaction.rb index dc84938949..87ee95b9c9 100644 --- a/app/models/raw_column_transaction.rb +++ b/app/models/raw_column_transaction.rb @@ -20,11 +20,20 @@ class RawColumnTransaction < ApplicationRecord after_create :canonize, if: -> { canonical_transaction.nil? } def canonize - create_canonical_transaction!( + ct = create_canonical_transaction!( amount_cents:, memo:, date: date_posted, ) + + raw_pending_column_transaction = RawPendingColumnTransaction.find_by(column_id: column_transaction["transaction_id"]) + + if raw_pending_column_transaction&.canonical_pending_transaction + CanonicalPendingTransactionService::Settle.new( + canonical_transaction: ct, + canonical_pending_transaction: raw_pending_column_transaction.canonical_pending_transaction + ).run! + end end def memo diff --git a/app/models/raw_pending_column_transaction.rb b/app/models/raw_pending_column_transaction.rb new file mode 100644 index 0000000000..7ccc0e67c7 --- /dev/null +++ b/app/models/raw_pending_column_transaction.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: raw_pending_column_transactions +# +# id :bigint not null, primary key +# amount_cents :integer not null +# column_event_type :integer not null +# column_transaction :jsonb not null +# date_posted :date not null +# description :text not null +# created_at :datetime not null +# updated_at :datetime not null +# column_id :string not null +# +# Indexes +# +# index_raw_pending_column_transactions_on_column_id (column_id) UNIQUE +# +class RawPendingColumnTransaction < ApplicationRecord + has_one :canonical_pending_transaction + + after_create :create_canonical_pending_transaction, if: -> { canonical_pending_transaction.nil? } + + enum :column_event_type, { + "swift.incoming_transfer.completed": 0, + "wire.incoming_transfer.completed": 1, + "ach.incoming_transfer.settled": 2 + } + + def create_canonical_pending_transaction + column_account_number = Column::AccountNumber.find_by(column_id: column_transaction["account_number_id"]) + return unless column_account_number + + # create_canonical_pending_transaction!( + # amount_cents:, + # memo:, + # date: date_posted, + # event: column_account_number.event, + # fronted: true + # ) + end + + def memo + if column_id.start_with? "acht" + return "#{column_transaction.fetch("company_name")} #{column_transaction["company_entry_description"]}" + elsif column_id.start_with?("wire") || column_id.start_with?("swft_") + return column_transaction.fetch("originator_name") + end + end + +end diff --git a/app/services/transaction_grouping_engine/calculate/hcb_code.rb b/app/services/transaction_grouping_engine/calculate/hcb_code.rb index 446a9cfe23..f978a13626 100644 --- a/app/services/transaction_grouping_engine/calculate/hcb_code.rb +++ b/app/services/transaction_grouping_engine/calculate/hcb_code.rb @@ -265,7 +265,7 @@ def unknown_hcb_code [ HCB_CODE, UNKNOWN_CODE, - @ct_or_cp.id + @ct_or_cp.column_transaction_id || @ct_or_cp.id ].join(SEPARATOR) end diff --git a/db/migrate/20250625003228_create_raw_pending_column_transactions.rb b/db/migrate/20250625003228_create_raw_pending_column_transactions.rb new file mode 100644 index 0000000000..96e9cbade7 --- /dev/null +++ b/db/migrate/20250625003228_create_raw_pending_column_transactions.rb @@ -0,0 +1,14 @@ +class CreateRawPendingColumnTransactions < ActiveRecord::Migration[7.2] + def change + create_table :raw_pending_column_transactions do |t| + t.string :column_id, null: false + t.integer :column_event_type, null: false + t.jsonb :column_transaction, null: false + t.text :description, null: false + t.date :date_posted, null: false + t.integer :amount_cents, null: false + t.timestamps + end + add_index :raw_pending_column_transactions, :column_id, unique: true + end +end diff --git a/db/migrate/20250625004725_add_raw_pending_column_transaction_id_to_canonical_pending_transaction.rb b/db/migrate/20250625004725_add_raw_pending_column_transaction_id_to_canonical_pending_transaction.rb new file mode 100644 index 0000000000..d8a034173d --- /dev/null +++ b/db/migrate/20250625004725_add_raw_pending_column_transaction_id_to_canonical_pending_transaction.rb @@ -0,0 +1,8 @@ +class AddRawPendingColumnTransactionIdToCanonicalPendingTransaction < ActiveRecord::Migration[7.2] + disable_ddl_transaction! + + def change + add_reference :canonical_pending_transactions, :raw_pending_column_transaction, index: { algorithm: :concurrently, name: "index_canonical_pending_txs_on_rpct_id" } + add_index :canonical_pending_transactions, :raw_pending_column_transaction_id, unique: true, algorithm: :concurrently + end +end diff --git a/db/schema.rb b/db/schema.rb index 1001ed437b..12de7f9cec 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -398,11 +398,14 @@ t.bigint "paypal_transfer_id" t.bigint "reimbursement_payout_holding_id" t.bigint "wire_id" + t.bigint "raw_pending_column_transaction_id" t.index ["check_deposit_id"], name: "index_canonical_pending_transactions_on_check_deposit_id" t.index ["hcb_code"], name: "index_canonical_pending_transactions_on_hcb_code" t.index ["increase_check_id"], name: "index_canonical_pending_transactions_on_increase_check_id" t.index ["paypal_transfer_id"], name: "index_canonical_pending_transactions_on_paypal_transfer_id" t.index ["raw_pending_bank_fee_transaction_id"], name: "index_canonical_pending_txs_on_raw_pending_bank_fee_tx_id" + t.index ["raw_pending_column_transaction_id"], name: "idx_on_raw_pending_column_transaction_id_ceea9a99e1", unique: true + t.index ["raw_pending_column_transaction_id"], name: "index_canonical_pending_txs_on_rpct_id" t.index ["raw_pending_donation_transaction_id"], name: "index_canonical_pending_txs_on_raw_pending_donation_tx_id" t.index ["raw_pending_incoming_disbursement_transaction_id"], name: "index_cpts_on_raw_pending_incoming_disbursement_transaction_id" t.index ["raw_pending_invoice_transaction_id"], name: "index_canonical_pending_txs_on_raw_pending_invoice_tx_id" @@ -1632,6 +1635,18 @@ t.datetime "updated_at", null: false end + create_table "raw_pending_column_transactions", force: :cascade do |t| + t.string "column_id", null: false + t.integer "column_event_type", null: false + t.jsonb "column_transaction", null: false + t.text "description", null: false + t.date "date_posted", null: false + t.integer "amount_cents", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["column_id"], name: "index_raw_pending_column_transactions_on_column_id", unique: true + end + create_table "raw_pending_donation_transactions", force: :cascade do |t| t.integer "amount_cents" t.date "date_posted"