diff --git a/app/controllers/api/v1/customers_controller.rb b/app/controllers/api/v1/customers_controller.rb index 4a74a1df992..cfee3d39b5f 100644 --- a/app/controllers/api/v1/customers_controller.rb +++ b/app/controllers/api/v1/customers_controller.rb @@ -138,6 +138,14 @@ def create_params :value, :display_in_invoice ], + shipping_address: [ + :address_line1, + :address_line2, + :city, + :zipcode, + :state, + :country + ], tax_codes: [] ) end diff --git a/app/graphql/types/customers/address.rb b/app/graphql/types/customers/address.rb new file mode 100644 index 00000000000..b3ebfa0155f --- /dev/null +++ b/app/graphql/types/customers/address.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Types + module Customers + class Address < Types::BaseObject + graphql_name 'CustomerAddress' + + field :address_line1, String, null: true + field :address_line2, String, null: true + field :city, String, null: true + field :country, Types::CountryCodeEnum, null: true + field :state, String, null: true + field :zipcode, String, null: true + end + end +end diff --git a/app/graphql/types/customers/address_input.rb b/app/graphql/types/customers/address_input.rb new file mode 100644 index 00000000000..2acaff3d50a --- /dev/null +++ b/app/graphql/types/customers/address_input.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Types + module Customers + class AddressInput < BaseInputObject + graphql_name 'CustomerAddressInput' + + argument :address_line1, String, required: false + argument :address_line2, String, required: false + argument :city, String, required: false + argument :country, Types::CountryCodeEnum, required: false + argument :state, String, required: false + argument :zipcode, String, required: false + end + end +end diff --git a/app/graphql/types/customers/create_customer_input.rb b/app/graphql/types/customers/create_customer_input.rb index 348ab58ca8c..786909d5998 100644 --- a/app/graphql/types/customers/create_customer_input.rb +++ b/app/graphql/types/customers/create_customer_input.rb @@ -27,6 +27,8 @@ class CreateCustomerInput < BaseInputObject argument :url, String, required: false argument :zipcode, String, required: false + argument :shipping_address, Types::Customers::AddressInput, required: false + argument :metadata, [Types::Customers::Metadata::Input], required: false argument :payment_provider, Types::PaymentProviders::ProviderTypeEnum, required: false diff --git a/app/graphql/types/customers/object.rb b/app/graphql/types/customers/object.rb index c940fa3fc49..e8413d20dac 100644 --- a/app/graphql/types/customers/object.rb +++ b/app/graphql/types/customers/object.rb @@ -34,6 +34,8 @@ class Object < Types::BaseObject field :url, String, null: true field :zipcode, String, null: true + field :shipping_address, Types::Customers::Address, null: true + field :metadata, [Types::Customers::Metadata::Object], null: true field :billing_configuration, Types::Customers::BillingConfiguration, null: true diff --git a/app/graphql/types/customers/update_customer_input.rb b/app/graphql/types/customers/update_customer_input.rb index 0d9bfd0f1a6..80c479bc0c5 100644 --- a/app/graphql/types/customers/update_customer_input.rb +++ b/app/graphql/types/customers/update_customer_input.rb @@ -26,6 +26,8 @@ class UpdateCustomerInput < BaseInputObject argument :url, String, required: false, permission: 'customers:update' argument :zipcode, String, required: false, permission: 'customers:update' + argument :shipping_address, Types::Customers::AddressInput, required: false, permission: 'customers:update' + argument :metadata, [Types::Customers::Metadata::Input], required: false, permission: 'customers:update' argument :payment_provider, Types::PaymentProviders::ProviderTypeEnum, required: false, permission: 'customers:update' diff --git a/app/models/customer.rb b/app/models/customer.rb index 63cbd1ea380..b2f055e1791 100644 --- a/app/models/customer.rb +++ b/app/models/customer.rb @@ -116,6 +116,17 @@ def provider_customer end end + def shipping_address + { + address_line1: shipping_address_line1, + address_line2: shipping_address_line2, + city: shipping_city, + zipcode: shipping_zipcode, + state: shipping_state, + country: shipping_country + } + end + private def ensure_slug diff --git a/app/serializers/v1/customer_serializer.rb b/app/serializers/v1/customer_serializer.rb index 8890ed6c967..9e34664fd55 100644 --- a/app/serializers/v1/customer_serializer.rb +++ b/app/serializers/v1/customer_serializer.rb @@ -29,7 +29,8 @@ def serialize applicable_timezone: model.applicable_timezone, net_payment_term: model.net_payment_term, external_salesforce_id: model.external_salesforce_id, - billing_configuration: + billing_configuration:, + shipping_address: model.shipping_address }.merge(legacy_values.except(:billing_configuration)) payload = payload.merge(metadata) diff --git a/app/services/customers/create_service.rb b/app/services/customers/create_service.rb index b61ca98f409..3e119a1d3b0 100644 --- a/app/services/customers/create_service.rb +++ b/app/services/customers/create_service.rb @@ -7,6 +7,7 @@ class CreateService < BaseService def create_from_api(organization:, params:) customer = organization.customers.find_or_initialize_by(external_id: params[:external_id]) new_customer = customer.new_record? + shipping_address = params[:shipping_address] ||= {} unless valid_metadata_count?(metadata: params[:metadata]) return result.single_validation_failure!( @@ -31,6 +32,12 @@ def create_from_api(organization:, params:) customer.zipcode = params[:zipcode] if params.key?(:zipcode) customer.email = params[:email] if params.key?(:email) customer.city = params[:city] if params.key?(:city) + customer.shipping_address_line1 = shipping_address[:address_line1] if shipping_address.key?(:address_line1) + customer.shipping_address_line2 = shipping_address[:address_line2] if shipping_address.key?(:address_line2) + customer.shipping_city = shipping_address[:city] if shipping_address.key?(:city) + customer.shipping_zipcode = shipping_address[:zipcode] if shipping_address.key?(:zipcode) + customer.shipping_state = shipping_address[:state] if shipping_address.key?(:state) + customer.shipping_country = shipping_address[:country]&.upcase if shipping_address.key?(:country) customer.url = params[:url] if params.key?(:url) customer.phone = params[:phone] if params.key?(:phone) customer.logo_url = params[:logo_url] if params.key?(:logo_url) @@ -97,6 +104,7 @@ def create_from_api(organization:, params:) def create(**args) billing_configuration = args[:billing_configuration]&.to_h || {} + shipping_address = args[:shipping_address]&.to_h || {} unless valid_metadata_count?(metadata: args[:metadata]) return result.single_validation_failure!( @@ -114,6 +122,12 @@ def create(**args) address_line2: args[:address_line2], state: args[:state], zipcode: args[:zipcode], + shipping_address_line1: shipping_address[:address_line1], + shipping_address_line2: shipping_address[:address_line2], + shipping_country: shipping_address[:country]&.upcase, + shipping_state: shipping_address[:state], + shipping_zipcode: shipping_address[:zipcode], + shipping_city: shipping_address[:city], email: args[:email], city: args[:city], url: args[:url], diff --git a/app/services/customers/update_service.rb b/app/services/customers/update_service.rb index aabebfc127f..881e947bb90 100644 --- a/app/services/customers/update_service.rb +++ b/app/services/customers/update_service.rb @@ -19,6 +19,8 @@ def update(**args) old_provider_customer = customer.provider_customer ActiveRecord::Base.transaction do billing_configuration = args[:billing_configuration]&.to_h || {} + shipping_address = args[:shipping_address]&.to_h || {} + if args.key?(:currency) update_currency(customer:, currency: args[:currency], customer_update: true) result.raise_if_error! @@ -40,6 +42,12 @@ def update(**args) customer.legal_number = args[:legal_number] if args.key?(:legal_number) customer.net_payment_term = args[:net_payment_term] if args.key?(:net_payment_term) customer.external_salesforce_id = args[:external_salesforce_id] if args.key?(:external_salesforce_id) + customer.shipping_address_line1 = shipping_address[:address_line1] if shipping_address.key?(:address_line1) + customer.shipping_address_line2 = shipping_address[:address_line2] if shipping_address.key?(:address_line2) + customer.shipping_city = shipping_address[:city] if shipping_address.key?(:city) + customer.shipping_zipcode = shipping_address[:zipcode] if shipping_address.key?(:zipcode) + customer.shipping_state = shipping_address[:state] if shipping_address.key?(:state) + customer.shipping_country = shipping_address[:country]&.upcase if shipping_address.key?(:country) assign_premium_attributes(customer, args) diff --git a/db/migrate/20240626094521_add_shipping_address_to_customers.rb b/db/migrate/20240626094521_add_shipping_address_to_customers.rb new file mode 100644 index 00000000000..02b6e78407b --- /dev/null +++ b/db/migrate/20240626094521_add_shipping_address_to_customers.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class AddShippingAddressToCustomers < ActiveRecord::Migration[7.1] + change_table :customers, bulk: true do |t| + t.string :shipping_address_line1 + t.string :shipping_address_line2 + t.string :shipping_city + t.string :shipping_zipcode + t.string :shipping_state + t.string :shipping_country + end +end diff --git a/db/schema.rb b/db/schema.rb index 725c4ebe57b..9bde0210c54 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_06_25_090742) do +ActiveRecord::Schema[7.1].define(version: 2024_06_26_094521) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -418,6 +418,12 @@ t.integer "net_payment_term" t.string "external_salesforce_id" t.string "payment_provider_code" + t.string "shipping_address_line1" + t.string "shipping_address_line2" + t.string "shipping_city" + t.string "shipping_zipcode" + t.string "shipping_state" + t.string "shipping_country" t.index ["deleted_at"], name: "index_customers_on_deleted_at" t.index ["external_id", "organization_id"], name: "index_customers_on_external_id_and_organization_id", unique: true, where: "(deleted_at IS NULL)" t.index ["organization_id"], name: "index_customers_on_organization_id" diff --git a/schema.graphql b/schema.graphql index 7649f4ec841..86cb0c0c7da 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1838,6 +1838,7 @@ input CreateCustomerInput { paymentProviderCode: String phone: String providerCustomer: ProviderCustomerInput + shippingAddress: CustomerAddressInput state: String taxCodes: [String!] taxIdentificationNumber: String @@ -2994,6 +2995,7 @@ type Customer { phone: String providerCustomer: ProviderCustomer sequentialId: String! + shippingAddress: CustomerAddress slug: String! state: String @@ -3015,6 +3017,24 @@ type Customer { zipcode: String } +type CustomerAddress { + addressLine1: String + addressLine2: String + city: String + country: CountryCode + state: String + zipcode: String +} + +input CustomerAddressInput { + addressLine1: String + addressLine2: String + city: String + country: CountryCode + state: String + zipcode: String +} + type CustomerBillingConfiguration { documentLocale: String id: ID! @@ -6881,6 +6901,7 @@ input UpdateCustomerInput { paymentProviderCode: String phone: String providerCustomer: ProviderCustomerInput + shippingAddress: CustomerAddressInput state: String taxCodes: [String!] taxIdentificationNumber: String diff --git a/schema.json b/schema.json index b8faa7a52f9..a7544b2c35d 100644 --- a/schema.json +++ b/schema.json @@ -7114,6 +7114,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "shippingAddress", + "description": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "CustomerAddressInput", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "metadata", "description": null, @@ -12158,6 +12170,20 @@ ] }, + { + "name": "shippingAddress", + "description": null, + "type": { + "kind": "OBJECT", + "name": "CustomerAddress", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, { "name": "slug", "description": null, @@ -12349,6 +12375,186 @@ "inputFields": null, "enumValues": null }, + { + "kind": "OBJECT", + "name": "CustomerAddress", + "description": null, + "interfaces": [ + + ], + "possibleTypes": null, + "fields": [ + { + "name": "addressLine1", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "addressLine2", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "city", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "country", + "description": null, + "type": { + "kind": "ENUM", + "name": "CountryCode", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "state", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "zipcode", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + } + ], + "inputFields": null, + "enumValues": null + }, + { + "kind": "INPUT_OBJECT", + "name": "CustomerAddressInput", + "description": null, + "interfaces": null, + "possibleTypes": null, + "fields": null, + "inputFields": [ + { + "name": "addressLine1", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "addressLine2", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "city", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "country", + "description": null, + "type": { + "kind": "ENUM", + "name": "CountryCode", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "state", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "zipcode", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "enumValues": null + }, { "kind": "OBJECT", "name": "CustomerBillingConfiguration", @@ -33298,6 +33504,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "shippingAddress", + "description": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "CustomerAddressInput", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "metadata", "description": null, diff --git a/spec/graphql/mutations/customers/create_spec.rb b/spec/graphql/mutations/customers/create_spec.rb index 10ee3596563..bdbe5178b1b 100644 --- a/spec/graphql/mutations/customers/create_spec.rb +++ b/spec/graphql/mutations/customers/create_spec.rb @@ -27,6 +27,7 @@ canEditAttributes invoiceGracePeriod billingConfiguration { documentLocale } + shippingAddress { addressLine1 city state } metadata { id, key, value, displayInInvoice } taxes { code } } @@ -75,6 +76,12 @@ billingConfiguration: { documentLocale: 'fr' }, + shippingAddress: { + addressLine1: 'Test 12', + zipcode: '102030', + state: 'test state', + city: 'Paris' + }, metadata: [ { key: 'manager', @@ -102,6 +109,9 @@ expect(result_data['providerCustomer']['providerCustomerId']).to eq('cu_12345') expect(result_data['providerCustomer']['providerPaymentMethods']).to eq(['card']) expect(result_data['billingConfiguration']['documentLocale']).to eq('fr') + expect(result_data['shippingAddress']['addressLine1']).to eq('Test 12') + expect(result_data['shippingAddress']['city']).to eq('Paris') + expect(result_data['shippingAddress']['state']).to eq('test state') expect(result_data['netPaymentTerm']).to eq(30) expect(result_data['metadata'].count).to eq(1) expect(result_data['metadata'][0]['value']).to eq('John Doe') diff --git a/spec/graphql/types/customers/object_spec.rb b/spec/graphql/types/customers/object_spec.rb index e698ad23bad..d933ff5d8f1 100644 --- a/spec/graphql/types/customers/object_spec.rb +++ b/spec/graphql/types/customers/object_spec.rb @@ -39,6 +39,8 @@ it { is_expected.to have_field(:billing_configuration).of_type('CustomerBillingConfiguration') } + it { is_expected.to have_field(:shipping_address).of_type('CustomerAddress') } + it { is_expected.to have_field(:anrok_customer).of_type('AnrokCustomer') } it { is_expected.to have_field(:netsuite_customer).of_type('NetsuiteCustomer') } it { is_expected.to have_field(:provider_customer).of_type('ProviderCustomer') } diff --git a/spec/serializers/v1/customer_serializer_spec.rb b/spec/serializers/v1/customer_serializer_spec.rb index 80d558be20f..862cac8e7d0 100644 --- a/spec/serializers/v1/customer_serializer_spec.rb +++ b/spec/serializers/v1/customer_serializer_spec.rb @@ -15,6 +15,7 @@ before do metadata customer_applied_tax + customer.update!(shipping_city: 'Paris', shipping_address_line1: 'test1', shipping_zipcode: '002') end it 'serializes the object' do @@ -49,6 +50,9 @@ expect(result['customer']['billing_configuration']['invoice_grace_period']).to eq(customer.invoice_grace_period) expect(result['customer']['billing_configuration']['vat_rate']).to eq(customer.vat_rate) expect(result['customer']['billing_configuration']['document_locale']).to eq(customer.document_locale) + expect(result['customer']['shipping_address']['address_line1']).to eq('test1') + expect(result['customer']['shipping_address']['city']).to eq('Paris') + expect(result['customer']['shipping_address']['zipcode']).to eq('002') expect(result['customer']['metadata'].first['lago_id']).to eq(metadata.id) expect(result['customer']['metadata'].first['key']).to eq(metadata.key) expect(result['customer']['metadata'].first['value']).to eq(metadata.value) diff --git a/spec/services/customers/create_service_spec.rb b/spec/services/customers/create_service_spec.rb index bcd5788f339..953c61bcfbd 100644 --- a/spec/services/customers/create_service_spec.rb +++ b/spec/services/customers/create_service_spec.rb @@ -20,6 +20,14 @@ billing_configuration: { vat_rate: 20, document_locale: 'fr' + }, + shipping_address: { + address_line1: 'line1', + address_line2: 'line2', + city: 'Paris', + zipcode: '123456', + state: 'foobar', + country: 'FR' } } end @@ -47,6 +55,14 @@ expect(customer.vat_rate).to eq(billing[:vat_rate]) expect(customer.document_locale).to eq(billing[:document_locale]) expect(customer.invoice_grace_period).to be_nil + + shipping_address = create_args[:shipping_address] + expect(customer.shipping_address_line1).to eq(shipping_address[:address_line1]) + expect(customer.shipping_address_line2).to eq(shipping_address[:address_line2]) + expect(customer.shipping_city).to eq(shipping_address[:city]) + expect(customer.shipping_zipcode).to eq(shipping_address[:zipcode]) + expect(customer.shipping_state).to eq(shipping_address[:state]) + expect(customer.shipping_country).to eq(shipping_address[:country]) end end @@ -916,7 +932,15 @@ organization_id: organization.id, currency: 'EUR', timezone: 'Europe/Paris', - invoice_grace_period: 2 + invoice_grace_period: 2, + shipping_address: { + address_line1: 'line1', + address_line2: 'line2', + city: 'Paris', + zipcode: '123456', + state: 'foobar', + country: 'FR' + } } end @@ -939,6 +963,14 @@ expect(customer.currency).to eq('EUR') expect(customer.timezone).to be_nil expect(customer.invoice_grace_period).to be_nil + + shipping_address = create_args[:shipping_address] + expect(customer.shipping_address_line1).to eq(shipping_address[:address_line1]) + expect(customer.shipping_address_line2).to eq(shipping_address[:address_line2]) + expect(customer.shipping_city).to eq(shipping_address[:city]) + expect(customer.shipping_zipcode).to eq(shipping_address[:zipcode]) + expect(customer.shipping_state).to eq(shipping_address[:state]) + expect(customer.shipping_country).to eq(shipping_address[:country]) end end diff --git a/spec/services/customers/update_service_spec.rb b/spec/services/customers/update_service_spec.rb index 4967bf8c679..b926cb10445 100644 --- a/spec/services/customers/update_service_spec.rb +++ b/spec/services/customers/update_service_spec.rb @@ -25,6 +25,9 @@ external_id:, billing_configuration: { vat_rate: 20 + }, + shipping_address: { + city: 'Paris' } } end @@ -39,6 +42,9 @@ billing = update_args[:billing_configuration] expect(updated_customer.vat_rate).to eq(billing[:vat_rate]) + + shipping_address = update_args[:shipping_address] + expect(updated_customer.shipping_city).to eq(shipping_address[:city]) end end