forest_admin_datasource_mambu_payments 1.33.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 (68) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/Rakefile +6 -0
  4. data/forest_admin_datasource_mambu_payments.gemspec +37 -0
  5. data/lib/forest_admin_datasource_mambu_payments/client/reads.rb +66 -0
  6. data/lib/forest_admin_datasource_mambu_payments/client/writes.rb +42 -0
  7. data/lib/forest_admin_datasource_mambu_payments/client.rb +166 -0
  8. data/lib/forest_admin_datasource_mambu_payments/collections/account_holder.rb +64 -0
  9. data/lib/forest_admin_datasource_mambu_payments/collections/balance.rb +75 -0
  10. data/lib/forest_admin_datasource_mambu_payments/collections/base_collection.rb +254 -0
  11. data/lib/forest_admin_datasource_mambu_payments/collections/claim.rb +98 -0
  12. data/lib/forest_admin_datasource_mambu_payments/collections/connected_account.rb +103 -0
  13. data/lib/forest_admin_datasource_mambu_payments/collections/direct_debit_mandate.rb +125 -0
  14. data/lib/forest_admin_datasource_mambu_payments/collections/event.rb +133 -0
  15. data/lib/forest_admin_datasource_mambu_payments/collections/expected_payment.rb +132 -0
  16. data/lib/forest_admin_datasource_mambu_payments/collections/external_account.rb +121 -0
  17. data/lib/forest_admin_datasource_mambu_payments/collections/file.rb +89 -0
  18. data/lib/forest_admin_datasource_mambu_payments/collections/incoming_payment.rb +120 -0
  19. data/lib/forest_admin_datasource_mambu_payments/collections/internal_account.rb +136 -0
  20. data/lib/forest_admin_datasource_mambu_payments/collections/payee_verification_request.rb +88 -0
  21. data/lib/forest_admin_datasource_mambu_payments/collections/payment_capture.rb +136 -0
  22. data/lib/forest_admin_datasource_mambu_payments/collections/payment_order.rb +132 -0
  23. data/lib/forest_admin_datasource_mambu_payments/collections/reconciliation.rb +93 -0
  24. data/lib/forest_admin_datasource_mambu_payments/collections/return.rb +132 -0
  25. data/lib/forest_admin_datasource_mambu_payments/collections/transaction.rb +113 -0
  26. data/lib/forest_admin_datasource_mambu_payments/configuration.rb +36 -0
  27. data/lib/forest_admin_datasource_mambu_payments/datasource.rb +35 -0
  28. data/lib/forest_admin_datasource_mambu_payments/plugins/disable_search.rb +31 -0
  29. data/lib/forest_admin_datasource_mambu_payments/plugins/helpers.rb +94 -0
  30. data/lib/forest_admin_datasource_mambu_payments/plugins/messages.rb +30 -0
  31. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/holder_link_plugin.rb +56 -0
  32. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_account_holder_to_direct_debit_mandates.rb +14 -0
  33. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_account_holder_to_incoming_payments.rb +14 -0
  34. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_external_account_to_direct_debit_mandates.rb +13 -0
  35. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_external_account_to_incoming_payments.rb +13 -0
  36. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_external_account_to_payment_orders.rb +13 -0
  37. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_incoming_payment_to_events.rb +13 -0
  38. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_incoming_payment_to_expected_payments.rb +21 -0
  39. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_incoming_payment_to_returns.rb +12 -0
  40. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_incoming_payment_to_transactions.rb +20 -0
  41. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_internal_account_to_balances.rb +17 -0
  42. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_internal_account_to_incoming_payments.rb +13 -0
  43. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_internal_account_to_payment_orders.rb +17 -0
  44. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_payment_order_to_events.rb +13 -0
  45. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_payment_order_to_receiving_account_holder.rb +15 -0
  46. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_payment_order_to_returns.rb +12 -0
  47. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_payment_order_to_transactions.rb +51 -0
  48. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/one_to_many_link_plugin.rb +35 -0
  49. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/pivot_resolution.rb +73 -0
  50. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/two_step_connected_account_filter.rb +38 -0
  51. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/two_step_cross_reconciliation_filter.rb +55 -0
  52. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/two_step_holder_filter.rb +32 -0
  53. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/two_step_link_plugin.rb +64 -0
  54. data/lib/forest_admin_datasource_mambu_payments/plugins/relations/two_step_reconciliation_filter.rb +39 -0
  55. data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/approve_payment_order.rb +56 -0
  56. data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/cancel_payment_order.rb +66 -0
  57. data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/create_account_holder.rb +44 -0
  58. data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/create_external_account.rb +54 -0
  59. data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/create_internal_account.rb +58 -0
  60. data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/create_payment_order.rb +66 -0
  61. data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/trigger_payee_verification.rb +58 -0
  62. data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/update_account_holder.rb +67 -0
  63. data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/update_external_account.rb +75 -0
  64. data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/update_internal_account.rb +75 -0
  65. data/lib/forest_admin_datasource_mambu_payments/query/condition_tree_translator.rb +115 -0
  66. data/lib/forest_admin_datasource_mambu_payments/version.rb +3 -0
  67. data/lib/forest_admin_datasource_mambu_payments.rb +44 -0
  68. metadata +170 -0
@@ -0,0 +1,254 @@
1
+ module ForestAdminDatasourceMambuPayments
2
+ module Collections
3
+ # Shared read path (list / id-lookup / pagination / relation embedding) for
4
+ # every Numeral-backed collection. Subclasses declare their REST resource
5
+ # via `client_resource` and implement `serialize`; `collection_filters`
6
+ # lists the server-filterable fields and `reconcile_filter_operators!`
7
+ # narrows each column's advertised operators to what the API can serve.
8
+ # rubocop:disable Metrics/ClassLength
9
+ class BaseCollection < ForestAdminDatasourceToolkit::Collection
10
+ ColumnSchema = ForestAdminDatasourceToolkit::Schema::ColumnSchema
11
+ Operators = ForestAdminDatasourceToolkit::Components::Query::ConditionTree::Operators
12
+ Leaf = ForestAdminDatasourceToolkit::Components::Query::ConditionTree::Nodes::ConditionTreeLeaf
13
+ ForestException = ForestAdminDatasourceToolkit::Exceptions::ForestException
14
+
15
+ STRING_OPS = [Operators::EQUAL, Operators::NOT_EQUAL, Operators::IN, Operators::NOT_IN,
16
+ Operators::PRESENT, Operators::BLANK].freeze
17
+ NUMBER_OPS = (STRING_OPS + [Operators::GREATER_THAN, Operators::LESS_THAN]).freeze
18
+ DATE_OPS = [Operators::EQUAL, Operators::BEFORE, Operators::AFTER,
19
+ Operators::PRESENT, Operators::BLANK].freeze
20
+ BOOL_OPS = [Operators::EQUAL, Operators::NOT_EQUAL,
21
+ Operators::PRESENT, Operators::BLANK].freeze
22
+
23
+ # The id column is addressable (detail views, record selection) but the
24
+ # Numeral list endpoints have NO `id`/`ids` filter, so it is served by the
25
+ # find-by-id short-circuit (extract_id_lookup) and deliberately kept OUT of
26
+ # api_filters — a combined `id AND <field>` predicate raises loudly rather
27
+ # than silently sending an ignored param.
28
+ ID_OPS = [Operators::EQUAL, Operators::IN].freeze
29
+
30
+ class << self
31
+ attr_accessor :resource_singular, :resource_plural
32
+ end
33
+
34
+ # Declares the Numeral REST resource, wiring the read path to the matching
35
+ # `list_*` / `find_*` client methods.
36
+ def self.client_resource(singular, plural = nil)
37
+ self.resource_singular = singular.to_s
38
+ self.resource_plural = (plural || "#{singular}s").to_s
39
+ end
40
+
41
+ def list(_caller, filter, projection)
42
+ records = fetch_records(filter)
43
+ rows = records.map { |r| project(serialize(r), projection) }
44
+ embed_relations(rows, records, projection)
45
+ rows
46
+ end
47
+
48
+ # Numeral exposes no count/aggregate endpoint and paginates by cursor, so
49
+ # there is no way to count matching records without scanning every page.
50
+ # Collections are therefore declared non-countable (no `enable_count`) and
51
+ # Forest never requests an aggregation; this guard makes the unsupported
52
+ # path explicit rather than returning a wrong number.
53
+ def aggregate(_caller, _filter, _aggregation, _limit = nil)
54
+ raise ForestException,
55
+ 'Mambu Payments collections are not countable: Numeral exposes no count endpoint.'
56
+ end
57
+
58
+ # Per-id find_* (Numeral has no batch id filter); public for cross-collection embed.
59
+ def fetch_by_ids(ids)
60
+ ids = Array(ids).reject { |id| id.to_s.empty? }.uniq
61
+ return [] if ids.empty?
62
+
63
+ ids.filter_map { |id| client_find(id) }
64
+ end
65
+
66
+ protected
67
+
68
+ # Server-filterable fields the Numeral API accepts. Subclasses override
69
+ # `collection_filters` with entries like:
70
+ # { 'connected_account_id' => { ops: [Operators::EQUAL, Operators::IN] } }
71
+ # Anything not declared raises UnsupportedOperatorError when filtered on,
72
+ # so we never silently return unfiltered results. `id` is intentionally
73
+ # absent — see ID_OPS.
74
+ def api_filters
75
+ collection_filters
76
+ end
77
+
78
+ def collection_filters
79
+ {}
80
+ end
81
+
82
+ # ManyToOne relations to embed during `list`. Subclasses override with
83
+ # entries like:
84
+ # { foreign_key: 'connected_account_id', relation_name: 'connected_account',
85
+ # collection: 'MambuConnectedAccount' }
86
+ def many_to_one_embeds
87
+ []
88
+ end
89
+
90
+ # Aligns each column's advertised operators with what we can actually
91
+ # serve: the declared api_filters for server-side filtering, plus `id`
92
+ # (served locally by the find-by-id short-circuit). Run after
93
+ # `define_schema`.
94
+ def reconcile_filter_operators!
95
+ filters = api_filters
96
+ schema[:fields].each do |name, field|
97
+ next unless field.type == 'Column'
98
+
99
+ field.filter_operators = name == 'id' ? ID_OPS : Array(filters.dig(name, :ops))
100
+ end
101
+ end
102
+
103
+ def fetch_records(filter)
104
+ ids = extract_id_lookup(filter.condition_tree)
105
+ return fetch_by_ids(ids) if ids
106
+
107
+ paginate(filter.page, translate_filters(filter.condition_tree))
108
+ end
109
+
110
+ # Maps Forest's offset/limit window onto Numeral's `starting_after` cursor.
111
+ def paginate(page, params)
112
+ offset = page&.offset.to_i
113
+ limit = effective_limit(page)
114
+ fetch_window(params, offset, limit)[offset, limit] || []
115
+ end
116
+
117
+ def effective_limit(page)
118
+ limit = page&.limit
119
+ limit.nil? || limit <= 0 ? Client::MAX_PER_PAGE : limit
120
+ end
121
+
122
+ # Walks the cursor forward until at least `offset + limit` records are
123
+ # collected or the API runs out (a short page).
124
+ def fetch_window(params, offset, limit)
125
+ needed = offset + limit
126
+ collected = []
127
+ cursor = nil
128
+ loop do
129
+ chunk = [needed - collected.size, Client::MAX_PER_PAGE].min
130
+ batch = client_list(**cursor_params(params, cursor, chunk))
131
+ collected.concat(batch)
132
+ break if batch.size < chunk || collected.size >= needed
133
+
134
+ cursor = record_id(batch.last)
135
+ break if cursor.to_s.empty?
136
+ end
137
+ collected
138
+ end
139
+
140
+ def cursor_params(params, cursor, chunk)
141
+ page_params = params.merge(limit: chunk)
142
+ page_params[:starting_after] = cursor if cursor
143
+ page_params
144
+ end
145
+
146
+ def record_id(record)
147
+ attrs_of(record)['id']
148
+ end
149
+
150
+ def client_list(**params)
151
+ datasource.client.public_send("list_#{self.class.resource_plural}", **params)
152
+ end
153
+
154
+ def client_find(id)
155
+ datasource.client.public_send("find_#{self.class.resource_singular}", id)
156
+ end
157
+
158
+ def extract_id_lookup(node)
159
+ return nil unless node.is_a?(Leaf) && node.field == 'id'
160
+
161
+ case node.operator
162
+ when Operators::EQUAL then [node.value]
163
+ when Operators::IN then Array(node.value)
164
+ end
165
+ end
166
+
167
+ def translate_filters(condition_tree)
168
+ Query::ConditionTreeTranslator.call(condition_tree, api_filters: api_filters)
169
+ end
170
+
171
+ def project(record, projection)
172
+ return record if projection.nil?
173
+
174
+ # Relation paths (containing ':') are populated by embed_relations, not
175
+ # by the scalar projection. A projection of only relation paths yields
176
+ # an empty scalar row — returning the full record here would leak every
177
+ # column the caller did not ask for.
178
+ wanted = Array(projection).map(&:to_s).reject { |p| p.include?(':') }
179
+ wanted.to_h { |k| [k, record[k]] }
180
+ end
181
+
182
+ def ids_for(caller, filter)
183
+ # An id-lookup filter already carries the ids — no need to round-trip to
184
+ # the API just to read them back.
185
+ ids = extract_id_lookup(filter.condition_tree)
186
+ return ids.reject { |id| id.to_s.empty? }.uniq if ids
187
+
188
+ list(caller, filter, ['id']).filter_map { |row| row['id'] }
189
+ end
190
+
191
+ def attrs_of(record)
192
+ record.respond_to?(:attributes) ? record.attributes : record.to_h
193
+ end
194
+
195
+ # Returns the relation prefixes (everything before `:`) requested in the
196
+ # projection - e.g. ["connected_account"] for ["id", "connected_account:name"].
197
+ def relations_in(projection)
198
+ Array(projection).map(&:to_s).filter_map { |p| p.split(':').first if p.include?(':') }.uniq
199
+ end
200
+
201
+ # A ManyToOne relation to embed: which foreign key on the row, the
202
+ # relation name to populate, and the target collection that resolves and
203
+ # serializes the related records.
204
+ Embed = Struct.new(:foreign_key, :relation_name, :resolver, keyword_init: true)
205
+
206
+ # Embeds the declared ManyToOne relations onto each row. The customizer's
207
+ # relation decorator only handles emulated relations, so native datasource
208
+ # relations like ours must populate the sub-record themselves.
209
+ def embed_relations(rows, records, projection)
210
+ return if projection.nil? || many_to_one_embeds.empty?
211
+
212
+ sources = records.map { |r| attrs_of(r) }
213
+ many_to_one_embeds.each do |embed|
214
+ embed_many_to_one(rows, sources, projection, Embed.new(
215
+ foreign_key: embed[:foreign_key],
216
+ relation_name: embed[:relation_name],
217
+ resolver: datasource.get_collection(embed[:collection])
218
+ ))
219
+ end
220
+ end
221
+
222
+ # Bulk-fetches the related records for a ManyToOne relation in a single
223
+ # batched pass and writes the serialized record back onto each row.
224
+ def embed_many_to_one(rows, sources, projection, embed)
225
+ return unless relations_in(projection).include?(embed.relation_name)
226
+
227
+ ids = sources.filter_map { |s| s[embed.foreign_key] }.reject { |id| id.to_s.empty? }.uniq
228
+ return if ids.empty?
229
+
230
+ by_id = embed.resolver.fetch_by_ids(ids).to_h { |raw| [attrs_of(raw)['id'], raw] }
231
+ rows.each_with_index do |row, i|
232
+ fk_value = sources[i][embed.foreign_key]
233
+ next if fk_value.to_s.empty?
234
+
235
+ raw = by_id[fk_value]
236
+ row[embed.relation_name] = raw && embed.resolver.serialize(raw)
237
+ end
238
+ end
239
+
240
+ # Strips read-only columns and relation fields from a write payload,
241
+ # deriving the deny-list from the schema's `is_read_only` flags so it can
242
+ # never drift out of sync with the declared columns.
243
+ def build_payload(data)
244
+ drop = schema[:fields].reject { |_name, field| writable_column?(field) }.keys
245
+ data.transform_keys(&:to_s).except(*drop)
246
+ end
247
+
248
+ def writable_column?(field)
249
+ field.type == 'Column' && field.respond_to?(:is_read_only) && !field.is_read_only
250
+ end
251
+ end
252
+ # rubocop:enable Metrics/ClassLength
253
+ end
254
+ end
@@ -0,0 +1,98 @@
1
+ module ForestAdminDatasourceMambuPayments
2
+ module Collections
3
+ class Claim < BaseCollection
4
+ ManyToOneSchema = ForestAdminDatasourceToolkit::Schema::Relations::ManyToOneSchema
5
+
6
+ ENUM_TYPE = %w[sepa_non_receipt sepa_value_date_correction].freeze
7
+ ENUM_STATUS = %w[created processing sent received accepted rejected].freeze
8
+ ENUM_RELATED_PAYMENT = %w[payment_order incoming_payment].freeze
9
+
10
+ client_resource :claim
11
+
12
+ def initialize(datasource)
13
+ super(datasource, 'MambuClaim')
14
+ define_schema
15
+ define_relations
16
+ reconcile_filter_operators!
17
+ end
18
+
19
+ def serialize(record)
20
+ a = attrs_of(record)
21
+ {
22
+ 'id' => a['id'],
23
+ 'object' => a['object'],
24
+ 'type' => a['type'],
25
+ 'status' => a['status'],
26
+ 'status_details' => a['status_details'],
27
+ 'reason' => a['reason'],
28
+ 'value_date' => a['value_date'],
29
+ 'connected_account_id' => a['connected_account_id'],
30
+ 'related_payment_type' => a['related_payment_type'],
31
+ 'related_payment_id' => a['related_payment_id'],
32
+ 'related_payment' => a['related_payment'],
33
+ 'metadata' => a['metadata'],
34
+ 'bank_data' => a['bank_data'],
35
+ 'created_at' => a['created_at']
36
+ }
37
+ end
38
+
39
+ protected
40
+
41
+ def collection_filters
42
+ {
43
+ 'connected_account_id' => { ops: [Operators::EQUAL, Operators::IN] },
44
+ 'related_payment_id' => { ops: [Operators::EQUAL, Operators::IN] },
45
+ 'status' => { ops: [Operators::EQUAL, Operators::IN] },
46
+ 'type' => { ops: [Operators::EQUAL, Operators::IN] }
47
+ }
48
+ end
49
+
50
+ def many_to_one_embeds
51
+ [
52
+ { foreign_key: 'connected_account_id', relation_name: 'connected_account',
53
+ collection: 'MambuConnectedAccount' }
54
+ ]
55
+ end
56
+
57
+ private
58
+
59
+ # Claims are immutable from Forest's perspective: they arrive from the
60
+ # bank network (or the sandbox simulator) and the only way to act on
61
+ # them is accept/reject, which belong in a smart-action plugin. We mark
62
+ # every column read-only to match.
63
+ def define_schema
64
+ add_field('id', ColumnSchema.new(column_type: 'String', is_primary_key: true,
65
+ is_read_only: true, is_sortable: true))
66
+ add_field('object', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: false))
67
+ add_field('type', ColumnSchema.new(column_type: 'Enum', enum_values: ENUM_TYPE,
68
+ is_read_only: true, is_sortable: true))
69
+ add_field('status', ColumnSchema.new(column_type: 'Enum', enum_values: ENUM_STATUS,
70
+ is_read_only: true, is_sortable: true))
71
+ add_field('status_details', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: false))
72
+ add_field('reason', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: false))
73
+ add_field('value_date', ColumnSchema.new(column_type: 'Date', is_read_only: true, is_sortable: true))
74
+ add_field('connected_account_id', ColumnSchema.new(column_type: 'String',
75
+ is_read_only: true, is_sortable: true))
76
+ add_field('related_payment_type', ColumnSchema.new(column_type: 'Enum', enum_values: ENUM_RELATED_PAYMENT,
77
+ is_read_only: true, is_sortable: false))
78
+ # related_payment_id can target a payment_order OR an incoming_payment
79
+ # depending on related_payment_type. Forest can't model this polymorphism
80
+ # natively, so we expose it as a plain string column.
81
+ add_field('related_payment_id', ColumnSchema.new(column_type: 'String',
82
+ is_read_only: true, is_sortable: false))
83
+ add_field('related_payment', ColumnSchema.new(column_type: 'Json', is_read_only: true, is_sortable: false))
84
+ add_field('metadata', ColumnSchema.new(column_type: 'Json', is_read_only: true, is_sortable: false))
85
+ add_field('bank_data', ColumnSchema.new(column_type: 'Json', is_read_only: true, is_sortable: false))
86
+ add_field('created_at', ColumnSchema.new(column_type: 'Date', is_read_only: true, is_sortable: true))
87
+ end
88
+
89
+ def define_relations
90
+ add_field('connected_account', ManyToOneSchema.new(
91
+ foreign_collection: 'MambuConnectedAccount',
92
+ foreign_key: 'connected_account_id',
93
+ foreign_key_target: 'id'
94
+ ))
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,103 @@
1
+ # rubocop:disable Metrics/MethodLength
2
+ module ForestAdminDatasourceMambuPayments
3
+ module Collections
4
+ class ConnectedAccount < BaseCollection
5
+ OneToManySchema = ForestAdminDatasourceToolkit::Schema::Relations::OneToManySchema
6
+
7
+ client_resource :connected_account
8
+
9
+ def initialize(datasource)
10
+ super(datasource, 'MambuConnectedAccount')
11
+ define_schema
12
+ define_relations
13
+ reconcile_filter_operators!
14
+ end
15
+
16
+ def serialize(record)
17
+ a = attrs_of(record)
18
+ {
19
+ 'id' => a['id'],
20
+ 'object' => a['object'],
21
+ 'name' => a['name'],
22
+ 'distinguished_name' => a['distinguished_name'],
23
+ 'type' => a['type'],
24
+ 'currency' => a['currency'],
25
+ 'bank_id' => a['bank_id'],
26
+ 'bank_name' => a['bank_name'],
27
+ 'bank_code' => a['bank_code'],
28
+ 'bank_code_format' => a['bank_code_format'],
29
+ 'bank_address' => a['bank_address'],
30
+ 'account_number' => a['account_number'],
31
+ 'account_number_format' => a['account_number_format'],
32
+ 'settlement_account' => a['settlement_account'],
33
+ 'creditor_identifier' => a['creditor_identifier'],
34
+ 'legal_entity_identifier' => a['legal_entity_identifier'],
35
+ 'receiving_agent' => a['receiving_agent'],
36
+ 'services_activated' => a['services_activated'],
37
+ 'file_auto_approval' => a['file_auto_approval'],
38
+ 'return_auto_approval' => a['return_auto_approval'],
39
+ 'incoming_instant_payment_auto_approval' => a['incoming_instant_payment_auto_approval'],
40
+ 'address' => a['address'],
41
+ 'metadata' => a['metadata'],
42
+ 'bank_data' => a['bank_data'],
43
+ 'account_number_generation_settings' => a['account_number_generation_settings'],
44
+ 'disabled_at' => a['disabled_at'],
45
+ 'created_at' => a['created_at']
46
+ }
47
+ end
48
+
49
+ private
50
+
51
+ def define_schema
52
+ add_field('id', ColumnSchema.new(column_type: 'String', is_primary_key: true,
53
+ is_read_only: true, is_sortable: true))
54
+ add_field('object', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: false))
55
+ add_field('name', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: true))
56
+ add_field('distinguished_name', ColumnSchema.new(column_type: 'String', is_read_only: true,
57
+ is_sortable: false))
58
+ add_field('type', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: true))
59
+ add_field('currency', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: false))
60
+ add_field('bank_id', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: false))
61
+ add_field('bank_name', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: false))
62
+ add_field('bank_code', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: false))
63
+ add_field('bank_code_format', ColumnSchema.new(column_type: 'String', is_read_only: true,
64
+ is_sortable: false))
65
+ add_field('bank_address', ColumnSchema.new(column_type: 'Json', is_read_only: true, is_sortable: false))
66
+ add_field('account_number', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: false))
67
+ add_field('account_number_format', ColumnSchema.new(column_type: 'String', is_read_only: true,
68
+ is_sortable: false))
69
+ add_field('settlement_account', ColumnSchema.new(column_type: 'String', is_read_only: true,
70
+ is_sortable: false))
71
+ add_field('creditor_identifier', ColumnSchema.new(column_type: 'String', is_read_only: true,
72
+ is_sortable: false))
73
+ add_field('legal_entity_identifier', ColumnSchema.new(column_type: 'String', is_read_only: true,
74
+ is_sortable: false))
75
+ add_field('receiving_agent', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: false))
76
+ add_field('services_activated', ColumnSchema.new(column_type: 'Json', is_read_only: true, is_sortable: false))
77
+ add_field('file_auto_approval', ColumnSchema.new(column_type: 'Boolean', is_read_only: true,
78
+ is_sortable: false))
79
+ add_field('return_auto_approval', ColumnSchema.new(column_type: 'Boolean', is_read_only: true,
80
+ is_sortable: false))
81
+ add_field('incoming_instant_payment_auto_approval',
82
+ ColumnSchema.new(column_type: 'Boolean', is_read_only: true, is_sortable: false))
83
+ add_field('address', ColumnSchema.new(column_type: 'Json', is_read_only: true, is_sortable: false))
84
+ add_field('metadata', ColumnSchema.new(column_type: 'Json', is_read_only: true, is_sortable: false))
85
+ add_field('bank_data', ColumnSchema.new(column_type: 'Json', is_read_only: true, is_sortable: false))
86
+ add_field('account_number_generation_settings',
87
+ ColumnSchema.new(column_type: 'Json', is_read_only: true, is_sortable: false))
88
+ add_field('disabled_at', ColumnSchema.new(column_type: 'Date', is_read_only: true, is_sortable: true))
89
+ add_field('created_at', ColumnSchema.new(column_type: 'Date', is_read_only: true, is_sortable: true))
90
+ end
91
+
92
+ def define_relations
93
+ add_field('transactions', OneToManySchema.new(foreign_collection: 'MambuTransaction',
94
+ origin_key: 'connected_account_id', origin_key_target: 'id'))
95
+ add_field('payment_orders', OneToManySchema.new(foreign_collection: 'MambuPaymentOrder',
96
+ origin_key: 'connected_account_id', origin_key_target: 'id'))
97
+ add_field('balances', OneToManySchema.new(foreign_collection: 'MambuBalance',
98
+ origin_key: 'connected_account_id', origin_key_target: 'id'))
99
+ end
100
+ end
101
+ end
102
+ end
103
+ # rubocop:enable Metrics/MethodLength
@@ -0,0 +1,125 @@
1
+ # rubocop:disable Metrics/ClassLength
2
+ module ForestAdminDatasourceMambuPayments
3
+ module Collections
4
+ class DirectDebitMandate < BaseCollection
5
+ ManyToOneSchema = ForestAdminDatasourceToolkit::Schema::Relations::ManyToOneSchema
6
+
7
+ ENUM_SEQUENCE_TYPE = %w[one_off recurrent first final].freeze
8
+ ENUM_SCHEME = %w[sepa bacs ach].freeze
9
+
10
+ client_resource :direct_debit_mandate
11
+
12
+ def initialize(datasource)
13
+ super(datasource, 'MambuDirectDebitMandate')
14
+ define_schema
15
+ define_relations
16
+ reconcile_filter_operators!
17
+ end
18
+
19
+ def create(_caller, data)
20
+ serialize(datasource.client.create_direct_debit_mandate(build_payload(data)))
21
+ end
22
+
23
+ def update(caller, filter, patch)
24
+ payload = build_payload(patch)
25
+ ids_for(caller, filter).each { |id| datasource.client.update_direct_debit_mandate(id, payload) }
26
+ end
27
+
28
+ def delete(caller, filter)
29
+ ids_for(caller, filter).each { |id| datasource.client.delete_direct_debit_mandate(id) }
30
+ end
31
+
32
+ def serialize(record)
33
+ a = attrs_of(record)
34
+ {
35
+ 'id' => a['id'],
36
+ 'object' => a['object'],
37
+ 'connected_account_id' => a['connected_account_id'],
38
+ 'external_account_id' => a['external_account_id'],
39
+ 'type' => a['type'],
40
+ 'scheme' => a['scheme'],
41
+ 'status' => a['status'],
42
+ 'sequence_type' => a['sequence_type'],
43
+ 'reference' => a['reference'],
44
+ 'unique_mandate_reference' => a['unique_mandate_reference'],
45
+ 'creditor_identifier' => a['creditor_identifier'],
46
+ 'signature_date' => a['signature_date'],
47
+ 'signature_location' => a['signature_location'],
48
+ 'creditor' => a['creditor'],
49
+ 'debtor' => a['debtor'],
50
+ 'debtor_account' => a['debtor_account'],
51
+ 'amendment_information' => a['amendment_information'],
52
+ 'custom_fields' => a['custom_fields'],
53
+ 'metadata' => a['metadata'],
54
+ 'created_at' => a['created_at']
55
+ }
56
+ end
57
+
58
+ protected
59
+
60
+ def collection_filters
61
+ {
62
+ 'connected_account_id' => { ops: [Operators::EQUAL, Operators::IN] },
63
+ 'external_account_id' => { ops: [Operators::EQUAL, Operators::IN] }
64
+ }
65
+ end
66
+
67
+ def many_to_one_embeds
68
+ [
69
+ { foreign_key: 'connected_account_id', relation_name: 'connected_account',
70
+ collection: 'MambuConnectedAccount' },
71
+ { foreign_key: 'external_account_id', relation_name: 'external_account',
72
+ collection: 'MambuExternalAccount' }
73
+ ]
74
+ end
75
+
76
+ private
77
+
78
+ def define_schema
79
+ add_field('id', ColumnSchema.new(column_type: 'String', is_primary_key: true,
80
+ is_read_only: true, is_sortable: true))
81
+ add_field('object', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: false))
82
+ add_field('connected_account_id', ColumnSchema.new(column_type: 'String',
83
+ is_read_only: false, is_sortable: true))
84
+ add_field('external_account_id', ColumnSchema.new(column_type: 'String',
85
+ is_read_only: false, is_sortable: false))
86
+ add_field('type', ColumnSchema.new(column_type: 'String', is_read_only: false, is_sortable: true))
87
+ add_field('scheme', ColumnSchema.new(column_type: 'Enum', enum_values: ENUM_SCHEME,
88
+ is_read_only: false, is_sortable: true))
89
+ add_field('status', ColumnSchema.new(column_type: 'String', is_read_only: true, is_sortable: true))
90
+ add_field('sequence_type', ColumnSchema.new(column_type: 'Enum', enum_values: ENUM_SEQUENCE_TYPE,
91
+ is_read_only: false, is_sortable: true))
92
+ add_field('reference', ColumnSchema.new(column_type: 'String', is_read_only: false, is_sortable: false))
93
+ add_field('unique_mandate_reference', ColumnSchema.new(column_type: 'String', is_read_only: false,
94
+ is_sortable: true))
95
+ add_field('creditor_identifier', ColumnSchema.new(column_type: 'String', is_read_only: false,
96
+ is_sortable: false))
97
+ add_field('signature_date', ColumnSchema.new(column_type: 'Date', is_read_only: false, is_sortable: true))
98
+ add_field('signature_location', ColumnSchema.new(column_type: 'String', is_read_only: false,
99
+ is_sortable: false))
100
+ add_field('creditor', ColumnSchema.new(column_type: 'Json', is_read_only: false, is_sortable: false))
101
+ add_field('debtor', ColumnSchema.new(column_type: 'Json', is_read_only: false, is_sortable: false))
102
+ add_field('debtor_account', ColumnSchema.new(column_type: 'Json', is_read_only: false, is_sortable: false))
103
+ add_field('amendment_information', ColumnSchema.new(column_type: 'Json', is_read_only: false,
104
+ is_sortable: false))
105
+ add_field('custom_fields', ColumnSchema.new(column_type: 'Json', is_read_only: false, is_sortable: false))
106
+ add_field('metadata', ColumnSchema.new(column_type: 'Json', is_read_only: false, is_sortable: false))
107
+ add_field('created_at', ColumnSchema.new(column_type: 'Date', is_read_only: true, is_sortable: true))
108
+ end
109
+
110
+ def define_relations
111
+ add_field('connected_account', ManyToOneSchema.new(
112
+ foreign_collection: 'MambuConnectedAccount',
113
+ foreign_key: 'connected_account_id',
114
+ foreign_key_target: 'id'
115
+ ))
116
+ add_field('external_account', ManyToOneSchema.new(
117
+ foreign_collection: 'MambuExternalAccount',
118
+ foreign_key: 'external_account_id',
119
+ foreign_key_target: 'id'
120
+ ))
121
+ end
122
+ end
123
+ end
124
+ end
125
+ # rubocop:enable Metrics/ClassLength