dscf-banking 0.1.15 → 0.1.16
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 +4 -4
- data/app/controllers/dscf/banking/transaction_types_controller.rb +27 -0
- data/app/controllers/dscf/banking/transactions_controller.rb +275 -0
- data/app/models/dscf/banking/account.rb +24 -2
- data/app/models/dscf/banking/transaction.rb +68 -0
- data/app/models/dscf/banking/transaction_type.rb +20 -0
- data/app/serializers/dscf/banking/transaction_serializer.rb +19 -0
- data/app/serializers/dscf/banking/transaction_type_serializer.rb +9 -0
- data/app/services/dscf/banking/base_transaction_service.rb +99 -0
- data/app/services/dscf/banking/deposit_service.rb +97 -0
- data/app/services/dscf/banking/transfer_service.rb +78 -0
- data/app/services/dscf/banking/withdrawal_service.rb +100 -0
- data/config/routes.rb +14 -0
- data/db/migrate/20250919084147_add_account_type_to_accounts.rb +6 -0
- data/db/migrate/20250919084927_make_account_associations_optional.rb +6 -0
- data/db/migrate/20250919182831_create_dscf_banking_transaction_types.rb +14 -0
- data/db/migrate/20250919184220_create_dscf_banking_transactions.rb +21 -0
- data/db/seeds.rb +115 -41
- data/lib/dscf/banking/version.rb +1 -1
- data/spec/factories/dscf/banking/accounts.rb +28 -0
- data/spec/factories/dscf/banking/applications.rb +6 -5
- data/spec/factories/dscf/banking/transaction_types.rb +56 -0
- data/spec/factories/dscf/banking/transactions.rb +52 -0
- metadata +18 -2
@@ -0,0 +1,78 @@
|
|
1
|
+
module Dscf::Banking
|
2
|
+
class TransferService < BaseTransactionService
|
3
|
+
attr_reader :transaction
|
4
|
+
|
5
|
+
def initialize(debit_account:, credit_account:, amount:, description:, transaction_type_code: "TRANSFER")
|
6
|
+
super()
|
7
|
+
@debit_account = debit_account
|
8
|
+
@credit_account = credit_account
|
9
|
+
@amount = amount.to_f
|
10
|
+
@description = description
|
11
|
+
@transaction_type_code = transaction_type_code
|
12
|
+
@transaction = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute
|
16
|
+
return self unless validate_transfer
|
17
|
+
|
18
|
+
ActiveRecord::Base.transaction do
|
19
|
+
create_transfer_transaction
|
20
|
+
return self unless success?
|
21
|
+
|
22
|
+
process_transaction(@transaction)
|
23
|
+
return self unless success?
|
24
|
+
end
|
25
|
+
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def validate_transfer
|
32
|
+
# Validate debit account
|
33
|
+
return false unless validate_account_active(@debit_account, "Debit account")
|
34
|
+
|
35
|
+
# Validate credit account
|
36
|
+
return false unless validate_account_active(@credit_account, "Credit account")
|
37
|
+
|
38
|
+
# Validate amount
|
39
|
+
if @amount <= 0
|
40
|
+
add_error("Transfer amount must be greater than zero")
|
41
|
+
return false
|
42
|
+
end
|
43
|
+
|
44
|
+
# Validate sufficient funds
|
45
|
+
return false unless validate_sufficient_funds(@debit_account, @amount)
|
46
|
+
|
47
|
+
# Validate different accounts
|
48
|
+
if @debit_account.id == @credit_account.id
|
49
|
+
add_error("Cannot transfer to the same account")
|
50
|
+
return false
|
51
|
+
end
|
52
|
+
|
53
|
+
# Validate currency match
|
54
|
+
if @debit_account.currency != @credit_account.currency
|
55
|
+
add_error("Currency mismatch between accounts")
|
56
|
+
return false
|
57
|
+
end
|
58
|
+
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
def create_transfer_transaction
|
63
|
+
transaction_type = find_or_create_transaction_type(@transaction_type_code)
|
64
|
+
return unless transaction_type
|
65
|
+
|
66
|
+
@transaction = create_transaction(
|
67
|
+
account: @debit_account, # Primary account for the transaction
|
68
|
+
transaction_type: transaction_type,
|
69
|
+
debit_account: @debit_account,
|
70
|
+
credit_account: @credit_account,
|
71
|
+
amount: @amount,
|
72
|
+
currency: @debit_account.currency,
|
73
|
+
description: @description,
|
74
|
+
status: :pending
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Dscf::Banking
|
2
|
+
class WithdrawalService < BaseTransactionService
|
3
|
+
attr_reader :transaction
|
4
|
+
|
5
|
+
def initialize(account:, amount:, description:, transaction_type_code: "WITHDRAWAL")
|
6
|
+
super()
|
7
|
+
@account = account
|
8
|
+
@amount = amount.to_f
|
9
|
+
@description = description
|
10
|
+
@transaction_type_code = transaction_type_code
|
11
|
+
@transaction = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute
|
15
|
+
return self unless validate_withdrawal
|
16
|
+
|
17
|
+
ActiveRecord::Base.transaction do
|
18
|
+
create_withdrawal_transaction
|
19
|
+
return self unless success?
|
20
|
+
|
21
|
+
process_withdrawal
|
22
|
+
return self unless success?
|
23
|
+
end
|
24
|
+
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def validate_withdrawal
|
31
|
+
# Validate account
|
32
|
+
return false unless validate_account_active(@account, "Source account")
|
33
|
+
|
34
|
+
# Validate amount
|
35
|
+
if @amount <= 0
|
36
|
+
add_error("Withdrawal amount must be greater than zero")
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
|
40
|
+
# Validate sufficient funds
|
41
|
+
return false unless validate_sufficient_funds(@account, @amount)
|
42
|
+
|
43
|
+
true
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_withdrawal_transaction
|
47
|
+
transaction_type = find_or_create_transaction_type(@transaction_type_code)
|
48
|
+
return unless transaction_type
|
49
|
+
|
50
|
+
# For withdrawals, we need a system account as the credit account
|
51
|
+
# This represents the external destination
|
52
|
+
system_account = find_or_create_system_account
|
53
|
+
|
54
|
+
@transaction = create_transaction(
|
55
|
+
account: @account, # Primary account for the transaction
|
56
|
+
transaction_type: transaction_type,
|
57
|
+
debit_account: @account, # Customer account being debited
|
58
|
+
credit_account: system_account, # External destination
|
59
|
+
amount: @amount,
|
60
|
+
currency: @account.currency,
|
61
|
+
description: @description,
|
62
|
+
status: :pending
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
def process_withdrawal
|
67
|
+
@transaction.update(status: :processing)
|
68
|
+
|
69
|
+
# Update account balance (decrease for withdrawal)
|
70
|
+
new_balance = @account.current_balance - @amount
|
71
|
+
new_available = @account.available_balance - @amount
|
72
|
+
|
73
|
+
if @account.update(current_balance: new_balance, available_balance: new_available)
|
74
|
+
@transaction.update(status: :completed)
|
75
|
+
true
|
76
|
+
else
|
77
|
+
@account.errors.full_messages.each { |msg| add_error(msg) }
|
78
|
+
@transaction.update(status: :failed)
|
79
|
+
false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def find_or_create_system_account
|
84
|
+
# Find or create a system account for external withdrawals
|
85
|
+
system_account = Dscf::Banking::Account.system_accounts
|
86
|
+
.where(currency: @account.currency)
|
87
|
+
.where("name LIKE ?", "%Withdrawal%")
|
88
|
+
.first
|
89
|
+
|
90
|
+
unless system_account
|
91
|
+
# In a real system, this would be pre-created during setup
|
92
|
+
# For now, we'll assume it exists or create a placeholder
|
93
|
+
add_error("System withdrawal account not found for currency #{@account.currency}")
|
94
|
+
return nil
|
95
|
+
end
|
96
|
+
|
97
|
+
system_account
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/config/routes.rb
CHANGED
@@ -22,4 +22,18 @@ Dscf::Banking::Engine.routes.draw do
|
|
22
22
|
post :reject
|
23
23
|
end
|
24
24
|
end
|
25
|
+
|
26
|
+
resources :transaction_types
|
27
|
+
|
28
|
+
resources :transactions do
|
29
|
+
collection do
|
30
|
+
post :transfer
|
31
|
+
post :deposit
|
32
|
+
post :withdrawal
|
33
|
+
end
|
34
|
+
member do
|
35
|
+
post :cancel
|
36
|
+
get :details
|
37
|
+
end
|
38
|
+
end
|
25
39
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class CreateDscfBankingTransactionTypes < ActiveRecord::Migration[8.0]
|
2
|
+
def change
|
3
|
+
create_table :dscf_banking_transaction_types do |t|
|
4
|
+
t.string :code, null: false, limit: 20
|
5
|
+
t.string :name, null: false, limit: 100
|
6
|
+
t.text :description
|
7
|
+
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
|
11
|
+
add_index :dscf_banking_transaction_types, :code, unique: true
|
12
|
+
add_index :dscf_banking_transaction_types, :name, unique: true
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class CreateDscfBankingTransactions < ActiveRecord::Migration[8.0]
|
2
|
+
def change
|
3
|
+
create_table :dscf_banking_transactions do |t|
|
4
|
+
t.references :account, null: false, foreign_key: { to_table: :dscf_banking_accounts }
|
5
|
+
t.references :transaction_type, null: false, foreign_key: { to_table: :dscf_banking_transaction_types }
|
6
|
+
t.decimal :amount, precision: 20, scale: 4, null: false
|
7
|
+
t.string :currency, null: false, default: "ETB"
|
8
|
+
t.string :reference_number, null: false
|
9
|
+
t.text :description
|
10
|
+
t.integer :status, default: 0, null: false
|
11
|
+
t.references :debit_account, null: false, foreign_key: { to_table: :dscf_banking_accounts }
|
12
|
+
t.references :credit_account, null: false, foreign_key: { to_table: :dscf_banking_accounts }
|
13
|
+
|
14
|
+
t.timestamps
|
15
|
+
end
|
16
|
+
|
17
|
+
add_index :dscf_banking_transactions, :status
|
18
|
+
add_index :dscf_banking_transactions, :reference_number, unique: true
|
19
|
+
add_index :dscf_banking_transactions, [ :account_id, :created_at ]
|
20
|
+
end
|
21
|
+
end
|
data/db/seeds.rb
CHANGED
@@ -1,51 +1,125 @@
|
|
1
1
|
# db/seeds.rb
|
2
|
-
|
3
|
-
#
|
4
|
-
|
5
|
-
#
|
6
|
-
#
|
7
|
-
|
8
|
-
#
|
9
|
-
|
10
|
-
|
2
|
+
|
3
|
+
# DEBUG: Confirm seed file execution and model availability
|
4
|
+
puts "Seed file started"
|
5
|
+
puts "Dscf::Core::User loaded? => #{defined?(Dscf::Core::User)}"
|
6
|
+
puts "Dscf::Core::Role loaded? => #{defined?(Dscf::Core::Role)}"
|
7
|
+
|
8
|
+
# DSCF Banking Engine Seed Data
|
9
|
+
# This file seeds the database with sample data for all models in the correct dependency order
|
10
|
+
|
11
|
+
puts "Starting DSCF Banking Engine seed data..."
|
12
|
+
|
13
|
+
|
14
|
+
# 1. Seed roles (if model exists)
|
11
15
|
puts "Seeding roles..."
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
if defined?(Dscf::Core::Role)
|
17
|
+
user_role = Dscf::Core::Role.find_or_create_by(code: 'user') do |role|
|
18
|
+
role.name = 'User'
|
19
|
+
end
|
20
|
+
officer_role = Dscf::Core::Role.find_or_create_by(code: 'virtual_account_officer') do |role|
|
21
|
+
role.name = 'Virtual Account Officer'
|
22
|
+
end
|
23
|
+
manager_role = Dscf::Core::Role.find_or_create_by(code: 'virtual_account_manager') do |role|
|
24
|
+
role.name = 'Virtual Account Manager'
|
25
|
+
end
|
26
|
+
kyc_officer_role = Dscf::Core::Role.find_or_create_by(code: 'kyc_officer') do |role|
|
27
|
+
role.name = 'KYC Officer'
|
28
|
+
end
|
29
|
+
branch_manager_role = Dscf::Core::Role.find_or_create_by(code: 'branch_manager') do |role|
|
30
|
+
role.name = 'Branch Manager'
|
31
|
+
end
|
32
|
+
puts "✓ Roles created/found"
|
33
|
+
else
|
34
|
+
puts "Warning: Dscf::Core::Role model not found. Skipping role seeding."
|
35
|
+
end
|
36
|
+
# 2. Seed users with roles (if model exists)
|
20
37
|
puts "Seeding users..."
|
21
|
-
|
22
|
-
user1 = User.
|
23
|
-
|
24
|
-
|
38
|
+
if defined?(Dscf::Core::User)
|
39
|
+
user1 = Dscf::Core::User.find_or_create_by(email: "user@banking.com") do |u|
|
40
|
+
u.phone = "+251911123456"
|
41
|
+
u.password = "SecurePassword123!"
|
42
|
+
u.verified_at = Time.current
|
43
|
+
u.temp_password = false
|
44
|
+
end
|
45
|
+
user1.roles << user_role if defined?(user_role)
|
46
|
+
puts "Created user: #{user1.email} (user@banking.com)"
|
47
|
+
|
48
|
+
user2 = Dscf::Core::User.find_or_create_by(email: "virtual_account_officer@banking.com") do |u|
|
49
|
+
u.phone = "+251922123456"
|
50
|
+
u.password = "SecurePassword123!"
|
51
|
+
u.verified_at = Time.current
|
52
|
+
u.temp_password = false
|
53
|
+
end
|
54
|
+
user2.roles << officer_role if defined?(officer_role)
|
55
|
+
puts "Created user: #{user2.email} (virtual_account_officer@banking.com)"
|
25
56
|
|
26
|
-
|
27
|
-
|
28
|
-
|
57
|
+
user3 = Dscf::Core::User.find_or_create_by(email: "virtual_account_manager@banking.com") do |u|
|
58
|
+
u.phone = "+251933123456"
|
59
|
+
u.password = "SecurePassword123!"
|
60
|
+
u.verified_at = Time.current
|
61
|
+
u.temp_password = false
|
62
|
+
end
|
63
|
+
user3.roles << manager_role if defined?(manager_role)
|
64
|
+
puts "Created user: #{user3.email} (virtual_account_manager@banking.com)"
|
29
65
|
|
30
|
-
|
31
|
-
|
32
|
-
|
66
|
+
user4 = Dscf::Core::User.find_or_create_by(email: "user_virtual_account_officer@banking.com") do |u|
|
67
|
+
u.phone = "+251944123456"
|
68
|
+
u.password = "SecurePassword123!"
|
69
|
+
u.verified_at = Time.current
|
70
|
+
u.temp_password = false
|
71
|
+
end
|
72
|
+
user4.roles << user_role if defined?(user_role)
|
73
|
+
user4.roles << officer_role if defined?(officer_role)
|
74
|
+
puts "Created user: #{user4.email} (user_virtual_account_officer@banking.com)"
|
33
75
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
76
|
+
user5 = Dscf::Core::User.find_or_create_by(email: "virtual_account_manager2@banking.com") do |u|
|
77
|
+
u.phone = "+251955123456"
|
78
|
+
u.password = "SecurePassword123!"
|
79
|
+
u.verified_at = Time.current
|
80
|
+
u.temp_password = false
|
81
|
+
end
|
82
|
+
user5.roles << manager_role if defined?(manager_role)
|
83
|
+
puts "Created user: #{user5.email} (virtual_account_manager2@banking.com)"
|
38
84
|
|
39
|
-
|
40
|
-
|
41
|
-
|
85
|
+
user6 = Dscf::Core::User.find_or_create_by(email: "kyc_officer@banking.com") do |u|
|
86
|
+
u.phone = "+251966123456"
|
87
|
+
u.password = "SecurePassword123!"
|
88
|
+
u.verified_at = Time.current
|
89
|
+
u.temp_password = false
|
90
|
+
end
|
91
|
+
user6.roles << kyc_officer_role if defined?(kyc_officer_role)
|
92
|
+
puts "Created user: #{user6.email} (kyc_officer@banking.com)"
|
42
93
|
|
43
|
-
|
44
|
-
|
45
|
-
|
94
|
+
user7 = Dscf::Core::User.find_or_create_by(email: "branch_manager@banking.com") do |u|
|
95
|
+
u.phone = "+251977123456"
|
96
|
+
u.password = "SecurePassword123!"
|
97
|
+
u.verified_at = Time.current
|
98
|
+
u.temp_password = false
|
99
|
+
end
|
100
|
+
user7.roles << branch_manager_role if defined?(branch_manager_role)
|
101
|
+
puts "Created user: #{user7.email} (branch_manager@banking.com)"
|
102
|
+
else
|
103
|
+
puts "Warning: Dscf::Core::User model not found. Skipping user seeding."
|
104
|
+
end# 3. Seed transaction types (engine model)
|
105
|
+
puts "Seeding transaction types..."
|
106
|
+
if defined?(Dscf::Banking::TransactionType)
|
107
|
+
transaction_types = [
|
108
|
+
{ name: "Deposit", code: "DEP" },
|
109
|
+
{ name: "Withdrawal", code: "WDL" },
|
110
|
+
{ name: "Transfer", code: "TRF" },
|
111
|
+
{ name: "Interest Credit", code: "INTC" },
|
112
|
+
{ name: "Fee Debit", code: "FEED" }
|
113
|
+
]
|
46
114
|
|
47
|
-
|
48
|
-
|
49
|
-
|
115
|
+
transaction_types.each do |attrs|
|
116
|
+
Dscf::Banking::TransactionType.find_or_create_by(code: attrs[:code]) do |tt|
|
117
|
+
tt.name = attrs[:name]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
puts "✓ Transaction types created/found"
|
121
|
+
else
|
122
|
+
puts "Warning: Dscf::Banking::TransactionType not found. Skipping transaction type seeding."
|
123
|
+
end
|
50
124
|
|
51
|
-
puts "
|
125
|
+
puts "DSCF Banking Engine seed data completed successfully!"
|
data/lib/dscf/banking/version.rb
CHANGED
@@ -10,6 +10,7 @@ FactoryBot.define do
|
|
10
10
|
minimum_balance { 0 }
|
11
11
|
status { :draft }
|
12
12
|
active { true }
|
13
|
+
system_account { false }
|
13
14
|
account_properties { {} }
|
14
15
|
|
15
16
|
trait :active do
|
@@ -32,6 +33,33 @@ FactoryBot.define do
|
|
32
33
|
available_balance { 1000 }
|
33
34
|
minimum_balance { 100 }
|
34
35
|
end
|
36
|
+
|
37
|
+
trait :system_account do
|
38
|
+
system_account { true }
|
39
|
+
virtual_account_product { nil }
|
40
|
+
application { nil }
|
41
|
+
end
|
42
|
+
|
43
|
+
trait :settlement_account do
|
44
|
+
system_account { true }
|
45
|
+
name { "Core Banking Settlement Account" }
|
46
|
+
virtual_account_product { nil }
|
47
|
+
application { nil }
|
48
|
+
end
|
49
|
+
|
50
|
+
trait :interest_expense_account do
|
51
|
+
system_account { true }
|
52
|
+
name { "Interest Expense Account" }
|
53
|
+
virtual_account_product { nil }
|
54
|
+
application { nil }
|
55
|
+
end
|
56
|
+
|
57
|
+
trait :tax_liability_account do
|
58
|
+
system_account { true }
|
59
|
+
name { "Interest Tax Payable Account" }
|
60
|
+
virtual_account_product { nil }
|
61
|
+
application { nil }
|
62
|
+
end
|
35
63
|
end
|
36
64
|
|
37
65
|
# Alias for shared spec compatibility
|
@@ -4,6 +4,7 @@ FactoryBot.define do
|
|
4
4
|
association :virtual_account_product
|
5
5
|
applicant_type { :individual }
|
6
6
|
status { :draft }
|
7
|
+
sequence(:application_number) { |n| "IND#{Date.current.strftime('%Y%m%d')}#{n.to_s.rjust(4, '0')}" }
|
7
8
|
|
8
9
|
form_data do
|
9
10
|
{
|
@@ -23,7 +24,7 @@ FactoryBot.define do
|
|
23
24
|
|
24
25
|
trait :business do
|
25
26
|
applicant_type { :business }
|
26
|
-
|
27
|
+
sequence(:application_number) { |n| "BUS#{Date.current.strftime('%Y%m%d')}#{n.to_s.rjust(4, '0')}" }
|
27
28
|
form_data do
|
28
29
|
{
|
29
30
|
business_name: Faker::Company.name,
|
@@ -44,21 +45,21 @@ FactoryBot.define do
|
|
44
45
|
trait :under_review do
|
45
46
|
status { :under_review }
|
46
47
|
submitted_at { 2.days.ago }
|
47
|
-
association :assigned_kyc_officer, factory:
|
48
|
+
association :assigned_kyc_officer, factory: :user
|
48
49
|
end
|
49
50
|
|
50
51
|
trait :approved do
|
51
52
|
status { :approved }
|
52
53
|
submitted_at { 3.days.ago }
|
53
|
-
association :assigned_kyc_officer, factory:
|
54
|
-
association :assigned_branch_manager, factory:
|
54
|
+
association :assigned_kyc_officer, factory: :user
|
55
|
+
association :assigned_branch_manager, factory: :user
|
55
56
|
end
|
56
57
|
|
57
58
|
trait :rejected do
|
58
59
|
status { :rejected }
|
59
60
|
submitted_at { 3.days.ago }
|
60
61
|
rejection_reason { "Incomplete documentation" }
|
61
|
-
association :assigned_kyc_officer, factory:
|
62
|
+
association :assigned_kyc_officer, factory: :user
|
62
63
|
end
|
63
64
|
end
|
64
65
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
factory :dscf_banking_transaction_type, class: "Dscf::Banking::TransactionType" do
|
3
|
+
sequence(:code) { |n| "TXN_TYPE_#{n}" }
|
4
|
+
sequence(:name) { |n| "Transaction Type #{n}" }
|
5
|
+
description { "Test transaction type description" }
|
6
|
+
|
7
|
+
trait :deposit do
|
8
|
+
code { "DEPOSIT" }
|
9
|
+
name { "Deposit" }
|
10
|
+
description { "Customer deposit from external source" }
|
11
|
+
end
|
12
|
+
|
13
|
+
trait :withdrawal do
|
14
|
+
code { "WITHDRAWAL" }
|
15
|
+
name { "Withdrawal" }
|
16
|
+
description { "Customer withdrawal to external destination" }
|
17
|
+
end
|
18
|
+
|
19
|
+
trait :transfer do
|
20
|
+
code { "TRANSFER" }
|
21
|
+
name { "Transfer" }
|
22
|
+
description { "Transfer between customer accounts" }
|
23
|
+
end
|
24
|
+
|
25
|
+
trait :interest_credit do
|
26
|
+
code { "INTEREST_CREDIT" }
|
27
|
+
name { "Interest Credit" }
|
28
|
+
description { "Interest payment to customer account" }
|
29
|
+
end
|
30
|
+
|
31
|
+
trait :interest_expense do
|
32
|
+
code { "INTEREST_EXPENSE" }
|
33
|
+
name { "Interest Expense" }
|
34
|
+
description { "Interest expense booking" }
|
35
|
+
end
|
36
|
+
|
37
|
+
trait :tax_withholding do
|
38
|
+
code { "TAX_WITHHOLDING" }
|
39
|
+
name { "Tax Withholding" }
|
40
|
+
description { "Tax withholding on interest payments" }
|
41
|
+
end
|
42
|
+
|
43
|
+
trait :settlement do
|
44
|
+
code { "SETTLEMENT" }
|
45
|
+
name { "Settlement" }
|
46
|
+
description { "Settlement transaction with core banking system" }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Alias for shared spec compatibility
|
51
|
+
factory :transaction_type, class: "Dscf::Banking::TransactionType" do
|
52
|
+
sequence(:code) { |n| "TXN_TYPE_#{n}" }
|
53
|
+
sequence(:name) { |n| "Transaction Type #{n}" }
|
54
|
+
description { "Test transaction type description" }
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
factory :dscf_banking_transaction, class: "Dscf::Banking::Transaction" do
|
3
|
+
association :account, factory: :dscf_banking_account
|
4
|
+
association :transaction_type, factory: [ :dscf_banking_transaction_type, :deposit ]
|
5
|
+
association :debit_account, factory: :dscf_banking_account
|
6
|
+
association :credit_account, factory: :dscf_banking_account
|
7
|
+
|
8
|
+
amount { 1000.00 }
|
9
|
+
currency { "ETB" }
|
10
|
+
description { "Test transaction" }
|
11
|
+
status { :pending }
|
12
|
+
|
13
|
+
trait :deposit do
|
14
|
+
association :transaction_type, factory: [ :dscf_banking_transaction_type, :deposit ]
|
15
|
+
|
16
|
+
after(:build) do |transaction|
|
17
|
+
transaction.credit_account = transaction.account
|
18
|
+
transaction.debit_account = create(:dscf_banking_account, :settlement_account, currency: transaction.currency)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
trait :withdrawal do
|
23
|
+
association :transaction_type, factory: [ :dscf_banking_transaction_type, :withdrawal ]
|
24
|
+
|
25
|
+
after(:build) do |transaction|
|
26
|
+
transaction.debit_account = transaction.account
|
27
|
+
transaction.credit_account = create(:dscf_banking_account, :settlement_account, currency: transaction.currency)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
trait :completed do
|
32
|
+
status { :completed }
|
33
|
+
end
|
34
|
+
|
35
|
+
trait :failed do
|
36
|
+
status { :failed }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Alias for shared spec compatibility
|
41
|
+
factory :transaction, class: "Dscf::Banking::Transaction" do
|
42
|
+
association :account, factory: :dscf_banking_account
|
43
|
+
association :transaction_type, factory: [ :dscf_banking_transaction_type, :deposit ]
|
44
|
+
association :debit_account, factory: :dscf_banking_account
|
45
|
+
association :credit_account, factory: :dscf_banking_account
|
46
|
+
|
47
|
+
amount { 1000.00 }
|
48
|
+
currency { "ETB" }
|
49
|
+
description { "Test transaction" }
|
50
|
+
status { :pending }
|
51
|
+
end
|
52
|
+
end
|