ledger_sync 1.0.10 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
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