Skip to content

Commit

Permalink
feat(dunning): Add dunning campaign to customer, database changes
Browse files Browse the repository at this point in the history
 ## Roadmap Task

👉 https://getlago.canny.io/feature-requests/p/set-up-payment-retry-logic
👉 https://getlago.canny.io/feature-requests/p/send-reminders-for-overdue-invoices

 ## Context

We want to automate dunning process so that our users don't have to look at each customer to maximize their chances of being paid retrying payments of overdue balances and sending email reminders.

We're first automating the overdue balance payment request, before looking at individual invoices.

 ## Description

The goal of this change is to create set the relationship between
customers and dunning campaigns.

A customer could have one dunning campaign applied,
or, inherit its organization default campaign (none applied)
or, be excluded from dunning at all (with a boolean flag).

Also, when deleting a dunning campaign, all customers with the campaign
assigned must be reset.
  • Loading branch information
ancorcruz committed Oct 17, 2024
1 parent cb36779 commit e7da12f
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 52 deletions.
22 changes: 11 additions & 11 deletions app/models/clickhouse/events_enriched.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ class EventsEnriched < BaseRecord
#
# Table name: events_enriched
#
# code :string not null, primary key
# decimal_value :decimal(26, )
# enriched_at :datetime not null
# precise_total_amount_cents :decimal(40, 15)
# properties :string not null
# sorted_properties :string not null
# timestamp :datetime not null, primary key
# value :string
# external_subscription_id :string not null, primary key
# organization_id :string not null, primary key
# transaction_id :string not null
# aggregation_type :string
# code :string not null, primary key
# filters :string not null
# grouped_by :string not null
# properties :string not null
# timestamp :datetime not null
# value :string
# charge_id :string not null, primary key
# external_subscription_id :string not null, primary key
# organization_id :string not null, primary key
# transaction_id :string not null, primary key
#
89 changes: 48 additions & 41 deletions app/models/customer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class Customer < ApplicationRecord
before_save :ensure_slug

belongs_to :organization
belongs_to :applied_dunning_campaign, optional: true, class_name: 'DunningCampaign'

has_many :subscriptions
has_many :events
Expand Down Expand Up @@ -192,55 +193,61 @@ def ensure_slug
#
# Table name: customers
#
# id :uuid not null, primary key
# address_line1 :string
# address_line2 :string
# city :string
# country :string
# currency :string
# customer_type :enum
# deleted_at :datetime
# document_locale :string
# email :string
# finalize_zero_amount_invoice :integer default("inherit"), not null
# firstname :string
# invoice_grace_period :integer
# lastname :string
# legal_name :string
# legal_number :string
# logo_url :string
# name :string
# net_payment_term :integer
# payment_provider :string
# payment_provider_code :string
# phone :string
# shipping_address_line1 :string
# shipping_address_line2 :string
# shipping_city :string
# shipping_country :string
# shipping_state :string
# shipping_zipcode :string
# slug :string
# state :string
# tax_identification_number :string
# timezone :string
# url :string
# vat_rate :float
# zipcode :string
# created_at :datetime not null
# updated_at :datetime not null
# external_id :string not null
# external_salesforce_id :string
# organization_id :uuid not null
# sequential_id :bigint
# id :uuid not null, primary key
# address_line1 :string
# address_line2 :string
# city :string
# country :string
# currency :string
# customer_type :enum
# deleted_at :datetime
# document_locale :string
# email :string
# exclude_from_dunning_campaign :boolean default(FALSE), not null
# finalize_zero_amount_invoice :integer default("inherit"), not null
# firstname :string
# invoice_grace_period :integer
# last_dunning_campaign_attempt :integer default(0), not null
# last_dunning_campaign_attempt_at :datetime
# lastname :string
# legal_name :string
# legal_number :string
# logo_url :string
# name :string
# net_payment_term :integer
# payment_provider :string
# payment_provider_code :string
# phone :string
# shipping_address_line1 :string
# shipping_address_line2 :string
# shipping_city :string
# shipping_country :string
# shipping_state :string
# shipping_zipcode :string
# slug :string
# state :string
# tax_identification_number :string
# timezone :string
# url :string
# vat_rate :float
# zipcode :string
# created_at :datetime not null
# updated_at :datetime not null
# applied_dunning_campaign_id :uuid
# external_id :string not null
# external_salesforce_id :string
# organization_id :uuid not null
# sequential_id :bigint
#
# Indexes
#
# index_customers_on_applied_dunning_campaign_id (applied_dunning_campaign_id)
# index_customers_on_deleted_at (deleted_at)
# index_customers_on_external_id_and_organization_id (external_id,organization_id) UNIQUE WHERE (deleted_at IS NULL)
# index_customers_on_organization_id (organization_id)
#
# Foreign Keys
#
# fk_rails_... (applied_dunning_campaign_id => dunning_campaigns.id)
# fk_rails_... (organization_id => organizations.id)
#
3 changes: 3 additions & 0 deletions app/models/dunning_campaign.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ class DunningCampaign < ApplicationRecord
ORDERS = %w[name code].freeze

belongs_to :organization

has_many :thresholds, class_name: "DunningCampaignThreshold", dependent: :destroy
has_many :customers, foreign_key: :applied_dunning_campaign_id, dependent: :nullify

accepts_nested_attributes_for :thresholds

validates :name, presence: true
Expand Down
14 changes: 14 additions & 0 deletions db/migrate/20241016104211_add_dunning_campaign_to_customers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

class AddDunningCampaignToCustomers < ActiveRecord::Migration[7.1]
def change
safety_assured do
change_table :customers, bulk: true do |t|
t.references :applied_dunning_campaign, type: :uuid, null: true, foreign_key: {to_table: :dunning_campaigns}, index: true
t.boolean :exclude_from_dunning_campaign, default: false, null: false
t.integer :last_dunning_campaign_attempt, default: 0, null: false
t.timestamp :last_dunning_campaign_attempt_at
end
end
end
end
6 changes: 6 additions & 0 deletions db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions spec/models/customer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

it_behaves_like 'paper_trail traceable'

it { is_expected.to belong_to(:applied_dunning_campaign).optional }

it { is_expected.to have_many(:integration_customers).dependent(:destroy) }
it { is_expected.to have_many(:payment_requests) }

Expand Down
1 change: 1 addition & 0 deletions spec/models/dunning_campaign_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

it { is_expected.to belong_to(:organization) }
it { is_expected.to have_many(:thresholds).dependent(:destroy) }
it { is_expected.to have_many(:customers).dependent(:nullify) }

it { is_expected.to validate_presence_of(:name) }

Expand Down

0 comments on commit e7da12f

Please sign in to comment.