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

feat(invoices): Ability to skip 0 invoices #2462

Merged
merged 6 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions app/controllers/api/v1/customers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ def create_params
:timezone,
:net_payment_term,
:external_salesforce_id,
:finalize_zero_amount_invoice,
integration_customers: [
:id,
:external_customer_id,
Expand Down
1 change: 1 addition & 0 deletions app/controllers/api/v1/organizations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def input_params
:webhook_url,
:document_numbering,
:document_number_prefix,
:finalize_zero_amount_invoice,
email_settings: [],
billing_configuration: [
:invoice_footer,
Expand Down
84 changes: 47 additions & 37 deletions app/models/customer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ class Customer < ApplicationRecord
include Discard::Model
self.discard_column = :deleted_at

FINALIZE_ZERO_AMOUNT_INVOICE_OPTIONS = [
:inherit,
:skip,
:finalize
].freeze

attribute :finalize_zero_amount_invoice, :integer # rails 7.1 check the field exists when defining enum and when running the migration first time is not there
enum finalize_zero_amount_invoice: FINALIZE_ZERO_AMOUNT_INVOICE_OPTIONS, _prefix: :finalize_zero_amount_invoice

before_save :ensure_slug

belongs_to :organization
Expand Down Expand Up @@ -164,43 +173,44 @@ def ensure_slug
#
# Table name: customers
#
# id :uuid not null, primary key
# address_line1 :string
# address_line2 :string
# city :string
# country :string
# currency :string
# deleted_at :datetime
# document_locale :string
# email :string
# invoice_grace_period :integer
# 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
# deleted_at :datetime
# document_locale :string
# email :string
# finalize_zero_amount_invoice :integer default(0), not null
# invoice_grace_period :integer
# 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
#
# Indexes
#
Expand Down
3 changes: 2 additions & 1 deletion app/models/invoice.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class Invoice < ApplicationRecord
PAYMENT_STATUS = %i[pending succeeded failed].freeze

VISIBLE_STATUS = {draft: 0, finalized: 1, voided: 2, failed: 4}.freeze
INVISIBLE_STATUS = {generating: 3, open: 5}.freeze
INVISIBLE_STATUS = {generating: 3, open: 5, closed: 6}.freeze
STATUS = VISIBLE_STATUS.merge(INVISIBLE_STATUS).freeze

enum invoice_type: INVOICE_TYPES
Expand All @@ -77,6 +77,7 @@ class Invoice < ApplicationRecord
state :finalized
state :voided
state :failed
state :closed

event :finalize do
transitions from: :draft, to: :finalized
Expand Down
64 changes: 33 additions & 31 deletions app/models/organization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class Organization < ApplicationRecord
validates :name, presence: true
validates :timezone, timezone: true
validates :webhook_url, url: true, allow_nil: true
validates :finalize_zero_amount_invoice, inclusion: {in: [true, false]}

validate :validate_email_settings

Expand Down Expand Up @@ -137,37 +138,38 @@ def validate_email_settings
#
# Table name: organizations
#
# id :uuid not null, primary key
# address_line1 :string
# address_line2 :string
# api_key :string
# city :string
# clickhouse_aggregation :boolean default(FALSE), not null
# country :string
# custom_aggregation :boolean default(FALSE)
# default_currency :string default("USD"), not null
# document_locale :string default("en"), not null
# document_number_prefix :string
# document_numbering :integer default("per_customer"), not null
# email :string
# email_settings :string default([]), not null, is an Array
# eu_tax_management :boolean default(FALSE)
# invoice_footer :text
# invoice_grace_period :integer default(0), not null
# legal_name :string
# legal_number :string
# logo :string
# name :string not null
# net_payment_term :integer default(0), not null
# premium_integrations :string default([]), not null, is an Array
# state :string
# tax_identification_number :string
# timezone :string default("UTC"), not null
# vat_rate :float default(0.0), not null
# webhook_url :string
# zipcode :string
# created_at :datetime not null
# updated_at :datetime not null
# id :uuid not null, primary key
# address_line1 :string
# address_line2 :string
# api_key :string
# city :string
# clickhouse_aggregation :boolean default(FALSE), not null
# country :string
# custom_aggregation :boolean default(FALSE)
# default_currency :string default("USD"), not null
# document_locale :string default("en"), not null
# document_number_prefix :string
# document_numbering :integer default("per_customer"), not null
# email :string
# email_settings :string default([]), not null, is an Array
# eu_tax_management :boolean default(FALSE)
# finalize_zero_amount_invoice :boolean default(FALSE), not null
# invoice_footer :text
# invoice_grace_period :integer default(0), not null
# legal_name :string
# legal_number :string
# logo :string
# name :string not null
# net_payment_term :integer default(0), not null
# premium_integrations :string default([]), not null, is an Array
# state :string
# tax_identification_number :string
# timezone :string default("UTC"), not null
# vat_rate :float default(0.0), not null
# webhook_url :string
# zipcode :string
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
Expand Down
1 change: 1 addition & 0 deletions app/serializers/v1/customer_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def serialize
applicable_timezone: model.applicable_timezone,
net_payment_term: model.net_payment_term,
external_salesforce_id: model.external_salesforce_id,
finalize_zero_amount_invoice: model.finalize_zero_amount_invoice,
billing_configuration:,
shipping_address: model.shipping_address
}
Expand Down
1 change: 1 addition & 0 deletions app/serializers/v1/organization_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def serialize
document_numbering: model.document_numbering,
document_number_prefix: model.document_number_prefix,
tax_identification_number: model.tax_identification_number,
finalize_zero_amount_invoice: model.finalize_zero_amount_invoice,
billing_configuration:
}

Expand Down
20 changes: 15 additions & 5 deletions app/services/customers/create_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ def create_from_api(organization:, params:)
)
end

unless valid_finalize_zero_amount_invoice?(params[:finalize_zero_amount_invoice])
return result.single_validation_failure!(
field: :finalize_zero_amount_invoice,
error_code: 'invalid_value'
)
end

unless valid_integration_customers_count?(integration_customers: params[:integration_customers])
return result.single_validation_failure!(
field: :integration_customers,
Expand Down Expand Up @@ -45,6 +52,7 @@ def create_from_api(organization:, params:)
customer.legal_number = params[:legal_number] if params.key?(:legal_number)
customer.net_payment_term = params[:net_payment_term] if params.key?(:net_payment_term)
customer.external_salesforce_id = params[:external_salesforce_id] if params.key?(:external_salesforce_id)
customer.finalize_zero_amount_invoice = params[:finalize_zero_amount_invoice] if params.key?(:finalize_zero_amount_invoice)
if params.key?(:tax_identification_number)
customer.tax_identification_number = params[:tax_identification_number]
end
Expand Down Expand Up @@ -144,6 +152,10 @@ def create(**args)
tax_identification_number: args[:tax_identification_number]
)

if args.key?(:finalize_zero_amount_invoice)
customer.finalize_zero_amount_invoice = args[:finalize_zero_amount_invoice]
end

assign_premium_attributes(customer, args)

ActiveRecord::Base.transaction do
Expand Down Expand Up @@ -187,11 +199,9 @@ def create(**args)

private

def valid_email?(email)
return true if email.nil?

email_regexp = /\A[^@\s]+@[^@\s]+\z/
email_regexp.match?(email).present?
def valid_finalize_zero_amount_invoice?(value)
return true if value.nil?
Customer::FINALIZE_ZERO_AMOUNT_INVOICE_OPTIONS.include?(value.to_sym)
end

def valid_metadata_count?(metadata:)
Expand Down
2 changes: 1 addition & 1 deletion app/services/invoices/advance_charges_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def create_group_invoice
Invoices::ComputeAmountsFromFees.call(invoice:)

invoice.payment_status = :succeeded
invoice.status = :finalized
Invoices::TransitionToFinalStatus.call(invoice:)

invoice.save!
end
Expand Down
4 changes: 2 additions & 2 deletions app/services/invoices/create_one_off_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ def call

Invoices::ComputeAmountsFromFees.call(invoice:, provider_taxes: result.fees_taxes)
invoice.payment_status = invoice.total_amount_cents.positive? ? :pending : :succeeded

invoice.finalized!
Invoices::TransitionToFinalStatus.call(invoice:)
invoice.save!
end

Utils::SegmentTrack.invoice_created(invoice)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ def call
create_applied_prepaid_credit if should_create_applied_prepaid_credit?

invoice.payment_status = invoice.total_amount_cents.positive? ? :pending : :succeeded
invoice.finalized!
Invoices::TransitionToFinalStatus.call(invoice:)
invoice.save!
end

Utils::SegmentTrack.invoice_created(invoice)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def call
refresh_result.raise_if_error!

invoice.payment_due_date = payment_due_date
invoice.status = :finalized
Invoices::TransitionToFinalStatus.call(invoice:)
invoice.save!

invoice.credit_notes.each(&:finalized!)
Expand Down
5 changes: 5 additions & 0 deletions app/services/invoices/subscription_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ def call

fee_result = ActiveRecord::Base.transaction do
invoice.status = invoice_status
if invoice_status == :finalized
Invoices::TransitionToFinalStatus.call(invoice:)
else
invoice.status = :draft
end

fee_result = Invoices::CalculateFeesService.call(
invoice:,
Expand Down
36 changes: 36 additions & 0 deletions app/services/invoices/transition_to_final_status.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

module Invoices
class TransitionToFinalStatus < BaseService
def initialize(invoice:)
@invoice = invoice
@customer = @invoice.customer
@organization = @customer.organization
super
end

def call
invoice.status = if should_finalize_invoice?
:finalized
else
:closed
end
result.invoice = invoice
result
end

private

attr_reader :invoice, :customer, :organization

def should_finalize_invoice?
return true unless invoice.fees_amount_cents.zero?
customer_setting = customer.finalize_zero_amount_invoice
if customer_setting == 'inherit'
organization.finalize_zero_amount_invoice
else
customer_setting == 'finalize'
end
end
end
end
1 change: 1 addition & 0 deletions app/services/organizations/update_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def call
organization.net_payment_term = params[:net_payment_term] if params.key?(:net_payment_term)
organization.document_numbering = params[:document_numbering] if params.key?(:document_numbering)
organization.document_number_prefix = params[:document_number_prefix] if params.key?(:document_number_prefix)
organization.finalize_zero_amount_invoice = params[:finalize_zero_amount_invoice] if params.key?(:finalize_zero_amount_invoice)

billing = params[:billing_configuration]&.to_h || {}
organization.invoice_footer = billing[:invoice_footer] if billing.key?(:invoice_footer)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class AddFinalizeZeroAmountInvoiceToOrganizations < ActiveRecord::Migration[7.1]
def change
add_column :organizations, :finalize_zero_amount_invoice, :boolean, default: true, null: false
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class AddFinalizeZeroAmountInvoiceToCustomers < ActiveRecord::Migration[7.1]
def change
add_column :customers, :finalize_zero_amount_invoice, :integer, default: 0, null: false
end
end
Loading