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 (accounting-integrations): add logic for creating integration sales order #2049

Merged
merged 2 commits into from
May 22, 2024
Merged
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/models/integration_resource.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
class IntegrationResource < ApplicationRecord
belongs_to :syncable, polymorphic: true
belongs_to :integration, class_name: 'Integrations::BaseIntegration'

RESOURCE_TYPES = %i[invoice sales_order payment credit_note].freeze

enum resource_type: RESOURCE_TYPES
end
131 changes: 131 additions & 0 deletions app/services/integrations/aggregator/invoices/base_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# frozen_string_literal: true

module Integrations
module Aggregator
module Invoices
class BaseService < Integrations::Aggregator::BaseService
def initialize(invoice:)
@invoice = invoice

super(integration:)
end

private

attr_reader :invoice

delegate :customer, to: :invoice

def headers
{
'Connection-Id' => integration.connection_id,
'Authorization' => "Bearer #{secret_key}",
'Provider-Config-Key' => provider
}
end

def integration
return nil unless integration_customer

integration_customer&.integration
end

def integration_customer
@integration_customer ||= customer.integration_customers&.first
end

def billable_metric_item(fee)
integration
.integration_mappings
.find_by(mappable_type: 'BillableMetric', mappable_id: fee.billable_metric.id) || fallback_item
end

def add_on_item(fee)
integration
.integration_mappings
.find_by(mappable_type: 'AddOn', mappable_id: fee.add_on.id) || fallback_item
end

def item(fee)
mapped_item = if fee.charge?
billable_metric_item(fee)
elsif fee.add_on?
add_on_item(fee)
elsif fee.credit?
credit_item
elsif fee.commitment?
commitment_item
elsif fee.subscription?
subscription_item
end

{
'item' => mapped_item.external_id,
'account' => mapped_item.external_account_code,
'quantity' => fee.units,
'rate' => fee.precise_unit_amount
}
end

def discounts
output = []

if invoice.coupons_amount_cents > 0
output << {
'item' => coupon_item.external_id,
'account' => coupon_item.external_account_code,
'quantity' => 1,
'rate' => -amount(invoice.coupons_amount_cents)
}
end

if invoice.prepaid_credit_amount_cents > 0
output << {
'item' => credit_item.external_id,
'account' => credit_item.external_account_code,
'quantity' => 1,
'rate' => -amount(invoice.prepaid_credit_amount_cents)
}
end

if invoice.credit_notes_amount_cents > 0
output << {
'item' => credit_note_item.external_id,
'account' => credit_note_item.external_account_code,
'quantity' => 1,
'rate' => -amount(invoice.credit_notes_amount_cents)
}
end

output
end

def payload(type)
{
'type' => type,
'isDynamic' => false,
'columns' => {
'tranid' => invoice.id,
'entity' => integration_customer.external_customer_id,
'istaxable' => true,
'taxitem' => tax_item.external_id,
'taxamountoverride' => amount(invoice.taxes_amount_cents),
'otherrefnum' => invoice.number,
'custbody_lago_id' => invoice.id,
'custbody_ava_disable_tax_calculation' => true
},
'lines' => [
{
'sublistId' => 'item',
'lineItems' => invoice.fees.map { |fee| item(fee) } + discounts
},
],
'options' => {
'ignoreMandatoryFields' => false
}
}
end
end
end
end
end
136 changes: 11 additions & 125 deletions app/services/integrations/aggregator/invoices/create_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,7 @@
module Integrations
module Aggregator
module Invoices
class CreateService < Integrations::Aggregator::BaseService
def initialize(invoice:)
@invoice = invoice

super(integration:)
end

class CreateService < BaseService
def action_path
"v1/#{provider}/invoices"
end
Expand All @@ -20,126 +14,18 @@ def call
return unless invoice.finalized?
return unless fallback_item

response = http_client.post_with_response(payload, headers)
result.external_invoice_id = JSON.parse(response.body)

result
end

private

attr_reader :invoice
response = http_client.post_with_response(payload('invoice'), headers)
result.external_id = JSON.parse(response.body)

delegate :customer, to: :invoice

def headers
{
'Connection-Id' => integration.connection_id,
'Authorization' => "Bearer #{secret_key}",
'Provider-Config-Key' => provider
}
end

def integration
return nil unless integration_customer

integration_customer&.integration
end

def integration_customer
@integration_customer ||= customer.integration_customers&.first
end
IntegrationResource.create!(
integration:,
external_id: result.external_id,
syncable_id: invoice.id,
syncable_type: 'Invoice',
resource_type: :invoice,
)

def billable_metric_item(fee)
integration
.integration_mappings
.find_by(mappable_type: 'BillableMetric', mappable_id: fee.billable_metric.id) || fallback_item
end

def add_on_item(fee)
integration
.integration_mappings
.find_by(mappable_type: 'AddOn', mappable_id: fee.add_on.id) || fallback_item
end

def item(fee)
mapped_item = if fee.charge?
billable_metric_item(fee)
elsif fee.add_on?
add_on_item(fee)
elsif fee.credit?
credit_item
elsif fee.commitment?
commitment_item
elsif fee.subscription?
subscription_item
end

{
'item' => mapped_item.external_id,
'account' => mapped_item.external_account_code,
'quantity' => fee.units,
'rate' => fee.precise_unit_amount
}
end

def discounts
output = []

if invoice.coupons_amount_cents > 0
output << {
'item' => coupon_item.external_id,
'account' => coupon_item.external_account_code,
'quantity' => 1,
'rate' => -amount(invoice.coupons_amount_cents)
}
end

if invoice.prepaid_credit_amount_cents > 0
output << {
'item' => credit_item.external_id,
'account' => credit_item.external_account_code,
'quantity' => 1,
'rate' => -amount(invoice.prepaid_credit_amount_cents)
}
end

if invoice.credit_notes_amount_cents > 0
output << {
'item' => credit_note_item.external_id,
'account' => credit_note_item.external_account_code,
'quantity' => 1,
'rate' => -amount(invoice.credit_notes_amount_cents)
}
end

output
end

def payload
{
'type' => 'invoice',
'isDynamic' => false,
'columns' => {
'tranid' => invoice.id,
'entity' => integration_customer.external_customer_id,
'istaxable' => true,
'taxitem' => tax_item.external_id,
'taxamountoverride' => amount(invoice.taxes_amount_cents),
'otherrefnum' => invoice.number,
'custbody_lago_id' => invoice.id,
'custbody_ava_disable_tax_calculation' => true
},
'lines' => [
{
'sublistId' => 'item',
'lineItems' => invoice.fees.map { |fee| item(fee) } + discounts
},
],
'options' => {
'ignoreMandatoryFields' => false
}
}
result
end
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

module Integrations
module Aggregator
module SalesOrders
class CreateService < Integrations::Aggregator::Invoices::BaseService
def action_path
"v1/#{provider}/salesorders"
end

def call
return unless integration
return unless integration.sync_sales_orders
return unless invoice.finalized?
return unless fallback_item

response = http_client.post_with_response(payload('salesorder'), headers)
result.external_id = JSON.parse(response.body)

IntegrationResource.create!(
integration:,
external_id: result.external_id,
syncable_id: invoice.id,
syncable_type: 'Invoice',
resource_type: :sales_order,
)

result
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddResourceTypeToIntegrationResources < ActiveRecord::Migration[7.0]
def change
add_column :integration_resources, :resource_type, :integer, null: false, default: 0
end
end
3 changes: 2 additions & 1 deletion 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/integration_resource_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@

it { is_expected.to belong_to(:syncable) }
it { is_expected.to belong_to(:integration) }

it { is_expected.to define_enum_for(:resource_type).with_values(%i[invoice sales_order payment credit_note]) }
end
Original file line number Diff line number Diff line change
Expand Up @@ -221,14 +221,25 @@
allow(response).to receive(:body).and_return(body)
end

it 'returns invoice id' do
it 'returns external id' do
result = service_call

aggregate_failures do
expect(result).to be_success
expect(result.external_invoice_id).to eq('456')
expect(result.external_id).to eq('456')
end
end

it 'creates integration resource object' do
expect { service_call }
.to change(IntegrationResource, :count).by(1)

integration_resource = IntegrationResource.order(created_at: :desc).first

expect(integration_resource.syncable_id).to eq(invoice.id)
expect(integration_resource.syncable_type).to eq('Invoice')
expect(integration_resource.resource_type).to eq('invoice')
end
end

context 'when service call is not successful' do
Expand Down
Loading
Loading