ledger_sync 1.0.10 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +36 -20
  3. data/README.md +105 -66
  4. data/ledger_sync.gemspec +1 -0
  5. data/lib/ledger_sync.rb +23 -4
  6. data/lib/ledger_sync/adaptors/operation.rb +28 -28
  7. data/lib/ledger_sync/adaptors/quickbooks_online/account/mapping.rb +325 -0
  8. data/lib/ledger_sync/adaptors/quickbooks_online/account/operations/create.rb +54 -0
  9. data/lib/ledger_sync/adaptors/quickbooks_online/account/operations/find.rb +38 -0
  10. data/lib/ledger_sync/adaptors/quickbooks_online/account/operations/update.rb +61 -0
  11. data/lib/ledger_sync/adaptors/quickbooks_online/account/searcher.rb +67 -0
  12. data/lib/ledger_sync/adaptors/quickbooks_online/adaptor.rb +18 -16
  13. data/lib/ledger_sync/adaptors/quickbooks_online/bill/operations/create.rb +65 -0
  14. data/lib/ledger_sync/adaptors/quickbooks_online/bill/operations/find.rb +37 -0
  15. data/lib/ledger_sync/adaptors/quickbooks_online/bill/operations/update.rb +69 -0
  16. data/lib/ledger_sync/adaptors/quickbooks_online/bill/searcher.rb +28 -0
  17. data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/create.rb +6 -6
  18. data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/find.rb +1 -3
  19. data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/update.rb +6 -9
  20. data/lib/ledger_sync/adaptors/quickbooks_online/customer/searcher.rb +2 -38
  21. data/lib/ledger_sync/adaptors/quickbooks_online/deposit/operations/create.rb +61 -0
  22. data/lib/ledger_sync/adaptors/quickbooks_online/deposit/operations/find.rb +36 -0
  23. data/lib/ledger_sync/adaptors/quickbooks_online/deposit/operations/update.rb +65 -0
  24. data/lib/ledger_sync/adaptors/quickbooks_online/deposit/searcher.rb +28 -0
  25. data/lib/ledger_sync/adaptors/quickbooks_online/expense/mapping.rb +15 -0
  26. data/lib/ledger_sync/adaptors/quickbooks_online/expense/operations/create.rb +68 -0
  27. data/lib/ledger_sync/adaptors/quickbooks_online/expense/operations/find.rb +39 -0
  28. data/lib/ledger_sync/adaptors/quickbooks_online/expense/operations/update.rb +72 -0
  29. data/lib/ledger_sync/adaptors/quickbooks_online/expense/searcher.rb +28 -0
  30. data/lib/ledger_sync/adaptors/quickbooks_online/journal_entry/mapping.rb +14 -0
  31. data/lib/ledger_sync/adaptors/quickbooks_online/journal_entry/operations/create.rb +56 -0
  32. data/lib/ledger_sync/adaptors/quickbooks_online/journal_entry/operations/find.rb +34 -0
  33. data/lib/ledger_sync/adaptors/quickbooks_online/journal_entry/operations/update.rb +60 -0
  34. data/lib/ledger_sync/adaptors/quickbooks_online/journal_entry/searcher.rb +28 -0
  35. data/lib/ledger_sync/adaptors/quickbooks_online/operation.rb +30 -0
  36. data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/create.rb +8 -22
  37. data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/find.rb +0 -2
  38. data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/update.rb +6 -22
  39. data/lib/ledger_sync/adaptors/quickbooks_online/searcher.rb +45 -0
  40. data/lib/ledger_sync/adaptors/quickbooks_online/transfer/operations/create.rb +52 -0
  41. data/lib/ledger_sync/adaptors/quickbooks_online/transfer/operations/find.rb +36 -0
  42. data/lib/ledger_sync/adaptors/quickbooks_online/transfer/operations/update.rb +57 -0
  43. data/lib/ledger_sync/adaptors/quickbooks_online/vendor/operations/create.rb +46 -0
  44. data/lib/ledger_sync/adaptors/quickbooks_online/vendor/operations/find.rb +36 -0
  45. data/lib/ledger_sync/adaptors/quickbooks_online/vendor/operations/update.rb +51 -0
  46. data/lib/ledger_sync/adaptors/quickbooks_online/vendor/searcher.rb +64 -0
  47. data/lib/ledger_sync/adaptors/searcher.rb +3 -1
  48. data/lib/ledger_sync/adaptors/test/account/operations/create.rb +53 -0
  49. data/lib/ledger_sync/adaptors/test/account/operations/find.rb +38 -0
  50. data/lib/ledger_sync/adaptors/test/account/operations/invalid.rb +25 -0
  51. data/lib/ledger_sync/adaptors/test/account/operations/update.rb +50 -0
  52. data/lib/ledger_sync/adaptors/test/account/operations/valid.rb +31 -0
  53. data/lib/ledger_sync/adaptors/test/account/searcher.rb +40 -0
  54. data/lib/ledger_sync/adaptors/test/adaptor.rb +4 -4
  55. data/lib/ledger_sync/adaptors/test/customer/operations/create.rb +4 -6
  56. data/lib/ledger_sync/adaptors/test/customer/operations/find.rb +0 -2
  57. data/lib/ledger_sync/adaptors/test/customer/operations/update.rb +4 -6
  58. data/lib/ledger_sync/adaptors/test/customer/searcher.rb +2 -2
  59. data/lib/ledger_sync/adaptors/test/expense/operations/create.rb +54 -0
  60. data/lib/ledger_sync/adaptors/test/expense/operations/find.rb +39 -0
  61. data/lib/ledger_sync/adaptors/test/expense/operations/update.rb +57 -0
  62. data/lib/ledger_sync/adaptors/test/payment/operations/create.rb +4 -20
  63. data/lib/ledger_sync/adaptors/test/payment/operations/find.rb +0 -2
  64. data/lib/ledger_sync/adaptors/test/payment/operations/update.rb +4 -20
  65. data/lib/ledger_sync/adaptors/test/transfer/operations/create.rb +45 -0
  66. data/lib/ledger_sync/adaptors/test/transfer/operations/find.rb +36 -0
  67. data/lib/ledger_sync/adaptors/test/transfer/operations/update.rb +48 -0
  68. data/lib/ledger_sync/adaptors/test/vendor/operations/create.rb +47 -0
  69. data/lib/ledger_sync/adaptors/test/vendor/operations/find.rb +34 -0
  70. data/lib/ledger_sync/adaptors/test/vendor/operations/invalid.rb +21 -0
  71. data/lib/ledger_sync/adaptors/test/vendor/operations/update.rb +44 -0
  72. data/lib/ledger_sync/adaptors/test/vendor/operations/valid.rb +27 -0
  73. data/lib/ledger_sync/adaptors/test/vendor/searcher.rb +41 -0
  74. data/lib/ledger_sync/error/resource_errors.rb +24 -0
  75. data/lib/ledger_sync/resource.rb +24 -64
  76. data/lib/ledger_sync/resource_attribute.rb +50 -0
  77. data/lib/ledger_sync/resource_attribute/dirty_mixin.rb +49 -0
  78. data/lib/ledger_sync/resource_attribute/mixin.rb +90 -0
  79. data/lib/ledger_sync/resource_attribute/reference.rb +9 -0
  80. data/lib/ledger_sync/resource_attribute/reference/many.rb +34 -0
  81. data/lib/ledger_sync/resource_attribute/reference/one.rb +33 -0
  82. data/lib/ledger_sync/resource_attribute_set.rb +58 -0
  83. data/lib/ledger_sync/resources/account.rb +14 -0
  84. data/lib/ledger_sync/resources/bill.rb +17 -0
  85. data/lib/ledger_sync/resources/bill_line_item.rb +11 -0
  86. data/lib/ledger_sync/resources/customer.rb +5 -4
  87. data/lib/ledger_sync/resources/deposit.rb +16 -0
  88. data/lib/ledger_sync/resources/deposit_line_item.rb +11 -0
  89. data/lib/ledger_sync/resources/expense.rb +19 -0
  90. data/lib/ledger_sync/resources/expense_line_item.rb +11 -0
  91. data/lib/ledger_sync/resources/journal_entry.rb +13 -0
  92. data/lib/ledger_sync/resources/journal_entry_line_item.rb +12 -0
  93. data/lib/ledger_sync/resources/payment.rb +5 -3
  94. data/lib/ledger_sync/resources/transfer.rb +15 -0
  95. data/lib/ledger_sync/resources/vendor.rb +12 -0
  96. data/lib/ledger_sync/result.rb +0 -24
  97. data/lib/ledger_sync/type/boolean.rb +17 -0
  98. data/lib/ledger_sync/type/date.rb +17 -0
  99. data/lib/ledger_sync/type/float.rb +17 -0
  100. data/lib/ledger_sync/type/integer.rb +17 -0
  101. data/lib/ledger_sync/type/reference_many.rb +27 -0
  102. data/lib/ledger_sync/type/reference_one.rb +26 -0
  103. data/lib/ledger_sync/type/string.rb +17 -0
  104. data/lib/ledger_sync/type/value.rb +12 -0
  105. data/lib/ledger_sync/type/value_mixin.rb +19 -0
  106. data/lib/ledger_sync/util/hash_helpers.rb +16 -1
  107. data/lib/ledger_sync/util/resources_builder.rb +36 -9
  108. data/lib/ledger_sync/version.rb +1 -1
  109. metadata +92 -7
  110. data/lib/ledger_sync/adaptors/quickbooks_online/customer/operations/upsert.rb +0 -42
  111. data/lib/ledger_sync/adaptors/quickbooks_online/payment/operations/upsert.rb +0 -53
  112. data/lib/ledger_sync/adaptors/test/customer/operations/upsert.rb +0 -42
  113. data/lib/ledger_sync/adaptors/test/payment/operations/upsert.rb +0 -53
  114. data/lib/ledger_sync/sync.rb +0 -108
  115. data/lib/ledger_sync/util/coordinator.rb +0 -72
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ledger_sync/type/value'
4
+
5
+ Gem.find_files('ledger_sync/type/**/*.rb').each do |path|
6
+ require path
7
+ end
8
+
9
+ Gem.find_files('ledger_sync/resource_attribute/**/*.rb').each do |path|
10
+ require path
11
+ end
12
+
13
+ module LedgerSync
14
+ class ResourceAttribute
15
+ include Fingerprintable::Mixin
16
+ include SimplySerializable::Mixin
17
+
18
+ attr_accessor :value
19
+ attr_reader :name,
20
+ :reference,
21
+ :type
22
+
23
+ def initialize(name:, type:, value: nil)
24
+ @name = name.to_sym
25
+
26
+ type = type.new if type.respond_to?(:new) && !type.is_a?(Type::Value)
27
+
28
+ raise "Invalid Type: #{type}" unless type.is_a?(ActiveModel::Type::Value)
29
+
30
+ @type = type
31
+
32
+ @value = value
33
+ end
34
+
35
+ def cast(value)
36
+ type.cast(value)
37
+ end
38
+
39
+ # This is for ActiveModel::Dirty, since we define @attributes
40
+ # def forgetting_assignment; end
41
+
42
+ def reference?
43
+ is_a?(Reference)
44
+ end
45
+
46
+ def valid_with?(value:)
47
+ type.valid_without_casting?(value: value)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Helper methods for adding dirty tracked attributes
4
+ module LedgerSync
5
+ class ResourceAttribute
6
+ module DirtyMixin
7
+ def self.included(base)
8
+ base.include(ActiveModel::Dirty)
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ module ClassMethods
13
+ def dirty_attribute(*names)
14
+ names.each do |name|
15
+ class_eval do
16
+ attr_reader name
17
+ define_attribute_methods name # From ActiveModel
18
+ dirty_attributes[name] = {
19
+ name: name
20
+ }
21
+
22
+ define_method("#{name}=") do |val|
23
+ send("#{name}_will_change!") unless val == instance_variable_get("@#{name}")
24
+ instance_variable_set("@#{name}", val)
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ def dirty_attributes
31
+ @dirty_attributes ||= {}
32
+ end
33
+ end
34
+
35
+ # Normally you would just call `changes_applied`, but because we
36
+ # define an `@attributes` instance variable, the `ActiveModel::Dirty`
37
+ # mixin assumes it is a list of attributes in their format (spoiler: it
38
+ # isn't). So this is copying the code from `changes_applied` and setting
39
+ # `@mutations_from_database` to the value it would receive if
40
+ # `@attributes` did not exist.
41
+ def save
42
+ # changes_applied # Code reproduced below.
43
+ @mutations_before_last_save = mutations_from_database
44
+ # forget_attribute_assignments # skipped as our attributes do not implement this method
45
+ @mutations_from_database = ActiveModel::ForcedMutationTracker.new(self) # Manually set to expected value
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../resource_attribute_set'
4
+ require_relative 'dirty_mixin'
5
+
6
+ # Mixin for attribute functionality
7
+ module LedgerSync
8
+ class ResourceAttribute
9
+ module Mixin
10
+ def self.included(base)
11
+ base.include(DirtyMixin)
12
+ base.extend(ClassMethods)
13
+ end
14
+
15
+ module ClassMethods
16
+ def _define_attribute_methods(name)
17
+ class_eval do
18
+ define_attribute_methods name
19
+
20
+ define_method name do
21
+ resource_attributes[name].value
22
+ end
23
+
24
+ define_method "#{name}=" do |val|
25
+ public_send("#{name}_will_change!") unless val == resource_attributes[name] # For Dirty
26
+
27
+ attribute = resource_attributes[name]
28
+
29
+ unless attribute.valid_with?(value: val)
30
+ raise ResourceError::AttributeTypeError.new(
31
+ attribute: attribute,
32
+ resource: self,
33
+ value: val
34
+ )
35
+ end
36
+
37
+ attribute.value = val
38
+ end
39
+
40
+ serialize attributes: [name]
41
+ end
42
+ end
43
+
44
+ def attribute(name, type:)
45
+ resource_attribute = ResourceAttribute.new(name: name, type: type)
46
+
47
+ _define_attribute_methods(name)
48
+
49
+ resource_attributes.add(resource_attribute)
50
+
51
+ resource_attribute
52
+ end
53
+
54
+ def resource_attributes
55
+ @resource_attributes ||= ResourceAttributeSet.new(resource: self)
56
+ end
57
+ end
58
+
59
+ def initialize(**data)
60
+ # Initialize empty values
61
+ resource_attributes.keys.each { |e| instance_variable_set("@#{e}", nil) }
62
+
63
+ assign_attributes(data)
64
+
65
+ super()
66
+ end
67
+
68
+ def assign_attribute(name, value)
69
+ raise "#{name} is not an attribute of #{self.class.name}" unless resource_attributes.key?(name)
70
+
71
+ public_send("#{name}=", value)
72
+ end
73
+
74
+ def assign_attributes(attribute_hash)
75
+ attribute_hash.each do |name, value|
76
+ assign_attribute(name, value)
77
+ end
78
+ end
79
+
80
+ # Store attribute instance values separately
81
+ def resource_attributes
82
+ @resource_attributes ||= Marshal.load(Marshal.dump(self.class.resource_attributes))
83
+ end
84
+
85
+ def serialize_attributes
86
+ Hash[resource_attributes.map { |k, v| [k, v.value] }]
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Reference attribute type that enforces values are a resource
4
+ module LedgerSync
5
+ class ResourceAttribute
6
+ class Reference < ResourceAttribute
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../reference'
4
+
5
+ # Reference attribute type for has_one
6
+ module LedgerSync
7
+ class ResourceAttribute
8
+ class Reference
9
+ class Many < Reference
10
+ module Mixin
11
+ def self.included(base)
12
+ base.extend(ClassMethods)
13
+ end
14
+
15
+ module ClassMethods
16
+ def references_many(name, to:)
17
+ resource_attribute = ResourceAttribute::Reference::Many.new(name: name, to: to)
18
+ resource_attributes.add resource_attribute
19
+ _define_attribute_methods(name)
20
+ end
21
+ end
22
+ end
23
+
24
+ def initialize(name:, to:)
25
+ super(
26
+ name: name,
27
+ type: Type::ReferenceMany.new(resource_class: to),
28
+ value: []
29
+ )
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../reference'
4
+
5
+ # Reference attribute type for has_one
6
+ module LedgerSync
7
+ class ResourceAttribute
8
+ class Reference
9
+ class One < Reference
10
+ module Mixin
11
+ def self.included(base)
12
+ base.extend(ClassMethods)
13
+ end
14
+
15
+ module ClassMethods
16
+ def references_one(name, to:)
17
+ resource_attribute = ResourceAttribute::Reference::One.new(name: name, to: to)
18
+ resource_attributes.add resource_attribute
19
+ _define_attribute_methods(name)
20
+ end
21
+ end
22
+ end
23
+
24
+ def initialize(name:, to:)
25
+ super(
26
+ name: name,
27
+ type: Type::ReferenceOne.new(resource_class: to)
28
+ )
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LedgerSync
4
+ class ResourceAttributeSet
5
+ attr_reader :attributes,
6
+ :references,
7
+ :references_one,
8
+ :references_many,
9
+ :resource
10
+
11
+ delegate :[],
12
+ :each,
13
+ :include?,
14
+ :key?,
15
+ :keys,
16
+ :map,
17
+ to: :attributes
18
+
19
+ alias names keys
20
+
21
+ def initialize(resource:)
22
+ @attributes = {}
23
+ @references = []
24
+ @references_one = []
25
+ @references_many = []
26
+
27
+ @resource = resource
28
+ end
29
+
30
+ def add(attribute)
31
+ name = attribute.name
32
+ raise "Attribute #{name} already exists on #{resource.name}." if attributes.key?(name)
33
+
34
+ case attribute
35
+ when ResourceAttribute
36
+ nil
37
+ when ResourceAttribute::Reference::One
38
+ @references << attribute
39
+ @references_one << attribute
40
+ when ResourceAttribute::Reference::Many
41
+ @references << attribute
42
+ @references_many << attribute
43
+ else
44
+ raise 'Unknown attribute class'
45
+ end
46
+
47
+ @attributes[attribute.name] = attribute
48
+ end
49
+
50
+ def to_a
51
+ attributes.values
52
+ end
53
+
54
+ def to_h
55
+ Hash[attributes.map { |k, v| [k, v.value] }]
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LedgerSync
4
+ class Account < LedgerSync::Resource
5
+ attribute :name, type: Type::String
6
+ attribute :classification, type: Type::String
7
+ attribute :account_type, type: Type::String
8
+ attribute :account_sub_type, type: Type::String
9
+ attribute :number, type: Type::Integer
10
+ attribute :currency, type: Type::String
11
+ attribute :description, type: Type::String
12
+ attribute :active, type: Type::Boolean
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ module LedgerSync
2
+ class Bill < LedgerSync::Resource
3
+ attribute :currency, type: Type::String
4
+ attribute :memo, type: Type::String
5
+ attribute :transaction_date, type: Type::Date
6
+ attribute :due_date, type: Type::Date
7
+
8
+ references_one :vendor, to: Vendor
9
+ references_one :account, to: Account
10
+
11
+ references_many :line_items, to: BillLineItem
12
+
13
+ def name
14
+ "Bill: #{transaction_date.to_s}"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ module LedgerSync
2
+ class BillLineItem < LedgerSync::Resource
3
+ references_one :account, to: Account
4
+ attribute :amount, type: Type::Integer
5
+ attribute :description, type: Type::String
6
+
7
+ def name
8
+ description
9
+ end
10
+ end
11
+ end
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module LedgerSync
2
4
  class Customer < LedgerSync::Resource
3
- attribute :email
4
- attribute :name
5
- attribute :phone_number
6
- # attribute :active
5
+ attribute :email, type: Type::String
6
+ attribute :name, type: Type::String
7
+ attribute :phone_number, type: Type::String
7
8
  end
8
9
  end
@@ -0,0 +1,16 @@
1
+ module LedgerSync
2
+ class Deposit < LedgerSync::Resource
3
+ attribute :currency, type: Type::String
4
+ attribute :memo, type: Type::String
5
+ attribute :transaction_date, type: Type::Date
6
+ attribute :exchange_rate, type: Type::Float
7
+
8
+ references_one :account, to: Account
9
+
10
+ references_many :line_items, to: DepositLineItem
11
+
12
+ def name
13
+ "Deposit: #{currency}"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ module LedgerSync
2
+ class DepositLineItem < LedgerSync::Resource
3
+ references_one :account, to: Account
4
+ attribute :amount, type: Type::Integer
5
+ attribute :description, type: Type::String
6
+
7
+ def name
8
+ description
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,19 @@
1
+ module LedgerSync
2
+ class Expense < LedgerSync::Resource
3
+ attribute :currency, type: Type::String
4
+ attribute :amount, type: Type::Integer
5
+ attribute :memo, type: Type::String
6
+ attribute :payment_type, type: Type::String
7
+ attribute :transaction_date, type: Type::Date
8
+ attribute :exchange_rate, type: Type::Float
9
+
10
+ references_one :vendor, to: Vendor
11
+ references_one :account, to: Account
12
+
13
+ references_many :line_items, to: ExpenseLineItem
14
+
15
+ def name
16
+ "Purchase: #{amount} #{currency}"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ module LedgerSync
2
+ class ExpenseLineItem < LedgerSync::Resource
3
+ references_one :account, to: Account
4
+ attribute :amount, type: Type::Integer
5
+ attribute :description, type: Type::String
6
+
7
+ def name
8
+ description
9
+ end
10
+ end
11
+ end