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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/Rakefile +6 -0
- data/forest_admin_datasource_mambu_payments.gemspec +37 -0
- data/lib/forest_admin_datasource_mambu_payments/client/reads.rb +66 -0
- data/lib/forest_admin_datasource_mambu_payments/client/writes.rb +42 -0
- data/lib/forest_admin_datasource_mambu_payments/client.rb +166 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/account_holder.rb +64 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/balance.rb +75 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/base_collection.rb +254 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/claim.rb +98 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/connected_account.rb +103 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/direct_debit_mandate.rb +125 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/event.rb +133 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/expected_payment.rb +132 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/external_account.rb +121 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/file.rb +89 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/incoming_payment.rb +120 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/internal_account.rb +136 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/payee_verification_request.rb +88 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/payment_capture.rb +136 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/payment_order.rb +132 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/reconciliation.rb +93 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/return.rb +132 -0
- data/lib/forest_admin_datasource_mambu_payments/collections/transaction.rb +113 -0
- data/lib/forest_admin_datasource_mambu_payments/configuration.rb +36 -0
- data/lib/forest_admin_datasource_mambu_payments/datasource.rb +35 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/disable_search.rb +31 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/helpers.rb +94 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/messages.rb +30 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/holder_link_plugin.rb +56 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_account_holder_to_direct_debit_mandates.rb +14 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_account_holder_to_incoming_payments.rb +14 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_external_account_to_direct_debit_mandates.rb +13 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_external_account_to_incoming_payments.rb +13 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_external_account_to_payment_orders.rb +13 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_incoming_payment_to_events.rb +13 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_incoming_payment_to_expected_payments.rb +21 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_incoming_payment_to_returns.rb +12 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_incoming_payment_to_transactions.rb +20 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_internal_account_to_balances.rb +17 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_internal_account_to_incoming_payments.rb +13 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_internal_account_to_payment_orders.rb +17 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_payment_order_to_events.rb +13 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_payment_order_to_receiving_account_holder.rb +15 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_payment_order_to_returns.rb +12 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/link_payment_order_to_transactions.rb +51 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/one_to_many_link_plugin.rb +35 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/pivot_resolution.rb +73 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/two_step_connected_account_filter.rb +38 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/two_step_cross_reconciliation_filter.rb +55 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/two_step_holder_filter.rb +32 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/two_step_link_plugin.rb +64 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/relations/two_step_reconciliation_filter.rb +39 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/approve_payment_order.rb +56 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/cancel_payment_order.rb +66 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/create_account_holder.rb +44 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/create_external_account.rb +54 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/create_internal_account.rb +58 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/create_payment_order.rb +66 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/trigger_payee_verification.rb +58 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/update_account_holder.rb +67 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/update_external_account.rb +75 -0
- data/lib/forest_admin_datasource_mambu_payments/plugins/smart_actions/update_internal_account.rb +75 -0
- data/lib/forest_admin_datasource_mambu_payments/query/condition_tree_translator.rb +115 -0
- data/lib/forest_admin_datasource_mambu_payments/version.rb +3 -0
- data/lib/forest_admin_datasource_mambu_payments.rb +44 -0
- 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
|