diff --git a/Gemfile b/Gemfile index 74e2582..41ecd30 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,3 @@ -ruby '2.7.6' source 'https://rubygems.org' -gem 'jazzy', '~> 0.14.2' \ No newline at end of file +gem 'jazzy', '~> 0.14.3' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index dad174a..ecda25a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,30 +1,29 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.5) + CFPropertyList (3.0.6) rexml - activesupport (6.1.6) + activesupport (7.0.4.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - zeitwerk (~> 2.3) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + addressable (2.8.4) + public_suffix (>= 2.0.2, < 6.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) atomos (0.1.3) claide (1.1.0) - cocoapods (1.11.3) + cocoapods (1.12.0) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.11.3) + cocoapods-core (= 1.12.0) cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.4.0, < 2.0) + cocoapods-downloader (>= 1.6.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.4.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) @@ -32,10 +31,10 @@ GEM gh_inspector (~> 1.0) molinillo (~> 0.8.0) nap (~> 1.0) - ruby-macho (>= 1.0, < 3.0) + ruby-macho (>= 2.3.0, < 3.0) xcodeproj (>= 1.21.0, < 2.0) - cocoapods-core (1.11.3) - activesupport (>= 5.0, < 7) + cocoapods-core (1.12.0) + activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) concurrent-ruby (~> 1.1) @@ -54,18 +53,18 @@ GEM netrc (~> 0.11) cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.1.10) + concurrent-ruby (1.2.2) escape (0.0.4) - ethon (0.15.0) + ethon (0.16.0) ffi (>= 1.15.0) ffi (1.15.5) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) httpclient (2.8.3) - i18n (1.10.0) + i18n (1.12.0) concurrent-ruby (~> 1.0) - jazzy (0.14.2) + jazzy (0.14.3) cocoapods (~> 1.5) mustache (~> 1.1) open4 (~> 1.3) @@ -75,9 +74,9 @@ GEM sassc (~> 2.1) sqlite3 (~> 1.3) xcinvoke (~> 0.3.0) - json (2.6.2) + json (2.6.3) liferaft (0.0.6) - minitest (5.16.2) + minitest (5.18.0) molinillo (0.8.0) mustache (1.1.1) nanaimo (0.3.0) @@ -85,16 +84,17 @@ GEM netrc (0.11.0) open4 (1.3.4) public_suffix (4.0.7) - redcarpet (3.5.1) + redcarpet (3.6.0) rexml (3.2.5) - rouge (3.29.0) + rouge (3.30.0) ruby-macho (2.5.1) sassc (2.4.0) ffi (~> 1.9) - sqlite3 (1.4.4) + sqlite3 (1.6.2-arm64-darwin) + sqlite3 (1.6.2-x86_64-darwin) typhoeus (1.4.0) ethon (>= 0.9.0) - tzinfo (2.0.4) + tzinfo (2.0.6) concurrent-ruby (~> 1.0) xcinvoke (0.3.0) liferaft (~> 0.0.6) @@ -105,17 +105,14 @@ GEM colored2 (~> 3.1) nanaimo (~> 0.3.0) rexml (~> 3.2.4) - zeitwerk (2.6.0) PLATFORMS arm64-darwin-21 + arm64-darwin-22 x86_64-darwin-21 DEPENDENCIES - jazzy (~> 0.14.2) - -RUBY VERSION - ruby 2.7.6p219 + jazzy (~> 0.14.3) BUNDLED WITH 2.3.17 diff --git a/SwiftYNAB.podspec b/SwiftYNAB.podspec index 294a424..9e95a1e 100644 --- a/SwiftYNAB.podspec +++ b/SwiftYNAB.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "SwiftYNAB" - s.version = "2.0.1" + s.version = "2.1.0" s.summary = "YNAB API Framework" s.description = "SwiftYNAB is an iOS/macOS/tvOS/WatchOS framework for the You Need a Budget API" s.homepage = "http://github.com/andrebocchini/swiftynab" diff --git a/SwiftYNAB/SwiftYNAB.xcodeproj/project.pbxproj b/SwiftYNAB/SwiftYNAB.xcodeproj/project.pbxproj index a681139..a97e802 100644 --- a/SwiftYNAB/SwiftYNAB.xcodeproj/project.pbxproj +++ b/SwiftYNAB/SwiftYNAB.xcodeproj/project.pbxproj @@ -233,6 +233,7 @@ 307350F82278CE8500456287 /* TransactionDetail.json in Resources */ = {isa = PBXBuildFile; fileRef = 307350F72278CE8500456287 /* TransactionDetail.json */; }; 307350FC2278D22100456287 /* TransactionsResponse.json in Resources */ = {isa = PBXBuildFile; fileRef = 307350FB2278D22100456287 /* TransactionsResponse.json */; }; 307350FE2278D27500456287 /* Serializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307350FD2278D27500456287 /* Serializer.swift */; }; + 3074C81529D3E0450068F00B /* DebtTransactionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3074C81429D3E0450068F00B /* DebtTransactionType.swift */; }; 3076E6AA28B9C8A6006BAADC /* RequestMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3076E6A928B9C8A6006BAADC /* RequestMethod.swift */; }; 3076E6AC28B9CC13006BAADC /* RequestMethodTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3076E6AB28B9CC13006BAADC /* RequestMethodTests.swift */; }; 307EA3B12878794A00E380E7 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307EA3B02878794A00E380E7 /* Response.swift */; }; @@ -257,6 +258,11 @@ 307EA3D728788EB300E380E7 /* SaveTransactionsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307EA3D628788EB300E380E7 /* SaveTransactionsResponse.swift */; }; 307EA3D928788ECE00E380E7 /* HybridTransactionsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307EA3D828788ECE00E380E7 /* HybridTransactionsResponse.swift */; }; 307EA3DB2878A13C00E380E7 /* SerializerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 307EA3DA2878A13C00E380E7 /* SerializerType.swift */; }; + 3083131C29E6DB3700C33A45 /* DeleteTransactionRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3083131B29E6DB3700C33A45 /* DeleteTransactionRequest.swift */; }; + 3083131E29E6DD4200C33A45 /* DeleteTransactionResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3083131D29E6DD4200C33A45 /* DeleteTransactionResponse.swift */; }; + 3083132029E6E08D00C33A45 /* DeleteTransactionResponse.json in Resources */ = {isa = PBXBuildFile; fileRef = 3083131F29E6E08D00C33A45 /* DeleteTransactionResponse.json */; }; + 3083132229E6E1B000C33A45 /* DeleteTransactionResponseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3083132129E6E1B000C33A45 /* DeleteTransactionResponseTests.swift */; }; + 3083132429E6E22200C33A45 /* DeleteTransactionRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3083132329E6E22200C33A45 /* DeleteTransactionRequestTests.swift */; }; 308C2B42227B8377005C5477 /* ScheduledTransactionDetail.json in Resources */ = {isa = PBXBuildFile; fileRef = 308C2B41227B8377005C5477 /* ScheduledTransactionDetail.json */; }; 308C2B48227B85B6005C5477 /* ScheduledTransactionDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 308C2B47227B85B6005C5477 /* ScheduledTransactionDetail.swift */; }; 308C2B4A227B85EB005C5477 /* ScheduledTransactionDetailTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 308C2B49227B85EB005C5477 /* ScheduledTransactionDetailTests.swift */; }; @@ -553,6 +559,7 @@ 307350F72278CE8500456287 /* TransactionDetail.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = TransactionDetail.json; sourceTree = ""; }; 307350FB2278D22100456287 /* TransactionsResponse.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = TransactionsResponse.json; sourceTree = ""; }; 307350FD2278D27500456287 /* Serializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Serializer.swift; sourceTree = ""; }; + 3074C81429D3E0450068F00B /* DebtTransactionType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebtTransactionType.swift; sourceTree = ""; }; 3076E6A928B9C8A6006BAADC /* RequestMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestMethod.swift; sourceTree = ""; }; 3076E6AB28B9CC13006BAADC /* RequestMethodTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestMethodTests.swift; sourceTree = ""; }; 307EA3B02878794A00E380E7 /* Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = ""; }; @@ -577,6 +584,11 @@ 307EA3D628788EB300E380E7 /* SaveTransactionsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveTransactionsResponse.swift; sourceTree = ""; }; 307EA3D828788ECE00E380E7 /* HybridTransactionsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HybridTransactionsResponse.swift; sourceTree = ""; }; 307EA3DA2878A13C00E380E7 /* SerializerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerializerType.swift; sourceTree = ""; }; + 3083131B29E6DB3700C33A45 /* DeleteTransactionRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteTransactionRequest.swift; sourceTree = ""; }; + 3083131D29E6DD4200C33A45 /* DeleteTransactionResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteTransactionResponse.swift; sourceTree = ""; }; + 3083131F29E6E08D00C33A45 /* DeleteTransactionResponse.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = DeleteTransactionResponse.json; sourceTree = ""; }; + 3083132129E6E1B000C33A45 /* DeleteTransactionResponseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteTransactionResponseTests.swift; sourceTree = ""; }; + 3083132329E6E22200C33A45 /* DeleteTransactionRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteTransactionRequestTests.swift; sourceTree = ""; }; 308C2B41227B8377005C5477 /* ScheduledTransactionDetail.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ScheduledTransactionDetail.json; sourceTree = ""; }; 308C2B47227B85B6005C5477 /* ScheduledTransactionDetail.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScheduledTransactionDetail.swift; sourceTree = ""; }; 308C2B49227B85EB005C5477 /* ScheduledTransactionDetailTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScheduledTransactionDetailTests.swift; sourceTree = ""; }; @@ -767,6 +779,7 @@ 302F16C728793D6A00A336AD /* TransactionsByCategoryRequestTests.swift */, 302F16CB28793E3100A336AD /* TransactionsByPayeeRequestTests.swift */, 30EBB51822791C18000FE07D /* UserRequestTests.swift */, + 3083132329E6E22200C33A45 /* DeleteTransactionRequestTests.swift */, ); path = requests; sourceTree = ""; @@ -817,6 +830,7 @@ 30EBB567227BC629000FE07D /* CategoryResponse.json */, 307350E92278CA5F00456287 /* CurrencyFormat.json */, 307350ED2278CCE500456287 /* DateFormat.json */, + 3083131F29E6E08D00C33A45 /* DeleteTransactionResponse.json */, 307350F12278CDF800456287 /* ErrorDetail.json */, 307350F22278CDF800456287 /* ErrorResponse.json */, 308C2B4B227B870D005C5477 /* HybridTransaction.json */, @@ -971,6 +985,7 @@ 308C2B59227B8B82005C5477 /* TransactionSummary.swift */, 30DA1CA3227A27690005E34E /* TransactionType.swift */, 30EBB50A22791929000FE07D /* User.swift */, + 3074C81429D3E0450068F00B /* DebtTransactionType.swift */, ); path = models; sourceTree = ""; @@ -999,6 +1014,7 @@ 302F16952879306A00A336AD /* CategoriesRequest.swift */, 302F1699287930F800A336AD /* CategoryByMonthRequest.swift */, 30EBB563227BC32C000FE07D /* CategoryRequest.swift */, + 3083131B29E6DB3700C33A45 /* DeleteTransactionRequest.swift */, 302F16A92879359A00A336AD /* LocationsForPayeeRequest.swift */, 302F16A12879332100A336AD /* MonthRequest.swift */, 30BA9B6E227C962100BD4281 /* MonthsRequest.swift */, @@ -1087,6 +1103,7 @@ 307EA3B62878854100E380E7 /* BudgetSummaryResponse.swift */, 307EA3BC28788A7800E380E7 /* CategoriesResponse.swift */, 307EA3BE28788A9B00E380E7 /* CategoryResponse.swift */, + 3083131D29E6DD4200C33A45 /* DeleteTransactionResponse.swift */, 30E1633D2277ED8D00A1222B /* ErrorResponse.swift */, 307EA3D828788ECE00E380E7 /* HybridTransactionsResponse.swift */, 307EA3C228788B3D00E380E7 /* MonthResponse.swift */, @@ -1117,6 +1134,7 @@ 3008B4492878AD420025034C /* BudgetSummaryResponseTests.swift */, 3008B44D2878AD9F0025034C /* CategoriesResponseTests.swift */, 30EBB55D227BC104000FE07D /* CategoryResponseTests.swift */, + 3083132129E6E1B000C33A45 /* DeleteTransactionResponseTests.swift */, 30DA1CC3227A50AC0005E34E /* ErrorResponseTests.swift */, 3008B45D2878AF150025034C /* HybridTransactionsResponseTests.swift */, 30BA9B64227C94C600BD4281 /* MonthResponseTests.swift */, @@ -1393,6 +1411,7 @@ 30BA9B7F227CCB6C00BD4281 /* HybridTransactionsResponse.json in Resources */, 307350F82278CE8500456287 /* TransactionDetail.json in Resources */, 30307FB1227B50F600CBF404 /* Payee.json in Resources */, + 3083132029E6E08D00C33A45 /* DeleteTransactionResponse.json in Resources */, 308C2B5C227B8CB1005C5477 /* TransactionSummary.json in Resources */, 30EBB56A227BC629000FE07D /* CategoryResponse.json in Resources */, 30EBB569227BC629000FE07D /* Category.json in Resources */, @@ -1622,6 +1641,7 @@ 302F16B62879392700A336AD /* SaveTransactionRequest.swift in Sources */, 3046F3DD227B6613007778A1 /* PayeeRequest.swift in Sources */, 3046A4EF2876A098006CB35A /* ClientType.swift in Sources */, + 3083131C29E6DB3700C33A45 /* DeleteTransactionRequest.swift in Sources */, 307EA3D728788EB300E380E7 /* SaveTransactionsResponse.swift in Sources */, 302F16C628793D4900A336AD /* TransactionsByCategoryRequest.swift in Sources */, 3046F3EF227B7281007778A1 /* Category.swift in Sources */, @@ -1647,7 +1667,9 @@ 30E1633E2277ED8D00A1222B /* ErrorResponse.swift in Sources */, 302F169A287930F800A336AD /* CategoryByMonthRequest.swift in Sources */, 307EA3C128788B1C00E380E7 /* MonthsResponse.swift in Sources */, + 3083131E29E6DD4200C33A45 /* DeleteTransactionResponse.swift in Sources */, 307EA3CB28788D1300E380E7 /* PayeesResponse.swift in Sources */, + 3074C81529D3E0450068F00B /* DebtTransactionType.swift in Sources */, 307EA3C328788B3D00E380E7 /* MonthResponse.swift in Sources */, 307350CC2278A0D200456287 /* SaveTransaction.swift in Sources */, 307EA3D128788E4700E380E7 /* TransactionResponse.swift in Sources */, @@ -1720,6 +1742,7 @@ 302F169C2879311500A336AD /* CategoryByMonthRequestTests.swift in Sources */, 3046F3F1227B73DD007778A1 /* PayeeLocationTests.swift in Sources */, 308F59A328B8DBF500445343 /* MonthServiceTests.swift in Sources */, + 3083132229E6E1B000C33A45 /* DeleteTransactionResponseTests.swift in Sources */, 307350DC2278BF8A00456287 /* BudgetSummaryTests.swift in Sources */, 308C2B4A227B85EB005C5477 /* ScheduledTransactionDetailTests.swift in Sources */, 302F16BC28793B3600A336AD /* SaveTransactionsRequestTests.swift in Sources */, @@ -1740,6 +1763,7 @@ 307350DE2278BFB600456287 /* JSONTools.swift in Sources */, 302F16B42879371E00A336AD /* ScheduledTransactionsRequestTests.swift in Sources */, 308F59A528B93ECF00445343 /* PayeeLocationServiceTests.swift in Sources */, + 3083132429E6E22200C33A45 /* DeleteTransactionRequestTests.swift in Sources */, 30BA9B51227C84BE00BD4281 /* PayeeLocationResponseTests.swift in Sources */, 308F599F28B86D3C00445343 /* BudgetServiceTests.swift in Sources */, 30DA1CC4227A50AC0005E34E /* ErrorResponseTests.swift in Sources */, diff --git a/SwiftYNAB/SwiftYNAB/Info.plist b/SwiftYNAB/SwiftYNAB/Info.plist index e47deeb..571d51d 100644 --- a/SwiftYNAB/SwiftYNAB/Info.plist +++ b/SwiftYNAB/SwiftYNAB/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.0.1 + 2.1.0 CFBundleVersion - 2.0.1 + 2.1.0 diff --git a/SwiftYNAB/SwiftYNAB/YNAB.swift b/SwiftYNAB/SwiftYNAB/YNAB.swift index fb00b0d..151df5b 100644 --- a/SwiftYNAB/SwiftYNAB/YNAB.swift +++ b/SwiftYNAB/SwiftYNAB/YNAB.swift @@ -45,7 +45,7 @@ public class YNAB { /// - accessToken: Personal API access token, or token obtained via OAuth login /// - urlSession: By default, it uses the default `URLSession.shared`, but allows the injection of a custom session public init(accessToken: String, urlSession: URLSession = .shared) { - self.client = Client( + client = Client( accessToken: accessToken, urlSession: urlSession, serializer: Serializer.shared diff --git a/SwiftYNAB/SwiftYNAB/models/Account.swift b/SwiftYNAB/SwiftYNAB/models/Account.swift index ec06ff8..f812ed2 100644 --- a/SwiftYNAB/SwiftYNAB/models/Account.swift +++ b/SwiftYNAB/SwiftYNAB/models/Account.swift @@ -40,6 +40,27 @@ public struct Account: Codable, Equatable { /// Payee id for transfers public let transferPayeeId: String + /// Indicates whether the account is linked via direct import + public let directImportLinked: Bool + + /// Indicates whether direct import is in an error state + public let directImportInError: Bool + + /// Date when account was last reconciled + public let lastReconciledAt: String? + + /// Original balance for a debt account + public let debtOriginalBalance: Int? + + /// Interest rate at different points in time + public let debtInterestRates: [String: Int] + + /// Minimum payment at different points in time + public let debtMinimumPayments: [String: Int] + + /// Escrow amount at different points in time + public let debtEscrowAmounts: [String: Int] + /// Deleted account or not public let deleted: Bool } diff --git a/SwiftYNAB/SwiftYNAB/models/Category.swift b/SwiftYNAB/SwiftYNAB/models/Category.swift index cc73112..ed3d818 100644 --- a/SwiftYNAB/SwiftYNAB/models/Category.swift +++ b/SwiftYNAB/SwiftYNAB/models/Category.swift @@ -40,6 +40,15 @@ public struct Category: Codable, Equatable { /// Type of goal associated with this category public let goalType: String? + /// The day of the goal + public let goalDay: Int? + + /// The goal cadence + public let goalCadence: Int? + + /// The goal cadence frequency + public let goalCadenceFrequency: Int? + /// Goal creation month public let goalCreationMonth: String? @@ -52,6 +61,21 @@ public struct Category: Codable, Equatable { /// Goal percentage complete public let goalPercentageComplete: Int? + /// The number of months, including the current month, left in the current goal period. + public let goalMonthsToBudget: Int? + + /// The amount of funding still needed in the current month to stay on track towards completing the goal within the current + /// goal period. This amount will generally correspond to the ‘Underfunded’ amount in the web and mobile clients except when + /// viewing a category with a Needed for Spending Goal in a future month. The web and mobile clients will ignore any funding + /// from a prior goal period when viewing category with a Needed for Spending Goal in a future month. + public let goalUnderFunded: Int? + + /// The total amount funded towards the goal within the current goal period. + public let goalOverallFunded: Int? + + /// The amount of funding still needed to complete the goal within the current goal period. + public let goalOverallLeft: Int? + /// Whether or not the goal is deleted public let deleted: Bool } diff --git a/SwiftYNAB/SwiftYNAB/models/DebtTransactionType.swift b/SwiftYNAB/SwiftYNAB/models/DebtTransactionType.swift new file mode 100644 index 0000000..5ddaafd --- /dev/null +++ b/SwiftYNAB/SwiftYNAB/models/DebtTransactionType.swift @@ -0,0 +1,20 @@ +// +// DebtTransactionType.swift +// SwiftYNAB +// +// Created by Andre Bocchini on 3/28/23. +// Copyright © 2023 Andre Bocchini. All rights reserved. +// + +import Foundation + +public enum DebtTransactionType: String, Codable { + case payment + case refund + case fee + case interest + case escrow + case balancedAdjustment + case credit + case charge +} diff --git a/SwiftYNAB/SwiftYNAB/models/HybridTransaction.swift b/SwiftYNAB/SwiftYNAB/models/HybridTransaction.swift index 8967a56..3ff37c8 100644 --- a/SwiftYNAB/SwiftYNAB/models/HybridTransaction.swift +++ b/SwiftYNAB/SwiftYNAB/models/HybridTransaction.swift @@ -52,6 +52,9 @@ public struct HybridTransaction: Codable, Equatable { /// Category id public let categoryId: String + /// Category name + public let categoryName: String + /// Transfer account id public let transferAccountId: String? @@ -64,6 +67,15 @@ public struct HybridTransaction: Codable, Equatable { /// Import id public let importId: String? + /// If the transaction was imported, the payee name that was used when importing and before applying any payee rename rules + public let importPayeeName: String? + + /// If the transaction was imported, the original payee name as it appeared on the statement + public let importPayeeOriginal: String? + + /// If the transaction is a debt/loan account transaction, the type of transaction + public let debtTransactionType: DebtTransactionType? + /// Whether or not the transaction is deleted public let deleted: Bool } diff --git a/SwiftYNAB/SwiftYNAB/models/SubTransaction.swift b/SwiftYNAB/SwiftYNAB/models/SubTransaction.swift index 6a15cd1..8647576 100644 --- a/SwiftYNAB/SwiftYNAB/models/SubTransaction.swift +++ b/SwiftYNAB/SwiftYNAB/models/SubTransaction.swift @@ -25,12 +25,21 @@ public struct SubTransaction: Codable, Equatable { /// Payee id public let payeeId: String? + /// Payee name + public let payeeName: String? + /// Category id public let categoryId: String? + /// Category name + public let categoryName: String? + /// If it's a transfer transaction, the transfer account id public let transferAccountId: String? + /// If a transfer, the id of transaction on the other side of the transfer + public let transferTransactionId: String? + /// Whether or not the transaction has been deleted public let deleted: Bool } diff --git a/SwiftYNAB/SwiftYNAB/models/TransactionSummary.swift b/SwiftYNAB/SwiftYNAB/models/TransactionSummary.swift index c56459e..b22386c 100644 --- a/SwiftYNAB/SwiftYNAB/models/TransactionSummary.swift +++ b/SwiftYNAB/SwiftYNAB/models/TransactionSummary.swift @@ -46,9 +46,25 @@ public struct TransactionSummary: Codable, Equatable { /// If it's a transfer transaction, the transfer transaction id public let transferTransactionId: String? - /// Import id + /// If transaction is matched, the id of the matched transaction + public let matchedTransactionId: String? + + /// If the transaction was imported, this field is a unique (by account) import identifier. If this transaction was imported through + /// File Based Import or Direct Import and not through the API, the import_id will have the + /// format: 'YNAB:[milliunit_amount]:[iso_date]:[occurrence]'. For example, a transaction dated 2015-12-30 in the amount + /// of -$294.23 USD would have an import_id of 'YNAB:-294230:2015-12-30:1’. If a second transaction on the same + /// account was imported and had the same date and same amount, its import_id would be 'YNAB:-294230:2015-12-30:2’. public let importId: String? + /// If the transaction was imported, the payee name that was used when importing and before applying any payee rename rules + public let importPayeeName: String? + + /// If the transaction was imported, the original payee name as it appeared on the statement + public let importPayeeNameOriginal: String? + + /// If the transaction was imported, the original payee name as it appeared on the statement + public let debtTransactionType: DebtTransactionType? + /// Whether or not the transaction has been deleted public let deleted: Bool } diff --git a/SwiftYNAB/SwiftYNAB/networking/Client.swift b/SwiftYNAB/SwiftYNAB/networking/Client.swift index 53d40f3..b6c874b 100644 --- a/SwiftYNAB/SwiftYNAB/networking/Client.swift +++ b/SwiftYNAB/SwiftYNAB/networking/Client.swift @@ -14,7 +14,7 @@ class Client { private let serializer: SerializerType init(accessToken: String, urlSession: URLSessionType, serializer: SerializerType) { - self.authorizationHeader = "Bearer \(accessToken)" + authorizationHeader = "Bearer \(accessToken)" self.urlSession = urlSession self.serializer = serializer } diff --git a/SwiftYNAB/SwiftYNAB/requests/DeleteTransactionRequest.swift b/SwiftYNAB/SwiftYNAB/requests/DeleteTransactionRequest.swift new file mode 100644 index 0000000..3d523ef --- /dev/null +++ b/SwiftYNAB/SwiftYNAB/requests/DeleteTransactionRequest.swift @@ -0,0 +1,36 @@ +// +// DeleteTransactionRequest.swift +// SwiftYNAB +// +// Created by Andre Bocchini on 4/12/23. +// Copyright © 2023 Andre Bocchini. All rights reserved. +// + +import Foundation + +struct DeleteTransactionRequest { + let budgetId: String + let transactionId: String +} + +extension DeleteTransactionRequest { + struct Body: Codable { + let budgetId: String + let transactionId: String + } +} + +extension DeleteTransactionRequest: Request { + var path: String { + "/v1/budgets/\(budgetId)/transactions/\(transactionId)" + } + + var method: RequestMethod { + .delete + } + + var body: Data? { + let body = Body(budgetId: budgetId, transactionId: transactionId) + return try? Serializer.shared.encode(body) + } +} diff --git a/SwiftYNAB/SwiftYNAB/requests/Request.swift b/SwiftYNAB/SwiftYNAB/requests/Request.swift index 9bb7c12..4a8d284 100644 --- a/SwiftYNAB/SwiftYNAB/requests/Request.swift +++ b/SwiftYNAB/SwiftYNAB/requests/Request.swift @@ -54,7 +54,7 @@ extension Request { case .patch, .post, .put: request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = body - case .get: + case .delete, .get: break } diff --git a/SwiftYNAB/SwiftYNAB/requests/RequestMethod.swift b/SwiftYNAB/SwiftYNAB/requests/RequestMethod.swift index bdcf9d1..eae1074 100644 --- a/SwiftYNAB/SwiftYNAB/requests/RequestMethod.swift +++ b/SwiftYNAB/SwiftYNAB/requests/RequestMethod.swift @@ -9,6 +9,7 @@ import Foundation enum RequestMethod: String { + case delete = "DELETE" case get = "GET" case patch = "PATCH" case post = "POST" diff --git a/SwiftYNAB/SwiftYNAB/responses/DeleteTransactionResponse.swift b/SwiftYNAB/SwiftYNAB/responses/DeleteTransactionResponse.swift new file mode 100644 index 0000000..c897278 --- /dev/null +++ b/SwiftYNAB/SwiftYNAB/responses/DeleteTransactionResponse.swift @@ -0,0 +1,13 @@ +// +// DeleteTransactionResponse.swift +// SwiftYNAB +// +// Created by Andre Bocchini on 4/12/23. +// Copyright © 2023 Andre Bocchini. All rights reserved. +// + +import Foundation + +struct DeleteTransactionResponse: Codable { + let transaction: TransactionDetail +} diff --git a/SwiftYNAB/SwiftYNAB/services/TransactionService.swift b/SwiftYNAB/SwiftYNAB/services/TransactionService.swift index 09d8b26..a66a515 100644 --- a/SwiftYNAB/SwiftYNAB/services/TransactionService.swift +++ b/SwiftYNAB/SwiftYNAB/services/TransactionService.swift @@ -193,7 +193,7 @@ extension TransactionService: TransactionServiceType { ) } - /// Updates a single transactions. + /// Updates a single transaction. /// /// - Parameters: /// - budgetId: The id of the budget (*last_used* can also be used to specify the last used budget) @@ -242,4 +242,23 @@ extension TransactionService: TransactionServiceType { response.duplicateImportIds ) } + + /// Delete a single transaction. + /// + /// - Parameters: + /// - budgetId: The id of the budget (*last_used* can also be used to specify the last used budget) + /// - transactionId: Id of the rransaction to be deleted + /// + /// - Returns: The updated transaction + public func deleteTransaction( + budgetId: String, + transactionId: String + ) async throws -> TransactionDetail { + let request = DeleteTransactionRequest( + budgetId: budgetId, + transactionId: transactionId + ) + let response: DeleteTransactionResponse = try await client.request(request) + return response.transaction + } } diff --git a/SwiftYNAB/SwiftYNABTests/formatters/DateConverterTests.swift b/SwiftYNAB/SwiftYNABTests/formatters/DateConverterTests.swift index dda6a70..78d630b 100644 --- a/SwiftYNAB/SwiftYNABTests/formatters/DateConverterTests.swift +++ b/SwiftYNAB/SwiftYNABTests/formatters/DateConverterTests.swift @@ -52,7 +52,7 @@ class DateConverterTests: XCTestCase { XCTAssertEqual("2015.02.25", converter.budgetFormatDateString(from: "2015-02-25")) } - func testBudgetFormatDateStringIsNilWithInvalidDateString () { + func testBudgetFormatDateStringIsNilWithInvalidDateString() { let format = DateFormat(format: "YYY.MM.DD") let converter = DateConverter(dateFormat: format) XCTAssertNil(converter.budgetFormatDateString(from: "i am not a date")) diff --git a/SwiftYNAB/SwiftYNABTests/json/Account.json b/SwiftYNAB/SwiftYNABTests/json/Account.json index 1d30211..c07edad 100644 --- a/SwiftYNAB/SwiftYNABTests/json/Account.json +++ b/SwiftYNAB/SwiftYNABTests/json/Account.json @@ -9,5 +9,10 @@ "cleared_balance": 0, "uncleared_balance": 0, "transfer_payee_id": "0bfe752a-89e1-65d4-73d4-0ae2c2f71d54", - "deleted": false + "deleted": false, + "direct_import_linked": false, + "direct_import_in_error": false, + "debt_interest_rates": {}, + "debt_minimum_payments": {}, + "debt_escrow_amounts": {} } diff --git a/SwiftYNAB/SwiftYNABTests/json/AccountResponse.json b/SwiftYNAB/SwiftYNABTests/json/AccountResponse.json index 12f51f4..90e20a6 100644 --- a/SwiftYNAB/SwiftYNABTests/json/AccountResponse.json +++ b/SwiftYNAB/SwiftYNABTests/json/AccountResponse.json @@ -10,6 +10,11 @@ "cleared_balance": 0, "uncleared_balance": 0, "transfer_payee_id": "0bfe752a-89e1-65d4-73d4-0ae2c2f71d54", - "deleted": false -} + "deleted": false, + "direct_import_linked": false, + "direct_import_in_error": false, + "debt_interest_rates": {}, + "debt_minimum_payments": {}, + "debt_escrow_amounts": {} + } } diff --git a/SwiftYNAB/SwiftYNABTests/json/AccountsResponse.json b/SwiftYNAB/SwiftYNABTests/json/AccountsResponse.json index 2f3f641..08ea648 100644 --- a/SwiftYNAB/SwiftYNABTests/json/AccountsResponse.json +++ b/SwiftYNAB/SwiftYNABTests/json/AccountsResponse.json @@ -10,7 +10,12 @@ "cleared_balance": 0, "uncleared_balance": 0, "transfer_payee_id": "0bfe752a-89e1-65d4-73d4-0ae2c2f71d54", - "deleted": false + "deleted": false, + "direct_import_linked": false, + "direct_import_in_error": false, + "debt_interest_rates": {}, + "debt_minimum_payments": {}, + "debt_escrow_amounts": {} }], "server_knowledge": 18814 } diff --git a/SwiftYNAB/SwiftYNABTests/json/BudgetDetail.json b/SwiftYNAB/SwiftYNABTests/json/BudgetDetail.json index 65909f4..57d521f 100644 --- a/SwiftYNAB/SwiftYNABTests/json/BudgetDetail.json +++ b/SwiftYNAB/SwiftYNABTests/json/BudgetDetail.json @@ -29,7 +29,12 @@ "cleared_balance": 0, "uncleared_balance": 0, "transfer_payee_id": "0bfe752a-89e1-65d4-73d4-0ae2c2f71d54", - "deleted": false + "deleted": false, + "direct_import_linked": false, + "direct_import_in_error": false, + "debt_interest_rates": {}, + "debt_minimum_payments": {}, + "debt_escrow_amounts": {} } ], "payees": [ @@ -166,4 +171,4 @@ "deleted": false } ] -} \ No newline at end of file +} diff --git a/SwiftYNAB/SwiftYNABTests/json/BudgetDetailResponse.json b/SwiftYNAB/SwiftYNABTests/json/BudgetDetailResponse.json index 7249c0c..e0a8109 100644 --- a/SwiftYNAB/SwiftYNABTests/json/BudgetDetailResponse.json +++ b/SwiftYNAB/SwiftYNABTests/json/BudgetDetailResponse.json @@ -30,7 +30,12 @@ "cleared_balance": 0, "uncleared_balance": 0, "transfer_payee_id": "0bfe752a-89e1-65d4-73d4-0ae2c2f71d54", - "deleted": false + "deleted": false, + "direct_import_linked": false, + "direct_import_in_error": false, + "debt_interest_rates": {}, + "debt_minimum_payments": {}, + "debt_escrow_amounts": {} } ], "payees": [ diff --git a/SwiftYNAB/SwiftYNABTests/json/CategoryGroupWithCategories.json b/SwiftYNAB/SwiftYNABTests/json/CategoryGroupWithCategories.json index 1772303..43108a8 100644 --- a/SwiftYNAB/SwiftYNABTests/json/CategoryGroupWithCategories.json +++ b/SwiftYNAB/SwiftYNABTests/json/CategoryGroupWithCategories.json @@ -22,4 +22,4 @@ "deleted": false } ] -} \ No newline at end of file +} diff --git a/SwiftYNAB/SwiftYNABTests/json/DeleteTransactionResponse.json b/SwiftYNAB/SwiftYNABTests/json/DeleteTransactionResponse.json new file mode 100644 index 0000000..7f49384 --- /dev/null +++ b/SwiftYNAB/SwiftYNABTests/json/DeleteTransactionResponse.json @@ -0,0 +1,30 @@ +{ + "transaction_ids": [ + "8c203ffc-d0d0-44b3-8ab4-5aae9901b928" + ], + "transaction": { + "id": "8f203c6c-d1d0-42b3-82d4-5aae9900b928", + "date": "2020-06-24", + "amount": 10000, + "memo": null, + "cleared": "cleared", + "approved": true, + "flag_color": null, + "account_id": "7099375a-7905-46c5-b6ff-edef5676da96", + "account_name": "BofA", + "payee_id": "d76a5430-cbbb-482d-a22a-6c8cfcc198a6", + "payee_name": "Starting Balance", + "category_id": "5b88038c-7d15-48c4-8aa3-98c9774f1275", + "category_name": "Inflow: Ready to Assign", + "transfer_account_id": null, + "transfer_transaction_id": null, + "matched_transaction_id": null, + "import_id": null, + "import_payee_name": null, + "import_payee_name_original": null, + "debt_transaction_type": null, + "deleted": true, + "subtransactions": [] + }, + "server_knowledge": 1010 +} diff --git a/SwiftYNAB/SwiftYNABTests/json/NewBudgetAccountResponse.json b/SwiftYNAB/SwiftYNABTests/json/NewBudgetAccountResponse.json index 8aaa3a5..87b3516 100644 --- a/SwiftYNAB/SwiftYNABTests/json/NewBudgetAccountResponse.json +++ b/SwiftYNAB/SwiftYNABTests/json/NewBudgetAccountResponse.json @@ -11,7 +11,12 @@ "cleared_balance": 0, "uncleared_balance": 0, "transfer_payee_id": "47c0ff92-43rd-47da-1234-13d9e16cb394", - "deleted": false + "deleted": false, + "direct_import_linked": false, + "direct_import_in_error": false, + "debt_interest_rates": {}, + "debt_minimum_payments": {}, + "debt_escrow_amounts": {} }, "server_knowledge": 36971 } diff --git a/SwiftYNAB/SwiftYNABTests/requests/DeleteTransactionRequestTests.swift b/SwiftYNAB/SwiftYNABTests/requests/DeleteTransactionRequestTests.swift new file mode 100644 index 0000000..0558290 --- /dev/null +++ b/SwiftYNAB/SwiftYNABTests/requests/DeleteTransactionRequestTests.swift @@ -0,0 +1,30 @@ +// +// DeleteTransactionRequestTests.swift +// SwiftYNABTests +// +// Created by Andre Bocchini on 4/12/23. +// Copyright © 2023 Andre Bocchini. All rights reserved. +// + +import Foundation +import XCTest +@testable import SwiftYNAB + +class DeleteTransactionRequestTests: XCTestCase { + func deleteTransactionRequest() throws { + let request = DeleteTransactionRequest( + budgetId: "budget_id", + transactionId: "transaction_id" + ) + + let body = DeleteTransactionRequest.Body( + budgetId: "budget_id", + transactionId: "transaction_id" + ) + let expectedBody = try XCTUnwrap(Serializer.shared.encode(body)) + + XCTAssertEqual(request.method, .delete) + XCTAssertEqual(request.body, expectedBody) + XCTAssertEqual(request.path, "/v1/budgets/budget_id/transactions/transaction_id") + } +} diff --git a/SwiftYNAB/SwiftYNABTests/responses/DeleteTransactionResponseTests.swift b/SwiftYNAB/SwiftYNABTests/responses/DeleteTransactionResponseTests.swift new file mode 100644 index 0000000..487e7a1 --- /dev/null +++ b/SwiftYNAB/SwiftYNABTests/responses/DeleteTransactionResponseTests.swift @@ -0,0 +1,16 @@ +// +// DeleteTransactionResponseTests.swift +// SwiftYNABTests +// +// Created by Andre Bocchini on 4/12/23. +// Copyright © 2023 Andre Bocchini. All rights reserved. +// + +import XCTest +@testable import SwiftYNAB + +class DeleteTransactionResponseTests: XCTestCase { + func testDeleteTransactionResponseDecoding() { + XCTAssertNoThrow(try JSONTools.testDecoding(type: DeleteTransactionResponse.self)) + } +} diff --git a/SwiftYNAB/SwiftYNABTests/services/AccountServiceTests.swift b/SwiftYNAB/SwiftYNABTests/services/AccountServiceTests.swift index 27d9c13..20ffe0d 100644 --- a/SwiftYNAB/SwiftYNABTests/services/AccountServiceTests.swift +++ b/SwiftYNAB/SwiftYNABTests/services/AccountServiceTests.swift @@ -23,6 +23,13 @@ class AccountServiceTests: XCTestCase { clearedBalance: 0, unclearedBalance: 0, transferPayeeId: "payeed_id", + directImportLinked: false, + directImportInError: false, + lastReconciledAt: nil, + debtOriginalBalance: nil, + debtInterestRates: [:], + debtMinimumPayments: [:], + debtEscrowAmounts: [:], deleted: false ) let expectedResponse = AccountResponse(account: expectedAccount) @@ -61,6 +68,13 @@ class AccountServiceTests: XCTestCase { clearedBalance: 0, unclearedBalance: 0, transferPayeeId: "payeed_id", + directImportLinked: false, + directImportInError: false, + lastReconciledAt: nil, + debtOriginalBalance: nil, + debtInterestRates: [:], + debtMinimumPayments: [:], + debtEscrowAmounts: [:], deleted: false ) let expectedResponse = AccountsResponse(accounts: [expectedAccount], serverKnowledge: 2) @@ -100,6 +114,13 @@ class AccountServiceTests: XCTestCase { clearedBalance: 0, unclearedBalance: 0, transferPayeeId: "payeed_id", + directImportLinked: false, + directImportInError: false, + lastReconciledAt: nil, + debtOriginalBalance: nil, + debtInterestRates: [:], + debtMinimumPayments: [:], + debtEscrowAmounts: [:], deleted: false ) let expectedResponse = AccountResponse(account: expectedAccount) diff --git a/SwiftYNAB/SwiftYNABTests/services/CategoryServiceTests.swift b/SwiftYNAB/SwiftYNABTests/services/CategoryServiceTests.swift index 9cda7eb..a08466c 100644 --- a/SwiftYNAB/SwiftYNABTests/services/CategoryServiceTests.swift +++ b/SwiftYNAB/SwiftYNABTests/services/CategoryServiceTests.swift @@ -53,10 +53,17 @@ class CategoryServiceTests: XCTestCase { activity: 0, balance: 0, goalType: nil, + goalDay: nil, + goalCadence: nil, + goalCadenceFrequency: nil, goalCreationMonth: nil, goalTarget: nil, goalTargetMonth: nil, goalPercentageComplete: nil, + goalMonthsToBudget: nil, + goalUnderFunded: nil, + goalOverallFunded: nil, + goalOverallLeft: nil, deleted: false ) let expectedResponse = CategoryResponse(category: expectedCategory) @@ -92,10 +99,17 @@ class CategoryServiceTests: XCTestCase { activity: 0, balance: 0, goalType: nil, + goalDay: nil, + goalCadence: nil, + goalCadenceFrequency: nil, goalCreationMonth: nil, goalTarget: nil, goalTargetMonth: nil, goalPercentageComplete: nil, + goalMonthsToBudget: nil, + goalUnderFunded: nil, + goalOverallFunded: nil, + goalOverallLeft: nil, deleted: false ) let expectedResponse = CategoryResponse(category: expectedCategory) @@ -139,10 +153,17 @@ class CategoryServiceTests: XCTestCase { activity: 0, balance: 0, goalType: nil, + goalDay: nil, + goalCadence: nil, + goalCadenceFrequency: nil, goalCreationMonth: nil, goalTarget: nil, goalTargetMonth: nil, goalPercentageComplete: nil, + goalMonthsToBudget: nil, + goalUnderFunded: nil, + goalOverallFunded: nil, + goalOverallLeft: nil, deleted: false ) let expectedResponse = CategoryResponse(category: expectedCategory) diff --git a/SwiftYNAB/SwiftYNABTests/services/TransactionServiceTests.swift b/SwiftYNAB/SwiftYNABTests/services/TransactionServiceTests.swift index 5fa98fb..fbfa811 100644 --- a/SwiftYNAB/SwiftYNABTests/services/TransactionServiceTests.swift +++ b/SwiftYNAB/SwiftYNABTests/services/TransactionServiceTests.swift @@ -170,10 +170,14 @@ class TransactionServiceTests: XCTestCase { payeeId: nil, payeeName: nil, categoryId: "category_id", + categoryName: "", transferAccountId: nil, transferTransactionId: nil, matchedTransactionId: nil, importId: nil, + importPayeeName: nil, + importPayeeOriginal: nil, + debtTransactionType: .charge, deleted: false ) let expectedResponse = HybridTransactionsResponse(transactions: [expectedTransaction]) @@ -217,10 +221,14 @@ class TransactionServiceTests: XCTestCase { payeeId: "payee_id", payeeName: nil, categoryId: "category_id", + categoryName: "", transferAccountId: nil, transferTransactionId: nil, matchedTransactionId: nil, importId: nil, + importPayeeName: nil, + importPayeeOriginal: nil, + debtTransactionType: .charge, deleted: false ) let expectedResponse = HybridTransactionsResponse(transactions: [expectedTransaction]) @@ -517,4 +525,54 @@ class TransactionServiceTests: XCTestCase { XCTAssertEqual(error as? SwiftYNABError, .httpError(statusCode: 500)) } } + + func testDeleteTransactionsReturnsTransactionsWhenRequestSucceeds() async throws { + let expectedTransaction = TransactionDetail( + id: "transaction_id", + date: "2022-07-07", + amount: 0, + memo: nil, + cleared: "cleared", + approved: false, + flagColor: nil, + accountId: "account_id", + accountName: "account_name", + payeeId: "payee_id", + payeeName: nil, + categoryId: nil, + categoryName: nil, + transferAccountId: nil, + transferTransactionId: nil, + matchedTransactionId: nil, + importId: nil, + deleted: false, + subtransactions: [] + ) + let expectedResponse = DeleteTransactionResponse(transaction: expectedTransaction) + + let client = MockSuccessClient(expectedResponse: expectedResponse) + let service = TransactionService(client: client) + let actualResponse = try await service.deleteTransaction( + budgetId: "budget_id", + transactionId: "transaction_id" + ) + + XCTAssertEqual(actualResponse, expectedTransaction) + } + + func testDeleteTransactionsThrowsErrorWhenRequestFails() async throws { + let expectedError = SwiftYNABError.httpError(statusCode: 500) + let client = MockFailureClient(expectedError: expectedError) + let service = TransactionService(client: client) + + do { + _ = try await service.deleteTransaction( + budgetId: "budget_id", + transactionId: "transaction_id" + ) + XCTFail("Expected error to be thrown") + } catch { + XCTAssertEqual(error as? SwiftYNABError, .httpError(statusCode: 500)) + } + } } diff --git a/docs/Classes.html b/docs/Classes.html index 883a9cf..4c4f733 100644 --- a/docs/Classes.html +++ b/docs/Classes.html @@ -80,6 +80,9 @@