Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Include draft invoices inside wallet's ongoing balance #2852

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/graphql/types/customer_portal/wallets/object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ class Object < Types::BaseObject
field :consumed_credits, GraphQL::Types::Float, null: false
field :credits_balance, GraphQL::Types::Float, null: false
field :credits_ongoing_balance, GraphQL::Types::Float, null: false
field :credits_ongoing_draft_invoices_balance, GraphQL::Types::Float, null: false

field :ongoing_balance_cents, GraphQL::Types::BigInt, null: false
field :ongoing_draft_invoices_balance_cents, GraphQL::Types::BigInt, null: false
field :ongoing_usage_balance_cents, GraphQL::Types::BigInt, null: false

field :rate_amount, GraphQL::Types::Float, null: false

field :last_balance_sync_at, GraphQL::Types::ISO8601DateTime, null: true
Expand Down
2 changes: 2 additions & 0 deletions app/graphql/types/wallets/object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ class Object < Types::BaseObject
field :balance_cents, GraphQL::Types::BigInt, null: false
field :consumed_amount_cents, GraphQL::Types::BigInt, null: false
field :ongoing_balance_cents, GraphQL::Types::BigInt, null: false
field :ongoing_draft_invoices_balance_cents, GraphQL::Types::BigInt, null: false
field :ongoing_usage_balance_cents, GraphQL::Types::BigInt, null: false

field :consumed_credits, GraphQL::Types::Float, null: false
field :credits_balance, GraphQL::Types::Float, null: false
field :credits_ongoing_balance, GraphQL::Types::Float, null: false
field :credits_ongoing_draft_invoices_balance, GraphQL::Types::Float, null: false
field :credits_ongoing_usage_balance, GraphQL::Types::Float, null: false

field :last_balance_sync_at, GraphQL::Types::ISO8601DateTime, null: true
Expand Down
6 changes: 6 additions & 0 deletions app/models/customer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,12 @@ def reset_dunning_campaign!
)
end

def flag_wallets_for_refresh
return unless wallets.active.any?

wallets.active.update_all(ready_to_be_refreshed: true) # rubocop:disable Rails/SkipsModelValidations
end

private

def ensure_slug
Expand Down
11 changes: 10 additions & 1 deletion app/models/wallet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ class Wallet < ApplicationRecord
has_many :wallet_transactions
has_many :recurring_transaction_rules

monetize :balance_cents, :ongoing_balance_cents, :ongoing_usage_balance_cents
monetize :balance_cents
monetize :consumed_amount_cents
monetize :ongoing_balance_cents, :ongoing_usage_balance_cents, with_model_currency: :balance_currency

validates :rate_amount, numericality: {greater_than: 0}

Expand All @@ -38,6 +39,14 @@ def currency=(currency)
def currency
balance_currency
end

def credits_ongoing_draft_invoices_balance
ongoing_draft_invoices_balance_cents.to_f.fdiv(ongoing_balance.currency.subunit_to_unit).fdiv(rate_amount)
end

def ongoing_draft_invoices_balance_cents
customer.invoices.draft.sum(:total_amount_cents)
end
end

# == Schema Information
Expand Down
2 changes: 2 additions & 0 deletions app/serializers/v1/wallet_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ def serialize
credits_balance: model.credits_balance,
credits_ongoing_balance: model.credits_ongoing_balance,
credits_ongoing_usage_balance: model.credits_ongoing_usage_balance,
credits_ongoing_draft_invoices_balance: model.credits_ongoing_draft_invoices_balance,
balance_cents: model.balance_cents,
ongoing_balance_cents: model.ongoing_balance_cents,
ongoing_usage_balance_cents: model.ongoing_usage_balance_cents,
ongoing_draft_invoices_balance_cents: model.ongoing_draft_invoices_balance_cents,
consumed_credits: model.consumed_credits,
created_at: model.created_at&.iso8601,
expiration_at: model.expiration_at&.iso8601,
Expand Down
9 changes: 1 addition & 8 deletions app/services/events/post_process_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def call

expire_cached_charges(subscriptions)
flag_refresh_from_subscription
flag_wallets_for_refresh
customer&.flag_wallets_for_refresh

handle_pay_in_advance

Expand Down Expand Up @@ -99,13 +99,6 @@ def flag_refresh_from_subscription
subscriptions.select(&:active?).each { |s| LifetimeUsages::FlagRefreshFromSubscriptionService.new(subscription: s).call }
end

def flag_wallets_for_refresh
return unless customer
return unless customer.wallets.active.any?

customer.wallets.active.update_all(ready_to_be_refreshed: true) # rubocop:disable Rails/SkipsModelValidations
end

def handle_pay_in_advance
return unless billable_metric
return unless charges.any?
Expand Down
6 changes: 5 additions & 1 deletion app/services/invoices/refresh_draft_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def call

ActiveRecord::Base.transaction do
invoice.update!(ready_to_be_refreshed: false) if invoice.ready_to_be_refreshed?
old_total_amount_cents = invoice.total_amount_cents

old_fee_values = invoice_credit_note_items.map do |item|
{credit_note_item_id: item.id, fee_amount_cents: item.fee&.amount_cents}
Expand Down Expand Up @@ -67,7 +68,10 @@ def call
end
calculate_result.raise_if_error!

flag_lifetime_usage_for_refresh
if old_total_amount_cents != invoice.total_amount_cents
flag_lifetime_usage_for_refresh
invoice.customer.flag_wallets_for_refresh
end

# NOTE: In case of a refresh the same day of the termination.
invoice.fees.update_all(created_at: invoice.created_at) # rubocop:disable Rails/SkipsModelValidations
Expand Down
10 changes: 9 additions & 1 deletion app/services/subscriptions/terminate_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,15 @@ def call
# NOTE: We should bill subscription and generate invoice for all cases except for the upgrade
# For upgrade we will create only one invoice for termination charges and for in advance charges
# It is handled in subscriptions/create_service.rb
bill_subscription unless upgrade
unless upgrade
bill_subscription

if subscription.customer.wallets.active.any?
Wallets::Balance::RefreshOngoingService.call(
wallet: subscription.customer.wallets.active.first
)
end
end
end

# NOTE: Pending next subscription should be canceled as well
Expand Down
2 changes: 1 addition & 1 deletion app/services/wallets/balance/update_ongoing_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def credits_ongoing_usage_balance
end

def ongoing_balance_cents
wallet.balance_cents - total_usage_amount_cents + pay_in_advance_usage_amount_cents
wallet.balance_cents - total_usage_amount_cents - wallet.ongoing_draft_invoices_balance_cents + pay_in_advance_usage_amount_cents
end

def credits_ongoing_balance
Expand Down
4 changes: 4 additions & 0 deletions schema.graphql

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

72 changes: 72 additions & 0 deletions schema.json

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

2 changes: 2 additions & 0 deletions spec/graphql/types/customer_portal/wallets/object_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
it { is_expected.to have_field(:consumed_amount_cents).of_type("BigInt!") }
it { is_expected.to have_field(:consumed_credits).of_type("Float!") }
it { is_expected.to have_field(:credits_balance).of_type("Float!") }
it { is_expected.to have_field(:credits_ongoing_draft_invoices_balance).of_type("Float!") }
it { is_expected.to have_field(:credits_ongoing_balance).of_type("Float!") }
it { is_expected.to have_field(:ongoing_balance_cents).of_type("BigInt!") }
it { is_expected.to have_field(:ongoing_draft_invoices_balance_cents).of_type("BigInt!") }
it { is_expected.to have_field(:ongoing_usage_balance_cents).of_type("BigInt!") }
it { is_expected.to have_field(:rate_amount).of_type("Float!") }
it { is_expected.to have_field(:last_balance_sync_at).of_type('ISO8601DateTime') }
Expand Down
2 changes: 2 additions & 0 deletions spec/graphql/types/wallets/object_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
it { is_expected.to have_field(:consumed_amount_cents).of_type('BigInt!') }
it { is_expected.to have_field(:ongoing_balance_cents).of_type('BigInt!') }
it { is_expected.to have_field(:ongoing_usage_balance_cents).of_type('BigInt!') }
it { is_expected.to have_field(:ongoing_draft_invoices_balance_cents).of_type('BigInt!') }

it { is_expected.to have_field(:consumed_credits).of_type('Float!') }
it { is_expected.to have_field(:credits_balance).of_type('Float!') }
it { is_expected.to have_field(:credits_ongoing_balance).of_type('Float!') }
it { is_expected.to have_field(:credits_ongoing_usage_balance).of_type('Float!') }
it { is_expected.to have_field(:credits_ongoing_draft_invoices_balance).of_type('Float!') }

it { is_expected.to have_field(:last_balance_sync_at).of_type('ISO8601DateTime') }
it { is_expected.to have_field(:last_consumed_credit_at).of_type('ISO8601DateTime') }
Expand Down
26 changes: 26 additions & 0 deletions spec/models/customer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -613,4 +613,30 @@
.and change(customer, :last_dunning_campaign_attempt_at).to(nil)
end
end

describe "#flag_wallets_for_refresh" do
context "without any wallets" do
it "returns nil" do
expect(customer.flag_wallets_for_refresh).to be_nil
end
end

context "without active wallets" do
it "does not flag wallets for refresh" do
wallet = create(:wallet, :terminated, customer:)

expect { customer.flag_wallets_for_refresh }.not_to change {
wallet.reload.ready_to_be_refreshed
}.from(false)
end
end

it "flags all active wallets for refresh" do
wallet = create(:wallet, customer:)

expect { customer.flag_wallets_for_refresh }.to change {
wallet.reload.ready_to_be_refreshed
}.from(false).to(true)
end
end
end
40 changes: 31 additions & 9 deletions spec/models/wallet_spec.rb
Original file line number Diff line number Diff line change
@@ -1,28 +1,50 @@
# frozen_string_literal: true

require 'rails_helper'
require "rails_helper"

RSpec.describe Wallet, type: :model do
subject(:wallet) { build(:wallet) }

describe 'validations' do
describe "validations" do
it { is_expected.to validate_numericality_of(:rate_amount).is_greater_than(0) }
end

describe 'currency=' do
it 'assigns the currency to all amounts' do
wallet.currency = 'CAD'
describe "currency=" do
it "assigns the currency to all amounts" do
wallet.currency = "CAD"

expect(wallet).to have_attributes(
balance_currency: 'CAD',
consumed_amount_currency: 'CAD'
balance_currency: "CAD",
consumed_amount_currency: "CAD"
)
end
end

describe 'currency' do
it 'returns the wallet currency' do
describe "currency" do
it "returns the wallet currency" do
expect(wallet.currency).to eq(wallet.balance_currency)
end
end

describe "#ongoing_draft_invoices_balance_cents" do
it "returns the sum of ongoing draft invoices" do
create(:invoice, :draft, customer: wallet.customer, total_amount_cents: 100)
create(:invoice, :draft, customer: wallet.customer, total_amount_cents: 200)
create(:invoice, customer: wallet.customer, total_amount_cents: 400)

expect(wallet.ongoing_draft_invoices_balance_cents).to eq(300)
end
end

describe "#credits_ongoing_draft_invoices_balance" do
subject(:wallet) { build(:wallet, rate_amount: 1) }

it "returns the number of credits for ongoing draft invoices" do
create(:invoice, :draft, customer: wallet.customer, total_amount_cents: 100)
create(:invoice, :draft, customer: wallet.customer, total_amount_cents: 200)
create(:invoice, customer: wallet.customer, total_amount_cents: 400)

expect(wallet.credits_ongoing_draft_invoices_balance).to eq(3)
end
end
end
Loading
Loading