From 973101e6dc64182031803579c0f3e5912ddc7b53 Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Tue, 4 Mar 2025 19:46:12 -0500 Subject: [PATCH 01/26] created end-to-end mocks for item fetching --- app/app/services/argyle_service.rb | 4 +++- app/spec/services/argyle_service_spec.rb | 18 ++++++++++++++++ app/spec/support/argyle_api_helper.rb | 21 +++++++++++++++++++ .../fixtures/argyle/request_items.json | 1 + 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 app/spec/services/argyle_service_spec.rb create mode 100644 app/spec/support/argyle_api_helper.rb create mode 100644 app/spec/support/fixtures/argyle/request_items.json diff --git a/app/app/services/argyle_service.rb b/app/app/services/argyle_service.rb index f582e5b99..d5d7b29f1 100644 --- a/app/app/services/argyle_service.rb +++ b/app/app/services/argyle_service.rb @@ -11,6 +11,8 @@ class ArgyleService } } + ITEMS_ENDPOINT = "items" + def initialize(environment, api_key_id = nil, api_key_secret = nil) @api_key_id = api_key_id || ENVIRONMENTS.fetch(environment.to_sym)[:api_key_id] @api_key_secret = api_key_secret || ENVIRONMENTS.fetch(environment.to_sym)[:api_key_secret] @@ -38,6 +40,6 @@ def initialize(environment, api_key_id = nil, api_key_secret = nil) # Fetch all Argyle items def items(query = nil) - @http.get("items", { q: query }).body + @http.get(ITEMS_ENDPOINT, { q: query }).body end end diff --git a/app/spec/services/argyle_service_spec.rb b/app/spec/services/argyle_service_spec.rb new file mode 100644 index 000000000..df252709c --- /dev/null +++ b/app/spec/services/argyle_service_spec.rb @@ -0,0 +1,18 @@ +require 'rails_helper' + +RSpec.describe ArgyleService, type: :service do + include ArgyleApiHelper + let(:service) { ArgyleService.new("sandbox", "FAKE_API_KEY") } + let(:end_user_id) { 'abc123' } + + describe '#fetch_items' do + before do + stub_request_items_response + end + + it 'returns a non-empty response' do + response = service.items('test') + expect(response).not_to be_empty + end + end +end \ No newline at end of file diff --git a/app/spec/support/argyle_api_helper.rb b/app/spec/support/argyle_api_helper.rb new file mode 100644 index 000000000..34f38e996 --- /dev/null +++ b/app/spec/support/argyle_api_helper.rb @@ -0,0 +1,21 @@ +module ArgyleApiHelper + def stub_request_items_response + stub_request(:get,%r{#{ArgyleService::ITEMS_ENDPOINT}}) + .to_return( + status: 200, + body: load_relative_json_file('request_items.json').to_json, + headers: { 'Content-Type': 'application/json;charset=UTF-8' } + ) + end + + def load_relative_file(filename) + File.read(File.join( + File.dirname(__FILE__), + "fixtures/argyle/#{filename}" + )) + end + + def load_relative_json_file(filename) + JSON.parse(load_relative_file(filename)) + end +end \ No newline at end of file diff --git a/app/spec/support/fixtures/argyle/request_items.json b/app/spec/support/fixtures/argyle/request_items.json new file mode 100644 index 000000000..5d1494f45 --- /dev/null +++ b/app/spec/support/fixtures/argyle/request_items.json @@ -0,0 +1 @@ +{"next":"https://api-sandbox.argyle.com/v2/items?q\u0026cursor=cD1ORVhUJnM9NTAwNDM2NCZ1PVVTUFMmb3JkZXJpbmc9LXNjb3JlJmxpbWl0PTEw","previous":null,"results":[{"id":"item_000248659","name":"Amazon","kind":"employer","known_limitations":null,"status":"healthy","status_details":null,"mapping_status":"verified","logo_url":"https://res.cloudinary.com/argyle-media/image/upload/v1728915995/partner-logos/amazon.png","is_grouping":true,"matching_score":null},{"id":"item_000002102","name":"Amazon A to Z","kind":"employer","known_limitations":"Due to platform integration with myADP, myADP platform limitation is inherited and Historical Payouts data is limited to 3 years per employment.","status":"healthy","status_details":null,"mapping_status":"verified","logo_url":"https://res.cloudinary.com/argyle-media/image/upload/v1726049653/partner-logos/Amazon%20A%20to%20Z.png","is_grouping":false,"matching_score":null},{"id":"item_000002104","name":"Amazon Flex","kind":"gig","known_limitations":"Historical activity retrieval is limited to 2018-01-01.","status":"healthy","status_details":null,"mapping_status":"verified","logo_url":"https://res.cloudinary.com/argyle-media/image/upload/v1566985961/partner-logos/amazon_flex.png","is_grouping":false,"matching_score":null},{"id":"item_000012375","name":"DoorDash","kind":"gig","known_limitations":"Historical activity retrieval is limited to 2018-01-01, and the detailed task information is limited to 2022.","status":"healthy","status_details":null,"mapping_status":"verified","logo_url":"https://res.cloudinary.com/argyle-media/image/upload/v1739973927/partner-logos/doordash.png","is_grouping":false,"matching_score":null},{"id":"item_000017676","name":"Grubhub Drivers","kind":"gig","known_limitations":"Due to platform limitations, only 2 months of historical Gig data is available.","status":"healthy","status_details":null,"mapping_status":"verified","logo_url":"https://res.cloudinary.com/argyle-media/image/upload/v1657284184/partner-logos/grubhub.png","is_grouping":false,"matching_score":null},{"id":"item_000019016","name":"Home Depot","kind":"employer","known_limitations":null,"status":"healthy","status_details":null,"mapping_status":"verified","logo_url":"https://res.cloudinary.com/argyle-media/image/upload/v1698400914/partner-logos/Home%20Depot.png","is_grouping":false,"matching_score":null},{"id":"item_000020392","name":"Instacart Shopper","kind":"gig","known_limitations":"Due to the Instacart app’s lack of concurrent session support, when a user connects their Instacart account via Argyle Link or when Argyle re-scans a connected account, the user is signed out of the Instacart app. To reduce the chances of a user being signed out while using the app, Instacart accounts are re-scanned every 24 hours between 0600 and 1200 UTC.\nIn addition, Instacart prevents Direct Deposit updates for 60 minutes after a user connects their account. For this reason we recommend a two-stage user flow to perform a DDS update: 1) asking the user to connect their account, 2) 60+ minutes later prompting them to return to confirm their new DDS settings. During this interim users should not sign in to their Instacart account outside of Argyle Link.\nHistorical activity retrieval is limited to 2018-01-01.","status":"healthy","status_details":null,"mapping_status":"verified","logo_url":"https://res.cloudinary.com/argyle-media/image/upload/v1713529256/partner-logos/Instacart%20Shopper.png","is_grouping":false,"matching_score":null},{"id":"item_000024123","name":"Lyft Driver","kind":"gig","known_limitations":null,"status":"healthy","status_details":null,"mapping_status":"verified","logo_url":"https://res.cloudinary.com/argyle-media/image/upload/v1734954175/partner-logos/Lyft%20Driver.png","is_grouping":false,"matching_score":null},{"id":"item_000041078","name":"Uber Driver","kind":"gig","known_limitations":"Due to platform limitations:\nHistorical gig and paystub availability is restricted to 2 years.\nOnly the last 2 digits of SSN are provided.\nRental car-related data availability is limited.","status":"healthy","status_details":null,"mapping_status":"verified","logo_url":"https://res.cloudinary.com/argyle-media/image/upload/v1730994902/partner-logos/Uber%20Driver.png","is_grouping":false,"matching_score":null},{"id":"item_000041653","name":"USPS","kind":"employer","known_limitations":"After January 15, 2023, employees are required to perform a one-time reset to their Self-Service Profile (SSP) password and set up Multifactor Authentication (MFA) to access LiteBlue.\n\nDue to platform limitations, data refresh requires user re-authentication.\nDue to platform limitations, only the last 41 Paystubs are available. This corresponds to approximately 1.5 years of historical paystub information.","status":"healthy","status_details":null,"mapping_status":"verified","logo_url":"https://res.cloudinary.com/argyle-media/image/upload/v1713529235/partner-logos/usps.png","is_grouping":false,"matching_score":null}]} From 9bc20680d1205b6e19659d3705d408217f0605c7 Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Wed, 5 Mar 2025 14:02:39 -0500 Subject: [PATCH 02/26] testing fetch_identity --- app/app/services/argyle_service.rb | 21 + app/spec/services/argyle_service_spec.rb | 15 +- app/spec/support/argyle_api_helper.rb | 9 + .../fixtures/argyle/request_identity.json | 0 .../fixtures/argyle/request_items.json | 127 +++- .../fixtures/argyle/request_paystubs.json | 703 ++++++++++++++++++ .../support/fixtures/argyle/request_user.json | 15 + 7 files changed, 887 insertions(+), 3 deletions(-) create mode 100644 app/spec/support/fixtures/argyle/request_identity.json create mode 100644 app/spec/support/fixtures/argyle/request_paystubs.json create mode 100644 app/spec/support/fixtures/argyle/request_user.json diff --git a/app/app/services/argyle_service.rb b/app/app/services/argyle_service.rb index d5d7b29f1..24d72b1a0 100644 --- a/app/app/services/argyle_service.rb +++ b/app/app/services/argyle_service.rb @@ -12,6 +12,8 @@ class ArgyleService } ITEMS_ENDPOINT = "items" + PAYSTUBS_ENDPOINT = "paystubs" + IDENTITIES_ENDPOINT = "identities" def initialize(environment, api_key_id = nil, api_key_secret = nil) @api_key_id = api_key_id || ENVIRONMENTS.fetch(environment.to_sym)[:api_key_id] @@ -42,4 +44,23 @@ def initialize(environment, api_key_id = nil, api_key_secret = nil) def items(query = nil) @http.get(ITEMS_ENDPOINT, { q: query }).body end + + def fetch_user(end_user_id:) + @http.get(build_url("users/#{end_user_id}")).body + end + + # https://docs.argyle.com/api-reference/identities#retrieve + def fetch_identity(end_user_id:) + @http.get(build_url("identities/#{end_user_id}")).body + end + + def fetch_paystubs(account_id:) + # json["data"].map { |paystub_json| ResponseObjects::Paystub.from_pinwheel(paystub_json) } + @http.get(PAYSTUBS_ENDPOINT, { q: { account: account_id } }).body + end + + # TODO: refactor this into common function between argyle_service/pinwheel_service + def build_url(endpoint) + @http.build_url(endpoint).to_s + end end diff --git a/app/spec/services/argyle_service_spec.rb b/app/spec/services/argyle_service_spec.rb index df252709c..3503611a6 100644 --- a/app/spec/services/argyle_service_spec.rb +++ b/app/spec/services/argyle_service_spec.rb @@ -11,8 +11,19 @@ end it 'returns a non-empty response' do - response = service.items('test') + response = service.items('test') expect(response).not_to be_empty end end -end \ No newline at end of file + + describe '#fetch_paystubs' do + before do + stub_request_paystubs_response + end + + it 'returns a non-empty response' do + response = service.fetch_paystubs(account_id: end_user_id) + expect(response).not_to be_empty + end + end +end diff --git a/app/spec/support/argyle_api_helper.rb b/app/spec/support/argyle_api_helper.rb index 34f38e996..ee490a9d4 100644 --- a/app/spec/support/argyle_api_helper.rb +++ b/app/spec/support/argyle_api_helper.rb @@ -8,6 +8,15 @@ def stub_request_items_response ) end + def stub_request_paystubs_response + stub_request(:get,%r{#{ArgyleService::PAYSTUBS_ENDPOINT}}) + .to_return( + status: 200, + body: load_relative_json_file('request_paystubs.json').to_json, + headers: { 'Content-Type': 'application/json;charset=UTF-8' } + ) + end + def load_relative_file(filename) File.read(File.join( File.dirname(__FILE__), diff --git a/app/spec/support/fixtures/argyle/request_identity.json b/app/spec/support/fixtures/argyle/request_identity.json new file mode 100644 index 000000000..e69de29bb diff --git a/app/spec/support/fixtures/argyle/request_items.json b/app/spec/support/fixtures/argyle/request_items.json index 5d1494f45..c04e19c47 100644 --- a/app/spec/support/fixtures/argyle/request_items.json +++ b/app/spec/support/fixtures/argyle/request_items.json @@ -1 +1,126 @@ -{"next":"https://api-sandbox.argyle.com/v2/items?q\u0026cursor=cD1ORVhUJnM9NTAwNDM2NCZ1PVVTUFMmb3JkZXJpbmc9LXNjb3JlJmxpbWl0PTEw","previous":null,"results":[{"id":"item_000248659","name":"Amazon","kind":"employer","known_limitations":null,"status":"healthy","status_details":null,"mapping_status":"verified","logo_url":"https://res.cloudinary.com/argyle-media/image/upload/v1728915995/partner-logos/amazon.png","is_grouping":true,"matching_score":null},{"id":"item_000002102","name":"Amazon A to Z","kind":"employer","known_limitations":"Due to platform integration with myADP, myADP platform limitation is inherited and Historical Payouts data is limited to 3 years per employment.","status":"healthy","status_details":null,"mapping_status":"verified","logo_url":"https://res.cloudinary.com/argyle-media/image/upload/v1726049653/partner-logos/Amazon%20A%20to%20Z.png","is_grouping":false,"matching_score":null},{"id":"item_000002104","name":"Amazon Flex","kind":"gig","known_limitations":"Historical activity retrieval is limited to 2018-01-01.","status":"healthy","status_details":null,"mapping_status":"verified","logo_url":"https://res.cloudinary.com/argyle-media/image/upload/v1566985961/partner-logos/amazon_flex.png","is_grouping":false,"matching_score":null},{"id":"item_000012375","name":"DoorDash","kind":"gig","known_limitations":"Historical activity retrieval is limited to 2018-01-01, and the detailed task information is limited to 2022.","status":"healthy","status_details":null,"mapping_status":"verified","logo_url":"https://res.cloudinary.com/argyle-media/image/upload/v1739973927/partner-logos/doordash.png","is_grouping":false,"matching_score":null},{"id":"item_000017676","name":"Grubhub Drivers","kind":"gig","known_limitations":"Due to platform limitations, only 2 months of historical Gig data is available.","status":"healthy","status_details":null,"mapping_status":"verified","logo_url":"https://res.cloudinary.com/argyle-media/image/upload/v1657284184/partner-logos/grubhub.png","is_grouping":false,"matching_score":null},{"id":"item_000019016","name":"Home Depot","kind":"employer","known_limitations":null,"status":"healthy","status_details":null,"mapping_status":"verified","logo_url":"https://res.cloudinary.com/argyle-media/image/upload/v1698400914/partner-logos/Home%20Depot.png","is_grouping":false,"matching_score":null},{"id":"item_000020392","name":"Instacart Shopper","kind":"gig","known_limitations":"Due to the Instacart app’s lack of concurrent session support, when a user connects their Instacart account via Argyle Link or when Argyle re-scans a connected account, the user is signed out of the Instacart app. To reduce the chances of a user being signed out while using the app, Instacart accounts are re-scanned every 24 hours between 0600 and 1200 UTC.\nIn addition, Instacart prevents Direct Deposit updates for 60 minutes after a user connects their account. For this reason we recommend a two-stage user flow to perform a DDS update: 1) asking the user to connect their account, 2) 60+ minutes later prompting them to return to confirm their new DDS settings. During this interim users should not sign in to their Instacart account outside of Argyle Link.\nHistorical activity retrieval is limited to 2018-01-01.","status":"healthy","status_details":null,"mapping_status":"verified","logo_url":"https://res.cloudinary.com/argyle-media/image/upload/v1713529256/partner-logos/Instacart%20Shopper.png","is_grouping":false,"matching_score":null},{"id":"item_000024123","name":"Lyft Driver","kind":"gig","known_limitations":null,"status":"healthy","status_details":null,"mapping_status":"verified","logo_url":"https://res.cloudinary.com/argyle-media/image/upload/v1734954175/partner-logos/Lyft%20Driver.png","is_grouping":false,"matching_score":null},{"id":"item_000041078","name":"Uber Driver","kind":"gig","known_limitations":"Due to platform limitations:\nHistorical gig and paystub availability is restricted to 2 years.\nOnly the last 2 digits of SSN are provided.\nRental car-related data availability is limited.","status":"healthy","status_details":null,"mapping_status":"verified","logo_url":"https://res.cloudinary.com/argyle-media/image/upload/v1730994902/partner-logos/Uber%20Driver.png","is_grouping":false,"matching_score":null},{"id":"item_000041653","name":"USPS","kind":"employer","known_limitations":"After January 15, 2023, employees are required to perform a one-time reset to their Self-Service Profile (SSP) password and set up Multifactor Authentication (MFA) to access LiteBlue.\n\nDue to platform limitations, data refresh requires user re-authentication.\nDue to platform limitations, only the last 41 Paystubs are available. This corresponds to approximately 1.5 years of historical paystub information.","status":"healthy","status_details":null,"mapping_status":"verified","logo_url":"https://res.cloudinary.com/argyle-media/image/upload/v1713529235/partner-logos/usps.png","is_grouping":false,"matching_score":null}]} +{ + "next": "https://api-sandbox.argyle.com/v2/items?q&cursor=cD1ORVhUJnM9NTAwNDM2NCZ1PVVTUFMmb3JkZXJpbmc9LXNjb3JlJmxpbWl0PTEw", + "previous": null, + "results": [ + { + "id": "item_000248659", + "name": "Amazon", + "kind": "employer", + "known_limitations": null, + "status": "healthy", + "status_details": null, + "mapping_status": "verified", + "logo_url": "https://res.cloudinary.com/argyle-media/image/upload/v1728915995/partner-logos/amazon.png", + "is_grouping": true, + "matching_score": null + }, + { + "id": "item_000002102", + "name": "Amazon A to Z", + "kind": "employer", + "known_limitations": "Due to platform integration with myADP, myADP platform limitation is inherited and Historical Payouts data is limited to 3 years per employment.", + "status": "healthy", + "status_details": null, + "mapping_status": "verified", + "logo_url": "https://res.cloudinary.com/argyle-media/image/upload/v1726049653/partner-logos/Amazon%20A%20to%20Z.png", + "is_grouping": false, + "matching_score": null + }, + { + "id": "item_000002104", + "name": "Amazon Flex", + "kind": "gig", + "known_limitations": "Historical activity retrieval is limited to 2018-01-01.", + "status": "healthy", + "status_details": null, + "mapping_status": "verified", + "logo_url": "https://res.cloudinary.com/argyle-media/image/upload/v1566985961/partner-logos/amazon_flex.png", + "is_grouping": false, + "matching_score": null + }, + { + "id": "item_000012375", + "name": "DoorDash", + "kind": "gig", + "known_limitations": "Historical activity retrieval is limited to 2018-01-01, and the detailed task information is limited to 2022.", + "status": "healthy", + "status_details": null, + "mapping_status": "verified", + "logo_url": "https://res.cloudinary.com/argyle-media/image/upload/v1739973927/partner-logos/doordash.png", + "is_grouping": false, + "matching_score": null + }, + { + "id": "item_000017676", + "name": "Grubhub Drivers", + "kind": "gig", + "known_limitations": "Due to platform limitations, only 2 months of historical Gig data is available.", + "status": "healthy", + "status_details": null, + "mapping_status": "verified", + "logo_url": "https://res.cloudinary.com/argyle-media/image/upload/v1657284184/partner-logos/grubhub.png", + "is_grouping": false, + "matching_score": null + }, + { + "id": "item_000019016", + "name": "Home Depot", + "kind": "employer", + "known_limitations": null, + "status": "healthy", + "status_details": null, + "mapping_status": "verified", + "logo_url": "https://res.cloudinary.com/argyle-media/image/upload/v1698400914/partner-logos/Home%20Depot.png", + "is_grouping": false, + "matching_score": null + }, + { + "id": "item_000020392", + "name": "Instacart Shopper", + "kind": "gig", + "known_limitations": "Due to the Instacart app’s lack of concurrent session support, when a user connects their Instacart account via Argyle Link or when Argyle re-scans a connected account, the user is signed out of the Instacart app. To reduce the chances of a user being signed out while using the app, Instacart accounts are re-scanned every 24 hours between 0600 and 1200 UTC.\nIn addition, Instacart prevents Direct Deposit updates for 60 minutes after a user connects their account. For this reason we recommend a two-stage user flow to perform a DDS update: 1) asking the user to connect their account, 2) 60+ minutes later prompting them to return to confirm their new DDS settings. During this interim users should not sign in to their Instacart account outside of Argyle Link.\nHistorical activity retrieval is limited to 2018-01-01.", + "status": "healthy", + "status_details": null, + "mapping_status": "verified", + "logo_url": "https://res.cloudinary.com/argyle-media/image/upload/v1713529256/partner-logos/Instacart%20Shopper.png", + "is_grouping": false, + "matching_score": null + }, + { + "id": "item_000024123", + "name": "Lyft Driver", + "kind": "gig", + "known_limitations": null, + "status": "healthy", + "status_details": null, + "mapping_status": "verified", + "logo_url": "https://res.cloudinary.com/argyle-media/image/upload/v1734954175/partner-logos/Lyft%20Driver.png", + "is_grouping": false, + "matching_score": null + }, + { + "id": "item_000041078", + "name": "Uber Driver", + "kind": "gig", + "known_limitations": "Due to platform limitations:\nHistorical gig and paystub availability is restricted to 2 years.\nOnly the last 2 digits of SSN are provided.\nRental car-related data availability is limited.", + "status": "healthy", + "status_details": null, + "mapping_status": "verified", + "logo_url": "https://res.cloudinary.com/argyle-media/image/upload/v1730994902/partner-logos/Uber%20Driver.png", + "is_grouping": false, + "matching_score": null + }, + { + "id": "item_000041653", + "name": "USPS", + "kind": "employer", + "known_limitations": "After January 15, 2023, employees are required to perform a one-time reset to their Self-Service Profile (SSP) password and set up Multifactor Authentication (MFA) to access LiteBlue.\n\nDue to platform limitations, data refresh requires user re-authentication.\nDue to platform limitations, only the last 41 Paystubs are available. This corresponds to approximately 1.5 years of historical paystub information.", + "status": "healthy", + "status_details": null, + "mapping_status": "verified", + "logo_url": "https://res.cloudinary.com/argyle-media/image/upload/v1713529235/partner-logos/usps.png", + "is_grouping": false, + "matching_score": null + } + ] +} \ No newline at end of file diff --git a/app/spec/support/fixtures/argyle/request_paystubs.json b/app/spec/support/fixtures/argyle/request_paystubs.json new file mode 100644 index 000000000..924247596 --- /dev/null +++ b/app/spec/support/fixtures/argyle/request_paystubs.json @@ -0,0 +1,703 @@ +{ + "results": [ + { + "id": "c7f88cf2-5267-37a9-9d78-b2f9dd942a2e", + "account": "01954440-8c8b-cd52-4a1f-f7aa07d136ed", + "payroll_document": null, + "deduction_list": [], + "filing_status": [], + "tax_list": [], + "status": "completed", + "paystub_date": "2025-02-24T00:00:00Z", + "paystub_period": { + "start_date": null, + "end_date": null + }, + "currency": "USD", + "gross_pay": "19.53", + "deductions": null, + "taxes": null, + "net_pay": "19.53", + "reimbursements": null, + "hours": null, + "fees": null, + "net_pay_ytd": "429.15", + "gross_pay_ytd": "429.15", + "metadata": {}, + "employer": "Uber Driver", + "employer_address": null, + "created_at": "2025-02-26T21:56:26.219Z", + "updated_at": "2025-02-26T21:56:26.219Z", + "destinations": [], + "hours_ytd": "0.00", + "deductions_ytd": "0.00", + "taxes_ytd": "0.00", + "gross_pay_list": [], + "gross_pay_list_totals": null, + "employment": "26396fa4-6213-39a6-902a-f9a85d26db4d" + }, + { + "id": "b2c1d851-e1e0-33d4-bbfa-2e3868979361", + "account": "01954449-1753-b8b9-8cd9-77b4be11db19", + "payroll_document": null, + "deduction_list": [], + "filing_status": [], + "tax_list": [], + "status": "completed", + "paystub_date": "2025-02-20T00:00:00Z", + "paystub_period": { + "start_date": null, + "end_date": null + }, + "currency": "USD", + "gross_pay": "36.33", + "deductions": null, + "taxes": null, + "net_pay": "36.33", + "reimbursements": null, + "hours": null, + "fees": null, + "net_pay_ytd": "369.77", + "gross_pay_ytd": "369.77", + "metadata": {}, + "employer": "Lyft Driver", + "employer_address": null, + "created_at": "2025-02-26T22:03:41.088Z", + "updated_at": "2025-02-26T22:03:41.088Z", + "destinations": [], + "hours_ytd": "0.00", + "deductions_ytd": "0.00", + "taxes_ytd": "0.00", + "gross_pay_list": [], + "gross_pay_list_totals": null, + "employment": "31188b69-703a-3d6e-b6f6-24073ca84263" + }, + { + "id": "14251c2b-ccaf-3c22-a093-cea40a3c0b18", + "account": "019539bc-f99a-54df-bf3a-1b44eda07475", + "payroll_document": "019539bd-3daa-2fc2-b78c-03634c3b8b3f", + "deduction_list": [ + { + "amount": "11.93", + "name": "Dental", + "tax_classification": null, + "amount_ytd": "95.43" + }, + { + "amount": "23.86", + "name": "Vision", + "tax_classification": "pre_tax", + "amount_ytd": "23.86" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "married filing separately" + }, + { + "type": "state", + "location": "NY", + "status": "single" + } + ], + "tax_list": [ + { + "amount": "83.50", + "name": "OASDI", + "type": "fica", + "amount_ytd": "298.22" + }, + { + "amount": "131.22", + "name": "CA State Income Tax", + "type": "state", + "amount_ytd": "381.73" + } + ], + "status": "completed", + "paystub_date": "2025-02-17T00:00:00Z", + "paystub_period": { + "start_date": "2025-01-30T00:00:00Z", + "end_date": "2025-02-13T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "1192.87", + "deductions": "35.79", + "taxes": "214.72", + "net_pay": "942.36", + "reimbursements": "0.00", + "hours": "36.97", + "fees": null, + "net_pay_ytd": "3666.17", + "gross_pay_ytd": "4863.81", + "metadata": {}, + "employer": "Accenture", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-02-24T20:54:18.427Z", + "updated_at": "2025-02-24T20:54:18.427Z", + "destinations": [], + "hours_ytd": "273.07", + "deductions_ytd": "250.50", + "taxes_ytd": "954.31", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2025-01-30", + "end_date": "2025-02-13", + "rate": "32.2696", + "hours": "36.9657", + "amount": "1192.87", + "hours_ytd": "273.0700", + "amount_ytd": "4771.48" + }, + { + "name": "Commission", + "type": "commission", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "92.33" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "1192.87", + "amount_ytd": "4771.48", + "hours": "36.9657", + "hours_ytd": "273.0700", + "rate_implied": "32.2700", + "rate_implied_ytd": "17.4700" + }, + "overtime": null, + "commission": { + "amount": null, + "amount_ytd": "92.33", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "bonus": null, + "other": null + }, + "employment": "b528a78b-152a-3bc3-a5b8-44a03c0b2ddc" + }, + { + "id": "461baf58-3383-3958-b4c9-adb25abb118d", + "account": "01954423-a310-c7f7-1cf5-744f0033821f", + "payroll_document": "01954423-fd40-3c8a-8f4d-52f1b15aecae", + "deduction_list": [ + { + "amount": "20.55", + "name": "Vision", + "tax_classification": "pre_tax", + "amount_ytd": "20.55" + }, + { + "amount": "82.19", + "name": "Dental", + "tax_classification": null, + "amount_ytd": "143.84" + }, + { + "amount": "61.65", + "name": "Dental", + "tax_classification": null, + "amount_ytd": "205.49" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "married filing separately" + }, + { + "type": "local", + "location": "New York", + "status": "single" + } + ], + "tax_list": [ + { + "amount": "164.39", + "name": "OASDI", + "type": "fica", + "amount_ytd": "575.36" + } + ], + "status": "completed", + "paystub_date": "2025-02-17T00:00:00Z", + "paystub_period": { + "start_date": "2025-01-26T00:00:00Z", + "end_date": "2025-02-09T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "2054.85", + "deductions": "164.39", + "taxes": "164.39", + "net_pay": "1726.07", + "reimbursements": "0.00", + "hours": "47.19", + "fees": null, + "net_pay_ytd": "6356.83", + "gross_pay_ytd": "8219.40", + "metadata": {}, + "employer": "ACME", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-02-26T21:22:44.290Z", + "updated_at": "2025-02-26T21:22:44.290Z", + "destinations": [], + "hours_ytd": "233.61", + "deductions_ytd": "410.98", + "taxes_ytd": "1458.95", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2025-01-26", + "end_date": "2025-02-09", + "rate": "43.5471", + "hours": "47.1868", + "amount": "2054.85", + "hours_ytd": "233.6100", + "amount_ytd": "8219.40" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "2054.85", + "amount_ytd": "8219.40", + "hours": "47.1868", + "hours_ytd": "233.6100", + "rate_implied": "43.5500", + "rate_implied_ytd": "35.1800" + }, + "overtime": null, + "commission": null, + "bonus": null, + "other": null + }, + "employment": "1932bc3e-687f-36d4-94ce-3b576ad0967e" + }, + { + "id": "999de8a4-2662-3a61-be0c-9c2544a91e5f", + "account": "0195435d-6b2a-548c-306d-007f609cfadd", + "payroll_document": "0195435d-baeb-b1eb-2f23-fae0300e6126", + "deduction_list": [ + { + "amount": "64.71", + "name": "Roth", + "tax_classification": "post_tax", + "amount_ytd": "97.06" + }, + { + "amount": "64.71", + "name": "Roth", + "tax_classification": "post_tax", + "amount_ytd": "161.77" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "married filing separately" + } + ], + "tax_list": [ + { + "amount": "323.54", + "name": "Federal Income Tax", + "type": "federal", + "amount_ytd": "647.08" + } + ], + "status": "completed", + "paystub_date": "2025-02-17T00:00:00Z", + "paystub_period": { + "start_date": "2025-01-28T00:00:00Z", + "end_date": "2025-02-11T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "3617.52", + "deductions": "129.42", + "taxes": "323.54", + "net_pay": "3169.56", + "reimbursements": "5.00", + "hours": "47.84", + "fees": null, + "net_pay_ytd": "11167.61", + "gross_pay_ytd": "13323.66", + "metadata": {}, + "employer": "ACM LLC", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-02-26T17:46:11.338Z", + "updated_at": "2025-02-26T17:46:11.338Z", + "destinations": [], + "hours_ytd": "252.99", + "deductions_ytd": "355.89", + "taxes_ytd": "1811.82", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2025-01-28", + "end_date": "2025-02-11", + "rate": "67.6280", + "hours": "47.8408", + "amount": "3235.38", + "hours_ytd": "252.9900", + "amount_ytd": "12941.52" + }, + { + "name": "Commission", + "type": "commission", + "start_date": "2025-02-05", + "end_date": "2025-02-06", + "rate": null, + "hours": null, + "amount": "382.14", + "hours_ytd": null, + "amount_ytd": "382.14" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "3235.38", + "amount_ytd": "12941.52", + "hours": "47.8408", + "hours_ytd": "252.9900", + "rate_implied": "67.6300", + "rate_implied_ytd": "51.1500" + }, + "overtime": null, + "commission": { + "amount": "382.14", + "amount_ytd": "382.14", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "bonus": null, + "other": null + }, + "employment": "5c05c7a3-8281-31d2-ae83-699099ca45d6" + }, + { + "id": "a87f5acb-c7d3-3c33-89f5-f168910b9f58", + "account": "019539d9-25f1-6077-f204-ada14bd6b914", + "payroll_document": "019539d9-5867-9c2b-df9b-aaa347ba1526", + "deduction_list": [ + { + "amount": "33.75", + "name": "401K", + "tax_classification": "pre_tax", + "amount_ytd": "50.95" + }, + { + "amount": "45.00", + "name": "Vision", + "tax_classification": "pre_tax", + "amount_ytd": "72.44" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "married filing separately" + } + ], + "tax_list": [ + { + "amount": "135.00", + "name": "Social Security Tax", + "type": "fica", + "amount_ytd": "272.57" + } + ], + "status": "completed", + "paystub_date": "2025-02-17T00:00:00Z", + "paystub_period": { + "start_date": "2025-01-29T00:00:00Z", + "end_date": "2025-02-17T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "1222.43", + "deductions": "78.75", + "taxes": "135.00", + "net_pay": "1008.68", + "reimbursements": "0.00", + "hours": "46.13", + "fees": null, + "net_pay_ytd": "4883.01", + "gross_pay_ytd": "6145.24", + "metadata": {}, + "employer": "Accenture", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-02-24T21:25:00.243Z", + "updated_at": "2025-02-24T21:25:00.243Z", + "destinations": [], + "hours_ytd": "243.50", + "deductions_ytd": "257.57", + "taxes_ytd": "1014.46", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2025-01-29", + "end_date": "2025-02-17", + "rate": "24.3900", + "hours": "46.1271", + "amount": "1125.04", + "hours_ytd": "243.5000", + "amount_ytd": "5938.99" + }, + { + "name": "Bonus", + "type": "bonus", + "start_date": "2025-02-16", + "end_date": "2025-02-16", + "rate": null, + "hours": null, + "amount": "13.54", + "hours_ytd": null, + "amount_ytd": "13.54" + }, + { + "name": "Incentive", + "type": "bonus", + "start_date": "2025-02-11", + "end_date": "2025-02-11", + "rate": null, + "hours": null, + "amount": "83.85", + "hours_ytd": null, + "amount_ytd": "83.85" + }, + { + "name": "Commission", + "type": "commission", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "108.86" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "1125.04", + "amount_ytd": "5938.99", + "hours": "46.1271", + "hours_ytd": "243.5000", + "rate_implied": "24.3900", + "rate_implied_ytd": "24.3900" + }, + "overtime": null, + "commission": { + "amount": null, + "amount_ytd": "108.86", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "bonus": { + "amount": "97.39", + "amount_ytd": "97.39", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "other": null + }, + "employment": "52615d3b-1ff6-3fe3-9165-25986cd5645a" + }, + { + "id": "f92d0a1c-28fc-3d6b-878c-aed569b0e9df", + "account": "01954440-8c8b-cd52-4a1f-f7aa07d136ed", + "payroll_document": null, + "deduction_list": [], + "filing_status": [], + "tax_list": [], + "status": "completed", + "paystub_date": "2025-02-17T00:00:00Z", + "paystub_period": { + "start_date": null, + "end_date": null + }, + "currency": "USD", + "gross_pay": "59.62", + "deductions": null, + "taxes": null, + "net_pay": "59.62", + "reimbursements": null, + "hours": null, + "fees": null, + "net_pay_ytd": "409.62", + "gross_pay_ytd": "409.62", + "metadata": {}, + "employer": "Uber Driver", + "employer_address": null, + "created_at": "2025-02-26T21:56:26.188Z", + "updated_at": "2025-02-26T21:56:26.188Z", + "destinations": [], + "hours_ytd": "0.00", + "deductions_ytd": "0.00", + "taxes_ytd": "0.00", + "gross_pay_list": [], + "gross_pay_list_totals": null, + "employment": "26396fa4-6213-39a6-902a-f9a85d26db4d" + }, + { + "id": "bbdbd350-643c-377f-adfe-eb73feecaf18", + "account": "01954449-1753-b8b9-8cd9-77b4be11db19", + "payroll_document": null, + "deduction_list": [], + "filing_status": [], + "tax_list": [], + "status": "completed", + "paystub_date": "2025-02-13T00:00:00Z", + "paystub_period": { + "start_date": null, + "end_date": null + }, + "currency": "USD", + "gross_pay": "66.49", + "deductions": null, + "taxes": null, + "net_pay": "66.49", + "reimbursements": null, + "hours": null, + "fees": null, + "net_pay_ytd": "333.44", + "gross_pay_ytd": "333.44", + "metadata": {}, + "employer": "Lyft Driver", + "employer_address": null, + "created_at": "2025-02-26T22:03:41.126Z", + "updated_at": "2025-02-26T22:03:41.126Z", + "destinations": [], + "hours_ytd": "0.00", + "deductions_ytd": "0.00", + "taxes_ytd": "0.00", + "gross_pay_list": [], + "gross_pay_list_totals": null, + "employment": "31188b69-703a-3d6e-b6f6-24073ca84263" + }, + { + "id": "4d73d910-a1b6-3cab-aaa0-99f9b8c53321", + "account": "01954440-8c8b-cd52-4a1f-f7aa07d136ed", + "payroll_document": null, + "deduction_list": [], + "filing_status": [], + "tax_list": [], + "status": "completed", + "paystub_date": "2025-02-10T00:00:00Z", + "paystub_period": { + "start_date": null, + "end_date": null + }, + "currency": "USD", + "gross_pay": "82.91", + "deductions": null, + "taxes": null, + "net_pay": "82.91", + "reimbursements": null, + "hours": null, + "fees": null, + "net_pay_ytd": "350.00", + "gross_pay_ytd": "350.00", + "metadata": {}, + "employer": "Uber Driver", + "employer_address": null, + "created_at": "2025-02-26T21:56:26.204Z", + "updated_at": "2025-02-26T21:56:26.204Z", + "destinations": [], + "hours_ytd": "0.00", + "deductions_ytd": "0.00", + "taxes_ytd": "0.00", + "gross_pay_list": [], + "gross_pay_list_totals": null, + "employment": "26396fa4-6213-39a6-902a-f9a85d26db4d" + }, + { + "id": "80130ab0-4338-3f0c-8d59-af68d9b09bac", + "account": "01954449-1753-b8b9-8cd9-77b4be11db19", + "payroll_document": null, + "deduction_list": [], + "filing_status": [], + "tax_list": [], + "status": "completed", + "paystub_date": "2025-02-06T00:00:00Z", + "paystub_period": { + "start_date": null, + "end_date": null + }, + "currency": "USD", + "gross_pay": "11.21", + "deductions": null, + "taxes": null, + "net_pay": "11.21", + "reimbursements": null, + "hours": null, + "fees": null, + "net_pay_ytd": "266.95", + "gross_pay_ytd": "266.95", + "metadata": {}, + "employer": "Lyft Driver", + "employer_address": null, + "created_at": "2025-02-26T22:03:41.144Z", + "updated_at": "2025-02-26T22:03:41.144Z", + "destinations": [], + "hours_ytd": "0.00", + "deductions_ytd": "0.00", + "taxes_ytd": "0.00", + "gross_pay_list": [], + "gross_pay_list_totals": null, + "employment": "31188b69-703a-3d6e-b6f6-24073ca84263" + } + ], + "next": "https://api-sandbox.argyle.com/v2/paystubs?cursor=Y2xpZW50SWQ9MDE4ZjA4MzQtZDY5YS04NzliLTA1Y2ItZGRkZTAzMzAyNGYwJmludmVyc2VTb3J0PWZhbHNlJmxpbWl0PTEwJnJvd0tleT1TJTIzQyUyMzAxOGYwODM0LWQ2OWEtODc5Yi0wNWNiLWRkZGUwMzMwMjRmMCUyMzQ2MTE2ODQyNzk4ODY1ODc5MDMlMjMxNjZhMDkzNy03ZTU2LTM3YzctODZmYy1iNDFlNWM1MDViOTE%3D", + "previous": null +} \ No newline at end of file diff --git a/app/spec/support/fixtures/argyle/request_user.json b/app/spec/support/fixtures/argyle/request_user.json new file mode 100644 index 000000000..fcbace752 --- /dev/null +++ b/app/spec/support/fixtures/argyle/request_user.json @@ -0,0 +1,15 @@ +{ + "id": "0195441c-5a5f-7d86-3be1-fa5797a441a6", + "created_at": "2025-02-26T21:14:23.712349Z", + "items_connected": [ + "item_000024123", + "item_000020392", + "item_000041078" + ], + "employers_connected": [ + "Uber Driver", + "Lyft Driver" + ], + "external_metadata": {}, + "external_id": "testing" +} \ No newline at end of file From 36a07faad08c7619cf84dfcd33639ddb6946fba8 Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Thu, 6 Mar 2025 15:46:35 -0500 Subject: [PATCH 03/26] save changes to response_object and fetching --- app/app/models/response_objects/employment.rb | 11 ++ app/app/models/response_objects/identity.rb | 6 + app/app/models/response_objects/income.rb | 8 + app/app/models/response_objects/paystub.rb | 27 ++- app/app/services/argyle_service.rb | 54 ++++- app/spec/services/argyle_service_spec.rb | 2 + .../request_accounts.json | 187 ++++++++++++++++++ .../request_employment.json | 22 +++ .../request_identity.json | 96 +++++++++ .../request_paystubs.json | 78 ++++++++ .../request_user.json | 15 ++ 11 files changed, 499 insertions(+), 7 deletions(-) create mode 100644 app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_accounts.json create mode 100644 app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_employment.json create mode 100644 app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_identity.json create mode 100644 app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_paystubs.json create mode 100644 app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_user.json diff --git a/app/app/models/response_objects/employment.rb b/app/app/models/response_objects/employment.rb index fa4c942b6..f0a4466ab 100644 --- a/app/app/models/response_objects/employment.rb +++ b/app/app/models/response_objects/employment.rb @@ -21,5 +21,16 @@ def self.from_pinwheel(response_body) employer_address: response_body.dig("employer_address", "raw") ) end + def self.from_argyle(identity_response_body) + new( + account_id: identity_response_body["account"], + employer_name: identity_response_body["employer"], + start_date: identity_response_body["hire_date"], + termination_date: identity_response_body["termination_date"], + status: identity_response_body["employment_status"], + employer_phone_number: "", + employer_address: "" + ) + end end end diff --git a/app/app/models/response_objects/identity.rb b/app/app/models/response_objects/identity.rb index 7d2f450c4..4e98276c1 100644 --- a/app/app/models/response_objects/identity.rb +++ b/app/app/models/response_objects/identity.rb @@ -11,5 +11,11 @@ def self.from_pinwheel(response_body) full_name: response_body["full_name"], ) end + def self.from_argyle(identity_response_body) + new( + account_id: identity_response_body["account"], + full_name: identity_response_body["full_name"], + ) + end end end diff --git a/app/app/models/response_objects/income.rb b/app/app/models/response_objects/income.rb index e462d369a..a423e3957 100644 --- a/app/app/models/response_objects/income.rb +++ b/app/app/models/response_objects/income.rb @@ -15,5 +15,13 @@ def self.from_pinwheel(response_body) compensation_unit: response_body["compensation_unit"], ) end + def self.from_argyle(identities_response_body) + new( + account_id: identities_response_body["account"], + pay_frequency: identities_response_body["base_pay"]["period"], + compensation_amount: identities_response_body["base_pay"]["amount"], + compensation_unit: identities_response_body["base_pay"]["currency"], + ) + end end end diff --git a/app/app/models/response_objects/paystub.rb b/app/app/models/response_objects/paystub.rb index 9ece18f82..a88fa4d3d 100644 --- a/app/app/models/response_objects/paystub.rb +++ b/app/app/models/response_objects/paystub.rb @@ -33,10 +33,35 @@ def self.from_pinwheel(response_body) ) end + + def self.from_argyle(paystubs_response_body) + new( + account_id: response_body["account"], + gross_pay_amount: response_body["gross_pay"], + net_pay_amount: response_body["net_pay"], + gross_pay_ytd: response_body["gross_pay_ytd"], + pay_period_start: response_body["pay_period"]["start_date"], + pay_period_end: response_body["pay_period"]["end_date"], + pay_date: response_body["paystub_date"], + hours: response_body["hours"], + hours_by_earning_category: response_body["gross_pay_list"].map do |gross_pay_item| + OpenStruct.new( + category: gross_pay_item["type"], + hours: gross_pay_item["hours"], + ) + end, + deductions: response_body["deduction_list"].map do |deduction| + OpenStruct.new( + category: deduction["tax_classification"], + amount: deduction["amount"], + ) + end, + ) + end + alias_attribute :start, :pay_period_start alias_attribute :end, :pay_period_end end - module PinwheelMethods def self.hours(earnings) base_hours = earnings diff --git a/app/app/services/argyle_service.rb b/app/app/services/argyle_service.rb index 24d72b1a0..b4c5de2a6 100644 --- a/app/app/services/argyle_service.rb +++ b/app/app/services/argyle_service.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "faraday" +require "fileutils" class ArgyleService ENVIRONMENTS = { @@ -41,26 +42,67 @@ def initialize(environment, api_key_id = nil, api_key_secret = nil) end # Fetch all Argyle items + # https://docs.argyle.com/api-reference/items#list def items(query = nil) @http.get(ITEMS_ENDPOINT, { q: query }).body end - def fetch_user(end_user_id:) - @http.get(build_url("users/#{end_user_id}")).body + # https://docs.argyle.com/api-reference/users#retrieve + def fetch_user(user:) + @http.get(build_url("users/#{user}")).body end # https://docs.argyle.com/api-reference/identities#retrieve - def fetch_identity(end_user_id:) - @http.get(build_url("identities/#{end_user_id}")).body + def fetch_identity(**params) + # todo: paginate + @http.get("identities", params).body end - def fetch_paystubs(account_id:) + # https://docs.argyle.com/api-reference/accounts#list + def fetch_accounts(**params) + # TODO: paginate # json["data"].map { |paystub_json| ResponseObjects::Paystub.from_pinwheel(paystub_json) } - @http.get(PAYSTUBS_ENDPOINT, { q: { account: account_id } }).body + @http.get("accounts", params).body + end + + # https://docs.argyle.com/api-reference/paystubs#list + def fetch_paystubs(**params) + # json["data"].map { |paystub_json| ResponseObjects::Paystub.from_pinwheel(paystub_json) } + # TODO: paginate + @http.get(PAYSTUBS_ENDPOINT, params).body + end + + # https://docs.argyle.com/api-reference/employments#list + def fetch_employment(**params) + # json["data"].map { |paystub_json| ResponseObjects::Paystub.from_pinwheel(paystub_json) } + @http.get("employments", params).body end # TODO: refactor this into common function between argyle_service/pinwheel_service def build_url(endpoint) @http.build_url(endpoint).to_s end + + def _tmp_fetch_all(user_id:) + FileUtils.mkdir_p "spec/support/fixtures/argyle/#{user_id}" + + File.open("spec/support/fixtures/argyle/#{user_id}/request_user.json", "wb") { + |f| f.puts(fetch_user(user: user_id).to_json) + } + + File.open("spec/support/fixtures/argyle/#{user_id}/request_identity.json", "wb") { + |f| f.puts(fetch_identity(user: user_id).to_json) + } + + File.open("spec/support/fixtures/argyle/#{user_id}/request_employment.json", "wb") { + |f| f.puts(fetch_employment(user: user_id).to_json) + } + + File.open("spec/support/fixtures/argyle/#{user_id}/request_accounts.json", "wb") { + |f| f.puts(fetch_accounts(user: user_id).to_json) + } + File.open("spec/support/fixtures/argyle/#{user_id}/request_paystubs.json", "wb") { + |f| f.puts(fetch_paystubs(user: user_id, from_start_date: "2025-02-20", to_start_date: "2025-02-26").to_json) + } + end end diff --git a/app/spec/services/argyle_service_spec.rb b/app/spec/services/argyle_service_spec.rb index 3503611a6..27970ff8f 100644 --- a/app/spec/services/argyle_service_spec.rb +++ b/app/spec/services/argyle_service_spec.rb @@ -22,8 +22,10 @@ end it 'returns a non-empty response' do + # a.fetch_paystubs(user: "0195441c-5a5f-7d86-3be1-fa5797a441a6", from_start_date: "2025-02-20", to_start_ response = service.fetch_paystubs(account_id: end_user_id) expect(response).not_to be_empty end end + # a.fetch_employment(user: "0195441c-5a5f-7d86-3be1-fa5797a441a6" ) end diff --git a/app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_accounts.json b/app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_accounts.json new file mode 100644 index 000000000..bba018b9a --- /dev/null +++ b/app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_accounts.json @@ -0,0 +1,187 @@ +{ + "next": null, + "previous": null, + "results": [ + { + "id": "01954449-1753-b8b9-8cd9-77b4be11db19", + "user": "0195441c-5a5f-7d86-3be1-fa5797a441a6", + "employers": [ + "Lyft Driver" + ], + "item": "item_000024123", + "source": "argyle_sandbox", + "created_at": "2025-02-26T22:03:15.718479Z", + "updated_at": "2025-02-26T22:05:14.987616Z", + "scanned_at": "2025-02-26T22:04:52.909487Z", + "connection": { + "status": "connected", + "error_code": null, + "error_message": null, + "updated_at": "2025-02-26T22:03:30.606428Z" + }, + "direct_deposit_switch": { + "status": "idle", + "error_code": null, + "error_message": null, + "updated_at": "2025-02-26T22:03:16.059091Z" + }, + "availability": { + "gigs": { + "status": "synced", + "updated_at": "2025-02-26T22:05:14.591846Z", + "available_count": 2393, + "available_from": "2022-05-26T22:03:30Z", + "available_to": "2025-02-26T12:02:10Z" + }, + "shifts": { + "status": "synced", + "updated_at": "2025-02-26T22:04:50.502237Z", + "available_count": 0, + "available_from": null, + "available_to": null + }, + "paystubs": { + "status": "synced", + "updated_at": "2025-02-26T22:05:14.421661Z", + "available_count": 144, + "available_from": "2022-05-26T22:03:30Z", + "available_to": "2025-02-20T00:00:00Z" + }, + "payroll_documents": { + "status": "synced", + "updated_at": "2025-02-26T22:03:33.868143Z" + }, + "identities": { + "status": "synced", + "updated_at": "2025-02-26T22:05:14.792144Z" + }, + "ratings": { + "status": "synced", + "updated_at": "2025-02-26T22:04:52.642065Z" + }, + "vehicles": { + "status": "synced", + "updated_at": "2025-02-26T22:03:33.887125Z" + }, + "deposit_destinations": { + "status": "synced", + "updated_at": "2025-02-26T22:03:32.177667Z" + }, + "user_forms": null, + "user_uploads": null + }, + "ongoing_refresh": { + "status": "enabled" + } + }, + { + "id": "01954447-d202-faca-2a01-1861f39e0e86", + "user": "0195441c-5a5f-7d86-3be1-fa5797a441a6", + "employers": [], + "item": "item_000020392", + "source": "argyle_sandbox", + "created_at": "2025-02-26T22:01:52.441034Z", + "updated_at": "2025-02-26T22:11:53.634198Z", + "scanned_at": null, + "connection": { + "status": "error", + "error_code": "mfa_timeout", + "error_message": "This user did not complete multi-factor authentication.", + "updated_at": "2025-02-26T22:11:53.417285Z" + }, + "direct_deposit_switch": { + "status": "idle", + "error_code": null, + "error_message": null, + "updated_at": "2025-02-26T22:01:52.868602Z" + }, + "availability": { + "gigs": null, + "shifts": null, + "paystubs": null, + "payroll_documents": null, + "identities": null, + "ratings": null, + "vehicles": null, + "deposit_destinations": null, + "user_forms": null, + "user_uploads": null + }, + "ongoing_refresh": { + "status": "idle" + } + }, + { + "id": "01954440-8c8b-cd52-4a1f-f7aa07d136ed", + "user": "0195441c-5a5f-7d86-3be1-fa5797a441a6", + "employers": [ + "Uber Driver" + ], + "item": "item_000041078", + "source": "argyle_sandbox", + "created_at": "2025-02-26T21:53:55.907174Z", + "updated_at": "2025-02-26T21:57:41.151118Z", + "scanned_at": "2025-02-26T21:57:36.482239Z", + "connection": { + "status": "connected", + "error_code": null, + "error_message": null, + "updated_at": "2025-02-26T21:56:17.467233Z" + }, + "direct_deposit_switch": { + "status": "idle", + "error_code": null, + "error_message": null, + "updated_at": "2025-02-26T21:53:56.374685Z" + }, + "availability": { + "gigs": { + "status": "synced", + "updated_at": "2025-02-26T21:57:40.873143Z", + "available_count": 2368, + "available_from": "2022-06-27T21:56:17Z", + "available_to": "2025-02-26T20:40:29Z" + }, + "shifts": { + "status": "synced", + "updated_at": "2025-02-26T21:57:34.254754Z", + "available_count": 0, + "available_from": null, + "available_to": null + }, + "paystubs": { + "status": "synced", + "updated_at": "2025-02-26T21:57:40.708501Z", + "available_count": 140, + "available_from": "2022-06-27T21:56:17Z", + "available_to": "2025-02-24T00:00:00Z" + }, + "payroll_documents": { + "status": "synced", + "updated_at": "2025-02-26T21:56:20.360100Z" + }, + "identities": { + "status": "synced", + "updated_at": "2025-02-26T21:57:40.940274Z" + }, + "ratings": { + "status": "synced", + "updated_at": "2025-02-26T21:57:36.238637Z" + }, + "vehicles": { + "status": "synced", + "updated_at": "2025-02-26T21:56:20.375923Z" + }, + "deposit_destinations": { + "status": "synced", + "updated_at": "2025-02-26T21:56:18.662289Z" + }, + "user_forms": null, + "user_uploads": null + }, + "ongoing_refresh": { + "status": "enabled" + } + } + ] +} \ No newline at end of file diff --git a/app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_employment.json b/app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_employment.json new file mode 100644 index 000000000..f14528d80 --- /dev/null +++ b/app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_employment.json @@ -0,0 +1,22 @@ +{ + "results": [ + { + "id": "31188b69-703a-3d6e-b6f6-24073ca84263", + "employer": "Lyft Driver", + "employment_status": "active", + "origin": "retrieved", + "user": "0195441c-5a5f-7d86-3be1-fa5797a441a6", + "account": "01954449-1753-b8b9-8cd9-77b4be11db19", + "created_at": "2025-02-26T22:03:33.246Z" + }, + { + "id": "26396fa4-6213-39a6-902a-f9a85d26db4d", + "employer": "Uber Driver", + "employment_status": "active", + "origin": "retrieved", + "user": "0195441c-5a5f-7d86-3be1-fa5797a441a6", + "account": "01954440-8c8b-cd52-4a1f-f7aa07d136ed", + "created_at": "2025-02-26T21:56:19.699Z" + } + ] +} \ No newline at end of file diff --git a/app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_identity.json b/app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_identity.json new file mode 100644 index 000000000..2800226cb --- /dev/null +++ b/app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_identity.json @@ -0,0 +1,96 @@ +{ + "results": [ + { + "id": "87555e33-69a2-3860-b431-1f36a3dee3d2", + "account": "01954449-1753-b8b9-8cd9-77b4be11db19", + "address": { + "city": "New York", + "line1": "759 Victoria Plaza", + "line2": null, + "state": "NY", + "country": "US", + "postal_code": "10014" + }, + "first_name": "Bob", + "last_name": "Jones", + "full_name": "Bob Jones", + "birth_date": "1980-10-10", + "email": "test1@argyle.com", + "phone_number": "+18009000010", + "picture_url": "https://api-sandbox.argyle.com/v2/payroll-documents/01954449-5c6c-0fcb-cd9b-681bc0dc29ac/file", + "employment_status": "active", + "employment_type": "contractor", + "job_title": null, + "ssn": "522-09-1191", + "marital_status": "Married filing jointly", + "gender": "Male", + "hire_date": "2022-05-26", + "original_hire_date": "2022-05-26", + "termination_date": null, + "termination_reason": null, + "employer": "Lyft Driver", + "base_pay": { + "amount": null, + "period": null, + "currency": null + }, + "pay_cycle": "weekly", + "platform_ids": { + "employee_id": null, + "position_id": null, + "platform_user_id": "0HVZXB0KPT20I814" + }, + "created_at": "2025-02-26T22:03:33.246Z", + "updated_at": "2025-02-26T22:05:14.780Z", + "metadata": {}, + "employment": "31188b69-703a-3d6e-b6f6-24073ca84263" + }, + { + "id": "c37f205a-3d53-3b18-ab79-8d201d6365a6", + "account": "01954440-8c8b-cd52-4a1f-f7aa07d136ed", + "address": { + "city": "New York", + "line1": "759 Victoria Plaza", + "line2": null, + "state": "NY", + "country": "US", + "postal_code": "10014" + }, + "first_name": "Bob", + "last_name": "Jones", + "full_name": "Bob Jones", + "birth_date": "1980-10-10", + "email": "test1@argyle.com", + "phone_number": "+18009000010", + "picture_url": "https://api-sandbox.argyle.com/v2/payroll-documents/01954442-befa-3df4-01b8-98a95b2f5651/file", + "employment_status": "active", + "employment_type": "contractor", + "job_title": null, + "ssn": "522-09-1191", + "marital_status": "Married filing jointly", + "gender": "Male", + "hire_date": "2022-06-27", + "original_hire_date": "2022-06-27", + "termination_date": null, + "termination_reason": null, + "employer": "Uber Driver", + "base_pay": { + "amount": null, + "period": null, + "currency": null + }, + "pay_cycle": "weekly", + "platform_ids": { + "employee_id": null, + "position_id": null, + "platform_user_id": "UJH7BSZUE9WTV9AV" + }, + "created_at": "2025-02-26T21:56:19.699Z", + "updated_at": "2025-02-26T21:57:40.926Z", + "metadata": {}, + "employment": "26396fa4-6213-39a6-902a-f9a85d26db4d" + } + ], + "next": null, + "previous": null +} \ No newline at end of file diff --git a/app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_paystubs.json b/app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_paystubs.json new file mode 100644 index 000000000..e05f7e56c --- /dev/null +++ b/app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_paystubs.json @@ -0,0 +1,78 @@ +{ + "results": [ + { + "id": "c7f88cf2-5267-37a9-9d78-b2f9dd942a2e", + "account": "01954440-8c8b-cd52-4a1f-f7aa07d136ed", + "payroll_document": null, + "deduction_list": [], + "filing_status": [], + "tax_list": [], + "status": "completed", + "paystub_date": "2025-02-24T00:00:00Z", + "paystub_period": { + "start_date": null, + "end_date": null + }, + "currency": "USD", + "gross_pay": "19.53", + "deductions": null, + "taxes": null, + "net_pay": "19.53", + "reimbursements": null, + "hours": null, + "fees": null, + "net_pay_ytd": "429.15", + "gross_pay_ytd": "429.15", + "metadata": {}, + "employer": "Uber Driver", + "employer_address": null, + "created_at": "2025-02-26T21:56:26.219Z", + "updated_at": "2025-02-26T21:56:26.219Z", + "destinations": [], + "hours_ytd": "0.00", + "deductions_ytd": "0.00", + "taxes_ytd": "0.00", + "gross_pay_list": [], + "gross_pay_list_totals": null, + "employment": "26396fa4-6213-39a6-902a-f9a85d26db4d" + }, + { + "id": "b2c1d851-e1e0-33d4-bbfa-2e3868979361", + "account": "01954449-1753-b8b9-8cd9-77b4be11db19", + "payroll_document": null, + "deduction_list": [], + "filing_status": [], + "tax_list": [], + "status": "completed", + "paystub_date": "2025-02-20T00:00:00Z", + "paystub_period": { + "start_date": null, + "end_date": null + }, + "currency": "USD", + "gross_pay": "36.33", + "deductions": null, + "taxes": null, + "net_pay": "36.33", + "reimbursements": null, + "hours": null, + "fees": null, + "net_pay_ytd": "369.77", + "gross_pay_ytd": "369.77", + "metadata": {}, + "employer": "Lyft Driver", + "employer_address": null, + "created_at": "2025-02-26T22:03:41.088Z", + "updated_at": "2025-02-26T22:03:41.088Z", + "destinations": [], + "hours_ytd": "0.00", + "deductions_ytd": "0.00", + "taxes_ytd": "0.00", + "gross_pay_list": [], + "gross_pay_list_totals": null, + "employment": "31188b69-703a-3d6e-b6f6-24073ca84263" + } + ], + "next": null, + "previous": null +} \ No newline at end of file diff --git a/app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_user.json b/app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_user.json new file mode 100644 index 000000000..fcbace752 --- /dev/null +++ b/app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_user.json @@ -0,0 +1,15 @@ +{ + "id": "0195441c-5a5f-7d86-3be1-fa5797a441a6", + "created_at": "2025-02-26T21:14:23.712349Z", + "items_connected": [ + "item_000024123", + "item_000020392", + "item_000041078" + ], + "employers_connected": [ + "Uber Driver", + "Lyft Driver" + ], + "external_metadata": {}, + "external_id": "testing" +} \ No newline at end of file From ba1fd41fd64e897c6885b669fc96c403fe89329c Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Thu, 6 Mar 2025 16:48:09 -0500 Subject: [PATCH 04/26] add sample api responses for all 3 sandbox users --- app/app/services/argyle_service.rb | 5 +- .../request_accounts.json | 78 + .../request_employment.json | 13 + .../request_identity.json | 51 + .../request_paystubs.json | 1386 ++++++++++++++++ .../request_user.json | 12 + .../request_accounts.json | 78 + .../request_employment.json | 13 + .../request_identity.json | 51 + .../request_paystubs.json | 1439 +++++++++++++++++ .../request_user.json | 12 + 11 files changed, 3136 insertions(+), 2 deletions(-) create mode 100644 app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_accounts.json create mode 100644 app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_employment.json create mode 100644 app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_identity.json create mode 100644 app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_paystubs.json create mode 100644 app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_user.json create mode 100644 app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_accounts.json create mode 100644 app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_employment.json create mode 100644 app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_identity.json create mode 100644 app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_paystubs.json create mode 100644 app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_user.json diff --git a/app/app/services/argyle_service.rb b/app/app/services/argyle_service.rb index e414cb64a..cbd362f49 100644 --- a/app/app/services/argyle_service.rb +++ b/app/app/services/argyle_service.rb @@ -102,11 +102,12 @@ def _tmp_fetch_all(user_id:) |f| f.puts(fetch_accounts(user: user_id).to_json) } File.open("spec/support/fixtures/argyle/#{user_id}/request_paystubs.json", "wb") { - |f| f.puts(fetch_paystubs(user: user_id, from_start_date: "2025-02-20", to_start_date: "2025-02-26").to_json) + |f| f.puts(fetch_paystubs(user: user_id).to_json) + # , from_start_date: "2025-02-20", to_start_date: "2025-02-26").to_json) } end def create_user - @http.post("/users").body + @http.post("users").body end end diff --git a/app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_accounts.json b/app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_accounts.json new file mode 100644 index 000000000..9e25305c2 --- /dev/null +++ b/app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_accounts.json @@ -0,0 +1,78 @@ +{ + "next": null, + "previous": null, + "results": [ + { + "id": "01956d5f-cb8d-af2f-9232-38bce8531f58", + "user": "01956d5f-9482-5a52-1f87-84c0a238e107", + "employers": [ + "Whole Foods" + ], + "item": "item_000026933", + "source": "argyle_sandbox", + "created_at": "2025-03-06T21:32:29.501021Z", + "updated_at": "2025-03-06T21:33:38.230481Z", + "scanned_at": "2025-03-06T21:33:37.885124Z", + "connection": { + "status": "connected", + "error_code": null, + "error_message": null, + "updated_at": "2025-03-06T21:32:41.771112Z" + }, + "direct_deposit_switch": { + "status": "idle", + "error_code": null, + "error_message": null, + "updated_at": "2025-03-06T21:32:29.947531Z" + }, + "availability": { + "gigs": { + "status": "synced", + "updated_at": "2025-03-06T21:33:36.262090Z", + "available_count": 0, + "available_from": null, + "available_to": null + }, + "shifts": { + "status": "synced", + "updated_at": "2025-03-06T21:33:37.904215Z", + "available_count": 209, + "available_from": "2022-08-08T21:32:41Z", + "available_to": "2025-03-04T00:00:00Z" + }, + "paystubs": { + "status": "synced", + "updated_at": "2025-03-06T21:33:38.019051Z", + "available_count": 69, + "available_from": "2022-08-08T21:32:41Z", + "available_to": "2025-03-03T00:00:00Z" + }, + "payroll_documents": { + "status": "synced", + "updated_at": "2025-03-06T21:32:44.461976Z" + }, + "identities": { + "status": "synced", + "updated_at": "2025-03-06T21:32:52.299785Z" + }, + "ratings": { + "status": "synced", + "updated_at": "2025-03-06T21:32:44.475444Z" + }, + "vehicles": { + "status": "synced", + "updated_at": "2025-03-06T21:32:44.478235Z" + }, + "deposit_destinations": { + "status": "synced", + "updated_at": "2025-03-06T21:32:42.928647Z" + }, + "user_forms": null, + "user_uploads": null + }, + "ongoing_refresh": { + "status": "enabled" + } + } + ] +} \ No newline at end of file diff --git a/app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_employment.json b/app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_employment.json new file mode 100644 index 000000000..6b165d670 --- /dev/null +++ b/app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_employment.json @@ -0,0 +1,13 @@ +{ + "results": [ + { + "id": "defea132-21cb-32bf-84d1-9aa11d4976fa", + "employer": "Whole Foods", + "employment_status": "active", + "origin": "acquired", + "user": "01956d5f-9482-5a52-1f87-84c0a238e107", + "account": "01956d5f-cb8d-af2f-9232-38bce8531f58", + "created_at": "2025-03-06T21:32:43.951Z" + } + ] +} \ No newline at end of file diff --git a/app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_identity.json b/app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_identity.json new file mode 100644 index 000000000..f0ee73793 --- /dev/null +++ b/app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_identity.json @@ -0,0 +1,51 @@ +{ + "results": [ + { + "id": "fabbdb07-7e6b-3cfd-b952-571ba55f083a", + "account": "01956d5f-cb8d-af2f-9232-38bce8531f58", + "address": { + "city": "New York", + "line1": "250 Vesey Street", + "line2": null, + "state": "NY", + "country": "US", + "postal_code": "10281" + }, + "first_name": "Sarah", + "last_name": "Longfield", + "full_name": "Sarah Longfield", + "birth_date": "1985-04-17", + "email": "test2@argyle.com", + "phone_number": "+18009000020", + "picture_url": "https://api-sandbox.argyle.com/v2/payroll-documents/01956d60-051a-5af2-7788-1517025e4635/file", + "employment_status": "active", + "employment_type": "full-time", + "job_title": "1635 - Helpline Agent", + "ssn": "213-53-7066", + "marital_status": "Married filing jointly", + "gender": "Female", + "hire_date": "2022-08-08", + "original_hire_date": "2022-08-08", + "termination_date": null, + "termination_reason": null, + "employer": "Whole Foods", + "base_pay": { + "amount": "23.16", + "period": "hourly", + "currency": "USD" + }, + "pay_cycle": "biweekly", + "platform_ids": { + "employee_id": "IQ2OXKN1W", + "position_id": "QTSV3TF8W", + "platform_user_id": "MQ6F803WI6G8JBYK" + }, + "created_at": "2025-03-06T21:32:43.951Z", + "updated_at": "2025-03-06T21:32:52.285Z", + "metadata": {}, + "employment": "defea132-21cb-32bf-84d1-9aa11d4976fa" + } + ], + "next": null, + "previous": null +} \ No newline at end of file diff --git a/app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_paystubs.json b/app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_paystubs.json new file mode 100644 index 000000000..f5d801245 --- /dev/null +++ b/app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_paystubs.json @@ -0,0 +1,1386 @@ +{ + "results": [ + { + "id": "6798b682-5ade-3ff3-8033-e0451132e3b1", + "account": "01956d5f-cb8d-af2f-9232-38bce8531f58", + "payroll_document": "01956d60-224b-39bb-1fdf-8a3cb5471749", + "deduction_list": [ + { + "amount": "45.57", + "name": "Dental", + "tax_classification": null, + "amount_ytd": "45.57" + }, + { + "amount": "15.19", + "name": "Garnishment", + "tax_classification": "post_tax", + "amount_ytd": "67.89" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "single" + }, + { + "type": "local", + "location": "New York", + "status": "single" + } + ], + "tax_list": [ + { + "amount": "167.09", + "name": "CA State Income Tax", + "type": "state", + "amount_ytd": "463.15" + }, + { + "amount": "151.90", + "name": "MEDICARE", + "type": "fica", + "amount_ytd": "151.90" + } + ], + "status": "completed", + "paystub_date": "2025-03-03T00:00:00Z", + "paystub_period": { + "start_date": "2025-02-10T00:00:00Z", + "end_date": "2025-02-24T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "1518.97", + "deductions": "60.76", + "taxes": "318.99", + "net_pay": "1140.39", + "reimbursements": "1.17", + "hours": "65.59", + "fees": null, + "net_pay_ytd": "6781.06", + "gross_pay_ytd": "8197.38", + "metadata": {}, + "employer": "Whole Foods", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-03-06T21:32:51.840Z", + "updated_at": "2025-03-06T21:32:51.840Z", + "destinations": [], + "hours_ytd": "342.16", + "deductions_ytd": "253.02", + "taxes_ytd": "1169.17", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2025-02-10", + "end_date": "2025-02-24", + "rate": "23.1599", + "hours": "65.5861", + "amount": "1518.97", + "hours_ytd": "342.1600", + "amount_ytd": "7924.45" + }, + { + "name": "Commission", + "type": "commission", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "272.93" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "1518.97", + "amount_ytd": "7924.45", + "hours": "65.5861", + "hours_ytd": "342.1600", + "rate_implied": "23.1600", + "rate_implied_ytd": "23.1600" + }, + "overtime": null, + "commission": { + "amount": null, + "amount_ytd": "272.93", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "bonus": null, + "other": null + }, + "employment": "defea132-21cb-32bf-84d1-9aa11d4976fa" + }, + { + "id": "8b155165-ba1e-35a6-a3ab-cb744cb40ab7", + "account": "01956d5f-cb8d-af2f-9232-38bce8531f58", + "payroll_document": "01956d60-221f-fff0-039e-c46331fd408b", + "deduction_list": [ + { + "amount": "54.70", + "name": "401K", + "tax_classification": "pre_tax", + "amount_ytd": "54.70" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "married filing separately" + } + ], + "tax_list": [ + { + "amount": "109.40", + "name": "Federal Income Tax", + "type": "federal", + "amount_ytd": "218.05" + } + ], + "status": "completed", + "paystub_date": "2025-02-17T00:00:00Z", + "paystub_period": { + "start_date": "2025-01-27T00:00:00Z", + "end_date": "2025-02-10T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "1367.52", + "deductions": "54.70", + "taxes": "109.40", + "net_pay": "1206.08", + "reimbursements": "2.66", + "hours": "59.05", + "fees": null, + "net_pay_ytd": "5640.67", + "gross_pay_ytd": "6678.41", + "metadata": {}, + "employer": "Whole Foods", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-03-06T21:32:51.958Z", + "updated_at": "2025-03-06T21:32:51.958Z", + "destinations": [], + "hours_ytd": "276.58", + "deductions_ytd": "192.26", + "taxes_ytd": "850.18", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2025-01-27", + "end_date": "2025-02-10", + "rate": "23.1600", + "hours": "59.0467", + "amount": "1367.52", + "hours_ytd": "276.5800", + "amount_ytd": "6405.48" + }, + { + "name": "Commission", + "type": "commission", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "272.93" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "1367.52", + "amount_ytd": "6405.48", + "hours": "59.0467", + "hours_ytd": "276.5800", + "rate_implied": "23.1600", + "rate_implied_ytd": "23.1600" + }, + "overtime": null, + "commission": { + "amount": null, + "amount_ytd": "272.93", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "bonus": null, + "other": null + }, + "employment": "defea132-21cb-32bf-84d1-9aa11d4976fa" + }, + { + "id": "bd2fbefb-5d8b-32f3-8e3f-a85de4811124", + "account": "01956d5f-cb8d-af2f-9232-38bce8531f58", + "payroll_document": "01956d60-21f4-349b-c8d2-e43fb98baa13", + "deduction_list": [ + { + "amount": "27.16", + "name": "Roth", + "tax_classification": "post_tax", + "amount_ytd": "27.16" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "single" + } + ], + "tax_list": [ + { + "amount": "108.65", + "name": "Federal Income Tax", + "type": "federal", + "amount_ytd": "108.65" + }, + { + "amount": "162.97", + "name": "OASDI", + "type": "fica", + "amount_ytd": "162.97" + } + ], + "status": "completed", + "paystub_date": "2025-02-03T00:00:00Z", + "paystub_period": { + "start_date": "2025-01-13T00:00:00Z", + "end_date": "2025-01-27T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "1631.01", + "deductions": "27.16", + "taxes": "271.62", + "net_pay": "1332.23", + "reimbursements": "0.00", + "hours": "58.64", + "fees": null, + "net_pay_ytd": "4434.59", + "gross_pay_ytd": "5310.89", + "metadata": {}, + "employer": "Whole Foods", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-03-06T21:32:51.940Z", + "updated_at": "2025-03-06T21:32:51.940Z", + "destinations": [], + "hours_ytd": "217.53", + "deductions_ytd": "137.56", + "taxes_ytd": "740.78", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2025-01-13", + "end_date": "2025-01-27", + "rate": "23.1600", + "hours": "58.6390", + "amount": "1358.08", + "hours_ytd": "217.5300", + "amount_ytd": "5037.96" + }, + { + "name": "Commission", + "type": "commission", + "start_date": "2025-01-21", + "end_date": "2025-01-21", + "rate": null, + "hours": null, + "amount": "272.93", + "hours_ytd": null, + "amount_ytd": "272.93" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "1358.08", + "amount_ytd": "5037.96", + "hours": "58.6390", + "hours_ytd": "217.5300", + "rate_implied": "23.1600", + "rate_implied_ytd": "23.1600" + }, + "overtime": null, + "commission": { + "amount": "272.93", + "amount_ytd": "272.93", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "bonus": null, + "other": null + }, + "employment": "defea132-21cb-32bf-84d1-9aa11d4976fa" + }, + { + "id": "17ced22e-679d-354a-97bb-94b323c2cfa7", + "account": "01956d5f-cb8d-af2f-9232-38bce8531f58", + "payroll_document": "01956d60-21cb-54de-e5e8-79d563cdd393", + "deduction_list": [ + { + "amount": "57.70", + "name": "Vision", + "tax_classification": "pre_tax", + "amount_ytd": "57.70" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "single" + } + ], + "tax_list": [ + { + "amount": "173.10", + "name": "CA State Income Tax", + "type": "state", + "amount_ytd": "296.06" + }, + { + "amount": "173.10", + "name": "Social Security Tax", + "type": "fica", + "amount_ytd": "173.10" + } + ], + "status": "completed", + "paystub_date": "2025-01-20T00:00:00Z", + "paystub_period": { + "start_date": "2024-12-30T00:00:00Z", + "end_date": "2025-01-13T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "1923.28", + "deductions": "57.70", + "taxes": "346.20", + "net_pay": "1521.42", + "reimbursements": "2.04", + "hours": "83.04", + "fees": null, + "net_pay_ytd": "3102.36", + "gross_pay_ytd": "3679.88", + "metadata": {}, + "employer": "Whole Foods", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-03-06T21:32:51.977Z", + "updated_at": "2025-03-06T21:32:51.977Z", + "destinations": [], + "hours_ytd": "158.89", + "deductions_ytd": "110.40", + "taxes_ytd": "469.16", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2024-12-30", + "end_date": "2025-01-13", + "rate": "23.1600", + "hours": "83.0433", + "amount": "1923.28", + "hours_ytd": "158.8900", + "amount_ytd": "3679.88" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "1923.28", + "amount_ytd": "3679.88", + "hours": "83.0433", + "hours_ytd": "158.8900", + "rate_implied": "23.1600", + "rate_implied_ytd": "23.1600" + }, + "overtime": null, + "commission": null, + "bonus": null, + "other": null + }, + "employment": "defea132-21cb-32bf-84d1-9aa11d4976fa" + }, + { + "id": "919d988b-e4ab-38ff-a5c3-0582cfcde050", + "account": "01956d5f-cb8d-af2f-9232-38bce8531f58", + "payroll_document": "01956d60-4742-3cdb-b2cc-94451ddd0e5e", + "deduction_list": [ + { + "amount": "52.70", + "name": "Garnishment", + "tax_classification": "post_tax", + "amount_ytd": "52.70" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "married filing separately" + }, + { + "type": "local", + "location": "New York", + "status": "single" + } + ], + "tax_list": [ + { + "amount": "122.96", + "name": "CA State Income Tax", + "type": "state", + "amount_ytd": "122.96" + } + ], + "status": "completed", + "paystub_date": "2025-01-06T00:00:00Z", + "paystub_period": { + "start_date": "2024-12-16T00:00:00Z", + "end_date": "2024-12-30T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "1756.60", + "deductions": "52.70", + "taxes": "122.96", + "net_pay": "1580.94", + "reimbursements": "0.00", + "hours": "75.85", + "fees": null, + "net_pay_ytd": "1580.94", + "gross_pay_ytd": "1756.60", + "metadata": {}, + "employer": "Whole Foods", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-03-06T21:33:01.271Z", + "updated_at": "2025-03-06T21:33:01.271Z", + "destinations": [], + "hours_ytd": "75.85", + "deductions_ytd": "52.70", + "taxes_ytd": "122.96", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2024-12-16", + "end_date": "2024-12-30", + "rate": "23.1601", + "hours": "75.8461", + "amount": "1756.60", + "hours_ytd": "75.8500", + "amount_ytd": "1756.60" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "1756.60", + "amount_ytd": "1756.60", + "hours": "75.8461", + "hours_ytd": "75.8500", + "rate_implied": "23.1600", + "rate_implied_ytd": "23.1600" + }, + "overtime": null, + "commission": null, + "bonus": null, + "other": null + }, + "employment": "defea132-21cb-32bf-84d1-9aa11d4976fa" + }, + { + "id": "d3ca582f-84f5-34c9-9fdf-7c74e6e31f4c", + "account": "01956d5f-cb8d-af2f-9232-38bce8531f58", + "payroll_document": "01956d60-46f4-5c56-a125-5a2531a78c75", + "deduction_list": [ + { + "amount": "30.62", + "name": "Vision", + "tax_classification": "pre_tax", + "amount_ytd": "236.05" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "single" + }, + { + "type": "local", + "location": "New York", + "status": "single" + } + ], + "tax_list": [ + { + "amount": "199.03", + "name": "MEDICARE", + "type": "fica", + "amount_ytd": "1630.39" + }, + { + "amount": "137.79", + "name": "OASDI", + "type": "fica", + "amount_ytd": "1856.45" + }, + { + "amount": "107.17", + "name": "OASDI", + "type": "fica", + "amount_ytd": "1963.62" + } + ], + "status": "completed", + "paystub_date": "2024-12-30T00:00:00Z", + "paystub_period": { + "start_date": "2024-12-09T00:00:00Z", + "end_date": "2024-12-23T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "1531.03", + "deductions": "30.62", + "taxes": "443.99", + "net_pay": "1056.42", + "reimbursements": "0.00", + "hours": "66.11", + "fees": null, + "net_pay_ytd": "27935.35", + "gross_pay_ytd": "35969.07", + "metadata": {}, + "employer": "Whole Foods", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-03-06T21:33:01.228Z", + "updated_at": "2025-03-06T21:33:01.228Z", + "destinations": [], + "hours_ytd": "1463.72", + "deductions_ytd": "1561.74", + "taxes_ytd": "6507.57", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2024-12-09", + "end_date": "2024-12-23", + "rate": "23.1599", + "hours": "66.1068", + "amount": "1531.03", + "hours_ytd": "1445.9400", + "amount_ytd": "33487.88" + }, + { + "name": "Overtime", + "type": "overtime", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": "17.7800", + "amount_ytd": "617.64" + }, + { + "name": "Bonus", + "type": "bonus", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "1288.78" + }, + { + "name": "Incentive", + "type": "bonus", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "71.76" + }, + { + "name": "Commission", + "type": "commission", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "503.01" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "1531.03", + "amount_ytd": "33487.88", + "hours": "66.1068", + "hours_ytd": "1445.9400", + "rate_implied": "23.1600", + "rate_implied_ytd": "23.1600" + }, + "overtime": { + "amount": null, + "amount_ytd": "617.64", + "hours": null, + "hours_ytd": "17.7800", + "rate_implied": null, + "rate_implied_ytd": "34.7400" + }, + "commission": { + "amount": null, + "amount_ytd": "503.01", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "bonus": { + "amount": null, + "amount_ytd": "1360.54", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "other": null + }, + "employment": "defea132-21cb-32bf-84d1-9aa11d4976fa" + }, + { + "id": "22e7f432-4cbd-387b-83b6-9d6ccfd87781", + "account": "01956d5f-cb8d-af2f-9232-38bce8531f58", + "payroll_document": "01956d60-46c6-7f6c-da17-3834a53960ad", + "deduction_list": [ + { + "amount": "38.83", + "name": "Roth", + "tax_classification": "post_tax", + "amount_ytd": "211.26" + }, + { + "amount": "51.77", + "name": "Garnishment", + "tax_classification": "post_tax", + "amount_ytd": "286.22" + }, + { + "amount": "25.88", + "name": "Roth", + "tax_classification": "post_tax", + "amount_ytd": "237.14" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "single" + } + ], + "tax_list": [ + { + "amount": "129.42", + "name": "Federal Withholding", + "type": "federal", + "amount_ytd": "555.24" + }, + { + "amount": "142.36", + "name": "MEDICARE", + "type": "fica", + "amount_ytd": "1431.36" + }, + { + "amount": "155.30", + "name": "Social Security Tax", + "type": "fica", + "amount_ytd": "563.33" + } + ], + "status": "completed", + "paystub_date": "2024-12-16T00:00:00Z", + "paystub_period": { + "start_date": "2024-11-25T00:00:00Z", + "end_date": "2024-12-09T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "1294.18", + "deductions": "116.48", + "taxes": "427.08", + "net_pay": "751.82", + "reimbursements": "1.20", + "hours": "55.88", + "fees": null, + "net_pay_ytd": "26878.93", + "gross_pay_ytd": "34438.04", + "metadata": {}, + "employer": "Whole Foods", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-03-06T21:33:01.209Z", + "updated_at": "2025-03-06T21:33:01.209Z", + "destinations": [], + "hours_ytd": "1397.61", + "deductions_ytd": "1531.12", + "taxes_ytd": "6063.58", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2024-11-25", + "end_date": "2024-12-09", + "rate": "23.1600", + "hours": "55.8799", + "amount": "1294.18", + "hours_ytd": "1379.8300", + "amount_ytd": "31956.85" + }, + { + "name": "Overtime", + "type": "overtime", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": "17.7800", + "amount_ytd": "617.64" + }, + { + "name": "Bonus", + "type": "bonus", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "1288.78" + }, + { + "name": "Incentive", + "type": "bonus", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "71.76" + }, + { + "name": "Commission", + "type": "commission", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "503.01" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "1294.18", + "amount_ytd": "31956.85", + "hours": "55.8799", + "hours_ytd": "1379.8300", + "rate_implied": "23.1600", + "rate_implied_ytd": "23.1600" + }, + "overtime": { + "amount": null, + "amount_ytd": "617.64", + "hours": null, + "hours_ytd": "17.7800", + "rate_implied": null, + "rate_implied_ytd": "34.7400" + }, + "commission": { + "amount": null, + "amount_ytd": "503.01", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "bonus": { + "amount": null, + "amount_ytd": "1360.54", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "other": null + }, + "employment": "defea132-21cb-32bf-84d1-9aa11d4976fa" + }, + { + "id": "ea71ec0e-ac18-3323-8660-f76753761767", + "account": "01956d5f-cb8d-af2f-9232-38bce8531f58", + "payroll_document": "01956d60-4699-f8bf-f595-2828fb039f10", + "deduction_list": [ + { + "amount": "28.48", + "name": "Garnishment", + "tax_classification": "post_tax", + "amount_ytd": "234.45" + }, + { + "amount": "56.97", + "name": "Dental", + "tax_classification": null, + "amount_ytd": "471.17" + }, + { + "amount": "42.73", + "name": "Roth", + "tax_classification": "post_tax", + "amount_ytd": "172.43" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "married filing separately" + }, + { + "type": "state", + "location": "NY", + "status": "single" + } + ], + "tax_list": [ + { + "amount": "185.15", + "name": "Social Security Tax", + "type": "fica", + "amount_ytd": "408.03" + } + ], + "status": "completed", + "paystub_date": "2024-12-02T00:00:00Z", + "paystub_period": { + "start_date": "2024-11-11T00:00:00Z", + "end_date": "2024-11-25T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "1424.23", + "deductions": "128.18", + "taxes": "185.15", + "net_pay": "1110.90", + "reimbursements": "0.00", + "hours": "61.50", + "fees": null, + "net_pay_ytd": "26127.11", + "gross_pay_ytd": "33143.86", + "metadata": {}, + "employer": "Whole Foods", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-03-06T21:33:01.246Z", + "updated_at": "2025-03-06T21:33:01.246Z", + "destinations": [], + "hours_ytd": "1341.73", + "deductions_ytd": "1414.64", + "taxes_ytd": "5636.50", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2024-11-11", + "end_date": "2024-11-25", + "rate": "23.1601", + "hours": "61.4951", + "amount": "1424.23", + "hours_ytd": "1323.9500", + "amount_ytd": "30662.67" + }, + { + "name": "Overtime", + "type": "overtime", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": "17.7800", + "amount_ytd": "617.64" + }, + { + "name": "Bonus", + "type": "bonus", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "1288.78" + }, + { + "name": "Incentive", + "type": "bonus", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "71.76" + }, + { + "name": "Commission", + "type": "commission", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "503.01" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "1424.23", + "amount_ytd": "30662.67", + "hours": "61.4951", + "hours_ytd": "1323.9500", + "rate_implied": "23.1600", + "rate_implied_ytd": "23.1600" + }, + "overtime": { + "amount": null, + "amount_ytd": "617.64", + "hours": null, + "hours_ytd": "17.7800", + "rate_implied": null, + "rate_implied_ytd": "34.7400" + }, + "commission": { + "amount": null, + "amount_ytd": "503.01", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "bonus": { + "amount": null, + "amount_ytd": "1360.54", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "other": null + }, + "employment": "defea132-21cb-32bf-84d1-9aa11d4976fa" + }, + { + "id": "274cdf34-eba8-36be-9e07-d4eac2c7f39d", + "account": "01956d5f-cb8d-af2f-9232-38bce8531f58", + "payroll_document": "01956d60-466c-9376-ce9b-b2897830ec5c", + "deduction_list": [ + { + "amount": "36.04", + "name": "Dental", + "tax_classification": null, + "amount_ytd": "414.20" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "married filing separately" + } + ], + "tax_list": [ + { + "amount": "144.17", + "name": "MEDICARE", + "type": "fica", + "amount_ytd": "1289.00" + }, + { + "amount": "156.18", + "name": "CA State Income Tax", + "type": "state", + "amount_ytd": "1100.85" + } + ], + "status": "completed", + "paystub_date": "2024-11-18T00:00:00Z", + "paystub_period": { + "start_date": "2024-10-28T00:00:00Z", + "end_date": "2024-11-11T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "1201.39", + "deductions": "36.04", + "taxes": "300.35", + "net_pay": "865.00", + "reimbursements": "0.00", + "hours": "51.87", + "fees": null, + "net_pay_ytd": "25016.21", + "gross_pay_ytd": "31719.63", + "metadata": {}, + "employer": "Whole Foods", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-03-06T21:33:01.289Z", + "updated_at": "2025-03-06T21:33:01.289Z", + "destinations": [], + "hours_ytd": "1280.23", + "deductions_ytd": "1286.46", + "taxes_ytd": "5451.35", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2024-10-28", + "end_date": "2024-11-11", + "rate": "23.1600", + "hours": "51.8736", + "amount": "1201.39", + "hours_ytd": "1262.4600", + "amount_ytd": "29238.44" + }, + { + "name": "Overtime", + "type": "overtime", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": "17.7800", + "amount_ytd": "617.64" + }, + { + "name": "Bonus", + "type": "bonus", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "1288.78" + }, + { + "name": "Incentive", + "type": "bonus", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "71.76" + }, + { + "name": "Commission", + "type": "commission", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "503.01" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "1201.39", + "amount_ytd": "29238.44", + "hours": "51.8736", + "hours_ytd": "1262.4600", + "rate_implied": "23.1600", + "rate_implied_ytd": "23.1600" + }, + "overtime": { + "amount": null, + "amount_ytd": "617.64", + "hours": null, + "hours_ytd": "17.7800", + "rate_implied": null, + "rate_implied_ytd": "34.7400" + }, + "commission": { + "amount": null, + "amount_ytd": "503.01", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "bonus": { + "amount": null, + "amount_ytd": "1360.54", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "other": null + }, + "employment": "defea132-21cb-32bf-84d1-9aa11d4976fa" + }, + { + "id": "050232ea-2c1b-3979-bed8-8724a53fd818", + "account": "01956d5f-cb8d-af2f-9232-38bce8531f58", + "payroll_document": "01956d60-6c4f-ca0f-1235-231d59fd0c99", + "deduction_list": [ + { + "amount": "16.43", + "name": "Dental", + "tax_classification": null, + "amount_ytd": "328.86" + }, + { + "amount": "49.30", + "name": "Dental", + "tax_classification": null, + "amount_ytd": "378.16" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "single" + }, + { + "type": "state", + "location": "NY", + "status": "single" + } + ], + "tax_list": [ + { + "amount": "164.32", + "name": "OASDI", + "type": "fica", + "amount_ytd": "1521.47" + }, + { + "amount": "131.46", + "name": "Federal Income Tax", + "type": "federal", + "amount_ytd": "694.14" + }, + { + "amount": "197.19", + "name": "OASDI", + "type": "fica", + "amount_ytd": "1718.66" + } + ], + "status": "completed", + "paystub_date": "2024-11-04T00:00:00Z", + "paystub_period": { + "start_date": "2024-10-14T00:00:00Z", + "end_date": "2024-10-28T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "1643.23", + "deductions": "65.73", + "taxes": "492.97", + "net_pay": "1086.61", + "reimbursements": "2.08", + "hours": "70.95", + "fees": null, + "net_pay_ytd": "24151.21", + "gross_pay_ytd": "30518.24", + "metadata": {}, + "employer": "Whole Foods", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-03-06T21:33:10.735Z", + "updated_at": "2025-03-06T21:33:10.735Z", + "destinations": [], + "hours_ytd": "1228.36", + "deductions_ytd": "1250.42", + "taxes_ytd": "5151.00", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2024-10-14", + "end_date": "2024-10-28", + "rate": "23.1600", + "hours": "70.9513", + "amount": "1643.23", + "hours_ytd": "1210.5800", + "amount_ytd": "28037.05" + }, + { + "name": "Overtime", + "type": "overtime", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": "17.7800", + "amount_ytd": "617.64" + }, + { + "name": "Bonus", + "type": "bonus", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "1288.78" + }, + { + "name": "Incentive", + "type": "bonus", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "71.76" + }, + { + "name": "Commission", + "type": "commission", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "503.01" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "1643.23", + "amount_ytd": "28037.05", + "hours": "70.9513", + "hours_ytd": "1210.5800", + "rate_implied": "23.1600", + "rate_implied_ytd": "23.1600" + }, + "overtime": { + "amount": null, + "amount_ytd": "617.64", + "hours": null, + "hours_ytd": "17.7800", + "rate_implied": null, + "rate_implied_ytd": "34.7400" + }, + "commission": { + "amount": null, + "amount_ytd": "503.01", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "bonus": { + "amount": null, + "amount_ytd": "1360.54", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "other": null + }, + "employment": "defea132-21cb-32bf-84d1-9aa11d4976fa" + } + ], + "next": "https://api-sandbox.argyle.com/v2/paystubs?cursor=Y2xpZW50SWQ9MDE4ZjA4MzQtZDY5YS04NzliLTA1Y2ItZGRkZTAzMzAyNGYwJmludmVyc2VTb3J0PWZhbHNlJmxpbWl0PTEwJnVzZXJJZD0wMTk1NmQ1Zi05NDgyLTVhNTItMWY4Ny04NGMwYTIzOGUxMDcmcm93S2V5PVMlMjNVJTIzMDE5NTZkNWYtOTQ4Mi01YTUyLTFmODctODRjMGEyMzhlMTA3JTIzNDYxMTY4NDI4ODk1ODU4NzkwMyUyMzdmZjIxMDdkLTQ5OWItMzAyOS05YzM0LTA2NGRjYWEwZGM2OQ%3D%3D", + "previous": null +} \ No newline at end of file diff --git a/app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_user.json b/app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_user.json new file mode 100644 index 000000000..683a917ec --- /dev/null +++ b/app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_user.json @@ -0,0 +1,12 @@ +{ + "id": "01956d5f-9482-5a52-1f87-84c0a238e107", + "created_at": "2025-03-06T21:32:15.363718Z", + "items_connected": [ + "item_000026933" + ], + "employers_connected": [ + "Whole Foods" + ], + "external_metadata": {}, + "external_id": null +} \ No newline at end of file diff --git a/app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_accounts.json b/app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_accounts.json new file mode 100644 index 000000000..defad8b88 --- /dev/null +++ b/app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_accounts.json @@ -0,0 +1,78 @@ +{ + "next": null, + "previous": null, + "results": [ + { + "id": "01956d62-18a0-090f-bc09-2ac44b7edf99", + "user": "01956d61-d33a-6789-290e-621bed0a098f", + "employers": [ + "Conagra Brands" + ], + "item": "item_000009878", + "source": "argyle_sandbox", + "created_at": "2025-03-06T21:35:00.312659Z", + "updated_at": "2025-03-06T21:35:56.915098Z", + "scanned_at": "2025-03-06T21:35:56.540748Z", + "connection": { + "status": "connected", + "error_code": null, + "error_message": null, + "updated_at": "2025-03-06T21:35:10.943066Z" + }, + "direct_deposit_switch": { + "status": "idle", + "error_code": null, + "error_message": null, + "updated_at": "2025-03-06T21:35:00.790132Z" + }, + "availability": { + "gigs": { + "status": "synced", + "updated_at": "2025-03-06T21:35:54.894913Z", + "available_count": 0, + "available_from": null, + "available_to": null + }, + "shifts": { + "status": "synced", + "updated_at": "2025-03-06T21:35:56.427010Z", + "available_count": 226, + "available_from": "2022-06-06T21:35:10Z", + "available_to": "2025-02-28T00:00:00Z" + }, + "paystubs": { + "status": "synced", + "updated_at": "2025-03-06T21:35:56.450910Z", + "available_count": 33, + "available_from": "2022-07-01T21:35:10Z", + "available_to": "2025-03-03T00:00:00Z" + }, + "payroll_documents": { + "status": "synced", + "updated_at": "2025-03-06T21:35:13.915462Z" + }, + "identities": { + "status": "synced", + "updated_at": "2025-03-06T21:35:16.819311Z" + }, + "ratings": { + "status": "synced", + "updated_at": "2025-03-06T21:35:13.925812Z" + }, + "vehicles": { + "status": "synced", + "updated_at": "2025-03-06T21:35:13.929850Z" + }, + "deposit_destinations": { + "status": "synced", + "updated_at": "2025-03-06T21:35:12.299340Z" + }, + "user_forms": null, + "user_uploads": null + }, + "ongoing_refresh": { + "status": "enabled" + } + } + ] +} \ No newline at end of file diff --git a/app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_employment.json b/app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_employment.json new file mode 100644 index 000000000..7ccdb33e0 --- /dev/null +++ b/app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_employment.json @@ -0,0 +1,13 @@ +{ + "results": [ + { + "id": "f8a0ff8f-b99b-3584-b89a-6aac79463c3f", + "employer": "Conagra Brands", + "employment_status": "active", + "origin": "retrieved", + "user": "01956d61-d33a-6789-290e-621bed0a098f", + "account": "01956d62-18a0-090f-bc09-2ac44b7edf99", + "created_at": "2025-03-06T21:35:13.442Z" + } + ] +} \ No newline at end of file diff --git a/app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_identity.json b/app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_identity.json new file mode 100644 index 000000000..a88165f57 --- /dev/null +++ b/app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_identity.json @@ -0,0 +1,51 @@ +{ + "results": [ + { + "id": "091bf272-8bc6-3f7a-9758-2a37a6a18e73", + "account": "01956d62-18a0-090f-bc09-2ac44b7edf99", + "address": { + "city": "New York", + "line1": "150 Broadway", + "line2": null, + "state": "NY", + "country": "US", + "postal_code": "10038" + }, + "first_name": "Joe", + "last_name": "Burnam", + "full_name": "Joe Burnam", + "birth_date": "1987-02-11", + "email": "test3@argyle.com", + "phone_number": "+18009000030", + "picture_url": "https://api-sandbox.argyle.com/v2/payroll-documents/01956d62-4cff-86d2-2009-85752a6ce045/file", + "employment_status": "active", + "employment_type": "full-time", + "job_title": "1635 - Helpline Agent", + "ssn": "372-44-2872", + "marital_status": "Married filing jointly", + "gender": "Male", + "hire_date": "2022-06-06", + "original_hire_date": "2022-06-06", + "termination_date": null, + "termination_reason": null, + "employer": "Conagra Brands", + "base_pay": { + "amount": "65904.75", + "period": "annual", + "currency": "USD" + }, + "pay_cycle": "monthly", + "platform_ids": { + "employee_id": "G7GKLZWJG", + "position_id": "F1QFZYX6K", + "platform_user_id": "TIC4GH851GTAX1Z2" + }, + "created_at": "2025-03-06T21:35:13.442Z", + "updated_at": "2025-03-06T21:35:16.804Z", + "metadata": {}, + "employment": "f8a0ff8f-b99b-3584-b89a-6aac79463c3f" + } + ], + "next": null, + "previous": null +} \ No newline at end of file diff --git a/app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_paystubs.json b/app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_paystubs.json new file mode 100644 index 000000000..403b21cb8 --- /dev/null +++ b/app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_paystubs.json @@ -0,0 +1,1439 @@ +{ + "results": [ + { + "id": "fc04da26-02ba-3821-962d-1ff0035f4d20", + "account": "01956d62-18a0-090f-bc09-2ac44b7edf99", + "payroll_document": "01956d62-57e1-6b0d-9f37-efe03c6858de", + "deduction_list": [ + { + "amount": "109.84", + "name": "401K", + "tax_classification": "pre_tax", + "amount_ytd": "109.84" + }, + { + "amount": "219.68", + "name": "Vision", + "tax_classification": "pre_tax", + "amount_ytd": "219.68" + }, + { + "amount": "219.68", + "name": "Dental", + "tax_classification": null, + "amount_ytd": "549.20" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "single" + }, + { + "type": "state", + "location": "NY", + "status": "married filing separately" + } + ], + "tax_list": [ + { + "amount": "549.21", + "name": "Federal Income Tax", + "type": "federal", + "amount_ytd": "1153.34" + }, + { + "amount": "439.36", + "name": "OASDI", + "type": "fica", + "amount_ytd": "1098.41" + }, + { + "amount": "604.13", + "name": "Federal Withholding", + "type": "federal", + "amount_ytd": "1043.49" + } + ], + "status": "completed", + "paystub_date": "2025-03-03T00:00:00Z", + "paystub_period": { + "start_date": "2025-01-27T00:00:00Z", + "end_date": "2025-02-24T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "5492.06", + "deductions": "549.20", + "taxes": "1592.70", + "net_pay": "3350.16", + "reimbursements": "0.00", + "hours": "92.92", + "fees": null, + "net_pay_ytd": "10874.29", + "gross_pay_ytd": "16476.18", + "metadata": {}, + "employer": "Conagra Brands", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-03-06T21:35:16.592Z", + "updated_at": "2025-03-06T21:35:16.592Z", + "destinations": [], + "hours_ytd": "429.82", + "deductions_ytd": "1208.24", + "taxes_ytd": "4393.65", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2025-01-27", + "end_date": "2025-02-24", + "rate": "59.1067", + "hours": "92.9177", + "amount": "5492.06", + "hours_ytd": "429.8200", + "amount_ytd": "16476.18" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "5492.06", + "amount_ytd": "16476.18", + "hours": "92.9177", + "hours_ytd": "429.8200", + "rate_implied": "59.1100", + "rate_implied_ytd": "38.3300" + }, + "overtime": null, + "commission": null, + "bonus": null, + "other": null + }, + "employment": "f8a0ff8f-b99b-3584-b89a-6aac79463c3f" + }, + { + "id": "0c58a430-9d2a-3392-a89e-8ba337cbe265", + "account": "01956d62-18a0-090f-bc09-2ac44b7edf99", + "payroll_document": "01956d62-57b5-4e84-5328-399f94591743", + "deduction_list": [ + { + "amount": "164.76", + "name": "Dental", + "tax_classification": null, + "amount_ytd": "329.52" + }, + { + "amount": "164.76", + "name": "Roth", + "tax_classification": "post_tax", + "amount_ytd": "164.76" + }, + { + "amount": "164.76", + "name": "Garnishment", + "tax_classification": "post_tax", + "amount_ytd": "164.76" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "married filing separately" + } + ], + "tax_list": [ + { + "amount": "439.36", + "name": "MEDICARE", + "type": "fica", + "amount_ytd": "439.36" + }, + { + "amount": "659.05", + "name": "CA State Income Tax", + "type": "state", + "amount_ytd": "659.05" + } + ], + "status": "completed", + "paystub_date": "2025-02-03T00:00:00Z", + "paystub_period": { + "start_date": "2024-12-27T00:00:00Z", + "end_date": "2025-01-27T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "5492.06", + "deductions": "494.28", + "taxes": "1098.41", + "net_pay": "3899.37", + "reimbursements": "0.00", + "hours": "174.40", + "fees": null, + "net_pay_ytd": "7524.13", + "gross_pay_ytd": "10984.12", + "metadata": {}, + "employer": "Conagra Brands", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-03-06T21:35:16.613Z", + "updated_at": "2025-03-06T21:35:16.613Z", + "destinations": [], + "hours_ytd": "336.90", + "deductions_ytd": "659.04", + "taxes_ytd": "2800.95", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2024-12-27", + "end_date": "2025-01-27", + "rate": "31.4907", + "hours": "174.4026", + "amount": "5492.06", + "hours_ytd": "336.9000", + "amount_ytd": "10984.12" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "5492.06", + "amount_ytd": "10984.12", + "hours": "174.4026", + "hours_ytd": "336.9000", + "rate_implied": "31.4900", + "rate_implied_ytd": "32.6000" + }, + "overtime": null, + "commission": null, + "bonus": null, + "other": null + }, + "employment": "f8a0ff8f-b99b-3584-b89a-6aac79463c3f" + }, + { + "id": "d70103ae-6b21-3a87-8e24-225d17b56d94", + "account": "01956d62-18a0-090f-bc09-2ac44b7edf99", + "payroll_document": "01956d62-6e0e-f498-b749-03588912c26c", + "deduction_list": [ + { + "amount": "164.76", + "name": "Dental", + "tax_classification": null, + "amount_ytd": "164.76" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "married filing separately" + } + ], + "tax_list": [ + { + "amount": "659.05", + "name": "OASDI", + "type": "fica", + "amount_ytd": "659.05" + }, + { + "amount": "604.13", + "name": "Federal Income Tax", + "type": "federal", + "amount_ytd": "604.13" + }, + { + "amount": "439.36", + "name": "Federal Withholding", + "type": "federal", + "amount_ytd": "439.36" + } + ], + "status": "completed", + "paystub_date": "2025-01-01T00:00:00Z", + "paystub_period": { + "start_date": "2024-11-27T00:00:00Z", + "end_date": "2024-12-27T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "5492.06", + "deductions": "164.76", + "taxes": "1702.54", + "net_pay": "3624.76", + "reimbursements": "0.00", + "hours": "162.50", + "fees": null, + "net_pay_ytd": "3624.76", + "gross_pay_ytd": "5492.06", + "metadata": {}, + "employer": "Conagra Brands", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-03-06T21:35:22.295Z", + "updated_at": "2025-03-06T21:35:22.295Z", + "destinations": [], + "hours_ytd": "162.50", + "deductions_ytd": "164.76", + "taxes_ytd": "1702.54", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2024-11-27", + "end_date": "2024-12-27", + "rate": "33.7975", + "hours": "162.4988", + "amount": "5492.06", + "hours_ytd": "162.5000", + "amount_ytd": "5492.06" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "5492.06", + "amount_ytd": "5492.06", + "hours": "162.4988", + "hours_ytd": "162.5000", + "rate_implied": "33.8000", + "rate_implied_ytd": "33.8000" + }, + "overtime": null, + "commission": null, + "bonus": null, + "other": null + }, + "employment": "f8a0ff8f-b99b-3584-b89a-6aac79463c3f" + }, + { + "id": "80409ce6-01f8-3466-80d2-b9a4bdc71865", + "account": "01956d62-18a0-090f-bc09-2ac44b7edf99", + "payroll_document": "01956d62-6dd8-7b73-d7f9-48621bfdab53", + "deduction_list": [ + { + "amount": "164.76", + "name": "Dental", + "tax_classification": null, + "amount_ytd": "604.12" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "married filing separately" + } + ], + "tax_list": [ + { + "amount": "384.44", + "name": "Federal Withholding", + "type": "federal", + "amount_ytd": "1592.70" + } + ], + "status": "completed", + "paystub_date": "2024-12-02T00:00:00Z", + "paystub_period": { + "start_date": "2024-10-27T00:00:00Z", + "end_date": "2024-11-26T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "5492.06", + "deductions": "164.76", + "taxes": "384.44", + "net_pay": "4944.43", + "reimbursements": "1.57", + "hours": "139.50", + "fees": null, + "net_pay_ytd": "62999.21", + "gross_pay_ytd": "74135.63", + "metadata": {}, + "employer": "Conagra Brands", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-03-06T21:35:22.313Z", + "updated_at": "2025-03-06T21:35:22.313Z", + "destinations": [], + "hours_ytd": "1670.23", + "deductions_ytd": "1867.28", + "taxes_ytd": "9281.60", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2024-10-27", + "end_date": "2024-11-26", + "rate": "39.3686", + "hours": "139.5035", + "amount": "5492.06", + "hours_ytd": "1597.2100", + "amount_ytd": "65904.72" + }, + { + "name": "Overtime", + "type": "overtime", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": "63.6700", + "amount_ytd": "5605.53" + }, + { + "name": "Holiday Overtime", + "type": "overtime", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": "9.3500", + "amount_ytd": "854.77" + }, + { + "name": "Bonus", + "type": "bonus", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "761.32" + }, + { + "name": "Commission", + "type": "commission", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "1009.29" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "5492.06", + "amount_ytd": "65904.72", + "hours": "139.5035", + "hours_ytd": "1597.2100", + "rate_implied": "39.3700", + "rate_implied_ytd": "41.2600" + }, + "overtime": { + "amount": null, + "amount_ytd": "6460.30", + "hours": null, + "hours_ytd": "73.0200", + "rate_implied": null, + "rate_implied_ytd": "88.4700" + }, + "commission": { + "amount": null, + "amount_ytd": "1009.29", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "bonus": { + "amount": null, + "amount_ytd": "761.32", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "other": null + }, + "employment": "f8a0ff8f-b99b-3584-b89a-6aac79463c3f" + }, + { + "id": "0b913a51-9b82-30cf-b912-3d6356ce901f", + "account": "01956d62-18a0-090f-bc09-2ac44b7edf99", + "payroll_document": "01956d62-849a-6f57-c03f-78c758935ef1", + "deduction_list": [ + { + "amount": "54.92", + "name": "Garnishment", + "tax_classification": "post_tax", + "amount_ytd": "659.04" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "married filing separately" + } + ], + "tax_list": [ + { + "amount": "604.13", + "name": "Social Security Tax", + "type": "fica", + "amount_ytd": "1702.54" + } + ], + "status": "completed", + "paystub_date": "2024-11-01T00:00:00Z", + "paystub_period": { + "start_date": "2024-09-26T00:00:00Z", + "end_date": "2024-10-27T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "9735.94", + "deductions": "54.92", + "taxes": "604.13", + "net_pay": "9076.89", + "reimbursements": "0.00", + "hours": "115.27", + "fees": null, + "net_pay_ytd": "58054.78", + "gross_pay_ytd": "68643.57", + "metadata": {}, + "employer": "Conagra Brands", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-03-06T21:35:28.067Z", + "updated_at": "2025-03-06T21:35:28.067Z", + "destinations": [], + "hours_ytd": "1530.73", + "deductions_ytd": "1702.52", + "taxes_ytd": "8897.16", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2024-09-26", + "end_date": "2024-10-27", + "rate": "72.1913", + "hours": "76.0765", + "amount": "5492.06", + "hours_ytd": "1457.7100", + "amount_ytd": "60412.66" + }, + { + "name": "Overtime", + "type": "overtime", + "start_date": "2024-10-26", + "end_date": "2024-10-26", + "rate": "108.2870", + "hours": "39.1910", + "amount": "4243.88", + "hours_ytd": "63.6700", + "amount_ytd": "5605.53" + }, + { + "name": "Holiday Overtime", + "type": "overtime", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": "9.3500", + "amount_ytd": "854.77" + }, + { + "name": "Bonus", + "type": "bonus", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "761.32" + }, + { + "name": "Commission", + "type": "commission", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "1009.29" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "5492.06", + "amount_ytd": "60412.66", + "hours": "76.0765", + "hours_ytd": "1457.7100", + "rate_implied": "72.1900", + "rate_implied_ytd": "41.4400" + }, + "overtime": { + "amount": "4243.88", + "amount_ytd": "6460.30", + "hours": "39.1910", + "hours_ytd": "73.0200", + "rate_implied": "108.2900", + "rate_implied_ytd": "88.4700" + }, + "commission": { + "amount": null, + "amount_ytd": "1009.29", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "bonus": { + "amount": null, + "amount_ytd": "761.32", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "other": null + }, + "employment": "f8a0ff8f-b99b-3584-b89a-6aac79463c3f" + }, + { + "id": "4cc3bcf5-b15e-3f8c-9656-eab3178e379c", + "account": "01956d62-18a0-090f-bc09-2ac44b7edf99", + "payroll_document": "01956d62-8463-0b21-bcae-7da0c0e676ac", + "deduction_list": [ + { + "amount": "109.84", + "name": "Vision", + "tax_classification": "pre_tax", + "amount_ytd": "274.60" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "single" + }, + { + "type": "state", + "location": "NY", + "status": "married filing separately" + } + ], + "tax_list": [ + { + "amount": "439.36", + "name": "Federal Income Tax", + "type": "federal", + "amount_ytd": "1592.70" + } + ], + "status": "completed", + "paystub_date": "2024-10-01T00:00:00Z", + "paystub_period": { + "start_date": "2024-08-27T00:00:00Z", + "end_date": "2024-09-26T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "6041.25", + "deductions": "109.84", + "taxes": "439.36", + "net_pay": "5496.77", + "reimbursements": "4.72", + "hours": "98.21", + "fees": null, + "net_pay_ytd": "48977.89", + "gross_pay_ytd": "58907.63", + "metadata": {}, + "employer": "Conagra Brands", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-03-06T21:35:28.083Z", + "updated_at": "2025-03-06T21:35:28.083Z", + "destinations": [], + "hours_ytd": "1415.46", + "deductions_ytd": "1647.60", + "taxes_ytd": "8293.03", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2024-08-27", + "end_date": "2024-09-26", + "rate": "58.8626", + "hours": "93.3031", + "amount": "5492.06", + "hours_ytd": "1381.6300", + "amount_ytd": "54920.60" + }, + { + "name": "Overtime", + "type": "overtime", + "start_date": "2024-09-05", + "end_date": "2024-09-13", + "rate": "88.2939", + "hours": "0.9821", + "amount": "86.71", + "hours_ytd": "24.4800", + "amount_ytd": "1361.65" + }, + { + "name": "Holiday Overtime", + "type": "overtime", + "start_date": "2024-09-05", + "end_date": "2024-09-17", + "rate": "117.7252", + "hours": "3.9285", + "amount": "462.48", + "hours_ytd": "9.3500", + "amount_ytd": "854.77" + }, + { + "name": "Bonus", + "type": "bonus", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "761.32" + }, + { + "name": "Commission", + "type": "commission", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "1009.29" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "5492.06", + "amount_ytd": "54920.60", + "hours": "93.3031", + "hours_ytd": "1381.6300", + "rate_implied": "58.8600", + "rate_implied_ytd": "39.7500" + }, + "overtime": { + "amount": "549.19", + "amount_ytd": "2216.42", + "hours": "4.9106", + "hours_ytd": "33.8300", + "rate_implied": "111.8400", + "rate_implied_ytd": "65.5200" + }, + "commission": { + "amount": null, + "amount_ytd": "1009.29", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "bonus": { + "amount": null, + "amount_ytd": "761.32", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "other": null + }, + "employment": "f8a0ff8f-b99b-3584-b89a-6aac79463c3f" + }, + { + "id": "974c713b-147b-300f-ab36-883a63e399c0", + "account": "01956d62-18a0-090f-bc09-2ac44b7edf99", + "payroll_document": "01956d62-937f-9ce2-3314-046d21dda281", + "deduction_list": [ + { + "amount": "54.92", + "name": "Dental", + "tax_classification": null, + "amount_ytd": "439.36" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "married filing separately" + }, + { + "type": "local", + "location": "New York", + "status": "single" + } + ], + "tax_list": [ + { + "amount": "659.05", + "name": "Federal Withholding", + "type": "federal", + "amount_ytd": "1208.26" + }, + { + "amount": "604.13", + "name": "Federal Income Tax", + "type": "federal", + "amount_ytd": "1153.34" + } + ], + "status": "completed", + "paystub_date": "2024-09-02T00:00:00Z", + "paystub_period": { + "start_date": "2024-07-27T00:00:00Z", + "end_date": "2024-08-27T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "5492.06", + "deductions": "54.92", + "taxes": "1263.18", + "net_pay": "4175.51", + "reimbursements": "1.55", + "hours": "165.64", + "fees": null, + "net_pay_ytd": "43481.12", + "gross_pay_ytd": "52866.38", + "metadata": {}, + "employer": "Conagra Brands", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-03-06T21:35:31.934Z", + "updated_at": "2025-03-06T21:35:31.934Z", + "destinations": [], + "hours_ytd": "1317.24", + "deductions_ytd": "1537.76", + "taxes_ytd": "7853.67", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2024-07-27", + "end_date": "2024-08-27", + "rate": "33.1564", + "hours": "165.6409", + "amount": "5492.06", + "hours_ytd": "1288.3300", + "amount_ytd": "49428.54" + }, + { + "name": "Overtime", + "type": "overtime", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": "23.4900", + "amount_ytd": "1274.94" + }, + { + "name": "Holiday Overtime", + "type": "overtime", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": "5.4200", + "amount_ytd": "392.29" + }, + { + "name": "Bonus", + "type": "bonus", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "761.32" + }, + { + "name": "Commission", + "type": "commission", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "1009.29" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "5492.06", + "amount_ytd": "49428.54", + "hours": "165.6409", + "hours_ytd": "1288.3300", + "rate_implied": "33.1600", + "rate_implied_ytd": "38.3700" + }, + "overtime": { + "amount": null, + "amount_ytd": "1667.23", + "hours": null, + "hours_ytd": "28.9100", + "rate_implied": null, + "rate_implied_ytd": "57.6700" + }, + "commission": { + "amount": null, + "amount_ytd": "1009.29", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "bonus": { + "amount": null, + "amount_ytd": "761.32", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "other": null + }, + "employment": "f8a0ff8f-b99b-3584-b89a-6aac79463c3f" + }, + { + "id": "63132bc4-d197-3228-9c9f-2ac89da0290b", + "account": "01956d62-18a0-090f-bc09-2ac44b7edf99", + "payroll_document": "01956d62-9350-b3a8-ba78-f47ffc2a5aca", + "deduction_list": [ + { + "amount": "109.84", + "name": "Garnishment", + "tax_classification": "post_tax", + "amount_ytd": "604.12" + }, + { + "amount": "109.84", + "name": "Roth", + "tax_classification": "post_tax", + "amount_ytd": "219.68" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "single" + } + ], + "tax_list": [ + { + "amount": "494.29", + "name": "MEDICARE", + "type": "fica", + "amount_ytd": "878.73" + }, + { + "amount": "659.05", + "name": "CA State Income Tax", + "type": "state", + "amount_ytd": "1922.23" + } + ], + "status": "completed", + "paystub_date": "2024-08-01T00:00:00Z", + "paystub_period": { + "start_date": "2024-06-26T00:00:00Z", + "end_date": "2024-07-27T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "5492.06", + "deductions": "219.68", + "taxes": "1153.34", + "net_pay": "4119.04", + "reimbursements": "0.00", + "hours": "174.68", + "fees": null, + "net_pay_ytd": "39305.61", + "gross_pay_ytd": "47374.32", + "metadata": {}, + "employer": "Conagra Brands", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-03-06T21:35:31.918Z", + "updated_at": "2025-03-06T21:35:31.918Z", + "destinations": [], + "hours_ytd": "1151.60", + "deductions_ytd": "1482.84", + "taxes_ytd": "6590.49", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2024-06-26", + "end_date": "2024-07-27", + "rate": "31.4399", + "hours": "174.6846", + "amount": "5492.06", + "hours_ytd": "1122.6900", + "amount_ytd": "43936.48" + }, + { + "name": "Overtime", + "type": "overtime", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": "23.4900", + "amount_ytd": "1274.94" + }, + { + "name": "Holiday Overtime", + "type": "overtime", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": "5.4200", + "amount_ytd": "392.29" + }, + { + "name": "Bonus", + "type": "bonus", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "761.32" + }, + { + "name": "Commission", + "type": "commission", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "1009.29" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "5492.06", + "amount_ytd": "43936.48", + "hours": "174.6846", + "hours_ytd": "1122.6900", + "rate_implied": "31.4400", + "rate_implied_ytd": "39.1400" + }, + "overtime": { + "amount": null, + "amount_ytd": "1667.23", + "hours": null, + "hours_ytd": "28.9100", + "rate_implied": null, + "rate_implied_ytd": "57.6700" + }, + "commission": { + "amount": null, + "amount_ytd": "1009.29", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "bonus": { + "amount": null, + "amount_ytd": "761.32", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "other": null + }, + "employment": "f8a0ff8f-b99b-3584-b89a-6aac79463c3f" + }, + { + "id": "35e4a497-4979-3d51-afed-38250d4c326e", + "account": "01956d62-18a0-090f-bc09-2ac44b7edf99", + "payroll_document": "01956d62-9a02-206f-d9ba-adc2532d5f0a", + "deduction_list": [ + { + "amount": "219.68", + "name": "Dental", + "tax_classification": null, + "amount_ytd": "384.44" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "single" + }, + { + "type": "state", + "location": "NY", + "status": "single" + } + ], + "tax_list": [ + { + "amount": "439.36", + "name": "OASDI", + "type": "fica", + "amount_ytd": "1592.70" + } + ], + "status": "completed", + "paystub_date": "2024-07-01T00:00:00Z", + "paystub_period": { + "start_date": "2024-05-27T00:00:00Z", + "end_date": "2024-06-26T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "5492.06", + "deductions": "219.68", + "taxes": "439.36", + "net_pay": "4833.02", + "reimbursements": "0.00", + "hours": "92.47", + "fees": null, + "net_pay_ytd": "35186.57", + "gross_pay_ytd": "41882.26", + "metadata": {}, + "employer": "Conagra Brands", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-03-06T21:35:33.532Z", + "updated_at": "2025-03-06T21:35:33.532Z", + "destinations": [], + "hours_ytd": "976.92", + "deductions_ytd": "1263.16", + "taxes_ytd": "5437.15", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2024-05-27", + "end_date": "2024-06-26", + "rate": "59.3940", + "hours": "92.4682", + "amount": "5492.06", + "hours_ytd": "948.0000", + "amount_ytd": "38444.42" + }, + { + "name": "Overtime", + "type": "overtime", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": "23.4900", + "amount_ytd": "1274.94" + }, + { + "name": "Holiday Overtime", + "type": "overtime", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": "5.4200", + "amount_ytd": "392.29" + }, + { + "name": "Bonus", + "type": "bonus", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "761.32" + }, + { + "name": "Commission", + "type": "commission", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "1009.29" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "5492.06", + "amount_ytd": "38444.42", + "hours": "92.4682", + "hours_ytd": "948.0000", + "rate_implied": "59.3900", + "rate_implied_ytd": "40.5500" + }, + "overtime": { + "amount": null, + "amount_ytd": "1667.23", + "hours": null, + "hours_ytd": "28.9100", + "rate_implied": null, + "rate_implied_ytd": "57.6700" + }, + "commission": { + "amount": null, + "amount_ytd": "1009.29", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "bonus": { + "amount": null, + "amount_ytd": "761.32", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "other": null + }, + "employment": "f8a0ff8f-b99b-3584-b89a-6aac79463c3f" + }, + { + "id": "6e8a958e-0d4a-3a25-a59c-d51cc576bb2a", + "account": "01956d62-18a0-090f-bc09-2ac44b7edf99", + "payroll_document": "01956d62-99d4-0c8c-d199-81bf37c93099", + "deduction_list": [ + { + "amount": "109.84", + "name": "Dental", + "tax_classification": null, + "amount_ytd": "164.76" + } + ], + "filing_status": [ + { + "type": "federal", + "location": null, + "status": "married filing separately" + } + ], + "tax_list": [ + { + "amount": "549.21", + "name": "Federal Withholding", + "type": "federal", + "amount_ytd": "549.21" + }, + { + "amount": "659.05", + "name": "Social Security Tax", + "type": "fica", + "amount_ytd": "1098.41" + } + ], + "status": "completed", + "paystub_date": "2024-06-03T00:00:00Z", + "paystub_period": { + "start_date": "2024-04-26T00:00:00Z", + "end_date": "2024-05-27T00:00:00Z" + }, + "currency": "USD", + "gross_pay": "7159.29", + "deductions": "109.84", + "taxes": "1208.26", + "net_pay": "5841.19", + "reimbursements": "0.00", + "hours": "180.72", + "fees": null, + "net_pay_ytd": "30353.55", + "gross_pay_ytd": "36390.20", + "metadata": {}, + "employer": "Conagra Brands", + "employer_address": { + "city": "Seattle", + "state": "WA", + "country": "US", + "postal_code": "98109", + "line1": "202 Westlake Ave N", + "line2": null + }, + "created_at": "2025-03-06T21:35:33.549Z", + "updated_at": "2025-03-06T21:35:33.549Z", + "destinations": [], + "hours_ytd": "884.45", + "deductions_ytd": "1043.48", + "taxes_ytd": "4997.79", + "gross_pay_list": [ + { + "name": "Regular", + "type": "base", + "start_date": "2024-04-26", + "end_date": "2024-05-27", + "rate": "36.1787", + "hours": "151.8037", + "amount": "5492.06", + "hours_ytd": "855.5400", + "amount_ytd": "32952.36" + }, + { + "name": "Overtime", + "type": "overtime", + "start_date": "2024-05-18", + "end_date": "2024-05-25", + "rate": "54.2680", + "hours": "23.4934", + "amount": "1274.94", + "hours_ytd": "23.4900", + "amount_ytd": "1274.94" + }, + { + "name": "Holiday Overtime", + "type": "overtime", + "start_date": "2024-05-13", + "end_date": "2024-05-16", + "rate": "72.3574", + "hours": "5.4216", + "amount": "392.29", + "hours_ytd": "5.4200", + "amount_ytd": "392.29" + }, + { + "name": "Bonus", + "type": "bonus", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "761.32" + }, + { + "name": "Commission", + "type": "commission", + "start_date": null, + "end_date": null, + "rate": null, + "hours": null, + "amount": null, + "hours_ytd": null, + "amount_ytd": "1009.29" + } + ], + "gross_pay_list_totals": { + "base": { + "amount": "5492.06", + "amount_ytd": "32952.36", + "hours": "151.8037", + "hours_ytd": "855.5400", + "rate_implied": "36.1800", + "rate_implied_ytd": "38.5200" + }, + "overtime": { + "amount": "1667.23", + "amount_ytd": "1667.23", + "hours": "28.9150", + "hours_ytd": "28.9100", + "rate_implied": "57.6600", + "rate_implied_ytd": "57.6700" + }, + "commission": { + "amount": null, + "amount_ytd": "1009.29", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "bonus": { + "amount": null, + "amount_ytd": "761.32", + "hours": null, + "hours_ytd": null, + "rate_implied": null, + "rate_implied_ytd": null + }, + "other": null + }, + "employment": "f8a0ff8f-b99b-3584-b89a-6aac79463c3f" + } + ], + "next": "https://api-sandbox.argyle.com/v2/paystubs?cursor=Y2xpZW50SWQ9MDE4ZjA4MzQtZDY5YS04NzliLTA1Y2ItZGRkZTAzMzAyNGYwJmludmVyc2VTb3J0PWZhbHNlJmxpbWl0PTEwJnVzZXJJZD0wMTk1NmQ2MS1kMzNhLTY3ODktMjkwZS02MjFiZWQwYTA5OGYmcm93S2V5PVMlMjNVJTIzMDE5NTZkNjEtZDMzYS02Nzg5LTI5MGUtNjIxYmVkMGEwOThmJTIzNDYxMTY4NDMwMzkwNTc4NzkwMyUyMzAzNjU2ZmY4LTQwZjQtMzUwOS1iODg4LTg0MWE3ZjhjN2FiYg%3D%3D", + "previous": null +} \ No newline at end of file diff --git a/app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_user.json b/app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_user.json new file mode 100644 index 000000000..42250cea4 --- /dev/null +++ b/app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_user.json @@ -0,0 +1,12 @@ +{ + "id": "01956d61-d33a-6789-290e-621bed0a098f", + "created_at": "2025-03-06T21:34:42.491381Z", + "items_connected": [ + "item_000009878" + ], + "employers_connected": [ + "Conagra Brands" + ], + "external_metadata": {}, + "external_id": null +} \ No newline at end of file From 79f94e4da850106892d22b323f1a2d22591b7592 Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Fri, 7 Mar 2025 11:17:35 -0500 Subject: [PATCH 05/26] latest changes --- app/app/models/response_objects/paystub.rb | 6 ++--- app/app/services/argyle_service.rb | 7 +++-- app/spec/services/argyle_service_spec.rb | 27 +++++++++++-------- app/spec/support/argyle_api_helper.rb | 24 +++++++++-------- .../request_accounts.json | 0 .../request_employment.json | 0 .../request_identity.json | 0 .../request_paystubs.json | 0 .../request_user.json | 0 .../request_accounts.json | 0 .../request_employment.json | 0 .../request_identity.json | 0 .../request_paystubs.json | 0 .../request_user.json | 0 .../request_accounts.json | 0 .../request_employment.json | 0 .../request_identity.json | 0 .../request_paystubs.json | 0 .../request_user.json | 0 19 files changed, 37 insertions(+), 27 deletions(-) rename app/spec/support/fixtures/argyle/{0195441c-5a5f-7d86-3be1-fa5797a441a6 => bob}/request_accounts.json (100%) rename app/spec/support/fixtures/argyle/{0195441c-5a5f-7d86-3be1-fa5797a441a6 => bob}/request_employment.json (100%) rename app/spec/support/fixtures/argyle/{0195441c-5a5f-7d86-3be1-fa5797a441a6 => bob}/request_identity.json (100%) rename app/spec/support/fixtures/argyle/{0195441c-5a5f-7d86-3be1-fa5797a441a6 => bob}/request_paystubs.json (100%) rename app/spec/support/fixtures/argyle/{0195441c-5a5f-7d86-3be1-fa5797a441a6 => bob}/request_user.json (100%) rename app/spec/support/fixtures/argyle/{01956d61-d33a-6789-290e-621bed0a098f => joe}/request_accounts.json (100%) rename app/spec/support/fixtures/argyle/{01956d61-d33a-6789-290e-621bed0a098f => joe}/request_employment.json (100%) rename app/spec/support/fixtures/argyle/{01956d61-d33a-6789-290e-621bed0a098f => joe}/request_identity.json (100%) rename app/spec/support/fixtures/argyle/{01956d61-d33a-6789-290e-621bed0a098f => joe}/request_paystubs.json (100%) rename app/spec/support/fixtures/argyle/{01956d61-d33a-6789-290e-621bed0a098f => joe}/request_user.json (100%) rename app/spec/support/fixtures/argyle/{01956d5f-9482-5a52-1f87-84c0a238e107 => sarah}/request_accounts.json (100%) rename app/spec/support/fixtures/argyle/{01956d5f-9482-5a52-1f87-84c0a238e107 => sarah}/request_employment.json (100%) rename app/spec/support/fixtures/argyle/{01956d5f-9482-5a52-1f87-84c0a238e107 => sarah}/request_identity.json (100%) rename app/spec/support/fixtures/argyle/{01956d5f-9482-5a52-1f87-84c0a238e107 => sarah}/request_paystubs.json (100%) rename app/spec/support/fixtures/argyle/{01956d5f-9482-5a52-1f87-84c0a238e107 => sarah}/request_user.json (100%) diff --git a/app/app/models/response_objects/paystub.rb b/app/app/models/response_objects/paystub.rb index a88fa4d3d..e98b88a44 100644 --- a/app/app/models/response_objects/paystub.rb +++ b/app/app/models/response_objects/paystub.rb @@ -34,14 +34,14 @@ def self.from_pinwheel(response_body) end - def self.from_argyle(paystubs_response_body) + def self.from_argyle(response_body) new( account_id: response_body["account"], gross_pay_amount: response_body["gross_pay"], net_pay_amount: response_body["net_pay"], gross_pay_ytd: response_body["gross_pay_ytd"], - pay_period_start: response_body["pay_period"]["start_date"], - pay_period_end: response_body["pay_period"]["end_date"], + # pay_period_start: response_body["pay_period"]["start_date"], + # pay_period_end: response_body["pay_period"]["end_date"], pay_date: response_body["paystub_date"], hours: response_body["hours"], hours_by_earning_category: response_body["gross_pay_list"].map do |gross_pay_item| diff --git a/app/app/services/argyle_service.rb b/app/app/services/argyle_service.rb index cbd362f49..3cc6dedc4 100644 --- a/app/app/services/argyle_service.rb +++ b/app/app/services/argyle_service.rb @@ -67,9 +67,12 @@ def fetch_accounts(**params) # https://docs.argyle.com/api-reference/paystubs#list def fetch_paystubs(**params) - # json["data"].map { |paystub_json| ResponseObjects::Paystub.from_pinwheel(paystub_json) } # TODO: paginate - @http.get(PAYSTUBS_ENDPOINT, params).body + $stdout.puts "Hello" + + Rails.logger.debug "Interesting stuff" + json = @http.get(PAYSTUBS_ENDPOINT, params).body + json["results"].map { |paystub_json| ResponseObjects::Paystub.from_argyle(paystub_json) } end # https://docs.argyle.com/api-reference/employments#list diff --git a/app/spec/services/argyle_service_spec.rb b/app/spec/services/argyle_service_spec.rb index 27970ff8f..d0f0667e3 100644 --- a/app/spec/services/argyle_service_spec.rb +++ b/app/spec/services/argyle_service_spec.rb @@ -1,29 +1,34 @@ require 'rails_helper' +BOB_USER_FOLDER = "bob" +SARAH_USER_FOLDER = "sarah" +JOE_USER_FOLDER = "joe" + RSpec.describe ArgyleService, type: :service do include ArgyleApiHelper let(:service) { ArgyleService.new("sandbox", "FAKE_API_KEY") } let(:end_user_id) { 'abc123' } - describe '#fetch_items' do - before do - stub_request_items_response - end + # describe '#fetch_items' do + # before do + # stub_request_items_response(BOB_USER_FOLDER) + # end - it 'returns a non-empty response' do - response = service.items('test') - expect(response).not_to be_empty - end - end + # it 'returns a non-empty response' do + # response = service.items('test') + # expect(response).not_to be_empty + # end + # end describe '#fetch_paystubs' do before do - stub_request_paystubs_response + stub_request_paystubs_response(BOB_USER_FOLDER) end it 'returns a non-empty response' do # a.fetch_paystubs(user: "0195441c-5a5f-7d86-3be1-fa5797a441a6", from_start_date: "2025-02-20", to_start_ - response = service.fetch_paystubs(account_id: end_user_id) + puts("hello") + response = service.fetch_paystubs(account: end_user_id) expect(response).not_to be_empty end end diff --git a/app/spec/support/argyle_api_helper.rb b/app/spec/support/argyle_api_helper.rb index ee490a9d4..0671dbdb2 100644 --- a/app/spec/support/argyle_api_helper.rb +++ b/app/spec/support/argyle_api_helper.rb @@ -1,30 +1,32 @@ +:first_ + module ArgyleApiHelper - def stub_request_items_response - stub_request(:get,%r{#{ArgyleService::ITEMS_ENDPOINT}}) + def stub_request_items_response(userFolder) + stub_request(:get, %r{#{ArgyleService::ITEMS_ENDPOINT}}) .to_return( status: 200, - body: load_relative_json_file('request_items.json').to_json, + body: load_relative_json_file(userFolder, 'request_items.json').to_json, headers: { 'Content-Type': 'application/json;charset=UTF-8' } ) end - def stub_request_paystubs_response - stub_request(:get,%r{#{ArgyleService::PAYSTUBS_ENDPOINT}}) + def stub_request_paystubs_response(userFolder) + stub_request(:get, %r{#{ArgyleService::PAYSTUBS_ENDPOINT}}) .to_return( status: 200, - body: load_relative_json_file('request_paystubs.json').to_json, + body: load_relative_json_file(userFolder, 'request_paystubs.json').to_json, headers: { 'Content-Type': 'application/json;charset=UTF-8' } ) end - def load_relative_file(filename) + def load_relative_file(userFolder, filename) File.read(File.join( File.dirname(__FILE__), - "fixtures/argyle/#{filename}" + "fixtures/argyle/#{userFolder}/#{filename}" )) end - def load_relative_json_file(filename) - JSON.parse(load_relative_file(filename)) + def load_relative_json_file(userFolder, filename) + JSON.parse(load_relative_file(userFolder, filename)) end -end \ No newline at end of file +end diff --git a/app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_accounts.json b/app/spec/support/fixtures/argyle/bob/request_accounts.json similarity index 100% rename from app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_accounts.json rename to app/spec/support/fixtures/argyle/bob/request_accounts.json diff --git a/app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_employment.json b/app/spec/support/fixtures/argyle/bob/request_employment.json similarity index 100% rename from app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_employment.json rename to app/spec/support/fixtures/argyle/bob/request_employment.json diff --git a/app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_identity.json b/app/spec/support/fixtures/argyle/bob/request_identity.json similarity index 100% rename from app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_identity.json rename to app/spec/support/fixtures/argyle/bob/request_identity.json diff --git a/app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_paystubs.json b/app/spec/support/fixtures/argyle/bob/request_paystubs.json similarity index 100% rename from app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_paystubs.json rename to app/spec/support/fixtures/argyle/bob/request_paystubs.json diff --git a/app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_user.json b/app/spec/support/fixtures/argyle/bob/request_user.json similarity index 100% rename from app/spec/support/fixtures/argyle/0195441c-5a5f-7d86-3be1-fa5797a441a6/request_user.json rename to app/spec/support/fixtures/argyle/bob/request_user.json diff --git a/app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_accounts.json b/app/spec/support/fixtures/argyle/joe/request_accounts.json similarity index 100% rename from app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_accounts.json rename to app/spec/support/fixtures/argyle/joe/request_accounts.json diff --git a/app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_employment.json b/app/spec/support/fixtures/argyle/joe/request_employment.json similarity index 100% rename from app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_employment.json rename to app/spec/support/fixtures/argyle/joe/request_employment.json diff --git a/app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_identity.json b/app/spec/support/fixtures/argyle/joe/request_identity.json similarity index 100% rename from app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_identity.json rename to app/spec/support/fixtures/argyle/joe/request_identity.json diff --git a/app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_paystubs.json b/app/spec/support/fixtures/argyle/joe/request_paystubs.json similarity index 100% rename from app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_paystubs.json rename to app/spec/support/fixtures/argyle/joe/request_paystubs.json diff --git a/app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_user.json b/app/spec/support/fixtures/argyle/joe/request_user.json similarity index 100% rename from app/spec/support/fixtures/argyle/01956d61-d33a-6789-290e-621bed0a098f/request_user.json rename to app/spec/support/fixtures/argyle/joe/request_user.json diff --git a/app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_accounts.json b/app/spec/support/fixtures/argyle/sarah/request_accounts.json similarity index 100% rename from app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_accounts.json rename to app/spec/support/fixtures/argyle/sarah/request_accounts.json diff --git a/app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_employment.json b/app/spec/support/fixtures/argyle/sarah/request_employment.json similarity index 100% rename from app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_employment.json rename to app/spec/support/fixtures/argyle/sarah/request_employment.json diff --git a/app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_identity.json b/app/spec/support/fixtures/argyle/sarah/request_identity.json similarity index 100% rename from app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_identity.json rename to app/spec/support/fixtures/argyle/sarah/request_identity.json diff --git a/app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_paystubs.json b/app/spec/support/fixtures/argyle/sarah/request_paystubs.json similarity index 100% rename from app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_paystubs.json rename to app/spec/support/fixtures/argyle/sarah/request_paystubs.json diff --git a/app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_user.json b/app/spec/support/fixtures/argyle/sarah/request_user.json similarity index 100% rename from app/spec/support/fixtures/argyle/01956d5f-9482-5a52-1f87-84c0a238e107/request_user.json rename to app/spec/support/fixtures/argyle/sarah/request_user.json From 0224ae91f59cedb136d5515d57644e8c73dc6ec6 Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Fri, 7 Mar 2025 11:37:10 -0500 Subject: [PATCH 06/26] working on paystubs --- app/app/models/response_objects/paystub.rb | 4 ++-- app/spec/services/argyle_service_spec.rb | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/app/app/models/response_objects/paystub.rb b/app/app/models/response_objects/paystub.rb index e98b88a44..6a26a5c5b 100644 --- a/app/app/models/response_objects/paystub.rb +++ b/app/app/models/response_objects/paystub.rb @@ -40,8 +40,8 @@ def self.from_argyle(response_body) gross_pay_amount: response_body["gross_pay"], net_pay_amount: response_body["net_pay"], gross_pay_ytd: response_body["gross_pay_ytd"], - # pay_period_start: response_body["pay_period"]["start_date"], - # pay_period_end: response_body["pay_period"]["end_date"], + pay_period_start: DateTime.parse(response_body["paystub_period"]["start_date"]).strftime("%Y-%m-%d"), + pay_period_end: DateTime.parse(response_body["paystub_period"]["end_date"]).strftime("%Y-%m-%d"), pay_date: response_body["paystub_date"], hours: response_body["hours"], hours_by_earning_category: response_body["gross_pay_list"].map do |gross_pay_item| diff --git a/app/spec/services/argyle_service_spec.rb b/app/spec/services/argyle_service_spec.rb index d0f0667e3..62dc391aa 100644 --- a/app/spec/services/argyle_service_spec.rb +++ b/app/spec/services/argyle_service_spec.rb @@ -27,9 +27,21 @@ it 'returns a non-empty response' do # a.fetch_paystubs(user: "0195441c-5a5f-7d86-3be1-fa5797a441a6", from_start_date: "2025-02-20", to_start_ - puts("hello") - response = service.fetch_paystubs(account: end_user_id) - expect(response).not_to be_empty + paystubs = service.fetch_paystubs(account: end_user_id) + expect(paystubs.length).to eq(2) + + expect(paystubs[0]).to be_a(ResponseObjects::Paystub) + + expect(paystubs[0]).to have_attributes( + account_id: "01954440-8c8b-cd52-4a1f-f7aa07d136ed", + gross_pay_amount: "19.53", + net_pay_amount: "19.53", + gross_pay_ytd: "429.15", + pay_date: "2025-02-24", + hours_by_earning_category: [], + deductions: [] + ) + # expect(response).not_to be_empty end end # a.fetch_employment(user: "0195441c-5a5f-7d86-3be1-fa5797a441a6" ) From e06945199b7dff388c3a4910f8ee52a6f092992c Mon Sep 17 00:00:00 2001 From: Tom Dooner Date: Fri, 7 Mar 2025 09:05:32 -0800 Subject: [PATCH 07/26] Use format_date --- app/app/models/response_objects/paystub.rb | 15 +++- app/spec/services/argyle_service_spec.rb | 88 ++++++++++++++++------ 2 files changed, 76 insertions(+), 27 deletions(-) diff --git a/app/app/models/response_objects/paystub.rb b/app/app/models/response_objects/paystub.rb index 6a26a5c5b..15fa8d31e 100644 --- a/app/app/models/response_objects/paystub.rb +++ b/app/app/models/response_objects/paystub.rb @@ -40,9 +40,9 @@ def self.from_argyle(response_body) gross_pay_amount: response_body["gross_pay"], net_pay_amount: response_body["net_pay"], gross_pay_ytd: response_body["gross_pay_ytd"], - pay_period_start: DateTime.parse(response_body["paystub_period"]["start_date"]).strftime("%Y-%m-%d"), - pay_period_end: DateTime.parse(response_body["paystub_period"]["end_date"]).strftime("%Y-%m-%d"), - pay_date: response_body["paystub_date"], + pay_period_start: ArgyleMethods.format_date(response_body["paystub_period"]["start_date"]), + pay_period_end: ArgyleMethods.format_date(response_body["paystub_period"]["end_date"]), + pay_date: ArgyleMethods.format_date(response_body["paystub_date"]), hours: response_body["hours"], hours_by_earning_category: response_body["gross_pay_list"].map do |gross_pay_item| OpenStruct.new( @@ -62,6 +62,7 @@ def self.from_argyle(response_body) alias_attribute :start, :pay_period_start alias_attribute :end, :pay_period_end end + module PinwheelMethods def self.hours(earnings) base_hours = earnings @@ -90,4 +91,12 @@ def self.hours_by_earning_category(earnings) .transform_values { |earnings| earnings.sum { |e| e["hours"] } } end end + + module ArgyleMethods + def self.format_date(date) + return unless date + + DateTime.parse(date).strftime("%Y-%m-%d") + end + end end diff --git a/app/spec/services/argyle_service_spec.rb b/app/spec/services/argyle_service_spec.rb index 62dc391aa..1fdc0f56f 100644 --- a/app/spec/services/argyle_service_spec.rb +++ b/app/spec/services/argyle_service_spec.rb @@ -1,9 +1,5 @@ require 'rails_helper' -BOB_USER_FOLDER = "bob" -SARAH_USER_FOLDER = "sarah" -JOE_USER_FOLDER = "joe" - RSpec.describe ArgyleService, type: :service do include ArgyleApiHelper let(:service) { ArgyleService.new("sandbox", "FAKE_API_KEY") } @@ -21,28 +17,72 @@ # end describe '#fetch_paystubs' do - before do - stub_request_paystubs_response(BOB_USER_FOLDER) + context "for Bob, a Uber driver" do + before do + stub_request_paystubs_response("bob") + end + + it 'returns a non-empty response' do + paystubs = service.fetch_paystubs(account: end_user_id) + expect(paystubs.length).to eq(2) + + expect(paystubs[0]).to be_a(ResponseObjects::Paystub) + expect(paystubs[0]).to have_attributes( + account_id: "01954440-8c8b-cd52-4a1f-f7aa07d136ed", + gross_pay_amount: "19.53", + net_pay_amount: "19.53", + gross_pay_ytd: "429.15", + pay_date: "2025-02-24", + hours_by_earning_category: [], + deductions: [] + ) + expect(paystubs[1]).to have_attributes( + account_id: "01954449-1753-b8b9-8cd9-77b4be11db19", + gross_pay_amount: "36.33", + net_pay_amount: "36.33", + gross_pay_ytd: "369.77", + pay_date: "2025-02-20", + hours_by_earning_category: [], + deductions: [] + ) + end end - it 'returns a non-empty response' do - # a.fetch_paystubs(user: "0195441c-5a5f-7d86-3be1-fa5797a441a6", from_start_date: "2025-02-20", to_start_ - paystubs = service.fetch_paystubs(account: end_user_id) - expect(paystubs.length).to eq(2) - - expect(paystubs[0]).to be_a(ResponseObjects::Paystub) - - expect(paystubs[0]).to have_attributes( - account_id: "01954440-8c8b-cd52-4a1f-f7aa07d136ed", - gross_pay_amount: "19.53", - net_pay_amount: "19.53", - gross_pay_ytd: "429.15", - pay_date: "2025-02-24", - hours_by_earning_category: [], - deductions: [] - ) - # expect(response).not_to be_empty + context "for Joe, a W2 employee" do + before do + stub_request_paystubs_response("joe") + end + + it 'returns a non-empty response' do + paystubs = service.fetch_paystubs(account: end_user_id) + expect(paystubs.length).to eq(10) + + expect(paystubs[0]).to be_a(ResponseObjects::Paystub) + expect(paystubs[0]).to have_attributes( + account_id: "01956d62-18a0-090f-bc09-2ac44b7edf99", + gross_pay_amount: "5492.06", + net_pay_amount: "3350.16", + gross_pay_ytd: "16476.18", + pay_date: "2025-03-03", + hours_by_earning_category: { + "base" => 92.9177 + }, + deductions: match_array([ + have_attributes(category: "pre_tax", amount: "109.84"), + have_attributes(category: "pre_tax", amount: "219.68"), + have_attributes(category: nil, amount: "219.68") + ]) + ) + expect(paystubs[1]).to have_attributes( + account_id: "01954449-1753-b8b9-8cd9-77b4be11db19", + gross_pay_amount: "36.33", + net_pay_amount: "36.33", + gross_pay_ytd: "369.77", + pay_date: "2025-02-20", + hours_by_earning_category: [], + deductions: [] + ) + end end end - # a.fetch_employment(user: "0195441c-5a5f-7d86-3be1-fa5797a441a6" ) end From 48a39eb3b4d8c22e93685fbe39e61249b66b24f2 Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Fri, 7 Mar 2025 12:35:31 -0500 Subject: [PATCH 08/26] fix hours_by_earning --- app/app/models/response_objects/paystub.rb | 16 +++++++++------- app/spec/services/argyle_service_spec.rb | 22 ++++++++++++++-------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/app/app/models/response_objects/paystub.rb b/app/app/models/response_objects/paystub.rb index 15fa8d31e..f8c2a4ed9 100644 --- a/app/app/models/response_objects/paystub.rb +++ b/app/app/models/response_objects/paystub.rb @@ -44,15 +44,10 @@ def self.from_argyle(response_body) pay_period_end: ArgyleMethods.format_date(response_body["paystub_period"]["end_date"]), pay_date: ArgyleMethods.format_date(response_body["paystub_date"]), hours: response_body["hours"], - hours_by_earning_category: response_body["gross_pay_list"].map do |gross_pay_item| - OpenStruct.new( - category: gross_pay_item["type"], - hours: gross_pay_item["hours"], - ) - end, + hours_by_earning_category: ArgyleMethods.hours_by_earning_category(response_body["gross_pay_list"]), deductions: response_body["deduction_list"].map do |deduction| OpenStruct.new( - category: deduction["tax_classification"], + category: deduction["name"], amount: deduction["amount"], ) end, @@ -98,5 +93,12 @@ def self.format_date(date) DateTime.parse(date).strftime("%Y-%m-%d") end + + def self.hours_by_earning_category(gross_pay_list) + gross_pay_list + .filter { |e| e["hours"].present? } + .group_by { |e| e["type"] } + .transform_values { |earnings| earnings.sum { |e| e["hours"].to_f } } + end end end diff --git a/app/spec/services/argyle_service_spec.rb b/app/spec/services/argyle_service_spec.rb index 1fdc0f56f..e09ea56a0 100644 --- a/app/spec/services/argyle_service_spec.rb +++ b/app/spec/services/argyle_service_spec.rb @@ -33,7 +33,7 @@ net_pay_amount: "19.53", gross_pay_ytd: "429.15", pay_date: "2025-02-24", - hours_by_earning_category: [], + hours_by_earning_category: {}, deductions: [] ) expect(paystubs[1]).to have_attributes( @@ -42,7 +42,7 @@ net_pay_amount: "36.33", gross_pay_ytd: "369.77", pay_date: "2025-02-20", - hours_by_earning_category: [], + hours_by_earning_category: {}, deductions: [] ) end @@ -68,19 +68,25 @@ "base" => 92.9177 }, deductions: match_array([ - have_attributes(category: "pre_tax", amount: "109.84"), - have_attributes(category: "pre_tax", amount: "219.68"), - have_attributes(category: nil, amount: "219.68") + have_attributes(category: "401K", amount: "109.84"), + have_attributes(category: "Vision", amount: "219.68"), + have_attributes(category: "Dental", amount: "219.68") ]) ) expect(paystubs[1]).to have_attributes( - account_id: "01954449-1753-b8b9-8cd9-77b4be11db19", + account_id: "01956d62-18a0-090f-bc09-2ac44b7edf99", gross_pay_amount: "36.33", net_pay_amount: "36.33", gross_pay_ytd: "369.77", pay_date: "2025-02-20", - hours_by_earning_category: [], - deductions: [] + hours_by_earning_category: { + "base" => 174.4026 + }, + deductions: match_array([ + have_attributes(category: "Dental", amount: "164.76"), + have_attributes(category: "Roth", amount: "164.76"), + have_attributes(category: "Garnishment", amount: "164.76") + ]) ) end end From 506d9630112cbca750b60cb69883d4ffdb5d4829 Mon Sep 17 00:00:00 2001 From: Tom Dooner Date: Fri, 7 Mar 2025 09:58:56 -0800 Subject: [PATCH 09/26] Fix test data for bob --- app/TODO.txt | 10 + .../fixtures/argyle/bob/request_accounts.json | 153 ++------------ .../argyle/bob/request_employment.json | 19 +- .../fixtures/argyle/bob/request_identity.json | 65 +----- .../fixtures/argyle/bob/request_paystubs.json | 200 +++++++++++------- .../fixtures/argyle/bob/request_user.json | 13 +- 6 files changed, 174 insertions(+), 286 deletions(-) create mode 100644 app/TODO.txt diff --git a/app/TODO.txt b/app/TODO.txt new file mode 100644 index 000000000..f5ec9f12b --- /dev/null +++ b/app/TODO.txt @@ -0,0 +1,10 @@ +TODO on this branch: + +1. Wrap up Bob test data +2. Write tests for all three test users +3. User ID --> Account ID refactor +4. Rewrite ArgyleService methods to instantiate response objects based on /identities +5. Then, update PinwheelDataHelper to use ArgyleService +6. Figure out missing fields for employer address/phone +7. Update view logic as necessary +8. Verify that Argyle dollar amount strings are being rendered correctly diff --git a/app/spec/support/fixtures/argyle/bob/request_accounts.json b/app/spec/support/fixtures/argyle/bob/request_accounts.json index bba018b9a..c9b9b2c03 100644 --- a/app/spec/support/fixtures/argyle/bob/request_accounts.json +++ b/app/spec/support/fixtures/argyle/bob/request_accounts.json @@ -3,178 +3,69 @@ "previous": null, "results": [ { - "id": "01954449-1753-b8b9-8cd9-77b4be11db19", - "user": "0195441c-5a5f-7d86-3be1-fa5797a441a6", + "id": "019571bc-2f60-3955-d972-dbadfe0913a8", + "user": "019571bb-e574-a88e-7d7b-15c886499696", "employers": [ "Lyft Driver" ], "item": "item_000024123", "source": "argyle_sandbox", - "created_at": "2025-02-26T22:03:15.718479Z", - "updated_at": "2025-02-26T22:05:14.987616Z", - "scanned_at": "2025-02-26T22:04:52.909487Z", + "created_at": "2025-03-07T17:51:53.292760Z", + "updated_at": "2025-03-07T17:53:44.069848Z", + "scanned_at": "2025-03-07T17:53:28.924039Z", "connection": { "status": "connected", "error_code": null, "error_message": null, - "updated_at": "2025-02-26T22:03:30.606428Z" + "updated_at": "2025-03-07T17:52:00.025845Z" }, "direct_deposit_switch": { "status": "idle", "error_code": null, "error_message": null, - "updated_at": "2025-02-26T22:03:16.059091Z" + "updated_at": "2025-03-07T17:51:53.928586Z" }, "availability": { "gigs": { "status": "synced", - "updated_at": "2025-02-26T22:05:14.591846Z", - "available_count": 2393, - "available_from": "2022-05-26T22:03:30Z", - "available_to": "2025-02-26T12:02:10Z" + "updated_at": "2025-03-07T17:53:43.948396Z", + "available_count": 2483, + "available_from": "2022-04-07T17:51:59Z", + "available_to": "2025-03-06T19:01:04Z" }, "shifts": { "status": "synced", - "updated_at": "2025-02-26T22:04:50.502237Z", + "updated_at": "2025-03-07T17:53:27.404963Z", "available_count": 0, "available_from": null, "available_to": null }, "paystubs": { "status": "synced", - "updated_at": "2025-02-26T22:05:14.421661Z", - "available_count": 144, - "available_from": "2022-05-26T22:03:30Z", - "available_to": "2025-02-20T00:00:00Z" + "updated_at": "2025-03-07T17:53:43.629317Z", + "available_count": 153, + "available_from": "2022-04-07T17:51:59Z", + "available_to": "2025-03-06T00:00:00Z" }, "payroll_documents": { "status": "synced", - "updated_at": "2025-02-26T22:03:33.868143Z" + "updated_at": "2025-03-07T17:52:03.241234Z" }, "identities": { "status": "synced", - "updated_at": "2025-02-26T22:05:14.792144Z" + "updated_at": "2025-03-07T17:53:43.861262Z" }, "ratings": { "status": "synced", - "updated_at": "2025-02-26T22:04:52.642065Z" + "updated_at": "2025-03-07T17:53:28.678568Z" }, "vehicles": { "status": "synced", - "updated_at": "2025-02-26T22:03:33.887125Z" + "updated_at": "2025-03-07T17:52:02.980592Z" }, "deposit_destinations": { "status": "synced", - "updated_at": "2025-02-26T22:03:32.177667Z" - }, - "user_forms": null, - "user_uploads": null - }, - "ongoing_refresh": { - "status": "enabled" - } - }, - { - "id": "01954447-d202-faca-2a01-1861f39e0e86", - "user": "0195441c-5a5f-7d86-3be1-fa5797a441a6", - "employers": [], - "item": "item_000020392", - "source": "argyle_sandbox", - "created_at": "2025-02-26T22:01:52.441034Z", - "updated_at": "2025-02-26T22:11:53.634198Z", - "scanned_at": null, - "connection": { - "status": "error", - "error_code": "mfa_timeout", - "error_message": "This user did not complete multi-factor authentication.", - "updated_at": "2025-02-26T22:11:53.417285Z" - }, - "direct_deposit_switch": { - "status": "idle", - "error_code": null, - "error_message": null, - "updated_at": "2025-02-26T22:01:52.868602Z" - }, - "availability": { - "gigs": null, - "shifts": null, - "paystubs": null, - "payroll_documents": null, - "identities": null, - "ratings": null, - "vehicles": null, - "deposit_destinations": null, - "user_forms": null, - "user_uploads": null - }, - "ongoing_refresh": { - "status": "idle" - } - }, - { - "id": "01954440-8c8b-cd52-4a1f-f7aa07d136ed", - "user": "0195441c-5a5f-7d86-3be1-fa5797a441a6", - "employers": [ - "Uber Driver" - ], - "item": "item_000041078", - "source": "argyle_sandbox", - "created_at": "2025-02-26T21:53:55.907174Z", - "updated_at": "2025-02-26T21:57:41.151118Z", - "scanned_at": "2025-02-26T21:57:36.482239Z", - "connection": { - "status": "connected", - "error_code": null, - "error_message": null, - "updated_at": "2025-02-26T21:56:17.467233Z" - }, - "direct_deposit_switch": { - "status": "idle", - "error_code": null, - "error_message": null, - "updated_at": "2025-02-26T21:53:56.374685Z" - }, - "availability": { - "gigs": { - "status": "synced", - "updated_at": "2025-02-26T21:57:40.873143Z", - "available_count": 2368, - "available_from": "2022-06-27T21:56:17Z", - "available_to": "2025-02-26T20:40:29Z" - }, - "shifts": { - "status": "synced", - "updated_at": "2025-02-26T21:57:34.254754Z", - "available_count": 0, - "available_from": null, - "available_to": null - }, - "paystubs": { - "status": "synced", - "updated_at": "2025-02-26T21:57:40.708501Z", - "available_count": 140, - "available_from": "2022-06-27T21:56:17Z", - "available_to": "2025-02-24T00:00:00Z" - }, - "payroll_documents": { - "status": "synced", - "updated_at": "2025-02-26T21:56:20.360100Z" - }, - "identities": { - "status": "synced", - "updated_at": "2025-02-26T21:57:40.940274Z" - }, - "ratings": { - "status": "synced", - "updated_at": "2025-02-26T21:57:36.238637Z" - }, - "vehicles": { - "status": "synced", - "updated_at": "2025-02-26T21:56:20.375923Z" - }, - "deposit_destinations": { - "status": "synced", - "updated_at": "2025-02-26T21:56:18.662289Z" + "updated_at": "2025-03-07T17:52:01.238846Z" }, "user_forms": null, "user_uploads": null @@ -184,4 +75,4 @@ } } ] -} \ No newline at end of file +} diff --git a/app/spec/support/fixtures/argyle/bob/request_employment.json b/app/spec/support/fixtures/argyle/bob/request_employment.json index f14528d80..60437fa5b 100644 --- a/app/spec/support/fixtures/argyle/bob/request_employment.json +++ b/app/spec/support/fixtures/argyle/bob/request_employment.json @@ -1,22 +1,13 @@ { "results": [ { - "id": "31188b69-703a-3d6e-b6f6-24073ca84263", + "id": "ed98b483-04f3-374b-b6e0-700efadc9349", "employer": "Lyft Driver", "employment_status": "active", "origin": "retrieved", - "user": "0195441c-5a5f-7d86-3be1-fa5797a441a6", - "account": "01954449-1753-b8b9-8cd9-77b4be11db19", - "created_at": "2025-02-26T22:03:33.246Z" - }, - { - "id": "26396fa4-6213-39a6-902a-f9a85d26db4d", - "employer": "Uber Driver", - "employment_status": "active", - "origin": "retrieved", - "user": "0195441c-5a5f-7d86-3be1-fa5797a441a6", - "account": "01954440-8c8b-cd52-4a1f-f7aa07d136ed", - "created_at": "2025-02-26T21:56:19.699Z" + "user": "019571bb-e574-a88e-7d7b-15c886499696", + "account": "019571bc-2f60-3955-d972-dbadfe0913a8", + "created_at": "2025-03-07T17:52:02.336Z" } ] -} \ No newline at end of file +} diff --git a/app/spec/support/fixtures/argyle/bob/request_identity.json b/app/spec/support/fixtures/argyle/bob/request_identity.json index 2800226cb..db7d6514b 100644 --- a/app/spec/support/fixtures/argyle/bob/request_identity.json +++ b/app/spec/support/fixtures/argyle/bob/request_identity.json @@ -1,8 +1,8 @@ { "results": [ { - "id": "87555e33-69a2-3860-b431-1f36a3dee3d2", - "account": "01954449-1753-b8b9-8cd9-77b4be11db19", + "id": "408c332f-cc7b-327e-b3a4-0a46de8429ab", + "account": "019571bc-2f60-3955-d972-dbadfe0913a8", "address": { "city": "New York", "line1": "759 Victoria Plaza", @@ -17,15 +17,15 @@ "birth_date": "1980-10-10", "email": "test1@argyle.com", "phone_number": "+18009000010", - "picture_url": "https://api-sandbox.argyle.com/v2/payroll-documents/01954449-5c6c-0fcb-cd9b-681bc0dc29ac/file", + "picture_url": "https://api-sandbox.argyle.com/v2/payroll-documents/019571bc-53ce-5c4d-2e6c-4d7f91229595/file", "employment_status": "active", "employment_type": "contractor", "job_title": null, "ssn": "522-09-1191", "marital_status": "Married filing jointly", "gender": "Male", - "hire_date": "2022-05-26", - "original_hire_date": "2022-05-26", + "hire_date": "2022-04-07", + "original_hire_date": "2022-04-07", "termination_date": null, "termination_reason": null, "employer": "Lyft Driver", @@ -38,59 +38,14 @@ "platform_ids": { "employee_id": null, "position_id": null, - "platform_user_id": "0HVZXB0KPT20I814" + "platform_user_id": "Y9LWFN703R5WV487" }, - "created_at": "2025-02-26T22:03:33.246Z", - "updated_at": "2025-02-26T22:05:14.780Z", + "created_at": "2025-03-07T17:52:02.336Z", + "updated_at": "2025-03-07T17:53:43.839Z", "metadata": {}, - "employment": "31188b69-703a-3d6e-b6f6-24073ca84263" - }, - { - "id": "c37f205a-3d53-3b18-ab79-8d201d6365a6", - "account": "01954440-8c8b-cd52-4a1f-f7aa07d136ed", - "address": { - "city": "New York", - "line1": "759 Victoria Plaza", - "line2": null, - "state": "NY", - "country": "US", - "postal_code": "10014" - }, - "first_name": "Bob", - "last_name": "Jones", - "full_name": "Bob Jones", - "birth_date": "1980-10-10", - "email": "test1@argyle.com", - "phone_number": "+18009000010", - "picture_url": "https://api-sandbox.argyle.com/v2/payroll-documents/01954442-befa-3df4-01b8-98a95b2f5651/file", - "employment_status": "active", - "employment_type": "contractor", - "job_title": null, - "ssn": "522-09-1191", - "marital_status": "Married filing jointly", - "gender": "Male", - "hire_date": "2022-06-27", - "original_hire_date": "2022-06-27", - "termination_date": null, - "termination_reason": null, - "employer": "Uber Driver", - "base_pay": { - "amount": null, - "period": null, - "currency": null - }, - "pay_cycle": "weekly", - "platform_ids": { - "employee_id": null, - "position_id": null, - "platform_user_id": "UJH7BSZUE9WTV9AV" - }, - "created_at": "2025-02-26T21:56:19.699Z", - "updated_at": "2025-02-26T21:57:40.926Z", - "metadata": {}, - "employment": "26396fa4-6213-39a6-902a-f9a85d26db4d" + "employment": "ed98b483-04f3-374b-b6e0-700efadc9349" } ], "next": null, "previous": null -} \ No newline at end of file +} diff --git a/app/spec/support/fixtures/argyle/bob/request_paystubs.json b/app/spec/support/fixtures/argyle/bob/request_paystubs.json index e05f7e56c..022795ad3 100644 --- a/app/spec/support/fixtures/argyle/bob/request_paystubs.json +++ b/app/spec/support/fixtures/argyle/bob/request_paystubs.json @@ -1,78 +1,122 @@ -{ - "results": [ - { - "id": "c7f88cf2-5267-37a9-9d78-b2f9dd942a2e", - "account": "01954440-8c8b-cd52-4a1f-f7aa07d136ed", - "payroll_document": null, - "deduction_list": [], - "filing_status": [], - "tax_list": [], - "status": "completed", - "paystub_date": "2025-02-24T00:00:00Z", - "paystub_period": { - "start_date": null, - "end_date": null - }, - "currency": "USD", - "gross_pay": "19.53", - "deductions": null, - "taxes": null, - "net_pay": "19.53", - "reimbursements": null, - "hours": null, - "fees": null, - "net_pay_ytd": "429.15", - "gross_pay_ytd": "429.15", - "metadata": {}, - "employer": "Uber Driver", - "employer_address": null, - "created_at": "2025-02-26T21:56:26.219Z", - "updated_at": "2025-02-26T21:56:26.219Z", - "destinations": [], - "hours_ytd": "0.00", - "deductions_ytd": "0.00", - "taxes_ytd": "0.00", - "gross_pay_list": [], - "gross_pay_list_totals": null, - "employment": "26396fa4-6213-39a6-902a-f9a85d26db4d" - }, - { - "id": "b2c1d851-e1e0-33d4-bbfa-2e3868979361", - "account": "01954449-1753-b8b9-8cd9-77b4be11db19", - "payroll_document": null, - "deduction_list": [], - "filing_status": [], - "tax_list": [], - "status": "completed", - "paystub_date": "2025-02-20T00:00:00Z", - "paystub_period": { - "start_date": null, - "end_date": null - }, - "currency": "USD", - "gross_pay": "36.33", - "deductions": null, - "taxes": null, - "net_pay": "36.33", - "reimbursements": null, - "hours": null, - "fees": null, - "net_pay_ytd": "369.77", - "gross_pay_ytd": "369.77", - "metadata": {}, - "employer": "Lyft Driver", - "employer_address": null, - "created_at": "2025-02-26T22:03:41.088Z", - "updated_at": "2025-02-26T22:03:41.088Z", - "destinations": [], - "hours_ytd": "0.00", - "deductions_ytd": "0.00", - "taxes_ytd": "0.00", - "gross_pay_list": [], - "gross_pay_list_totals": null, - "employment": "31188b69-703a-3d6e-b6f6-24073ca84263" - } - ], - "next": null, - "previous": null -} \ No newline at end of file +[ + { + "account_id": "019571bc-2f60-3955-d972-dbadfe0913a8", + "gross_pay_amount": "34.56", + "net_pay_amount": "34.56", + "gross_pay_ytd": "547.68", + "pay_period_start": null, + "pay_period_end": null, + "pay_date": "2025-03-06", + "deductions": [], + "hours_by_earning_category": {}, + "hours": null + }, + { + "account_id": "019571bc-2f60-3955-d972-dbadfe0913a8", + "gross_pay_amount": "17.13", + "net_pay_amount": "17.13", + "gross_pay_ytd": "513.12", + "pay_period_start": null, + "pay_period_end": null, + "pay_date": "2025-02-27", + "deductions": [], + "hours_by_earning_category": {}, + "hours": null + }, + { + "account_id": "019571bc-2f60-3955-d972-dbadfe0913a8", + "gross_pay_amount": "93.69", + "net_pay_amount": "93.69", + "gross_pay_ytd": "495.99", + "pay_period_start": null, + "pay_period_end": null, + "pay_date": "2025-02-20", + "deductions": [], + "hours_by_earning_category": {}, + "hours": null + }, + { + "account_id": "019571bc-2f60-3955-d972-dbadfe0913a8", + "gross_pay_amount": "42.08", + "net_pay_amount": "42.08", + "gross_pay_ytd": "402.30", + "pay_period_start": null, + "pay_period_end": null, + "pay_date": "2025-02-13", + "deductions": [], + "hours_by_earning_category": {}, + "hours": null + }, + { + "account_id": "019571bc-2f60-3955-d972-dbadfe0913a8", + "gross_pay_amount": "77.85", + "net_pay_amount": "77.85", + "gross_pay_ytd": "360.22", + "pay_period_start": null, + "pay_period_end": null, + "pay_date": "2025-02-06", + "deductions": [], + "hours_by_earning_category": {}, + "hours": null + }, + { + "account_id": "019571bc-2f60-3955-d972-dbadfe0913a8", + "gross_pay_amount": "43.47", + "net_pay_amount": "43.47", + "gross_pay_ytd": "282.37", + "pay_period_start": null, + "pay_period_end": null, + "pay_date": "2025-01-30", + "deductions": [], + "hours_by_earning_category": {}, + "hours": null + }, + { + "account_id": "019571bc-2f60-3955-d972-dbadfe0913a8", + "gross_pay_amount": "75.36", + "net_pay_amount": "75.36", + "gross_pay_ytd": "238.90", + "pay_period_start": null, + "pay_period_end": null, + "pay_date": "2025-01-23", + "deductions": [], + "hours_by_earning_category": {}, + "hours": null + }, + { + "account_id": "019571bc-2f60-3955-d972-dbadfe0913a8", + "gross_pay_amount": "81.98", + "net_pay_amount": "81.98", + "gross_pay_ytd": "163.54", + "pay_period_start": null, + "pay_period_end": null, + "pay_date": "2025-01-16", + "deductions": [], + "hours_by_earning_category": {}, + "hours": null + }, + { + "account_id": "019571bc-2f60-3955-d972-dbadfe0913a8", + "gross_pay_amount": "6.58", + "net_pay_amount": "6.58", + "gross_pay_ytd": "81.56", + "pay_period_start": null, + "pay_period_end": null, + "pay_date": "2025-01-09", + "deductions": [], + "hours_by_earning_category": {}, + "hours": null + }, + { + "account_id": "019571bc-2f60-3955-d972-dbadfe0913a8", + "gross_pay_amount": "74.98", + "net_pay_amount": "74.98", + "gross_pay_ytd": "74.98", + "pay_period_start": null, + "pay_period_end": null, + "pay_date": "2025-01-02", + "deductions": [], + "hours_by_earning_category": {}, + "hours": null + } +] diff --git a/app/spec/support/fixtures/argyle/bob/request_user.json b/app/spec/support/fixtures/argyle/bob/request_user.json index fcbace752..9c0c780f5 100644 --- a/app/spec/support/fixtures/argyle/bob/request_user.json +++ b/app/spec/support/fixtures/argyle/bob/request_user.json @@ -1,15 +1,12 @@ { - "id": "0195441c-5a5f-7d86-3be1-fa5797a441a6", - "created_at": "2025-02-26T21:14:23.712349Z", + "id": "019571bb-e574-a88e-7d7b-15c886499696", + "created_at": "2025-03-07T17:51:34.261176Z", "items_connected": [ - "item_000024123", - "item_000020392", - "item_000041078" + "item_000024123" ], "employers_connected": [ - "Uber Driver", "Lyft Driver" ], "external_metadata": {}, - "external_id": "testing" -} \ No newline at end of file + "external_id": null +} From 444fe292e25c50bb5803c467f20ac6b169db1fa3 Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Fri, 7 Mar 2025 15:06:25 -0500 Subject: [PATCH 10/26] update bob's paystubs to be the actual api response, not the response objects --- .../fixtures/argyle/bob/request_paystubs.json | 488 +++++++++++++----- 1 file changed, 366 insertions(+), 122 deletions(-) diff --git a/app/spec/support/fixtures/argyle/bob/request_paystubs.json b/app/spec/support/fixtures/argyle/bob/request_paystubs.json index 022795ad3..19d75188a 100644 --- a/app/spec/support/fixtures/argyle/bob/request_paystubs.json +++ b/app/spec/support/fixtures/argyle/bob/request_paystubs.json @@ -1,122 +1,366 @@ -[ - { - "account_id": "019571bc-2f60-3955-d972-dbadfe0913a8", - "gross_pay_amount": "34.56", - "net_pay_amount": "34.56", - "gross_pay_ytd": "547.68", - "pay_period_start": null, - "pay_period_end": null, - "pay_date": "2025-03-06", - "deductions": [], - "hours_by_earning_category": {}, - "hours": null - }, - { - "account_id": "019571bc-2f60-3955-d972-dbadfe0913a8", - "gross_pay_amount": "17.13", - "net_pay_amount": "17.13", - "gross_pay_ytd": "513.12", - "pay_period_start": null, - "pay_period_end": null, - "pay_date": "2025-02-27", - "deductions": [], - "hours_by_earning_category": {}, - "hours": null - }, - { - "account_id": "019571bc-2f60-3955-d972-dbadfe0913a8", - "gross_pay_amount": "93.69", - "net_pay_amount": "93.69", - "gross_pay_ytd": "495.99", - "pay_period_start": null, - "pay_period_end": null, - "pay_date": "2025-02-20", - "deductions": [], - "hours_by_earning_category": {}, - "hours": null - }, - { - "account_id": "019571bc-2f60-3955-d972-dbadfe0913a8", - "gross_pay_amount": "42.08", - "net_pay_amount": "42.08", - "gross_pay_ytd": "402.30", - "pay_period_start": null, - "pay_period_end": null, - "pay_date": "2025-02-13", - "deductions": [], - "hours_by_earning_category": {}, - "hours": null - }, - { - "account_id": "019571bc-2f60-3955-d972-dbadfe0913a8", - "gross_pay_amount": "77.85", - "net_pay_amount": "77.85", - "gross_pay_ytd": "360.22", - "pay_period_start": null, - "pay_period_end": null, - "pay_date": "2025-02-06", - "deductions": [], - "hours_by_earning_category": {}, - "hours": null - }, - { - "account_id": "019571bc-2f60-3955-d972-dbadfe0913a8", - "gross_pay_amount": "43.47", - "net_pay_amount": "43.47", - "gross_pay_ytd": "282.37", - "pay_period_start": null, - "pay_period_end": null, - "pay_date": "2025-01-30", - "deductions": [], - "hours_by_earning_category": {}, - "hours": null - }, - { - "account_id": "019571bc-2f60-3955-d972-dbadfe0913a8", - "gross_pay_amount": "75.36", - "net_pay_amount": "75.36", - "gross_pay_ytd": "238.90", - "pay_period_start": null, - "pay_period_end": null, - "pay_date": "2025-01-23", - "deductions": [], - "hours_by_earning_category": {}, - "hours": null - }, - { - "account_id": "019571bc-2f60-3955-d972-dbadfe0913a8", - "gross_pay_amount": "81.98", - "net_pay_amount": "81.98", - "gross_pay_ytd": "163.54", - "pay_period_start": null, - "pay_period_end": null, - "pay_date": "2025-01-16", - "deductions": [], - "hours_by_earning_category": {}, - "hours": null - }, - { - "account_id": "019571bc-2f60-3955-d972-dbadfe0913a8", - "gross_pay_amount": "6.58", - "net_pay_amount": "6.58", - "gross_pay_ytd": "81.56", - "pay_period_start": null, - "pay_period_end": null, - "pay_date": "2025-01-09", - "deductions": [], - "hours_by_earning_category": {}, - "hours": null - }, - { - "account_id": "019571bc-2f60-3955-d972-dbadfe0913a8", - "gross_pay_amount": "74.98", - "net_pay_amount": "74.98", - "gross_pay_ytd": "74.98", - "pay_period_start": null, - "pay_period_end": null, - "pay_date": "2025-01-02", - "deductions": [], - "hours_by_earning_category": {}, - "hours": null - } -] +{ + "results": [ + { + "id": "c4a0595c-67c4-3f63-bb1e-3a8c4fde13cd", + "account": "019571bc-2f60-3955-d972-dbadfe0913a8", + "payroll_document": null, + "deduction_list": [], + "filing_status": [], + "tax_list": [], + "status": "completed", + "paystub_date": "2025-03-06T00:00:00Z", + "paystub_period": { + "start_date": null, + "end_date": null + }, + "currency": "USD", + "gross_pay": "34.56", + "deductions": null, + "taxes": null, + "net_pay": "34.56", + "reimbursements": null, + "hours": null, + "fees": null, + "net_pay_ytd": "547.68", + "gross_pay_ytd": "547.68", + "metadata": {}, + "employer": "Lyft Driver", + "employer_address": null, + "created_at": "2025-03-07T17:52:09.627Z", + "updated_at": "2025-03-07T17:52:09.627Z", + "destinations": [], + "hours_ytd": "0.00", + "deductions_ytd": "0.00", + "taxes_ytd": "0.00", + "gross_pay_list": [], + "gross_pay_list_totals": null, + "employment": "ed98b483-04f3-374b-b6e0-700efadc9349" + }, + { + "id": "45d11230-2486-360d-861f-58dd2d7b6355", + "account": "019571bc-2f60-3955-d972-dbadfe0913a8", + "payroll_document": null, + "deduction_list": [], + "filing_status": [], + "tax_list": [], + "status": "completed", + "paystub_date": "2025-02-27T00:00:00Z", + "paystub_period": { + "start_date": null, + "end_date": null + }, + "currency": "USD", + "gross_pay": "17.13", + "deductions": null, + "taxes": null, + "net_pay": "17.13", + "reimbursements": null, + "hours": null, + "fees": null, + "net_pay_ytd": "513.12", + "gross_pay_ytd": "513.12", + "metadata": {}, + "employer": "Lyft Driver", + "employer_address": null, + "created_at": "2025-03-07T17:52:09.682Z", + "updated_at": "2025-03-07T17:52:09.682Z", + "destinations": [], + "hours_ytd": "0.00", + "deductions_ytd": "0.00", + "taxes_ytd": "0.00", + "gross_pay_list": [], + "gross_pay_list_totals": null, + "employment": "ed98b483-04f3-374b-b6e0-700efadc9349" + }, + { + "id": "018d882d-53e5-3c77-9c26-7df1d1bf1c8d", + "account": "019571bc-2f60-3955-d972-dbadfe0913a8", + "payroll_document": null, + "deduction_list": [], + "filing_status": [], + "tax_list": [], + "status": "completed", + "paystub_date": "2025-02-20T00:00:00Z", + "paystub_period": { + "start_date": null, + "end_date": null + }, + "currency": "USD", + "gross_pay": "93.69", + "deductions": null, + "taxes": null, + "net_pay": "93.69", + "reimbursements": null, + "hours": null, + "fees": null, + "net_pay_ytd": "495.99", + "gross_pay_ytd": "495.99", + "metadata": {}, + "employer": "Lyft Driver", + "employer_address": null, + "created_at": "2025-03-07T17:52:09.663Z", + "updated_at": "2025-03-07T17:52:09.663Z", + "destinations": [], + "hours_ytd": "0.00", + "deductions_ytd": "0.00", + "taxes_ytd": "0.00", + "gross_pay_list": [], + "gross_pay_list_totals": null, + "employment": "ed98b483-04f3-374b-b6e0-700efadc9349" + }, + { + "id": "2c2c780a-904d-3c68-90fa-3eebc6ad985a", + "account": "019571bc-2f60-3955-d972-dbadfe0913a8", + "payroll_document": null, + "deduction_list": [], + "filing_status": [], + "tax_list": [], + "status": "completed", + "paystub_date": "2025-02-13T00:00:00Z", + "paystub_period": { + "start_date": null, + "end_date": null + }, + "currency": "USD", + "gross_pay": "42.08", + "deductions": null, + "taxes": null, + "net_pay": "42.08", + "reimbursements": null, + "hours": null, + "fees": null, + "net_pay_ytd": "402.30", + "gross_pay_ytd": "402.30", + "metadata": {}, + "employer": "Lyft Driver", + "employer_address": null, + "created_at": "2025-03-07T17:52:09.646Z", + "updated_at": "2025-03-07T17:52:09.646Z", + "destinations": [], + "hours_ytd": "0.00", + "deductions_ytd": "0.00", + "taxes_ytd": "0.00", + "gross_pay_list": [], + "gross_pay_list_totals": null, + "employment": "ed98b483-04f3-374b-b6e0-700efadc9349" + }, + { + "id": "47d6cbc8-bbaf-3fac-8b85-8f55c3f28f45", + "account": "019571bc-2f60-3955-d972-dbadfe0913a8", + "payroll_document": null, + "deduction_list": [], + "filing_status": [], + "tax_list": [], + "status": "completed", + "paystub_date": "2025-02-06T00:00:00Z", + "paystub_period": { + "start_date": null, + "end_date": null + }, + "currency": "USD", + "gross_pay": "77.85", + "deductions": null, + "taxes": null, + "net_pay": "77.85", + "reimbursements": null, + "hours": null, + "fees": null, + "net_pay_ytd": "360.22", + "gross_pay_ytd": "360.22", + "metadata": {}, + "employer": "Lyft Driver", + "employer_address": null, + "created_at": "2025-03-07T17:52:09.701Z", + "updated_at": "2025-03-07T17:52:09.701Z", + "destinations": [], + "hours_ytd": "0.00", + "deductions_ytd": "0.00", + "taxes_ytd": "0.00", + "gross_pay_list": [], + "gross_pay_list_totals": null, + "employment": "ed98b483-04f3-374b-b6e0-700efadc9349" + }, + { + "id": "c2befc6f-7d70-3750-8384-bf7e056c53e6", + "account": "019571bc-2f60-3955-d972-dbadfe0913a8", + "payroll_document": null, + "deduction_list": [], + "filing_status": [], + "tax_list": [], + "status": "completed", + "paystub_date": "2025-01-30T00:00:00Z", + "paystub_period": { + "start_date": null, + "end_date": null + }, + "currency": "USD", + "gross_pay": "43.47", + "deductions": null, + "taxes": null, + "net_pay": "43.47", + "reimbursements": null, + "hours": null, + "fees": null, + "net_pay_ytd": "282.37", + "gross_pay_ytd": "282.37", + "metadata": {}, + "employer": "Lyft Driver", + "employer_address": null, + "created_at": "2025-03-07T17:52:09.719Z", + "updated_at": "2025-03-07T17:52:09.719Z", + "destinations": [], + "hours_ytd": "0.00", + "deductions_ytd": "0.00", + "taxes_ytd": "0.00", + "gross_pay_list": [], + "gross_pay_list_totals": null, + "employment": "ed98b483-04f3-374b-b6e0-700efadc9349" + }, + { + "id": "f94156bb-18e6-3c7f-a8df-ce50e314a0b4", + "account": "019571bc-2f60-3955-d972-dbadfe0913a8", + "payroll_document": null, + "deduction_list": [], + "filing_status": [], + "tax_list": [], + "status": "completed", + "paystub_date": "2025-01-23T00:00:00Z", + "paystub_period": { + "start_date": null, + "end_date": null + }, + "currency": "USD", + "gross_pay": "75.36", + "deductions": null, + "taxes": null, + "net_pay": "75.36", + "reimbursements": null, + "hours": null, + "fees": null, + "net_pay_ytd": "238.90", + "gross_pay_ytd": "238.90", + "metadata": {}, + "employer": "Lyft Driver", + "employer_address": null, + "created_at": "2025-03-07T17:52:09.738Z", + "updated_at": "2025-03-07T17:52:09.738Z", + "destinations": [], + "hours_ytd": "0.00", + "deductions_ytd": "0.00", + "taxes_ytd": "0.00", + "gross_pay_list": [], + "gross_pay_list_totals": null, + "employment": "ed98b483-04f3-374b-b6e0-700efadc9349" + }, + { + "id": "5af40e4a-f2e2-30c5-a271-adb0d268557b", + "account": "019571bc-2f60-3955-d972-dbadfe0913a8", + "payroll_document": null, + "deduction_list": [], + "filing_status": [], + "tax_list": [], + "status": "completed", + "paystub_date": "2025-01-16T00:00:00Z", + "paystub_period": { + "start_date": null, + "end_date": null + }, + "currency": "USD", + "gross_pay": "81.98", + "deductions": null, + "taxes": null, + "net_pay": "81.98", + "reimbursements": null, + "hours": null, + "fees": null, + "net_pay_ytd": "163.54", + "gross_pay_ytd": "163.54", + "metadata": {}, + "employer": "Lyft Driver", + "employer_address": null, + "created_at": "2025-03-07T17:52:09.608Z", + "updated_at": "2025-03-07T17:52:09.608Z", + "destinations": [], + "hours_ytd": "0.00", + "deductions_ytd": "0.00", + "taxes_ytd": "0.00", + "gross_pay_list": [], + "gross_pay_list_totals": null, + "employment": "ed98b483-04f3-374b-b6e0-700efadc9349" + }, + { + "id": "004cab9b-bfd4-3b32-ab90-82b98e5426da", + "account": "019571bc-2f60-3955-d972-dbadfe0913a8", + "payroll_document": null, + "deduction_list": [], + "filing_status": [], + "tax_list": [], + "status": "completed", + "paystub_date": "2025-01-09T00:00:00Z", + "paystub_period": { + "start_date": null, + "end_date": null + }, + "currency": "USD", + "gross_pay": "6.58", + "deductions": null, + "taxes": null, + "net_pay": "6.58", + "reimbursements": null, + "hours": null, + "fees": null, + "net_pay_ytd": "81.56", + "gross_pay_ytd": "81.56", + "metadata": {}, + "employer": "Lyft Driver", + "employer_address": null, + "created_at": "2025-03-07T17:52:24.235Z", + "updated_at": "2025-03-07T17:52:24.235Z", + "destinations": [], + "hours_ytd": "0.00", + "deductions_ytd": "0.00", + "taxes_ytd": "0.00", + "gross_pay_list": [], + "gross_pay_list_totals": null, + "employment": "ed98b483-04f3-374b-b6e0-700efadc9349" + }, + { + "id": "c5e2806d-c4d0-3cc8-bcf3-41632a87b13b", + "account": "019571bc-2f60-3955-d972-dbadfe0913a8", + "payroll_document": null, + "deduction_list": [], + "filing_status": [], + "tax_list": [], + "status": "completed", + "paystub_date": "2025-01-02T00:00:00Z", + "paystub_period": { + "start_date": null, + "end_date": null + }, + "currency": "USD", + "gross_pay": "74.98", + "deductions": null, + "taxes": null, + "net_pay": "74.98", + "reimbursements": null, + "hours": null, + "fees": null, + "net_pay_ytd": "74.98", + "gross_pay_ytd": "74.98", + "metadata": {}, + "employer": "Lyft Driver", + "employer_address": null, + "created_at": "2025-03-07T17:52:24.251Z", + "updated_at": "2025-03-07T17:52:24.251Z", + "destinations": [], + "hours_ytd": "0.00", + "deductions_ytd": "0.00", + "taxes_ytd": "0.00", + "gross_pay_list": [], + "gross_pay_list_totals": null, + "employment": "ed98b483-04f3-374b-b6e0-700efadc9349" + } + ], + "next": "https://api-sandbox.argyle.com/v2/paystubs?cursor=Y2xpZW50SWQ9MDE4ZjA4MzQtZDY5YS04NzliLTA1Y2ItZGRkZTAzMzAyNGYwJmludmVyc2VTb3J0PWZhbHNlJmxpbWl0PTEwJnVzZXJJZD0wMTk1NzFiYi1lNTc0LWE4OGUtN2Q3Yi0xNWM4ODY0OTk2OTYmcm93S2V5PVMlMjNVJTIzMDE5NTcxYmItZTU3NC1hODhlLTdkN2ItMTVjODg2NDk5Njk2JTIzNDYxMTY4NDI4MzI1NjE4NzkwMyUyM2E4ODRmY2NhLWZlMDQtMzE4MS1iNTNjLTIyY2Y0OTkxYWQzOA%3D%3D", + "previous": null +} \ No newline at end of file From 79523677cba8574775972bf60a768c72cafb232f Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Fri, 7 Mar 2025 15:33:50 -0500 Subject: [PATCH 11/26] added test cases for fetch_paystubs. changed all currency amounts from string to floats --- app/app/models/response_objects/paystub.rb | 13 ++- app/app/services/argyle_service.rb | 31 +++--- app/spec/services/argyle_service_spec.rb | 104 +++++++++++++++------ 3 files changed, 98 insertions(+), 50 deletions(-) diff --git a/app/app/models/response_objects/paystub.rb b/app/app/models/response_objects/paystub.rb index f8c2a4ed9..441bb1a90 100644 --- a/app/app/models/response_objects/paystub.rb +++ b/app/app/models/response_objects/paystub.rb @@ -37,9 +37,9 @@ def self.from_pinwheel(response_body) def self.from_argyle(response_body) new( account_id: response_body["account"], - gross_pay_amount: response_body["gross_pay"], - net_pay_amount: response_body["net_pay"], - gross_pay_ytd: response_body["gross_pay_ytd"], + gross_pay_amount: ArgyleMethods.format_currency(response_body["gross_pay"]), + net_pay_amount: ArgyleMethods.format_currency(response_body["net_pay"]), + gross_pay_ytd: ArgyleMethods.format_currency(response_body["gross_pay_ytd"]), pay_period_start: ArgyleMethods.format_date(response_body["paystub_period"]["start_date"]), pay_period_end: ArgyleMethods.format_date(response_body["paystub_period"]["end_date"]), pay_date: ArgyleMethods.format_date(response_body["paystub_date"]), @@ -48,7 +48,7 @@ def self.from_argyle(response_body) deductions: response_body["deduction_list"].map do |deduction| OpenStruct.new( category: deduction["name"], - amount: deduction["amount"], + amount: ArgyleMethods.format_currency(deduction["amount"]), ) end, ) @@ -94,6 +94,11 @@ def self.format_date(date) DateTime.parse(date).strftime("%Y-%m-%d") end + def self.format_currency(amount) + return unless amount + amount.to_f + end + def self.hours_by_earning_category(gross_pay_list) gross_pay_list .filter { |e| e["hours"].present? } diff --git a/app/app/services/argyle_service.rb b/app/app/services/argyle_service.rb index 3cc6dedc4..d33ff9f8b 100644 --- a/app/app/services/argyle_service.rb +++ b/app/app/services/argyle_service.rb @@ -68,9 +68,6 @@ def fetch_accounts(**params) # https://docs.argyle.com/api-reference/paystubs#list def fetch_paystubs(**params) # TODO: paginate - $stdout.puts "Hello" - - Rails.logger.debug "Interesting stuff" json = @http.get(PAYSTUBS_ENDPOINT, params).body json["results"].map { |paystub_json| ResponseObjects::Paystub.from_argyle(paystub_json) } end @@ -86,25 +83,25 @@ def build_url(endpoint) @http.build_url(endpoint).to_s end - def _tmp_fetch_all(user_id:) + def _tmp_fetch_all(user_id:, user_name:) FileUtils.mkdir_p "spec/support/fixtures/argyle/#{user_id}" - File.open("spec/support/fixtures/argyle/#{user_id}/request_user.json", "wb") { - |f| f.puts(fetch_user(user: user_id).to_json) - } + # File.open("spec/support/fixtures/argyle/#{user_id}/request_user.json", "wb") { + # |f| f.puts(fetch_user(user: user_id).to_json) + # } - File.open("spec/support/fixtures/argyle/#{user_id}/request_identity.json", "wb") { - |f| f.puts(fetch_identity(user: user_id).to_json) - } + # File.open("spec/support/fixtures/argyle/#{user_id}/request_identity.json", "wb") { + # |f| f.puts(fetch_identity(account: user_id).to_json) + # } - File.open("spec/support/fixtures/argyle/#{user_id}/request_employment.json", "wb") { - |f| f.puts(fetch_employment(user: user_id).to_json) - } + # File.open("spec/support/fixtures/argyle/#{user_id}/request_employment.json", "wb") { + # |f| f.puts(fetch_employment(account: ).to_json) + # } - File.open("spec/support/fixtures/argyle/#{user_id}/request_accounts.json", "wb") { - |f| f.puts(fetch_accounts(user: user_id).to_json) - } - File.open("spec/support/fixtures/argyle/#{user_id}/request_paystubs.json", "wb") { + # File.open("spec/support/fixtures/argyle/#{user_id}/request_accounts.json", "wb") { + # |f| f.puts(fetch_accounts(account: ).to_json) + # } + File.open("spec/support/fixtures/argyle/#{user_name}/request_paystubs.json", "wb") { |f| f.puts(fetch_paystubs(user: user_id).to_json) # , from_start_date: "2025-02-20", to_start_date: "2025-02-26").to_json) } diff --git a/app/spec/services/argyle_service_spec.rb b/app/spec/services/argyle_service_spec.rb index e09ea56a0..ac1529938 100644 --- a/app/spec/services/argyle_service_spec.rb +++ b/app/spec/services/argyle_service_spec.rb @@ -3,7 +3,7 @@ RSpec.describe ArgyleService, type: :service do include ArgyleApiHelper let(:service) { ArgyleService.new("sandbox", "FAKE_API_KEY") } - let(:end_user_id) { 'abc123' } + let(:account_id) { 'abc123' } # describe '#fetch_items' do # before do @@ -22,26 +22,31 @@ stub_request_paystubs_response("bob") end - it 'returns a non-empty response' do - paystubs = service.fetch_paystubs(account: end_user_id) - expect(paystubs.length).to eq(2) + it 'returns an array of ResponseObjects::Paystub' do + paystubs = service.fetch_paystubs(account: account_id) + expect(paystubs.length).to eq(10) expect(paystubs[0]).to be_a(ResponseObjects::Paystub) + end + + it 'returns with expected attributes without deductions or earning_categories' do + paystubs = service.fetch_paystubs(account: account_id) + expect(paystubs[0]).to have_attributes( - account_id: "01954440-8c8b-cd52-4a1f-f7aa07d136ed", - gross_pay_amount: "19.53", - net_pay_amount: "19.53", - gross_pay_ytd: "429.15", - pay_date: "2025-02-24", + account_id: "019571bc-2f60-3955-d972-dbadfe0913a8", + gross_pay_amount: 34.56, + net_pay_amount: 34.56, + gross_pay_ytd: 547.68, + pay_date: "2025-03-06", hours_by_earning_category: {}, deductions: [] ) expect(paystubs[1]).to have_attributes( - account_id: "01954449-1753-b8b9-8cd9-77b4be11db19", - gross_pay_amount: "36.33", - net_pay_amount: "36.33", - gross_pay_ytd: "369.77", - pay_date: "2025-02-20", + account_id: "019571bc-2f60-3955-d972-dbadfe0913a8", + gross_pay_amount: 17.13, + net_pay_amount: 17.13, + gross_pay_ytd: 513.12, + pay_date: "2025-02-27", hours_by_earning_category: {}, deductions: [] ) @@ -53,39 +58,80 @@ stub_request_paystubs_response("joe") end - it 'returns a non-empty response' do - paystubs = service.fetch_paystubs(account: end_user_id) + it 'returns an array of ResponseObjects::Paystub' do + paystubs = service.fetch_paystubs(account: account_id) expect(paystubs.length).to eq(10) expect(paystubs[0]).to be_a(ResponseObjects::Paystub) + end + + it 'returns with expected attributes including 1 earning category and multiple deductions' do + paystubs = service.fetch_paystubs(account: account_id) + expect(paystubs[0]).to have_attributes( account_id: "01956d62-18a0-090f-bc09-2ac44b7edf99", - gross_pay_amount: "5492.06", - net_pay_amount: "3350.16", - gross_pay_ytd: "16476.18", + gross_pay_amount: 5492.06, + net_pay_amount: 3350.16, + gross_pay_ytd: 16476.18, pay_date: "2025-03-03", hours_by_earning_category: { "base" => 92.9177 }, deductions: match_array([ - have_attributes(category: "401K", amount: "109.84"), - have_attributes(category: "Vision", amount: "219.68"), - have_attributes(category: "Dental", amount: "219.68") + have_attributes(category: "401K", amount: 109.84), + have_attributes(category: "Vision", amount: 219.68), + have_attributes(category: "Dental", amount: 219.68) ]) ) expect(paystubs[1]).to have_attributes( account_id: "01956d62-18a0-090f-bc09-2ac44b7edf99", - gross_pay_amount: "36.33", - net_pay_amount: "36.33", - gross_pay_ytd: "369.77", - pay_date: "2025-02-20", + gross_pay_amount: 5492.06, + net_pay_amount: 3899.37, + gross_pay_ytd: 10984.12, + pay_date: "2025-02-03", hours_by_earning_category: { "base" => 174.4026 }, deductions: match_array([ - have_attributes(category: "Dental", amount: "164.76"), - have_attributes(category: "Roth", amount: "164.76"), - have_attributes(category: "Garnishment", amount: "164.76") + have_attributes(category: "Dental", amount: 164.76), + have_attributes(category: "Roth", amount: 164.76), + have_attributes(category: "Garnishment", amount: 164.76) + ]) + ) + end + it 'ignores earning categories that do not have hours (e.g. Bonus / Commission)' do + paystubs = service.fetch_paystubs(account: account_id) + + expect(paystubs[3]).to have_attributes( + account_id: "01956d62-18a0-090f-bc09-2ac44b7edf99", + gross_pay_amount: 5492.06, + net_pay_amount: 4944.43, + gross_pay_ytd: 74135.63, + pay_date: "2024-12-02", + hours_by_earning_category: { + "base" => 139.5035 + }, + deductions: match_array([ + have_attributes(category: "Dental", amount: 164.76) + ]) + ) + end + + it 'ignores earning categories that do not have hours (e.g. Bonus / Commission)' do + paystubs = service.fetch_paystubs(account: account_id) + + expect(paystubs[4]).to have_attributes( + account_id: "01956d62-18a0-090f-bc09-2ac44b7edf99", + gross_pay_amount: 9735.94, + net_pay_amount: 9076.89, + gross_pay_ytd: 68643.57, + pay_date: "2024-11-01", + hours_by_earning_category: { + "base" => 76.0765, + "overtime" => 39.191 + }, + deductions: match_array([ + have_attributes(category: "Garnishment", amount: 54.92) ]) ) end From 57bcfcefe3ced3c0bae4eac8a36e9e542a5835ee Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Fri, 7 Mar 2025 17:31:04 -0500 Subject: [PATCH 12/26] got income, employment, and paystubs to be populating properly! --- app/TODO.txt | 17 ++-- app/app/models/response_objects/employment.rb | 18 +++- app/app/models/response_objects/income.rb | 9 +- app/app/services/argyle_service.rb | 91 +++++++++++-------- app/spec/services/argyle_service_spec.rb | 78 ++++++++++++++++ app/spec/support/argyle_api_helper.rb | 10 ++ 6 files changed, 174 insertions(+), 49 deletions(-) diff --git a/app/TODO.txt b/app/TODO.txt index f5ec9f12b..d7924ffec 100644 --- a/app/TODO.txt +++ b/app/TODO.txt @@ -1,10 +1,11 @@ TODO on this branch: -1. Wrap up Bob test data -2. Write tests for all three test users -3. User ID --> Account ID refactor -4. Rewrite ArgyleService methods to instantiate response objects based on /identities -5. Then, update PinwheelDataHelper to use ArgyleService -6. Figure out missing fields for employer address/phone -7. Update view logic as necessary -8. Verify that Argyle dollar amount strings are being rendered correctly +[x] 1. Wrap up Bob test data +[ ] 2. Write tests for all three test users +[ ] 3. User ID --> Account ID refactor +[ ] 4. Rewrite ArgyleService methods to instantiate response objects based on /identities +[ ] 5. Then, update PinwheelDataHelper to use ArgyleService +[ ] 6. Figure out missing fields for employer address/phone +[ ] 7. Update view logic as necessary +[ ] 8. Verify that Argyle dollar amount strings are being rendered correctly +[ ] 9. DRY ArgyleMethods.format_currency and format_date diff --git a/app/app/models/response_objects/employment.rb b/app/app/models/response_objects/employment.rb index f0a4466ab..5216c8ac7 100644 --- a/app/app/models/response_objects/employment.rb +++ b/app/app/models/response_objects/employment.rb @@ -27,10 +27,22 @@ def self.from_argyle(identity_response_body) employer_name: identity_response_body["employer"], start_date: identity_response_body["hire_date"], termination_date: identity_response_body["termination_date"], - status: identity_response_body["employment_status"], - employer_phone_number: "", - employer_address: "" + status: ArgyleMethods.format_employment_status(identity_response_body["employment_status"]), ) end end + module ArgyleMethods + def self.format_employment_status(employment_status) + return unless employment_status + + case employment_status + when "active" + "employed" + when "inactive" + "furloughed" + else + employment_status + end + end + end end diff --git a/app/app/models/response_objects/income.rb b/app/app/models/response_objects/income.rb index a423e3957..a6f56e7e5 100644 --- a/app/app/models/response_objects/income.rb +++ b/app/app/models/response_objects/income.rb @@ -19,9 +19,16 @@ def self.from_argyle(identities_response_body) new( account_id: identities_response_body["account"], pay_frequency: identities_response_body["base_pay"]["period"], - compensation_amount: identities_response_body["base_pay"]["amount"], + compensation_amount: ArgyleMethods.format_currency(identities_response_body["base_pay"]["amount"]), compensation_unit: identities_response_body["base_pay"]["currency"], ) end end + + module ArgyleMethods + def self.format_currency(amount) + return unless amount + amount.to_f + end + end end diff --git a/app/app/services/argyle_service.rb b/app/app/services/argyle_service.rb index d33ff9f8b..f4aeecec0 100644 --- a/app/app/services/argyle_service.rb +++ b/app/app/services/argyle_service.rb @@ -41,73 +41,90 @@ def initialize(environment, api_key_id = nil, api_key_secret = nil) end end + def fetch_paystubs(**params) + json = fetch_paystubs_api(**params) + json["results"].map { |paystub_json| ResponseObjects::Paystub.from_argyle(paystub_json) } + end + + def fetch_employment(**params) + # Note: we actually fetch Argyle's identity API instead of employment for the correct data + json = fetch_identities_api(**params) + json["results"].map { |identity_json| ResponseObjects::Employment.from_argyle(identity_json) } + end + + def fetch_income(**params) + # Note: we actually fetch Argyle's identity API instead of employment for the correct data + json = fetch_identities_api(**params) + json["results"].map { |identity_json| ResponseObjects::Income.from_argyle(identity_json) } + end + # Fetch all Argyle items # https://docs.argyle.com/api-reference/items#list def items(query = nil) @http.get(ITEMS_ENDPOINT, { q: query }).body end + # https://docs.argyle.com/api-reference/users#retrieve - def fetch_user(user:) + def fetch_user_api(user:) @http.get(build_url("users/#{user}")).body end # https://docs.argyle.com/api-reference/identities#retrieve - def fetch_identity(**params) + def fetch_identities_api(**params) # todo: paginate @http.get("identities", params).body end # https://docs.argyle.com/api-reference/accounts#list - def fetch_accounts(**params) + def fetch_accounts_api(**params) # TODO: paginate # json["data"].map { |paystub_json| ResponseObjects::Paystub.from_pinwheel(paystub_json) } @http.get("accounts", params).body end # https://docs.argyle.com/api-reference/paystubs#list - def fetch_paystubs(**params) + def fetch_paystubs_api(**params) # TODO: paginate - json = @http.get(PAYSTUBS_ENDPOINT, params).body - json["results"].map { |paystub_json| ResponseObjects::Paystub.from_argyle(paystub_json) } + @http.get(PAYSTUBS_ENDPOINT, params).body end +end - # https://docs.argyle.com/api-reference/employments#list - def fetch_employment(**params) - # json["data"].map { |paystub_json| ResponseObjects::Paystub.from_pinwheel(paystub_json) } - @http.get("employments", params).body - end +# https://docs.argyle.com/api-reference/employments#list +def fetch_employment_api(**params) + # json["data"].map { |paystub_json| ResponseObjects::Paystub.from_pinwheel(paystub_json) } + @http.get("employments", params).body +end - # TODO: refactor this into common function between argyle_service/pinwheel_service - def build_url(endpoint) - @http.build_url(endpoint).to_s - end +# TODO: refactor this into common function between argyle_service/pinwheel_service +def build_url(endpoint) + @http.build_url(endpoint).to_s +end - def _tmp_fetch_all(user_id:, user_name:) - FileUtils.mkdir_p "spec/support/fixtures/argyle/#{user_id}" +def _tmp_fetch_all(user_id:, user_name:) + FileUtils.mkdir_p "spec/support/fixtures/argyle/#{user_id}" - # File.open("spec/support/fixtures/argyle/#{user_id}/request_user.json", "wb") { - # |f| f.puts(fetch_user(user: user_id).to_json) - # } + File.open("spec/support/fixtures/argyle/#{user_id}/request_user.json", "wb") { + |f| f.puts(fetch_user_api(user: user_id).to_json) + } - # File.open("spec/support/fixtures/argyle/#{user_id}/request_identity.json", "wb") { - # |f| f.puts(fetch_identity(account: user_id).to_json) - # } + File.open("spec/support/fixtures/argyle/#{user_id}/request_identity.json", "wb") { + |f| f.puts(fetch_identity_api(account: user_id).to_json) + } - # File.open("spec/support/fixtures/argyle/#{user_id}/request_employment.json", "wb") { - # |f| f.puts(fetch_employment(account: ).to_json) - # } + File.open("spec/support/fixtures/argyle/#{user_id}/request_employment.json", "wb") { + |f| f.puts(fetch_employment_api(account:).to_json) + } - # File.open("spec/support/fixtures/argyle/#{user_id}/request_accounts.json", "wb") { - # |f| f.puts(fetch_accounts(account: ).to_json) - # } - File.open("spec/support/fixtures/argyle/#{user_name}/request_paystubs.json", "wb") { - |f| f.puts(fetch_paystubs(user: user_id).to_json) - # , from_start_date: "2025-02-20", to_start_date: "2025-02-26").to_json) - } - end + File.open("spec/support/fixtures/argyle/#{user_id}/request_accounts.json", "wb") { + |f| f.puts(fetch_accounts_api(account:).to_json) + } + File.open("spec/support/fixtures/argyle/#{user_name}/request_paystubs.json", "wb") { + |f| f.puts(fetch_paystubs_api(user: user_id).to_json) + # , from_start_date: "2025-02-20", to_start_date: "2025-02-26").to_json) + } +end - def create_user - @http.post("users").body - end +def create_user + @http.post("users").body end diff --git a/app/spec/services/argyle_service_spec.rb b/app/spec/services/argyle_service_spec.rb index ac1529938..084a61db5 100644 --- a/app/spec/services/argyle_service_spec.rb +++ b/app/spec/services/argyle_service_spec.rb @@ -137,4 +137,82 @@ end end end + + + describe '#fetch_employment' do + context "for Bob, a Uber driver" do + before do + stub_request_identities_response("bob") + end + + it 'returns an array of ResponseObjects::Employment' do + employments = service.fetch_employment(account: account_id) + expect(employments.length).to eq(1) + + expect(employments[0]).to be_a(ResponseObjects::Employment) + end + + it 'returns an array of ResponseObjects::Employment' do + employments = service.fetch_employment(account: account_id) + + expect(employments[0]).to have_attributes( + account_id: "019571bc-2f60-3955-d972-dbadfe0913a8", + employer_name: "Lyft Driver", + start_date: "2022-04-07", + status: "employed", + ) + end + end + context "for mapping employment status" do + it 'employment status inactive => furloughed' do + employment = ResponseObjects::Employment.from_argyle({ + "employment_status" => "inactive" + }) + expect(employment).to have_attributes( + status: "furloughed", + ) + end + it 'employment status active => employed' do + employment = ResponseObjects::Employment.from_argyle({ + "employment_status" => "active" + }) + expect(employment).to have_attributes( + status: "employed", + ) + end + it 'employment status terminated = terminated' do + employment = ResponseObjects::Employment.from_argyle({ + "employment_status" => "terminated" + }) + expect(employment).to have_attributes( + status: "terminated", + ) + end + end + end + describe '#fetch_income' do + context "for Joe, a W2 employee" do + before do + stub_request_identities_response("joe") + end + + it 'returns an array of ResponseObjects::Income' do + incomes = service.fetch_income(account: account_id) + expect(incomes.length).to eq(1) + + expect(incomes[0]).to be_a(ResponseObjects::Income) + end + + it 'returns income object with expected attributes' do + incomes = service.fetch_income(account: account_id) + + expect(incomes[0]).to have_attributes( + account_id: "01956d62-18a0-090f-bc09-2ac44b7edf99", + pay_frequency: "annual", + compensation_amount: 65904.75, + compensation_unit: "USD" + ) + end + end + end end diff --git a/app/spec/support/argyle_api_helper.rb b/app/spec/support/argyle_api_helper.rb index 0671dbdb2..976a9e0e8 100644 --- a/app/spec/support/argyle_api_helper.rb +++ b/app/spec/support/argyle_api_helper.rb @@ -19,6 +19,16 @@ def stub_request_paystubs_response(userFolder) ) end + def stub_request_identities_response(userFolder) + stub_request(:get, %r{#{ArgyleService::IDENTITIES_ENDPOINT}}) + .to_return( + status: 200, + body: load_relative_json_file(userFolder, 'request_identity.json').to_json, + headers: { 'Content-Type': 'application/json;charset=UTF-8' } + ) + end + + def load_relative_file(userFolder, filename) File.read(File.join( File.dirname(__FILE__), From bd2e45486cd8c6a643a264c3eb6797796362f898 Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Fri, 7 Mar 2025 17:33:36 -0500 Subject: [PATCH 13/26] updated TODO --- app/TODO.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/TODO.txt b/app/TODO.txt index d7924ffec..a893edf03 100644 --- a/app/TODO.txt +++ b/app/TODO.txt @@ -1,11 +1,11 @@ TODO on this branch: [x] 1. Wrap up Bob test data -[ ] 2. Write tests for all three test users +[x] 2. Write tests for all three test users. (I skipped Sarah for now because bob + joe covered cases) [ ] 3. User ID --> Account ID refactor -[ ] 4. Rewrite ArgyleService methods to instantiate response objects based on /identities +[x] 4. Rewrite ArgyleService methods to instantiate response objects based on /identities [ ] 5. Then, update PinwheelDataHelper to use ArgyleService [ ] 6. Figure out missing fields for employer address/phone [ ] 7. Update view logic as necessary -[ ] 8. Verify that Argyle dollar amount strings are being rendered correctly +[x] 8. Verify that Argyle dollar amount strings are being rendered correctly [ ] 9. DRY ArgyleMethods.format_currency and format_date From dbea9db6a092d1a66ab48c4d1c1cc177dbd86421 Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Mon, 10 Mar 2025 16:42:09 -0400 Subject: [PATCH 14/26] fix merge conflicts --- app/app/services/argyle_service.rb | 63 ++++++++++++++---------------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/app/app/services/argyle_service.rb b/app/app/services/argyle_service.rb index 82fd8e68e..9196c4e60 100644 --- a/app/app/services/argyle_service.rb +++ b/app/app/services/argyle_service.rb @@ -64,7 +64,6 @@ def items(query = nil) @http.get(ITEMS_ENDPOINT, { q: query }).body end - # https://docs.argyle.com/api-reference/users#retrieve def fetch_user_api(user:) @http.get(build_url("users/#{user}")).body @@ -87,46 +86,44 @@ def fetch_accounts_api(**params) def fetch_paystubs_api(**params) # TODO: paginate @http.get(PAYSTUBS_ENDPOINT, params).body + end + def create_user @http.post("users").body end -end - -# https://docs.argyle.com/api-reference/employments#list -def fetch_employment_api(**params) - # json["data"].map { |paystub_json| ResponseObjects::Paystub.from_pinwheel(paystub_json) } - @http.get("employments", params).body -end -# TODO: refactor this into common function between argyle_service/pinwheel_service -def build_url(endpoint) - @http.build_url(endpoint).to_s -end + # https://docs.argyle.com/api-reference/employments#list + def fetch_employment_api(**params) + # json["data"].map { |paystub_json| ResponseObjects::Paystub.from_pinwheel(paystub_json) } + @http.get("employments", params).body + end -def _tmp_fetch_all(user_id:, user_name:) - FileUtils.mkdir_p "spec/support/fixtures/argyle/#{user_id}" + # TODO: refactor this into common function between argyle_service/pinwheel_service + def build_url(endpoint) + @http.build_url(endpoint).to_s + end - File.open("spec/support/fixtures/argyle/#{user_id}/request_user.json", "wb") { - |f| f.puts(fetch_user_api(user: user_id).to_json) - } + def _tmp_fetch_all(user_id:, user_name:) + FileUtils.mkdir_p "spec/support/fixtures/argyle/#{user_id}" - File.open("spec/support/fixtures/argyle/#{user_id}/request_identity.json", "wb") { - |f| f.puts(fetch_identity_api(account: user_id).to_json) - } + File.open("spec/support/fixtures/argyle/#{user_id}/request_user.json", "wb") { + |f| f.puts(fetch_user_api(user: user_id).to_json) + } - File.open("spec/support/fixtures/argyle/#{user_id}/request_employment.json", "wb") { - |f| f.puts(fetch_employment_api(account:).to_json) - } + File.open("spec/support/fixtures/argyle/#{user_id}/request_identity.json", "wb") { + |f| f.puts(fetch_identity_api(account: user_id).to_json) + } - File.open("spec/support/fixtures/argyle/#{user_id}/request_accounts.json", "wb") { - |f| f.puts(fetch_accounts_api(account:).to_json) - } - File.open("spec/support/fixtures/argyle/#{user_name}/request_paystubs.json", "wb") { - |f| f.puts(fetch_paystubs_api(user: user_id).to_json) - # , from_start_date: "2025-02-20", to_start_date: "2025-02-26").to_json) - } -end + File.open("spec/support/fixtures/argyle/#{user_id}/request_employment.json", "wb") { + |f| f.puts(fetch_employment_api(account:).to_json) + } -def create_user - @http.post("users").body + File.open("spec/support/fixtures/argyle/#{user_id}/request_accounts.json", "wb") { + |f| f.puts(fetch_accounts_api(account:).to_json) + } + File.open("spec/support/fixtures/argyle/#{user_name}/request_paystubs.json", "wb") { + |f| f.puts(fetch_paystubs_api(user: user_id).to_json) + # , from_start_date: "2025-02-20", to_start_date: "2025-02-26").to_json) + } + end end From 99c59a172e726a6a92b41419f9da7283cd3c4146 Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Mon, 10 Mar 2025 18:52:05 -0400 Subject: [PATCH 15/26] fix indent issues --- app/app/services/argyle_service.rb | 7 + app/spec/services/argyle_service_spec.rb | 205 +++++++++++++++-------- 2 files changed, 138 insertions(+), 74 deletions(-) diff --git a/app/app/services/argyle_service.rb b/app/app/services/argyle_service.rb index 9196c4e60..b69dd19e5 100644 --- a/app/app/services/argyle_service.rb +++ b/app/app/services/argyle_service.rb @@ -58,6 +58,13 @@ def fetch_income(**params) json["results"].map { |identity_json| ResponseObjects::Income.from_argyle(identity_json) } end + # https://docs.argyle.com/api-reference/identities#retrieve + def fetch_identities(**params) + # todo: paginate + json = fetch_identities_api(**params) + json["results"].map { |identity_json| ResponseObjects::Identity.from_argyle(identity_json) } + end + # Fetch all Argyle items # https://docs.argyle.com/api-reference/items#list def items(query = nil) diff --git a/app/spec/services/argyle_service_spec.rb b/app/spec/services/argyle_service_spec.rb index 084a61db5..1c0a123b3 100644 --- a/app/spec/services/argyle_service_spec.rb +++ b/app/spec/services/argyle_service_spec.rb @@ -5,16 +5,16 @@ let(:service) { ArgyleService.new("sandbox", "FAKE_API_KEY") } let(:account_id) { 'abc123' } - # describe '#fetch_items' do - # before do - # stub_request_items_response(BOB_USER_FOLDER) - # end + describe '#fetch_items' do + before do + stub_request_items_response(BOB_USER_FOLDER) + end - # it 'returns a non-empty response' do - # response = service.items('test') - # expect(response).not_to be_empty - # end - # end + it 'returns a non-empty response' do + response = service.items('test') + expect(response).not_to be_empty + end + end describe '#fetch_paystubs' do context "for Bob, a Uber driver" do @@ -99,6 +99,7 @@ ]) ) end + it 'ignores earning categories that do not have hours (e.g. Bonus / Commission)' do paystubs = service.fetch_paystubs(account: account_id) @@ -138,81 +139,137 @@ end end - describe '#fetch_employment' do - context "for Bob, a Uber driver" do - before do - stub_request_identities_response("bob") - end + context "for Bob, a Uber driver" do + before do + stub_request_identities_response("bob") + end + + it 'returns an array of ResponseObjects::Employment' do + employments = service.fetch_employment(account: account_id) + expect(employments.length).to eq(1) + + expect(employments[0]).to be_a(ResponseObjects::Employment) + end - it 'returns an array of ResponseObjects::Employment' do - employments = service.fetch_employment(account: account_id) - expect(employments.length).to eq(1) + it 'returns an array of ResponseObjects::Employment' do + employments = service.fetch_employment(account: account_id) - expect(employments[0]).to be_a(ResponseObjects::Employment) - end + expect(employments[0]).to have_attributes( + account_id: "019571bc-2f60-3955-d972-dbadfe0913a8", + employer_name: "Lyft Driver", + start_date: "2022-04-07", + status: "employed", + ) + end + end - it 'returns an array of ResponseObjects::Employment' do - employments = service.fetch_employment(account: account_id) + context "for mapping employment status" do + it 'employment status inactive => furloughed' do + employment = ResponseObjects::Employment.from_argyle({ + "employment_status" => "inactive" + }) + expect(employment).to have_attributes( + status: "furloughed", + ) + end - expect(employments[0]).to have_attributes( - account_id: "019571bc-2f60-3955-d972-dbadfe0913a8", - employer_name: "Lyft Driver", - start_date: "2022-04-07", + it 'employment status active => employed' do + employment = ResponseObjects::Employment.from_argyle({ + "employment_status" => "active" + }) + expect(employment).to have_attributes( status: "employed", ) - end - end - context "for mapping employment status" do - it 'employment status inactive => furloughed' do - employment = ResponseObjects::Employment.from_argyle({ - "employment_status" => "inactive" - }) - expect(employment).to have_attributes( - status: "furloughed", - ) - end - it 'employment status active => employed' do - employment = ResponseObjects::Employment.from_argyle({ - "employment_status" => "active" - }) - expect(employment).to have_attributes( - status: "employed", - ) - end - it 'employment status terminated = terminated' do - employment = ResponseObjects::Employment.from_argyle({ - "employment_status" => "terminated" - }) - expect(employment).to have_attributes( - status: "terminated", - ) - end - end + end + + it 'employment status terminated = terminated' do + employment = ResponseObjects::Employment.from_argyle({ + "employment_status" => "terminated" + }) + expect(employment).to have_attributes( + status: "terminated", + ) + end end + end + describe '#fetch_income' do - context "for Joe, a W2 employee" do - before do - stub_request_identities_response("joe") - end - - it 'returns an array of ResponseObjects::Income' do - incomes = service.fetch_income(account: account_id) - expect(incomes.length).to eq(1) - - expect(incomes[0]).to be_a(ResponseObjects::Income) - end - - it 'returns income object with expected attributes' do - incomes = service.fetch_income(account: account_id) - - expect(incomes[0]).to have_attributes( - account_id: "01956d62-18a0-090f-bc09-2ac44b7edf99", - pay_frequency: "annual", - compensation_amount: 65904.75, - compensation_unit: "USD" + context "for Joe, a W2 employee" do + before do + stub_request_identities_response("joe") + end + + it 'returns an array of ResponseObjects::Income' do + incomes = service.fetch_income(account: account_id) + expect(incomes.length).to eq(1) + + expect(incomes[0]).to be_a(ResponseObjects::Income) + end + + it 'returns income object with expected attributes' do + incomes = service.fetch_income(account: account_id) + + expect(incomes[0]).to have_attributes( + account_id: "01956d62-18a0-090f-bc09-2ac44b7edf99", + pay_frequency: "annual", + compensation_amount: 65904.75, + compensation_unit: "USD" + ) + end + end + end + + describe '#fetch_identity' do + context "for Bob, a Uber driver" do + before do + stub_request_identities_response("bob") + end + + it 'returns an array of ResponseObjects::Employment' do + employments = service.fetch_indentities(account: account_id) + expect(employments.length).to eq(1) + + expect(employments[0]).to be_a(ResponseObjects::Employment) + end + + it 'returns an array of ResponseObjects::Employment' do + employments = service.fetch_employment(account: account_id) + + expect(employments[0]).to have_attributes( + account_id: "019571bc-2f60-3955-d972-dbadfe0913a8", + employer_name: "Lyft Driver", + start_date: "2022-04-07", + status: "employed", + ) + end + end + + context "for mapping employment status" do + it 'employment status inactive => furloughed' do + employment = ResponseObjects::Employment.from_argyle({ + "employment_status" => "inactive" + }) + expect(employment).to have_attributes( + status: "furloughed", + ) + end + it 'employment status active => employed' do + employment = ResponseObjects::Employment.from_argyle({ + "employment_status" => "active" + }) + expect(employment).to have_attributes( + status: "employed", + ) + end + it 'employment status terminated = terminated' do + employment = ResponseObjects::Employment.from_argyle({ + "employment_status" => "terminated" + }) + expect(employment).to have_attributes( + status: "terminated", ) - end end end + end end From 473683468d8656601ac528e22411a92a2c4f7f3a Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Mon, 10 Mar 2025 18:57:25 -0400 Subject: [PATCH 16/26] fix tests --- app/spec/services/argyle_service_spec.rb | 45 +++++------------------- 1 file changed, 8 insertions(+), 37 deletions(-) diff --git a/app/spec/services/argyle_service_spec.rb b/app/spec/services/argyle_service_spec.rb index 1c0a123b3..6c8626fdd 100644 --- a/app/spec/services/argyle_service_spec.rb +++ b/app/spec/services/argyle_service_spec.rb @@ -226,50 +226,21 @@ stub_request_identities_response("bob") end - it 'returns an array of ResponseObjects::Employment' do - employments = service.fetch_indentities(account: account_id) - expect(employments.length).to eq(1) + it 'returns an array of ResponseObjects:Identity' do + identities = service.fetch_identities(account: account_id) + expect(identities.length).to eq(1) - expect(employments[0]).to be_a(ResponseObjects::Employment) + expect(identities[0]).to be_a(ResponseObjects::Identity) end - it 'returns an array of ResponseObjects::Employment' do - employments = service.fetch_employment(account: account_id) + it 'returns expected attributes' do + identities = service.fetch_identities(account: account_id) - expect(employments[0]).to have_attributes( + expect(identities[0]).to have_attributes( account_id: "019571bc-2f60-3955-d972-dbadfe0913a8", - employer_name: "Lyft Driver", - start_date: "2022-04-07", - status: "employed", + full_name: "Bob Jones" ) end end - - context "for mapping employment status" do - it 'employment status inactive => furloughed' do - employment = ResponseObjects::Employment.from_argyle({ - "employment_status" => "inactive" - }) - expect(employment).to have_attributes( - status: "furloughed", - ) - end - it 'employment status active => employed' do - employment = ResponseObjects::Employment.from_argyle({ - "employment_status" => "active" - }) - expect(employment).to have_attributes( - status: "employed", - ) - end - it 'employment status terminated = terminated' do - employment = ResponseObjects::Employment.from_argyle({ - "employment_status" => "terminated" - }) - expect(employment).to have_attributes( - status: "terminated", - ) - end - end end end From 16db2885fa921e4ba26283cb04b72711d6ad74d6 Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Mon, 10 Mar 2025 19:14:03 -0400 Subject: [PATCH 17/26] update comment --- app/app/services/argyle_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/app/services/argyle_service.rb b/app/app/services/argyle_service.rb index b69dd19e5..40270dd32 100644 --- a/app/app/services/argyle_service.rb +++ b/app/app/services/argyle_service.rb @@ -76,7 +76,7 @@ def fetch_user_api(user:) @http.get(build_url("users/#{user}")).body end - # https://docs.argyle.com/api-reference/identities#retrieve + # https://docs.argyle.com/api-reference/identities#list def fetch_identities_api(**params) # todo: paginate @http.get("identities", params).body From cacc70a18a1215aac9ac91799a093081b3f48a27 Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Mon, 10 Mar 2025 19:36:42 -0400 Subject: [PATCH 18/26] extract response_objects format methods into their own lib --- .../response_objects/ArgyleFormatMethods.rb | 32 + .../response_objects/PinwheelFormatMethods.rb | 28 + app/app/models/response_objects/employment.rb | 19 +- app/app/models/response_objects/income.rb | 9 +- app/app/models/response_objects/paystub.rb | 72 +- app/spec/services/argyle_service_spec.rb | 4 +- .../argyle/{ => bob}/request_items.json | 0 .../fixtures/argyle/request_identity.json | 0 .../fixtures/argyle/request_paystubs.json | 703 ------------------ .../support/fixtures/argyle/request_user.json | 15 - 10 files changed, 80 insertions(+), 802 deletions(-) create mode 100644 app/app/models/response_objects/ArgyleFormatMethods.rb create mode 100644 app/app/models/response_objects/PinwheelFormatMethods.rb rename app/spec/support/fixtures/argyle/{ => bob}/request_items.json (100%) delete mode 100644 app/spec/support/fixtures/argyle/request_identity.json delete mode 100644 app/spec/support/fixtures/argyle/request_paystubs.json delete mode 100644 app/spec/support/fixtures/argyle/request_user.json diff --git a/app/app/models/response_objects/ArgyleFormatMethods.rb b/app/app/models/response_objects/ArgyleFormatMethods.rb new file mode 100644 index 000000000..6aa90bac5 --- /dev/null +++ b/app/app/models/response_objects/ArgyleFormatMethods.rb @@ -0,0 +1,32 @@ +module ArgyleFormatMethods + def self.format_employment_status(employment_status) + return unless employment_status + + case employment_status + when "active" + "employed" + when "inactive" + "furloughed" + else + employment_status + end + end + + def self.format_date(date) + return unless date + + DateTime.parse(date).strftime("%Y-%m-%d") + end + + def self.format_currency(amount) + return unless amount + amount.to_f + end + + def self.hours_by_earning_category(gross_pay_list) + gross_pay_list + .filter { |e| e["hours"].present? } + .group_by { |e| e["type"] } + .transform_values { |earnings| earnings.sum { |e| e["hours"].to_f } } + end +end diff --git a/app/app/models/response_objects/PinwheelFormatMethods.rb b/app/app/models/response_objects/PinwheelFormatMethods.rb new file mode 100644 index 000000000..7cc60d8a7 --- /dev/null +++ b/app/app/models/response_objects/PinwheelFormatMethods.rb @@ -0,0 +1,28 @@ +module PinwheelFormatMethods + def self.hours(earnings) + base_hours = earnings + .filter { |e| e["category"] != "overtime" } + .map { |e| e["hours"] } + .compact + .max + return unless base_hours + + # Add overtime hours to the base hours, because they tend to be additional + # work beyond the other entries. (As opposed to category="premium", which + # often duplicates other earnings' hours.) + # + # See FFS-1773. + overtime_hours = earnings + .filter { |e| e["category"] == "overtime" } + .sum { |e| e["hours"] || 0.0 } + + base_hours + overtime_hours + end + + def self.hours_by_earning_category(earnings) + earnings + .filter { |e| e["hours"] && e["hours"] > 0 } + .group_by { |e| e["category"] } + .transform_values { |earnings| earnings.sum { |e| e["hours"] } } + end +end diff --git a/app/app/models/response_objects/employment.rb b/app/app/models/response_objects/employment.rb index 5216c8ac7..da678bde3 100644 --- a/app/app/models/response_objects/employment.rb +++ b/app/app/models/response_objects/employment.rb @@ -1,3 +1,5 @@ +require_relative "ArgyleFormatMethods" + EMPLOYMENT_FIELDS = %i[ account_id employer_name @@ -7,7 +9,6 @@ employer_phone_number employer_address ] - module ResponseObjects Employment = Struct.new(*EMPLOYMENT_FIELDS, keyword_init: true) do def self.from_pinwheel(response_body) @@ -27,22 +28,8 @@ def self.from_argyle(identity_response_body) employer_name: identity_response_body["employer"], start_date: identity_response_body["hire_date"], termination_date: identity_response_body["termination_date"], - status: ArgyleMethods.format_employment_status(identity_response_body["employment_status"]), + status: ArgyleFormatMethods.format_employment_status(identity_response_body["employment_status"]), ) end end - module ArgyleMethods - def self.format_employment_status(employment_status) - return unless employment_status - - case employment_status - when "active" - "employed" - when "inactive" - "furloughed" - else - employment_status - end - end - end end diff --git a/app/app/models/response_objects/income.rb b/app/app/models/response_objects/income.rb index a6f56e7e5..bfdcb13bf 100644 --- a/app/app/models/response_objects/income.rb +++ b/app/app/models/response_objects/income.rb @@ -1,3 +1,5 @@ +require_relative "ArgyleFormatMethods" + INCOME_FIELDS = %i[ account_id pay_frequency @@ -24,11 +26,4 @@ def self.from_argyle(identities_response_body) ) end end - - module ArgyleMethods - def self.format_currency(amount) - return unless amount - amount.to_f - end - end end diff --git a/app/app/models/response_objects/paystub.rb b/app/app/models/response_objects/paystub.rb index 441bb1a90..5ace2c127 100644 --- a/app/app/models/response_objects/paystub.rb +++ b/app/app/models/response_objects/paystub.rb @@ -1,3 +1,6 @@ +require_relative "ArgyleFormatMethods" +require_relative "PinwheelFormatMethods" + PAYSTUB_FIELDS = %i[ account_id gross_pay_amount @@ -22,8 +25,8 @@ def self.from_pinwheel(response_body) pay_period_start: response_body["pay_period_start"], pay_period_end: response_body["pay_period_end"], pay_date: response_body["pay_date"], - hours: PinwheelMethods.hours(response_body["earnings"]), - hours_by_earning_category: PinwheelMethods.hours_by_earning_category(response_body["earnings"]), + hours: PinwheelFormatMethods.hours(response_body["earnings"]), + hours_by_earning_category: PinwheelFormatMethods.hours_by_earning_category(response_body["earnings"]), deductions: response_body["deductions"].map do |deduction| OpenStruct.new( category: deduction["category"], @@ -37,18 +40,18 @@ def self.from_pinwheel(response_body) def self.from_argyle(response_body) new( account_id: response_body["account"], - gross_pay_amount: ArgyleMethods.format_currency(response_body["gross_pay"]), - net_pay_amount: ArgyleMethods.format_currency(response_body["net_pay"]), - gross_pay_ytd: ArgyleMethods.format_currency(response_body["gross_pay_ytd"]), - pay_period_start: ArgyleMethods.format_date(response_body["paystub_period"]["start_date"]), - pay_period_end: ArgyleMethods.format_date(response_body["paystub_period"]["end_date"]), - pay_date: ArgyleMethods.format_date(response_body["paystub_date"]), + gross_pay_amount: ArgyleFormatMethods.format_currency(response_body["gross_pay"]), + net_pay_amount: ArgyleFormatMethods.format_currency(response_body["net_pay"]), + gross_pay_ytd: ArgyleFormatMethods.format_currency(response_body["gross_pay_ytd"]), + pay_period_start: ArgyleFormatMethods.format_date(response_body["paystub_period"]["start_date"]), + pay_period_end: ArgyleFormatMethods.format_date(response_body["paystub_period"]["end_date"]), + pay_date: ArgyleFormatMethods.format_date(response_body["paystub_date"]), hours: response_body["hours"], - hours_by_earning_category: ArgyleMethods.hours_by_earning_category(response_body["gross_pay_list"]), + hours_by_earning_category: ArgyleFormatMethods.hours_by_earning_category(response_body["gross_pay_list"]), deductions: response_body["deduction_list"].map do |deduction| OpenStruct.new( category: deduction["name"], - amount: ArgyleMethods.format_currency(deduction["amount"]), + amount: ArgyleFormatMethods.format_currency(deduction["amount"]), ) end, ) @@ -57,53 +60,4 @@ def self.from_argyle(response_body) alias_attribute :start, :pay_period_start alias_attribute :end, :pay_period_end end - - module PinwheelMethods - def self.hours(earnings) - base_hours = earnings - .filter { |e| e["category"] != "overtime" } - .map { |e| e["hours"] } - .compact - .max - return unless base_hours - - # Add overtime hours to the base hours, because they tend to be additional - # work beyond the other entries. (As opposed to category="premium", which - # often duplicates other earnings' hours.) - # - # See FFS-1773. - overtime_hours = earnings - .filter { |e| e["category"] == "overtime" } - .sum { |e| e["hours"] || 0.0 } - - base_hours + overtime_hours - end - - def self.hours_by_earning_category(earnings) - earnings - .filter { |e| e["hours"] && e["hours"] > 0 } - .group_by { |e| e["category"] } - .transform_values { |earnings| earnings.sum { |e| e["hours"] } } - end - end - - module ArgyleMethods - def self.format_date(date) - return unless date - - DateTime.parse(date).strftime("%Y-%m-%d") - end - - def self.format_currency(amount) - return unless amount - amount.to_f - end - - def self.hours_by_earning_category(gross_pay_list) - gross_pay_list - .filter { |e| e["hours"].present? } - .group_by { |e| e["type"] } - .transform_values { |earnings| earnings.sum { |e| e["hours"].to_f } } - end - end end diff --git a/app/spec/services/argyle_service_spec.rb b/app/spec/services/argyle_service_spec.rb index 6c8626fdd..5927c4262 100644 --- a/app/spec/services/argyle_service_spec.rb +++ b/app/spec/services/argyle_service_spec.rb @@ -7,11 +7,11 @@ describe '#fetch_items' do before do - stub_request_items_response(BOB_USER_FOLDER) + stub_request_items_response("bob") end it 'returns a non-empty response' do - response = service.items('test') + response = service.items(query: "test") expect(response).not_to be_empty end end diff --git a/app/spec/support/fixtures/argyle/request_items.json b/app/spec/support/fixtures/argyle/bob/request_items.json similarity index 100% rename from app/spec/support/fixtures/argyle/request_items.json rename to app/spec/support/fixtures/argyle/bob/request_items.json diff --git a/app/spec/support/fixtures/argyle/request_identity.json b/app/spec/support/fixtures/argyle/request_identity.json deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/spec/support/fixtures/argyle/request_paystubs.json b/app/spec/support/fixtures/argyle/request_paystubs.json deleted file mode 100644 index 924247596..000000000 --- a/app/spec/support/fixtures/argyle/request_paystubs.json +++ /dev/null @@ -1,703 +0,0 @@ -{ - "results": [ - { - "id": "c7f88cf2-5267-37a9-9d78-b2f9dd942a2e", - "account": "01954440-8c8b-cd52-4a1f-f7aa07d136ed", - "payroll_document": null, - "deduction_list": [], - "filing_status": [], - "tax_list": [], - "status": "completed", - "paystub_date": "2025-02-24T00:00:00Z", - "paystub_period": { - "start_date": null, - "end_date": null - }, - "currency": "USD", - "gross_pay": "19.53", - "deductions": null, - "taxes": null, - "net_pay": "19.53", - "reimbursements": null, - "hours": null, - "fees": null, - "net_pay_ytd": "429.15", - "gross_pay_ytd": "429.15", - "metadata": {}, - "employer": "Uber Driver", - "employer_address": null, - "created_at": "2025-02-26T21:56:26.219Z", - "updated_at": "2025-02-26T21:56:26.219Z", - "destinations": [], - "hours_ytd": "0.00", - "deductions_ytd": "0.00", - "taxes_ytd": "0.00", - "gross_pay_list": [], - "gross_pay_list_totals": null, - "employment": "26396fa4-6213-39a6-902a-f9a85d26db4d" - }, - { - "id": "b2c1d851-e1e0-33d4-bbfa-2e3868979361", - "account": "01954449-1753-b8b9-8cd9-77b4be11db19", - "payroll_document": null, - "deduction_list": [], - "filing_status": [], - "tax_list": [], - "status": "completed", - "paystub_date": "2025-02-20T00:00:00Z", - "paystub_period": { - "start_date": null, - "end_date": null - }, - "currency": "USD", - "gross_pay": "36.33", - "deductions": null, - "taxes": null, - "net_pay": "36.33", - "reimbursements": null, - "hours": null, - "fees": null, - "net_pay_ytd": "369.77", - "gross_pay_ytd": "369.77", - "metadata": {}, - "employer": "Lyft Driver", - "employer_address": null, - "created_at": "2025-02-26T22:03:41.088Z", - "updated_at": "2025-02-26T22:03:41.088Z", - "destinations": [], - "hours_ytd": "0.00", - "deductions_ytd": "0.00", - "taxes_ytd": "0.00", - "gross_pay_list": [], - "gross_pay_list_totals": null, - "employment": "31188b69-703a-3d6e-b6f6-24073ca84263" - }, - { - "id": "14251c2b-ccaf-3c22-a093-cea40a3c0b18", - "account": "019539bc-f99a-54df-bf3a-1b44eda07475", - "payroll_document": "019539bd-3daa-2fc2-b78c-03634c3b8b3f", - "deduction_list": [ - { - "amount": "11.93", - "name": "Dental", - "tax_classification": null, - "amount_ytd": "95.43" - }, - { - "amount": "23.86", - "name": "Vision", - "tax_classification": "pre_tax", - "amount_ytd": "23.86" - } - ], - "filing_status": [ - { - "type": "federal", - "location": null, - "status": "married filing separately" - }, - { - "type": "state", - "location": "NY", - "status": "single" - } - ], - "tax_list": [ - { - "amount": "83.50", - "name": "OASDI", - "type": "fica", - "amount_ytd": "298.22" - }, - { - "amount": "131.22", - "name": "CA State Income Tax", - "type": "state", - "amount_ytd": "381.73" - } - ], - "status": "completed", - "paystub_date": "2025-02-17T00:00:00Z", - "paystub_period": { - "start_date": "2025-01-30T00:00:00Z", - "end_date": "2025-02-13T00:00:00Z" - }, - "currency": "USD", - "gross_pay": "1192.87", - "deductions": "35.79", - "taxes": "214.72", - "net_pay": "942.36", - "reimbursements": "0.00", - "hours": "36.97", - "fees": null, - "net_pay_ytd": "3666.17", - "gross_pay_ytd": "4863.81", - "metadata": {}, - "employer": "Accenture", - "employer_address": { - "city": "Seattle", - "state": "WA", - "country": "US", - "postal_code": "98109", - "line1": "202 Westlake Ave N", - "line2": null - }, - "created_at": "2025-02-24T20:54:18.427Z", - "updated_at": "2025-02-24T20:54:18.427Z", - "destinations": [], - "hours_ytd": "273.07", - "deductions_ytd": "250.50", - "taxes_ytd": "954.31", - "gross_pay_list": [ - { - "name": "Regular", - "type": "base", - "start_date": "2025-01-30", - "end_date": "2025-02-13", - "rate": "32.2696", - "hours": "36.9657", - "amount": "1192.87", - "hours_ytd": "273.0700", - "amount_ytd": "4771.48" - }, - { - "name": "Commission", - "type": "commission", - "start_date": null, - "end_date": null, - "rate": null, - "hours": null, - "amount": null, - "hours_ytd": null, - "amount_ytd": "92.33" - } - ], - "gross_pay_list_totals": { - "base": { - "amount": "1192.87", - "amount_ytd": "4771.48", - "hours": "36.9657", - "hours_ytd": "273.0700", - "rate_implied": "32.2700", - "rate_implied_ytd": "17.4700" - }, - "overtime": null, - "commission": { - "amount": null, - "amount_ytd": "92.33", - "hours": null, - "hours_ytd": null, - "rate_implied": null, - "rate_implied_ytd": null - }, - "bonus": null, - "other": null - }, - "employment": "b528a78b-152a-3bc3-a5b8-44a03c0b2ddc" - }, - { - "id": "461baf58-3383-3958-b4c9-adb25abb118d", - "account": "01954423-a310-c7f7-1cf5-744f0033821f", - "payroll_document": "01954423-fd40-3c8a-8f4d-52f1b15aecae", - "deduction_list": [ - { - "amount": "20.55", - "name": "Vision", - "tax_classification": "pre_tax", - "amount_ytd": "20.55" - }, - { - "amount": "82.19", - "name": "Dental", - "tax_classification": null, - "amount_ytd": "143.84" - }, - { - "amount": "61.65", - "name": "Dental", - "tax_classification": null, - "amount_ytd": "205.49" - } - ], - "filing_status": [ - { - "type": "federal", - "location": null, - "status": "married filing separately" - }, - { - "type": "local", - "location": "New York", - "status": "single" - } - ], - "tax_list": [ - { - "amount": "164.39", - "name": "OASDI", - "type": "fica", - "amount_ytd": "575.36" - } - ], - "status": "completed", - "paystub_date": "2025-02-17T00:00:00Z", - "paystub_period": { - "start_date": "2025-01-26T00:00:00Z", - "end_date": "2025-02-09T00:00:00Z" - }, - "currency": "USD", - "gross_pay": "2054.85", - "deductions": "164.39", - "taxes": "164.39", - "net_pay": "1726.07", - "reimbursements": "0.00", - "hours": "47.19", - "fees": null, - "net_pay_ytd": "6356.83", - "gross_pay_ytd": "8219.40", - "metadata": {}, - "employer": "ACME", - "employer_address": { - "city": "Seattle", - "state": "WA", - "country": "US", - "postal_code": "98109", - "line1": "202 Westlake Ave N", - "line2": null - }, - "created_at": "2025-02-26T21:22:44.290Z", - "updated_at": "2025-02-26T21:22:44.290Z", - "destinations": [], - "hours_ytd": "233.61", - "deductions_ytd": "410.98", - "taxes_ytd": "1458.95", - "gross_pay_list": [ - { - "name": "Regular", - "type": "base", - "start_date": "2025-01-26", - "end_date": "2025-02-09", - "rate": "43.5471", - "hours": "47.1868", - "amount": "2054.85", - "hours_ytd": "233.6100", - "amount_ytd": "8219.40" - } - ], - "gross_pay_list_totals": { - "base": { - "amount": "2054.85", - "amount_ytd": "8219.40", - "hours": "47.1868", - "hours_ytd": "233.6100", - "rate_implied": "43.5500", - "rate_implied_ytd": "35.1800" - }, - "overtime": null, - "commission": null, - "bonus": null, - "other": null - }, - "employment": "1932bc3e-687f-36d4-94ce-3b576ad0967e" - }, - { - "id": "999de8a4-2662-3a61-be0c-9c2544a91e5f", - "account": "0195435d-6b2a-548c-306d-007f609cfadd", - "payroll_document": "0195435d-baeb-b1eb-2f23-fae0300e6126", - "deduction_list": [ - { - "amount": "64.71", - "name": "Roth", - "tax_classification": "post_tax", - "amount_ytd": "97.06" - }, - { - "amount": "64.71", - "name": "Roth", - "tax_classification": "post_tax", - "amount_ytd": "161.77" - } - ], - "filing_status": [ - { - "type": "federal", - "location": null, - "status": "married filing separately" - } - ], - "tax_list": [ - { - "amount": "323.54", - "name": "Federal Income Tax", - "type": "federal", - "amount_ytd": "647.08" - } - ], - "status": "completed", - "paystub_date": "2025-02-17T00:00:00Z", - "paystub_period": { - "start_date": "2025-01-28T00:00:00Z", - "end_date": "2025-02-11T00:00:00Z" - }, - "currency": "USD", - "gross_pay": "3617.52", - "deductions": "129.42", - "taxes": "323.54", - "net_pay": "3169.56", - "reimbursements": "5.00", - "hours": "47.84", - "fees": null, - "net_pay_ytd": "11167.61", - "gross_pay_ytd": "13323.66", - "metadata": {}, - "employer": "ACM LLC", - "employer_address": { - "city": "Seattle", - "state": "WA", - "country": "US", - "postal_code": "98109", - "line1": "202 Westlake Ave N", - "line2": null - }, - "created_at": "2025-02-26T17:46:11.338Z", - "updated_at": "2025-02-26T17:46:11.338Z", - "destinations": [], - "hours_ytd": "252.99", - "deductions_ytd": "355.89", - "taxes_ytd": "1811.82", - "gross_pay_list": [ - { - "name": "Regular", - "type": "base", - "start_date": "2025-01-28", - "end_date": "2025-02-11", - "rate": "67.6280", - "hours": "47.8408", - "amount": "3235.38", - "hours_ytd": "252.9900", - "amount_ytd": "12941.52" - }, - { - "name": "Commission", - "type": "commission", - "start_date": "2025-02-05", - "end_date": "2025-02-06", - "rate": null, - "hours": null, - "amount": "382.14", - "hours_ytd": null, - "amount_ytd": "382.14" - } - ], - "gross_pay_list_totals": { - "base": { - "amount": "3235.38", - "amount_ytd": "12941.52", - "hours": "47.8408", - "hours_ytd": "252.9900", - "rate_implied": "67.6300", - "rate_implied_ytd": "51.1500" - }, - "overtime": null, - "commission": { - "amount": "382.14", - "amount_ytd": "382.14", - "hours": null, - "hours_ytd": null, - "rate_implied": null, - "rate_implied_ytd": null - }, - "bonus": null, - "other": null - }, - "employment": "5c05c7a3-8281-31d2-ae83-699099ca45d6" - }, - { - "id": "a87f5acb-c7d3-3c33-89f5-f168910b9f58", - "account": "019539d9-25f1-6077-f204-ada14bd6b914", - "payroll_document": "019539d9-5867-9c2b-df9b-aaa347ba1526", - "deduction_list": [ - { - "amount": "33.75", - "name": "401K", - "tax_classification": "pre_tax", - "amount_ytd": "50.95" - }, - { - "amount": "45.00", - "name": "Vision", - "tax_classification": "pre_tax", - "amount_ytd": "72.44" - } - ], - "filing_status": [ - { - "type": "federal", - "location": null, - "status": "married filing separately" - } - ], - "tax_list": [ - { - "amount": "135.00", - "name": "Social Security Tax", - "type": "fica", - "amount_ytd": "272.57" - } - ], - "status": "completed", - "paystub_date": "2025-02-17T00:00:00Z", - "paystub_period": { - "start_date": "2025-01-29T00:00:00Z", - "end_date": "2025-02-17T00:00:00Z" - }, - "currency": "USD", - "gross_pay": "1222.43", - "deductions": "78.75", - "taxes": "135.00", - "net_pay": "1008.68", - "reimbursements": "0.00", - "hours": "46.13", - "fees": null, - "net_pay_ytd": "4883.01", - "gross_pay_ytd": "6145.24", - "metadata": {}, - "employer": "Accenture", - "employer_address": { - "city": "Seattle", - "state": "WA", - "country": "US", - "postal_code": "98109", - "line1": "202 Westlake Ave N", - "line2": null - }, - "created_at": "2025-02-24T21:25:00.243Z", - "updated_at": "2025-02-24T21:25:00.243Z", - "destinations": [], - "hours_ytd": "243.50", - "deductions_ytd": "257.57", - "taxes_ytd": "1014.46", - "gross_pay_list": [ - { - "name": "Regular", - "type": "base", - "start_date": "2025-01-29", - "end_date": "2025-02-17", - "rate": "24.3900", - "hours": "46.1271", - "amount": "1125.04", - "hours_ytd": "243.5000", - "amount_ytd": "5938.99" - }, - { - "name": "Bonus", - "type": "bonus", - "start_date": "2025-02-16", - "end_date": "2025-02-16", - "rate": null, - "hours": null, - "amount": "13.54", - "hours_ytd": null, - "amount_ytd": "13.54" - }, - { - "name": "Incentive", - "type": "bonus", - "start_date": "2025-02-11", - "end_date": "2025-02-11", - "rate": null, - "hours": null, - "amount": "83.85", - "hours_ytd": null, - "amount_ytd": "83.85" - }, - { - "name": "Commission", - "type": "commission", - "start_date": null, - "end_date": null, - "rate": null, - "hours": null, - "amount": null, - "hours_ytd": null, - "amount_ytd": "108.86" - } - ], - "gross_pay_list_totals": { - "base": { - "amount": "1125.04", - "amount_ytd": "5938.99", - "hours": "46.1271", - "hours_ytd": "243.5000", - "rate_implied": "24.3900", - "rate_implied_ytd": "24.3900" - }, - "overtime": null, - "commission": { - "amount": null, - "amount_ytd": "108.86", - "hours": null, - "hours_ytd": null, - "rate_implied": null, - "rate_implied_ytd": null - }, - "bonus": { - "amount": "97.39", - "amount_ytd": "97.39", - "hours": null, - "hours_ytd": null, - "rate_implied": null, - "rate_implied_ytd": null - }, - "other": null - }, - "employment": "52615d3b-1ff6-3fe3-9165-25986cd5645a" - }, - { - "id": "f92d0a1c-28fc-3d6b-878c-aed569b0e9df", - "account": "01954440-8c8b-cd52-4a1f-f7aa07d136ed", - "payroll_document": null, - "deduction_list": [], - "filing_status": [], - "tax_list": [], - "status": "completed", - "paystub_date": "2025-02-17T00:00:00Z", - "paystub_period": { - "start_date": null, - "end_date": null - }, - "currency": "USD", - "gross_pay": "59.62", - "deductions": null, - "taxes": null, - "net_pay": "59.62", - "reimbursements": null, - "hours": null, - "fees": null, - "net_pay_ytd": "409.62", - "gross_pay_ytd": "409.62", - "metadata": {}, - "employer": "Uber Driver", - "employer_address": null, - "created_at": "2025-02-26T21:56:26.188Z", - "updated_at": "2025-02-26T21:56:26.188Z", - "destinations": [], - "hours_ytd": "0.00", - "deductions_ytd": "0.00", - "taxes_ytd": "0.00", - "gross_pay_list": [], - "gross_pay_list_totals": null, - "employment": "26396fa4-6213-39a6-902a-f9a85d26db4d" - }, - { - "id": "bbdbd350-643c-377f-adfe-eb73feecaf18", - "account": "01954449-1753-b8b9-8cd9-77b4be11db19", - "payroll_document": null, - "deduction_list": [], - "filing_status": [], - "tax_list": [], - "status": "completed", - "paystub_date": "2025-02-13T00:00:00Z", - "paystub_period": { - "start_date": null, - "end_date": null - }, - "currency": "USD", - "gross_pay": "66.49", - "deductions": null, - "taxes": null, - "net_pay": "66.49", - "reimbursements": null, - "hours": null, - "fees": null, - "net_pay_ytd": "333.44", - "gross_pay_ytd": "333.44", - "metadata": {}, - "employer": "Lyft Driver", - "employer_address": null, - "created_at": "2025-02-26T22:03:41.126Z", - "updated_at": "2025-02-26T22:03:41.126Z", - "destinations": [], - "hours_ytd": "0.00", - "deductions_ytd": "0.00", - "taxes_ytd": "0.00", - "gross_pay_list": [], - "gross_pay_list_totals": null, - "employment": "31188b69-703a-3d6e-b6f6-24073ca84263" - }, - { - "id": "4d73d910-a1b6-3cab-aaa0-99f9b8c53321", - "account": "01954440-8c8b-cd52-4a1f-f7aa07d136ed", - "payroll_document": null, - "deduction_list": [], - "filing_status": [], - "tax_list": [], - "status": "completed", - "paystub_date": "2025-02-10T00:00:00Z", - "paystub_period": { - "start_date": null, - "end_date": null - }, - "currency": "USD", - "gross_pay": "82.91", - "deductions": null, - "taxes": null, - "net_pay": "82.91", - "reimbursements": null, - "hours": null, - "fees": null, - "net_pay_ytd": "350.00", - "gross_pay_ytd": "350.00", - "metadata": {}, - "employer": "Uber Driver", - "employer_address": null, - "created_at": "2025-02-26T21:56:26.204Z", - "updated_at": "2025-02-26T21:56:26.204Z", - "destinations": [], - "hours_ytd": "0.00", - "deductions_ytd": "0.00", - "taxes_ytd": "0.00", - "gross_pay_list": [], - "gross_pay_list_totals": null, - "employment": "26396fa4-6213-39a6-902a-f9a85d26db4d" - }, - { - "id": "80130ab0-4338-3f0c-8d59-af68d9b09bac", - "account": "01954449-1753-b8b9-8cd9-77b4be11db19", - "payroll_document": null, - "deduction_list": [], - "filing_status": [], - "tax_list": [], - "status": "completed", - "paystub_date": "2025-02-06T00:00:00Z", - "paystub_period": { - "start_date": null, - "end_date": null - }, - "currency": "USD", - "gross_pay": "11.21", - "deductions": null, - "taxes": null, - "net_pay": "11.21", - "reimbursements": null, - "hours": null, - "fees": null, - "net_pay_ytd": "266.95", - "gross_pay_ytd": "266.95", - "metadata": {}, - "employer": "Lyft Driver", - "employer_address": null, - "created_at": "2025-02-26T22:03:41.144Z", - "updated_at": "2025-02-26T22:03:41.144Z", - "destinations": [], - "hours_ytd": "0.00", - "deductions_ytd": "0.00", - "taxes_ytd": "0.00", - "gross_pay_list": [], - "gross_pay_list_totals": null, - "employment": "31188b69-703a-3d6e-b6f6-24073ca84263" - } - ], - "next": "https://api-sandbox.argyle.com/v2/paystubs?cursor=Y2xpZW50SWQ9MDE4ZjA4MzQtZDY5YS04NzliLTA1Y2ItZGRkZTAzMzAyNGYwJmludmVyc2VTb3J0PWZhbHNlJmxpbWl0PTEwJnJvd0tleT1TJTIzQyUyMzAxOGYwODM0LWQ2OWEtODc5Yi0wNWNiLWRkZGUwMzMwMjRmMCUyMzQ2MTE2ODQyNzk4ODY1ODc5MDMlMjMxNjZhMDkzNy03ZTU2LTM3YzctODZmYy1iNDFlNWM1MDViOTE%3D", - "previous": null -} \ No newline at end of file diff --git a/app/spec/support/fixtures/argyle/request_user.json b/app/spec/support/fixtures/argyle/request_user.json deleted file mode 100644 index fcbace752..000000000 --- a/app/spec/support/fixtures/argyle/request_user.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "id": "0195441c-5a5f-7d86-3be1-fa5797a441a6", - "created_at": "2025-02-26T21:14:23.712349Z", - "items_connected": [ - "item_000024123", - "item_000020392", - "item_000041078" - ], - "employers_connected": [ - "Uber Driver", - "Lyft Driver" - ], - "external_metadata": {}, - "external_id": "testing" -} \ No newline at end of file From d4e30cf79fabe63a0436bf7e9432bfa822a84fc9 Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Mon, 10 Mar 2025 19:43:43 -0400 Subject: [PATCH 19/26] missed a stray --- app/TODO.txt | 2 +- app/app/models/response_objects/income.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/TODO.txt b/app/TODO.txt index a893edf03..30ea35e35 100644 --- a/app/TODO.txt +++ b/app/TODO.txt @@ -8,4 +8,4 @@ TODO on this branch: [ ] 6. Figure out missing fields for employer address/phone [ ] 7. Update view logic as necessary [x] 8. Verify that Argyle dollar amount strings are being rendered correctly -[ ] 9. DRY ArgyleMethods.format_currency and format_date +[x] 9. DRY ArgyleMethods.format_currency and format_date diff --git a/app/app/models/response_objects/income.rb b/app/app/models/response_objects/income.rb index bfdcb13bf..a2287e007 100644 --- a/app/app/models/response_objects/income.rb +++ b/app/app/models/response_objects/income.rb @@ -21,7 +21,7 @@ def self.from_argyle(identities_response_body) new( account_id: identities_response_body["account"], pay_frequency: identities_response_body["base_pay"]["period"], - compensation_amount: ArgyleMethods.format_currency(identities_response_body["base_pay"]["amount"]), + compensation_amount: ArgyleFormatMethods.format_currency(identities_response_body["base_pay"]["amount"]), compensation_unit: identities_response_body["base_pay"]["currency"], ) end From 8c85c97a08a7e6031817755caf5f56e40600cf36 Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Mon, 10 Mar 2025 19:45:45 -0400 Subject: [PATCH 20/26] code error --- app/spec/support/argyle_api_helper.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/spec/support/argyle_api_helper.rb b/app/spec/support/argyle_api_helper.rb index 976a9e0e8..06ced3638 100644 --- a/app/spec/support/argyle_api_helper.rb +++ b/app/spec/support/argyle_api_helper.rb @@ -1,5 +1,3 @@ -:first_ - module ArgyleApiHelper def stub_request_items_response(userFolder) stub_request(:get, %r{#{ArgyleService::ITEMS_ENDPOINT}}) From e002c2488bfe3633bca1e5eb6d2d881d508996f2 Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Mon, 10 Mar 2025 20:32:17 -0400 Subject: [PATCH 21/26] refactor mock data helper methods. We might want to delete these anyway --- app/app/services/argyle_service.rb | 53 +++++++++++++++++++----------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/app/app/services/argyle_service.rb b/app/app/services/argyle_service.rb index 40270dd32..911cd3654 100644 --- a/app/app/services/argyle_service.rb +++ b/app/app/services/argyle_service.rb @@ -2,6 +2,7 @@ require "faraday" require "fileutils" +require "json" class ArgyleService ENVIRONMENTS = { @@ -110,27 +111,39 @@ def build_url(endpoint) @http.build_url(endpoint).to_s end - def _tmp_fetch_all(user_id:, user_name:) - FileUtils.mkdir_p "spec/support/fixtures/argyle/#{user_id}" + def store_mock_response(responsePayload:, folderName: "other", fileName:) + FileUtils.mkdir_p "spec/support/fixtures/argyle/#{folderName}" - File.open("spec/support/fixtures/argyle/#{user_id}/request_user.json", "wb") { - |f| f.puts(fetch_user_api(user: user_id).to_json) - } - - File.open("spec/support/fixtures/argyle/#{user_id}/request_identity.json", "wb") { - |f| f.puts(fetch_identity_api(account: user_id).to_json) - } - - File.open("spec/support/fixtures/argyle/#{user_id}/request_employment.json", "wb") { - |f| f.puts(fetch_employment_api(account:).to_json) - } + File.open("spec/support/fixtures/argyle/#{folderName}/#{fileName}.json", "wb") do + |f| f.puts(JSON.pretty_generate(responsePayload)) + end + end - File.open("spec/support/fixtures/argyle/#{user_id}/request_accounts.json", "wb") { - |f| f.puts(fetch_accounts_api(account:).to_json) - } - File.open("spec/support/fixtures/argyle/#{user_name}/request_paystubs.json", "wb") { - |f| f.puts(fetch_paystubs_api(user: user_id).to_json) - # , from_start_date: "2025-02-20", to_start_date: "2025-02-26").to_json) - } + # Only for use in sandbox environment for test mocking + def fetch__and_store_mock_data_for_user(argyle_user_id:, folderName:) + store_mock_response( + folderName: folderName, + fileName: "request_user", + responsePayload: fetch_user_api(user: argyle_user_id)) + + store_mock_response( + folderName: folderName, + fileName: "request_identity", + responsePayload: fetch_identities_api(user: argyle_user_id)) + + store_mock_response( + folderName: folderName, + fileName: "request_employment", + responsePayload: fetch_employment_api(user: argyle_user_id)) + + store_mock_response( + folderName: folderName, + fileName: "request_accounts", + responsePayload: fetch_accounts_api(user: argyle_user_id)) + + store_mock_response( + folderName: folderName, + fileName: "request_paystubs", + responsePayload: fetch_paystubs_api(user: argyle_user_id)) end end From e0c3db937e7f9b326ebeed205d7f1c31624e2941 Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Mon, 10 Mar 2025 20:37:15 -0400 Subject: [PATCH 22/26] remove requires --- app/app/models/response_objects/employment.rb | 2 -- app/app/models/response_objects/income.rb | 2 -- app/app/models/response_objects/paystub.rb | 3 --- 3 files changed, 7 deletions(-) diff --git a/app/app/models/response_objects/employment.rb b/app/app/models/response_objects/employment.rb index da678bde3..9b4f2a5a8 100644 --- a/app/app/models/response_objects/employment.rb +++ b/app/app/models/response_objects/employment.rb @@ -1,5 +1,3 @@ -require_relative "ArgyleFormatMethods" - EMPLOYMENT_FIELDS = %i[ account_id employer_name diff --git a/app/app/models/response_objects/income.rb b/app/app/models/response_objects/income.rb index a2287e007..776aa0915 100644 --- a/app/app/models/response_objects/income.rb +++ b/app/app/models/response_objects/income.rb @@ -1,5 +1,3 @@ -require_relative "ArgyleFormatMethods" - INCOME_FIELDS = %i[ account_id pay_frequency diff --git a/app/app/models/response_objects/paystub.rb b/app/app/models/response_objects/paystub.rb index 5ace2c127..3cb9d07ae 100644 --- a/app/app/models/response_objects/paystub.rb +++ b/app/app/models/response_objects/paystub.rb @@ -1,6 +1,3 @@ -require_relative "ArgyleFormatMethods" -require_relative "PinwheelFormatMethods" - PAYSTUB_FIELDS = %i[ account_id gross_pay_amount From 413ff47f1ca3d4db4ba3f7005d3a737a2d7f4aaf Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Mon, 10 Mar 2025 20:59:23 -0400 Subject: [PATCH 23/26] ensured endpoint constant definitions for all to match whats in PinwheelService --- app/app/services/argyle_service.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/app/services/argyle_service.rb b/app/app/services/argyle_service.rb index 911cd3654..736d3d717 100644 --- a/app/app/services/argyle_service.rb +++ b/app/app/services/argyle_service.rb @@ -16,6 +16,9 @@ class ArgyleService ITEMS_ENDPOINT = "items" PAYSTUBS_ENDPOINT = "paystubs" IDENTITIES_ENDPOINT = "identities" + USERS_ENDPOINT = "users" + ACCOUNTS_ENDPOINT = "accounts" + EMPLOYMENTS_ENDPOINT = "employments" def initialize(environment, api_key_id = nil, api_key_secret = nil) @api_key_id = api_key_id || ENVIRONMENTS.fetch(environment.to_sym)[:api_key_id] @@ -74,20 +77,20 @@ def items(query = nil) # https://docs.argyle.com/api-reference/users#retrieve def fetch_user_api(user:) - @http.get(build_url("users/#{user}")).body + @http.get(build_url("#{USERS_ENDPOINT}/#{user}")).body end # https://docs.argyle.com/api-reference/identities#list def fetch_identities_api(**params) # todo: paginate - @http.get("identities", params).body + @http.get(IDENTITIES_ENDPOINT, params).body end # https://docs.argyle.com/api-reference/accounts#list def fetch_accounts_api(**params) # TODO: paginate # json["data"].map { |paystub_json| ResponseObjects::Paystub.from_pinwheel(paystub_json) } - @http.get("accounts", params).body + @http.get(ACCOUNTS_ENDPOINT, params).body end # https://docs.argyle.com/api-reference/paystubs#list @@ -97,13 +100,13 @@ def fetch_paystubs_api(**params) end def create_user - @http.post("users").body + @http.post(USERS_ENDPOINT).body end # https://docs.argyle.com/api-reference/employments#list - def fetch_employment_api(**params) + def fetch_employments_api(**params) # json["data"].map { |paystub_json| ResponseObjects::Paystub.from_pinwheel(paystub_json) } - @http.get("employments", params).body + @http.get(EMPLOYMENTS_ENDPOINT, params).body end # TODO: refactor this into common function between argyle_service/pinwheel_service From f60e817afc20e0077647667d173812be77650e61 Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Mon, 10 Mar 2025 21:03:56 -0400 Subject: [PATCH 24/26] remove TODO.txt --- app/TODO.txt | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 app/TODO.txt diff --git a/app/TODO.txt b/app/TODO.txt deleted file mode 100644 index 30ea35e35..000000000 --- a/app/TODO.txt +++ /dev/null @@ -1,11 +0,0 @@ -TODO on this branch: - -[x] 1. Wrap up Bob test data -[x] 2. Write tests for all three test users. (I skipped Sarah for now because bob + joe covered cases) -[ ] 3. User ID --> Account ID refactor -[x] 4. Rewrite ArgyleService methods to instantiate response objects based on /identities -[ ] 5. Then, update PinwheelDataHelper to use ArgyleService -[ ] 6. Figure out missing fields for employer address/phone -[ ] 7. Update view logic as necessary -[x] 8. Verify that Argyle dollar amount strings are being rendered correctly -[x] 9. DRY ArgyleMethods.format_currency and format_date From 057f5d10677d13868653d131747acd2eb278466a Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Mon, 10 Mar 2025 21:14:27 -0400 Subject: [PATCH 25/26] make sure the functions are named to be plural if they return multiple --- app/app/services/argyle_service.rb | 4 ++-- app/spec/services/argyle_service_spec.rb | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/app/services/argyle_service.rb b/app/app/services/argyle_service.rb index 736d3d717..fa986e887 100644 --- a/app/app/services/argyle_service.rb +++ b/app/app/services/argyle_service.rb @@ -50,13 +50,13 @@ def fetch_paystubs(**params) json["results"].map { |paystub_json| ResponseObjects::Paystub.from_argyle(paystub_json) } end - def fetch_employment(**params) + def fetch_employments(**params) # Note: we actually fetch Argyle's identity API instead of employment for the correct data json = fetch_identities_api(**params) json["results"].map { |identity_json| ResponseObjects::Employment.from_argyle(identity_json) } end - def fetch_income(**params) + def fetch_incomes(**params) # Note: we actually fetch Argyle's identity API instead of employment for the correct data json = fetch_identities_api(**params) json["results"].map { |identity_json| ResponseObjects::Income.from_argyle(identity_json) } diff --git a/app/spec/services/argyle_service_spec.rb b/app/spec/services/argyle_service_spec.rb index 5927c4262..3bebda5bb 100644 --- a/app/spec/services/argyle_service_spec.rb +++ b/app/spec/services/argyle_service_spec.rb @@ -139,21 +139,21 @@ end end - describe '#fetch_employment' do + describe '#fetch_employments' do context "for Bob, a Uber driver" do before do stub_request_identities_response("bob") end it 'returns an array of ResponseObjects::Employment' do - employments = service.fetch_employment(account: account_id) + employments = service.fetch_employments(account: account_id) expect(employments.length).to eq(1) expect(employments[0]).to be_a(ResponseObjects::Employment) end it 'returns an array of ResponseObjects::Employment' do - employments = service.fetch_employment(account: account_id) + employments = service.fetch_employments(account: account_id) expect(employments[0]).to have_attributes( account_id: "019571bc-2f60-3955-d972-dbadfe0913a8", @@ -194,21 +194,21 @@ end end - describe '#fetch_income' do + describe '#fetch_incomes' do context "for Joe, a W2 employee" do before do stub_request_identities_response("joe") end it 'returns an array of ResponseObjects::Income' do - incomes = service.fetch_income(account: account_id) + incomes = service.fetch_incomes(account: account_id) expect(incomes.length).to eq(1) expect(incomes[0]).to be_a(ResponseObjects::Income) end it 'returns income object with expected attributes' do - incomes = service.fetch_income(account: account_id) + incomes = service.fetch_incomes(account: account_id) expect(incomes[0]).to have_attributes( account_id: "01956d62-18a0-090f-bc09-2ac44b7edf99", From c9949c4adbff063459a8304c7869dbae92e042d8 Mon Sep 17 00:00:00 2001 From: Jeff Catania Date: Tue, 11 Mar 2025 14:00:15 -0400 Subject: [PATCH 26/26] moved format_methods into proper folders --- app/app/models/response_objects/employment.rb | 2 +- .../argyle.rb} | 2 +- .../pinwheel.rb} | 2 +- app/app/models/response_objects/income.rb | 2 +- app/app/models/response_objects/paystub.rb | 20 +++++++++---------- 5 files changed, 14 insertions(+), 14 deletions(-) rename app/app/models/response_objects/{ArgyleFormatMethods.rb => format_methods/argyle.rb} (93%) rename app/app/models/response_objects/{PinwheelFormatMethods.rb => format_methods/pinwheel.rb} (94%) diff --git a/app/app/models/response_objects/employment.rb b/app/app/models/response_objects/employment.rb index 9b4f2a5a8..492e2a808 100644 --- a/app/app/models/response_objects/employment.rb +++ b/app/app/models/response_objects/employment.rb @@ -26,7 +26,7 @@ def self.from_argyle(identity_response_body) employer_name: identity_response_body["employer"], start_date: identity_response_body["hire_date"], termination_date: identity_response_body["termination_date"], - status: ArgyleFormatMethods.format_employment_status(identity_response_body["employment_status"]), + status: ResponseObjects::FormatMethods::Argyle.format_employment_status(identity_response_body["employment_status"]), ) end end diff --git a/app/app/models/response_objects/ArgyleFormatMethods.rb b/app/app/models/response_objects/format_methods/argyle.rb similarity index 93% rename from app/app/models/response_objects/ArgyleFormatMethods.rb rename to app/app/models/response_objects/format_methods/argyle.rb index 6aa90bac5..56053929b 100644 --- a/app/app/models/response_objects/ArgyleFormatMethods.rb +++ b/app/app/models/response_objects/format_methods/argyle.rb @@ -1,4 +1,4 @@ -module ArgyleFormatMethods +module ResponseObjects::FormatMethods::Argyle def self.format_employment_status(employment_status) return unless employment_status diff --git a/app/app/models/response_objects/PinwheelFormatMethods.rb b/app/app/models/response_objects/format_methods/pinwheel.rb similarity index 94% rename from app/app/models/response_objects/PinwheelFormatMethods.rb rename to app/app/models/response_objects/format_methods/pinwheel.rb index 7cc60d8a7..4849edd7e 100644 --- a/app/app/models/response_objects/PinwheelFormatMethods.rb +++ b/app/app/models/response_objects/format_methods/pinwheel.rb @@ -1,4 +1,4 @@ -module PinwheelFormatMethods +module ResponseObjects::FormatMethods::Pinwheel def self.hours(earnings) base_hours = earnings .filter { |e| e["category"] != "overtime" } diff --git a/app/app/models/response_objects/income.rb b/app/app/models/response_objects/income.rb index 776aa0915..a01e9cbab 100644 --- a/app/app/models/response_objects/income.rb +++ b/app/app/models/response_objects/income.rb @@ -19,7 +19,7 @@ def self.from_argyle(identities_response_body) new( account_id: identities_response_body["account"], pay_frequency: identities_response_body["base_pay"]["period"], - compensation_amount: ArgyleFormatMethods.format_currency(identities_response_body["base_pay"]["amount"]), + compensation_amount: ResponseObjects::FormatMethods::Argyle.format_currency(identities_response_body["base_pay"]["amount"]), compensation_unit: identities_response_body["base_pay"]["currency"], ) end diff --git a/app/app/models/response_objects/paystub.rb b/app/app/models/response_objects/paystub.rb index 3cb9d07ae..22a89ad44 100644 --- a/app/app/models/response_objects/paystub.rb +++ b/app/app/models/response_objects/paystub.rb @@ -22,8 +22,8 @@ def self.from_pinwheel(response_body) pay_period_start: response_body["pay_period_start"], pay_period_end: response_body["pay_period_end"], pay_date: response_body["pay_date"], - hours: PinwheelFormatMethods.hours(response_body["earnings"]), - hours_by_earning_category: PinwheelFormatMethods.hours_by_earning_category(response_body["earnings"]), + hours: ResponseObjects::FormatMethods::Pinwheel.hours(response_body["earnings"]), + hours_by_earning_category: ResponseObjects::FormatMethods::Pinwheel.hours_by_earning_category(response_body["earnings"]), deductions: response_body["deductions"].map do |deduction| OpenStruct.new( category: deduction["category"], @@ -37,18 +37,18 @@ def self.from_pinwheel(response_body) def self.from_argyle(response_body) new( account_id: response_body["account"], - gross_pay_amount: ArgyleFormatMethods.format_currency(response_body["gross_pay"]), - net_pay_amount: ArgyleFormatMethods.format_currency(response_body["net_pay"]), - gross_pay_ytd: ArgyleFormatMethods.format_currency(response_body["gross_pay_ytd"]), - pay_period_start: ArgyleFormatMethods.format_date(response_body["paystub_period"]["start_date"]), - pay_period_end: ArgyleFormatMethods.format_date(response_body["paystub_period"]["end_date"]), - pay_date: ArgyleFormatMethods.format_date(response_body["paystub_date"]), + gross_pay_amount: ResponseObjects::FormatMethods::Argyle.format_currency(response_body["gross_pay"]), + net_pay_amount: ResponseObjects::FormatMethods::Argyle.format_currency(response_body["net_pay"]), + gross_pay_ytd: ResponseObjects::FormatMethods::Argyle.format_currency(response_body["gross_pay_ytd"]), + pay_period_start: ResponseObjects::FormatMethods::Argyle.format_date(response_body["paystub_period"]["start_date"]), + pay_period_end: ResponseObjects::FormatMethods::Argyle.format_date(response_body["paystub_period"]["end_date"]), + pay_date: ResponseObjects::FormatMethods::Argyle.format_date(response_body["paystub_date"]), hours: response_body["hours"], - hours_by_earning_category: ArgyleFormatMethods.hours_by_earning_category(response_body["gross_pay_list"]), + hours_by_earning_category: ResponseObjects::FormatMethods::Argyle.hours_by_earning_category(response_body["gross_pay_list"]), deductions: response_body["deduction_list"].map do |deduction| OpenStruct.new( category: deduction["name"], - amount: ArgyleFormatMethods.format_currency(deduction["amount"]), + amount: ResponseObjects::FormatMethods::Argyle.format_currency(deduction["amount"]), ) end, )