From f27737a1a81183d9ab6696b690a851aeb7a2e6af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Romain=20Semp=C3=A9?= Date: Fri, 11 Oct 2024 12:28:51 +0200 Subject: [PATCH] feat(dunning): Create dunning thresholds when creating the campaign --- .../create_input.rb | 12 ++++ .../types/dunning_campaigns/create_input.rb | 1 + app/models/dunning_campaign.rb | 1 + .../dunning_campaigns/create_service.rb | 28 ++++++---- .../create_input_spec.rb | 10 ++++ .../dunning_campaigns/create_input_spec.rb | 1 + .../dunning_campaigns/create_service_spec.rb | 55 +++++++++++++++++++ 7 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 app/graphql/types/dunning_campaign_thresholds/create_input.rb create mode 100644 spec/graphql/types/dunning_campaign_thresholds/create_input_spec.rb create mode 100644 spec/services/dunning_campaigns/create_service_spec.rb diff --git a/app/graphql/types/dunning_campaign_thresholds/create_input.rb b/app/graphql/types/dunning_campaign_thresholds/create_input.rb new file mode 100644 index 00000000000..dc55c6a375b --- /dev/null +++ b/app/graphql/types/dunning_campaign_thresholds/create_input.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Types + module DunningCampaignThresholds + class CreateInput < Types::BaseInputObject + graphql_name "CreateDunningCampaignThresholdInput" + + argument :amount_cents, GraphQL::Types::BigInt, required: true + argument :currency, Types::CurrencyEnum, required: true + end + end +end diff --git a/app/graphql/types/dunning_campaigns/create_input.rb b/app/graphql/types/dunning_campaigns/create_input.rb index f28da434bea..c23cbced3d9 100644 --- a/app/graphql/types/dunning_campaigns/create_input.rb +++ b/app/graphql/types/dunning_campaigns/create_input.rb @@ -10,6 +10,7 @@ class CreateInput < Types::BaseInputObject argument :days_between_attempts, Integer, required: true argument :max_attempts, Integer, required: true argument :name, String, required: true + argument :thresholds, [Types::DunningCampaignThresholds::CreateInput], required: true argument :description, String, required: false end diff --git a/app/models/dunning_campaign.rb b/app/models/dunning_campaign.rb index 100de2b34c5..da419538434 100644 --- a/app/models/dunning_campaign.rb +++ b/app/models/dunning_campaign.rb @@ -5,6 +5,7 @@ class DunningCampaign < ApplicationRecord belongs_to :organization has_many :thresholds, class_name: "DunningCampaignThreshold", dependent: :destroy + accepts_nested_attributes_for :thresholds validates :name, presence: true validates :days_between_attempts, numericality: {greater_than: 0} diff --git a/app/services/dunning_campaigns/create_service.rb b/app/services/dunning_campaigns/create_service.rb index c67100ac74e..c191cac29f7 100644 --- a/app/services/dunning_campaigns/create_service.rb +++ b/app/services/dunning_campaigns/create_service.rb @@ -10,18 +10,22 @@ def initialize(organization:, params:) end def call - dunning_campaign = organization.dunning_campaigns.new( - applied_to_organization: params[:applied_to_organization], - code: params[:code], - days_between_attempts: params[:days_between_attempts], - max_attempts: params[:max_attempts], - name: params[:name], - description: params[:description] - ) - - dunning_campaign.save! - - result.dunning_campaign = dunning_campaign + ActiveRecord::Base.transaction do + dunning_campaign = organization.dunning_campaigns.create!( + applied_to_organization: params[:applied_to_organization], + code: params[:code], + days_between_attempts: params[:days_between_attempts], + max_attempts: params[:max_attempts], + name: params[:name], + description: params[:description], + thresholds_attributes: params[:thresholds] + ) + + # TODO: If the dunning campaign is applied to the organization, we need to remove the flag from all other dunning campaigns. + + result.dunning_campaign = dunning_campaign + end + result rescue ActiveRecord::RecordInvalid => e result.record_validation_failure!(record: e.record) diff --git a/spec/graphql/types/dunning_campaign_thresholds/create_input_spec.rb b/spec/graphql/types/dunning_campaign_thresholds/create_input_spec.rb new file mode 100644 index 00000000000..86d87030cbf --- /dev/null +++ b/spec/graphql/types/dunning_campaign_thresholds/create_input_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Types::DunningCampaignThresholds::CreateInput do + subject { described_class } + + it { is_expected.to accept_argument(:amount_cents).of_type("BigInt!") } + it { is_expected.to accept_argument(:currency).of_type("CurrencyEnum!") } +end diff --git a/spec/graphql/types/dunning_campaigns/create_input_spec.rb b/spec/graphql/types/dunning_campaigns/create_input_spec.rb index b5c2ad90c97..a5dd6d6b083 100644 --- a/spec/graphql/types/dunning_campaigns/create_input_spec.rb +++ b/spec/graphql/types/dunning_campaigns/create_input_spec.rb @@ -10,6 +10,7 @@ it { is_expected.to accept_argument(:days_between_attempts).of_type('Int!') } it { is_expected.to accept_argument(:max_attempts).of_type('Int!') } it { is_expected.to accept_argument(:name).of_type('String!') } + it { is_expected.to accept_argument(:thresholds).of_type('[CreateDunningCampaignThresholdInput!]!') } it { is_expected.to accept_argument(:description).of_type('String') } end diff --git a/spec/services/dunning_campaigns/create_service_spec.rb b/spec/services/dunning_campaigns/create_service_spec.rb new file mode 100644 index 00000000000..41c526eeb9b --- /dev/null +++ b/spec/services/dunning_campaigns/create_service_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe DunningCampaigns::CreateService, type: :service do + subject(:create_service) { described_class.new(organization:, params:) } + + let(:membership) { create(:membership) } + let(:organization) { membership.organization } + let(:params) do + { + name: "Dunning Campaign", + code: "dunning-campaign", + days_between_attempts: 1, + max_attempts: 3, + description: "Dunning Campaign Description", + applied_to_organization: true, + thresholds: + } + end + + let(:thresholds) do + [ + { amount_cents: 10000, currency: "USD" }, + { amount_cents: 20000, currency: "EUR" } + ] + end + + describe "#call" do + it "creates a dunning campaign" do + expect { create_service.call }.to change(DunningCampaign, :count).by(1) + .and change(DunningCampaignThreshold, :count).by(2) + end + + it "returns dunning campaign in the result" do + result = create_service.call + expect(result.dunning_campaign).to be_a(DunningCampaign) + expect(result.dunning_campaign.thresholds.first).to be_a(DunningCampaignThreshold) + end + + context "with validation error" do + before { create(:dunning_campaign, organization: organization, code: "dunning-campaign") } + + it "returns an error" do + result = create_service.call + + aggregate_failures do + expect(result).not_to be_success + expect(result.error).to be_a(BaseService::ValidationFailure) + expect(result.error.messages[:code]).to eq(["value_already_exist"]) + end + end + end + end +end