dscf-banking 0.1.0 → 0.1.12
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/accounts_controller.rb +152 -0
- data/app/controllers/dscf/banking/applications_controller.rb +113 -0
- data/app/models/dscf/banking/account.rb +88 -0
- data/app/models/dscf/banking/application.rb +70 -0
- data/app/serializers/dscf/banking/account_serializer.rb +11 -0
- data/app/serializers/dscf/banking/application_serializer.rb +16 -0
- data/app/services/dscf/banking/account_creation_service.rb +53 -0
- data/config/routes.rb +13 -0
- data/db/migrate/20250912193134_create_dscf_banking_applications.rb +20 -0
- data/db/migrate/20250912203527_create_dscf_banking_accounts.rb +21 -0
- data/lib/dscf/banking/version.rb +1 -1
- data/spec/factories/dscf/banking/accounts.rb +51 -0
- data/spec/factories/dscf/banking/applications.rb +64 -0
- metadata +13 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a18bbdf6cbc7d9686c18ee99a8bce779e501210894a6518e1f111f350592a5d
|
4
|
+
data.tar.gz: 2f65b9bd050c1a61ff5b6e45687f34ed1edcb8f6fb79eff1d124625baca58b13
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 33b71cb30dcd71b6fbcb435c58509f82ce9c6f450cbf5d7de29309731e86462f808385084d90745f84f962de0e6169c865feccab3fb203fe4d476488ff2d26c8
|
7
|
+
data.tar.gz: 4341ddcaf03f9e40d28eb600c948e0ef8f1d85bebb860f8464d1f03267473b10963b169799b19d0153aec4b8d6441c7934666987f3991e1872b3b6ab725b7f2a
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module Dscf::Banking
|
2
|
+
class AccountsController < ApplicationController
|
3
|
+
include Dscf::Core::Common
|
4
|
+
|
5
|
+
def activate
|
6
|
+
account = Dscf::Banking::Account.find(params[:id])
|
7
|
+
|
8
|
+
unless account.can_be_activated?
|
9
|
+
return render json: {
|
10
|
+
success: false,
|
11
|
+
error: "Failed to activate account",
|
12
|
+
errors: [ "Account cannot be activated from current status: #{account.status}" ]
|
13
|
+
}, status: :unprocessable_entity
|
14
|
+
end
|
15
|
+
|
16
|
+
if account.activate!
|
17
|
+
render json: {
|
18
|
+
success: true,
|
19
|
+
data: account_data(account),
|
20
|
+
message: "Account activated successfully"
|
21
|
+
}
|
22
|
+
else
|
23
|
+
render json: {
|
24
|
+
success: false,
|
25
|
+
error: "Failed to activate account",
|
26
|
+
errors: account.errors.full_messages
|
27
|
+
}, status: :unprocessable_entity
|
28
|
+
end
|
29
|
+
rescue ActiveRecord::RecordNotFound
|
30
|
+
render json: {
|
31
|
+
success: false,
|
32
|
+
error: "Account not found"
|
33
|
+
}, status: :not_found
|
34
|
+
end
|
35
|
+
|
36
|
+
def suspend
|
37
|
+
account = Dscf::Banking::Account.find(params[:id])
|
38
|
+
|
39
|
+
unless account.can_be_suspended?
|
40
|
+
return render json: {
|
41
|
+
success: false,
|
42
|
+
error: "Failed to suspend account",
|
43
|
+
errors: [ "Account cannot be suspended from current status: #{account.status}" ]
|
44
|
+
}, status: :unprocessable_entity
|
45
|
+
end
|
46
|
+
|
47
|
+
if account.suspend!
|
48
|
+
render json: {
|
49
|
+
success: true,
|
50
|
+
data: account_data(account),
|
51
|
+
message: "Account suspended successfully"
|
52
|
+
}
|
53
|
+
else
|
54
|
+
render json: {
|
55
|
+
success: false,
|
56
|
+
error: "Failed to suspend account",
|
57
|
+
errors: account.errors.full_messages
|
58
|
+
}, status: :unprocessable_entity
|
59
|
+
end
|
60
|
+
rescue ActiveRecord::RecordNotFound
|
61
|
+
render json: {
|
62
|
+
success: false,
|
63
|
+
error: "Account not found"
|
64
|
+
}, status: :not_found
|
65
|
+
end
|
66
|
+
|
67
|
+
def close
|
68
|
+
account = Dscf::Banking::Account.find(params[:id])
|
69
|
+
|
70
|
+
unless account.can_be_closed?
|
71
|
+
return render json: {
|
72
|
+
success: false,
|
73
|
+
error: "Failed to close account",
|
74
|
+
errors: [ "Account cannot be closed from current status: #{account.status}" ]
|
75
|
+
}, status: :unprocessable_entity
|
76
|
+
end
|
77
|
+
|
78
|
+
if account.close!
|
79
|
+
render json: {
|
80
|
+
success: true,
|
81
|
+
data: account_data(account),
|
82
|
+
message: "Account closed successfully"
|
83
|
+
}
|
84
|
+
else
|
85
|
+
render json: {
|
86
|
+
success: false,
|
87
|
+
error: "Failed to close account",
|
88
|
+
errors: account.errors.full_messages
|
89
|
+
}, status: :unprocessable_entity
|
90
|
+
end
|
91
|
+
rescue ActiveRecord::RecordNotFound
|
92
|
+
render json: {
|
93
|
+
success: false,
|
94
|
+
error: "Account not found"
|
95
|
+
}, status: :not_found
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def account_data(account)
|
101
|
+
{
|
102
|
+
id: account.id,
|
103
|
+
account_number: account.account_number,
|
104
|
+
name: account.name,
|
105
|
+
status: account.status,
|
106
|
+
activation_date: account.activation_date,
|
107
|
+
closure_date: account.closure_date,
|
108
|
+
current_balance: account.current_balance,
|
109
|
+
available_balance: account.available_balance,
|
110
|
+
minimum_balance: account.minimum_balance,
|
111
|
+
currency: account.currency,
|
112
|
+
active: account.active,
|
113
|
+
application_id: account.application_id,
|
114
|
+
virtual_account_product_id: account.virtual_account_product_id,
|
115
|
+
created_at: account.created_at,
|
116
|
+
updated_at: account.updated_at
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
def model_params
|
121
|
+
params.require(:payload).permit(
|
122
|
+
:application_id,
|
123
|
+
:virtual_account_product_id,
|
124
|
+
:name,
|
125
|
+
:status,
|
126
|
+
:activation_date,
|
127
|
+
:closure_date,
|
128
|
+
:current_balance,
|
129
|
+
:available_balance,
|
130
|
+
:minimum_balance,
|
131
|
+
:currency,
|
132
|
+
:active,
|
133
|
+
account_properties: {}
|
134
|
+
)
|
135
|
+
end
|
136
|
+
|
137
|
+
def eager_loaded_associations
|
138
|
+
[ :application, :virtual_account_product ]
|
139
|
+
end
|
140
|
+
|
141
|
+
def allowed_order_columns
|
142
|
+
%w[id account_number name status activation_date closure_date current_balance created_at updated_at]
|
143
|
+
end
|
144
|
+
|
145
|
+
def default_serializer_includes
|
146
|
+
{
|
147
|
+
application: {},
|
148
|
+
virtual_account_product: {}
|
149
|
+
}
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Dscf::Banking
|
2
|
+
class ApplicationsController < ApplicationController
|
3
|
+
include Dscf::Core::Common
|
4
|
+
|
5
|
+
def approve
|
6
|
+
application = Dscf::Banking::Application.find(params[:id])
|
7
|
+
|
8
|
+
unless application.can_be_approved_via_api?
|
9
|
+
return render json: {
|
10
|
+
success: false,
|
11
|
+
error: "Failed to approve application",
|
12
|
+
errors: [ "Application cannot be approved from current status: #{application.status}" ]
|
13
|
+
}, status: :unprocessable_entity
|
14
|
+
end
|
15
|
+
|
16
|
+
if application.approve!
|
17
|
+
render json: {
|
18
|
+
success: true,
|
19
|
+
data: application_data(application),
|
20
|
+
message: "Application approved successfully"
|
21
|
+
}
|
22
|
+
else
|
23
|
+
render json: {
|
24
|
+
success: false,
|
25
|
+
error: "Failed to approve application",
|
26
|
+
errors: application.errors.full_messages
|
27
|
+
}, status: :unprocessable_entity
|
28
|
+
end
|
29
|
+
rescue ActiveRecord::RecordNotFound
|
30
|
+
render json: {
|
31
|
+
success: false,
|
32
|
+
error: "Application not found"
|
33
|
+
}, status: :not_found
|
34
|
+
end
|
35
|
+
|
36
|
+
def reject
|
37
|
+
application = Dscf::Banking::Application.find(params[:id])
|
38
|
+
rejection_reason = params[:rejection_reason]
|
39
|
+
|
40
|
+
unless application.can_be_rejected_via_api?
|
41
|
+
return render json: {
|
42
|
+
success: false,
|
43
|
+
error: "Failed to reject application",
|
44
|
+
errors: [ "Application cannot be rejected from current status: #{application.status}" ]
|
45
|
+
}, status: :unprocessable_entity
|
46
|
+
end
|
47
|
+
|
48
|
+
if application.reject!(rejection_reason)
|
49
|
+
render json: {
|
50
|
+
success: true,
|
51
|
+
data: application_data(application),
|
52
|
+
message: "Application rejected successfully"
|
53
|
+
}
|
54
|
+
else
|
55
|
+
render json: {
|
56
|
+
success: false,
|
57
|
+
error: "Failed to reject application",
|
58
|
+
errors: application.errors.full_messages
|
59
|
+
}, status: :unprocessable_entity
|
60
|
+
end
|
61
|
+
rescue ActiveRecord::RecordNotFound
|
62
|
+
render json: {
|
63
|
+
success: false,
|
64
|
+
error: "Application not found"
|
65
|
+
}, status: :not_found
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def application_data(application)
|
71
|
+
{
|
72
|
+
id: application.id,
|
73
|
+
application_number: application.application_number,
|
74
|
+
applicant_type: application.applicant_type,
|
75
|
+
status: application.status,
|
76
|
+
user_id: application.user_id,
|
77
|
+
virtual_account_product_id: application.virtual_account_product_id,
|
78
|
+
form_data: application.form_data,
|
79
|
+
submitted_at: application.submitted_at,
|
80
|
+
completed_at: application.completed_at,
|
81
|
+
rejection_reason: application.rejection_reason,
|
82
|
+
created_at: application.created_at,
|
83
|
+
updated_at: application.updated_at
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
def model_params
|
88
|
+
params.require(:payload).permit(
|
89
|
+
:user_id,
|
90
|
+
:virtual_account_product_id,
|
91
|
+
:applicant_type,
|
92
|
+
:status,
|
93
|
+
form_data: {}
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
def eager_loaded_associations
|
98
|
+
[ :user, :virtual_account_product ]
|
99
|
+
end
|
100
|
+
|
101
|
+
def allowed_order_columns
|
102
|
+
%w[id application_number status submitted_at completed_at created_at updated_at]
|
103
|
+
end
|
104
|
+
|
105
|
+
def default_serializer_includes
|
106
|
+
{
|
107
|
+
user: {},
|
108
|
+
virtual_account_product: {},
|
109
|
+
account: {}
|
110
|
+
}
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Dscf::Banking
|
2
|
+
class Account < ApplicationRecord
|
3
|
+
belongs_to :virtual_account_product
|
4
|
+
belongs_to :application
|
5
|
+
|
6
|
+
enum :status, {
|
7
|
+
draft: 0,
|
8
|
+
pending_activation: 1,
|
9
|
+
active: 2,
|
10
|
+
suspended: 3,
|
11
|
+
closed: 4,
|
12
|
+
dormant: 5
|
13
|
+
}
|
14
|
+
|
15
|
+
validates :account_number, presence: true, uniqueness: { case_sensitive: false }
|
16
|
+
validates :name, presence: true
|
17
|
+
validates :currency, presence: true
|
18
|
+
validates :current_balance, :available_balance, :minimum_balance,
|
19
|
+
numericality: { greater_than_or_equal_to: 0 }
|
20
|
+
|
21
|
+
before_validation :generate_account_number, on: :create
|
22
|
+
before_validation :set_defaults, on: :create
|
23
|
+
|
24
|
+
scope :active_accounts, -> { where(active: true, status: :active) }
|
25
|
+
scope :by_currency, ->(currency) { where(currency: currency) }
|
26
|
+
|
27
|
+
def sufficient_funds_for_withdrawal?(amount)
|
28
|
+
available_balance - amount >= minimum_balance
|
29
|
+
end
|
30
|
+
|
31
|
+
def suspend!(reason = nil)
|
32
|
+
update!(status: :suspended)
|
33
|
+
end
|
34
|
+
|
35
|
+
def activate!
|
36
|
+
update!(status: :active, activation_date: Date.current)
|
37
|
+
end
|
38
|
+
|
39
|
+
def close!(reason = nil)
|
40
|
+
update!(status: :closed, active: false, closure_date: Date.current)
|
41
|
+
end
|
42
|
+
|
43
|
+
def formatted_account_number
|
44
|
+
return nil unless account_number
|
45
|
+
account_number.gsub(/(\d{3})(\d{1})(\d{1})(\d{6})/, '\1 \2 \3 \4')
|
46
|
+
end
|
47
|
+
|
48
|
+
def can_be_activated?
|
49
|
+
draft? || pending_activation? || suspended?
|
50
|
+
end
|
51
|
+
|
52
|
+
def can_be_suspended?
|
53
|
+
active?
|
54
|
+
end
|
55
|
+
|
56
|
+
def can_be_closed?
|
57
|
+
active? || suspended? || dormant?
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def generate_account_number
|
63
|
+
return if account_number.present?
|
64
|
+
|
65
|
+
branch_code = "001"
|
66
|
+
product_scheme = "1"
|
67
|
+
voucher_type = "0"
|
68
|
+
|
69
|
+
loop do
|
70
|
+
sequence = SecureRandom.random_number(1000000).to_s.rjust(6, "0")
|
71
|
+
account_num = "#{branch_code}#{product_scheme}#{voucher_type}#{sequence}"
|
72
|
+
|
73
|
+
unless self.class.exists?(account_number: account_num)
|
74
|
+
self.account_number = account_num
|
75
|
+
break
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def set_defaults
|
81
|
+
return unless virtual_account_product
|
82
|
+
|
83
|
+
self.currency ||= "ETB"
|
84
|
+
self.minimum_balance ||= 0
|
85
|
+
self.name ||= "Account Holder"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Dscf::Banking
|
2
|
+
class Application < ApplicationRecord
|
3
|
+
include Dscf::Banking::Auditable
|
4
|
+
|
5
|
+
belongs_to :user, class_name: "Dscf::Core::User"
|
6
|
+
belongs_to :assigned_kyc_officer, class_name: "Dscf::Core::User", optional: true
|
7
|
+
belongs_to :assigned_branch_manager, class_name: "Dscf::Core::User", optional: true
|
8
|
+
belongs_to :virtual_account_product, class_name: "Dscf::Banking::VirtualAccountProduct"
|
9
|
+
has_one :account, class_name: "Dscf::Banking::Account"
|
10
|
+
|
11
|
+
validates :application_number, presence: true, uniqueness: true, length: { maximum: 50 }
|
12
|
+
validates :applicant_type, presence: true
|
13
|
+
validates :status, presence: true
|
14
|
+
validates :form_data, presence: true
|
15
|
+
validates :rejection_reason, presence: true, if: -> { status == "rejected" }
|
16
|
+
|
17
|
+
enum :applicant_type, { individual: 0, business: 1 }, prefix: :applicant_type
|
18
|
+
enum :status, { draft: 0, submitted: 1, under_review: 2, approved: 3, rejected: 4 }, prefix: :status
|
19
|
+
|
20
|
+
before_validation :generate_application_number, if: -> { new_record? && application_number.blank? }
|
21
|
+
before_update :set_timestamps_on_status_change
|
22
|
+
after_update :create_account_if_approved
|
23
|
+
|
24
|
+
def approve!(approved_by = nil)
|
25
|
+
update!(status: :approved, completed_at: Time.current)
|
26
|
+
end
|
27
|
+
|
28
|
+
def reject!(reason, rejected_by = nil)
|
29
|
+
update!(status: :rejected, rejection_reason: reason, completed_at: Time.current)
|
30
|
+
end
|
31
|
+
|
32
|
+
def can_be_approved_via_api?
|
33
|
+
status_submitted? || status_under_review?
|
34
|
+
end
|
35
|
+
|
36
|
+
def can_be_rejected_via_api?
|
37
|
+
status_submitted? || status_under_review?
|
38
|
+
end
|
39
|
+
|
40
|
+
def has_account?
|
41
|
+
Account.exists?(application: self)
|
42
|
+
end
|
43
|
+
|
44
|
+
def create_account_if_approved
|
45
|
+
if saved_change_to_status? && status_approved?
|
46
|
+
AccountCreationService.call(self)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def generate_application_number
|
53
|
+
prefix = applicant_type == "individual" ? "IND" : "BUS"
|
54
|
+
timestamp = Time.current.strftime("%Y%m%d")
|
55
|
+
random_suffix = SecureRandom.random_number(10000).to_s.rjust(4, "0")
|
56
|
+
self.application_number = "#{prefix}#{timestamp}#{random_suffix}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def set_timestamps_on_status_change
|
60
|
+
if status_changed?
|
61
|
+
case status
|
62
|
+
when "submitted"
|
63
|
+
self.submitted_at = Time.current
|
64
|
+
when "completed"
|
65
|
+
self.completed_at = Time.current
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Dscf::Banking
|
2
|
+
class AccountSerializer < ActiveModel::Serializer
|
3
|
+
attributes :id, :account_number, :name, :status, :activation_date, :closure_date,
|
4
|
+
:account_properties, :current_balance, :available_balance, :minimum_balance,
|
5
|
+
:currency, :active, :created_at, :updated_at
|
6
|
+
|
7
|
+
belongs_to :virtual_account_product, serializer: VirtualAccountProductSerializer
|
8
|
+
|
9
|
+
attribute :application_id
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Dscf::Banking
|
2
|
+
class ApplicationSerializer < ActiveModel::Serializer
|
3
|
+
attributes :id, :application_number, :applicant_type, :status, :form_data,
|
4
|
+
:submitted_at, :completed_at, :rejection_reason, :created_at, :updated_at
|
5
|
+
|
6
|
+
belongs_to :user
|
7
|
+
belongs_to :virtual_account_product, serializer: VirtualAccountProductSerializer
|
8
|
+
belongs_to :assigned_kyc_officer, optional: true
|
9
|
+
belongs_to :assigned_branch_manager, optional: true
|
10
|
+
has_one :account, serializer: AccountSerializer, if: :has_account?
|
11
|
+
|
12
|
+
def has_account?
|
13
|
+
object.has_account?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Dscf::Banking
|
2
|
+
class AccountCreationService
|
3
|
+
def self.call(application)
|
4
|
+
new(application).call
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize(application)
|
8
|
+
@application = application
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
return nil unless @application.status_approved?
|
13
|
+
return nil if account_already_exists?
|
14
|
+
|
15
|
+
create_account
|
16
|
+
rescue => e
|
17
|
+
Rails.logger.error "Account creation failed: #{e.message}" if defined?(Rails)
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :application
|
24
|
+
|
25
|
+
def account_already_exists?
|
26
|
+
Account.exists?(application: @application)
|
27
|
+
end
|
28
|
+
|
29
|
+
def create_account
|
30
|
+
account = Account.new(
|
31
|
+
application: @application,
|
32
|
+
virtual_account_product: @application.virtual_account_product,
|
33
|
+
name: generate_account_name,
|
34
|
+
currency: "ETB",
|
35
|
+
minimum_balance: 0,
|
36
|
+
current_balance: 0,
|
37
|
+
available_balance: 0,
|
38
|
+
status: :draft
|
39
|
+
)
|
40
|
+
|
41
|
+
if account.save
|
42
|
+
account
|
43
|
+
else
|
44
|
+
Rails.logger.error "Account validation failed: #{account.errors.full_messages}" if defined?(Rails)
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def generate_account_name
|
50
|
+
"Test Account - #{@application.application_number}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/config/routes.rb
CHANGED
@@ -1,4 +1,11 @@
|
|
1
1
|
Dscf::Banking::Engine.routes.draw do
|
2
|
+
resources :accounts do
|
3
|
+
member do
|
4
|
+
post :activate
|
5
|
+
post :suspend
|
6
|
+
post :close
|
7
|
+
end
|
8
|
+
end
|
2
9
|
resources :product_categories
|
3
10
|
resources :interest_rate_types
|
4
11
|
resources :virtual_account_products do
|
@@ -9,4 +16,10 @@ Dscf::Banking::Engine.routes.draw do
|
|
9
16
|
resources :interest_configurations
|
10
17
|
resources :interest_rate_tiers
|
11
18
|
resources :product_approvals
|
19
|
+
resources :applications do
|
20
|
+
member do
|
21
|
+
post :approve
|
22
|
+
post :reject
|
23
|
+
end
|
24
|
+
end
|
12
25
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class CreateDscfBankingApplications < ActiveRecord::Migration[8.0]
|
2
|
+
def change
|
3
|
+
create_table :dscf_banking_applications do |t|
|
4
|
+
t.string :application_number, null: false
|
5
|
+
t.references :user, null: false, foreign_key: { to_table: :dscf_core_users }
|
6
|
+
t.integer :applicant_type, null: false
|
7
|
+
t.integer :status, null: false, default: 0
|
8
|
+
|
9
|
+
t.jsonb :form_data
|
10
|
+
t.datetime :submitted_at
|
11
|
+
t.datetime :completed_at
|
12
|
+
t.text :rejection_reason
|
13
|
+
t.references :assigned_kyc_officer, null: true, foreign_key: { to_table: :dscf_core_users }
|
14
|
+
t.references :assigned_branch_manager, null: true, foreign_key: { to_table: :dscf_core_users }
|
15
|
+
t.references :virtual_account_product, null: false, foreign_key: { to_table: :dscf_banking_virtual_account_products }
|
16
|
+
|
17
|
+
t.timestamps
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class CreateDscfBankingAccounts < ActiveRecord::Migration[8.0]
|
2
|
+
def change
|
3
|
+
create_table :dscf_banking_accounts do |t|
|
4
|
+
t.string :account_number, null: false
|
5
|
+
t.references :virtual_account_product, null: false, foreign_key: { to_table: :dscf_banking_virtual_account_products }
|
6
|
+
t.references :application, null: false, foreign_key: { to_table: :dscf_banking_applications }
|
7
|
+
t.string :name, null: false
|
8
|
+
t.integer :status, default: 0
|
9
|
+
t.date :activation_date
|
10
|
+
t.date :closure_date
|
11
|
+
t.jsonb :account_properties, default: {}
|
12
|
+
t.decimal :current_balance, precision: 20, scale: 4, default: 0
|
13
|
+
t.decimal :available_balance, precision: 20, scale: 4, default: 0
|
14
|
+
t.decimal :minimum_balance, precision: 20, scale: 4, default: 0
|
15
|
+
t.string :currency, null: false, default: 'ETB'
|
16
|
+
t.boolean :active, default: true
|
17
|
+
|
18
|
+
t.timestamps
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/dscf/banking/version.rb
CHANGED
@@ -0,0 +1,51 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
factory :dscf_banking_account, class: "Dscf::Banking::Account" do
|
3
|
+
association :virtual_account_product, factory: :virtual_account_product
|
4
|
+
association :application, factory: :application
|
5
|
+
|
6
|
+
name { "Test Account" }
|
7
|
+
currency { "ETB" }
|
8
|
+
current_balance { 0 }
|
9
|
+
available_balance { 0 }
|
10
|
+
minimum_balance { 0 }
|
11
|
+
status { :draft }
|
12
|
+
active { true }
|
13
|
+
account_properties { {} }
|
14
|
+
|
15
|
+
trait :active do
|
16
|
+
status { :active }
|
17
|
+
activation_date { Date.current }
|
18
|
+
end
|
19
|
+
|
20
|
+
trait :suspended do
|
21
|
+
status { :suspended }
|
22
|
+
end
|
23
|
+
|
24
|
+
trait :closed do
|
25
|
+
status { :closed }
|
26
|
+
active { false }
|
27
|
+
closure_date { Date.current }
|
28
|
+
end
|
29
|
+
|
30
|
+
trait :with_balance do
|
31
|
+
current_balance { 1000 }
|
32
|
+
available_balance { 1000 }
|
33
|
+
minimum_balance { 100 }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Alias for shared spec compatibility
|
38
|
+
factory :account, class: "Dscf::Banking::Account" do
|
39
|
+
association :virtual_account_product, factory: :virtual_account_product
|
40
|
+
association :application, factory: :application
|
41
|
+
|
42
|
+
name { "Test Account" }
|
43
|
+
currency { "ETB" }
|
44
|
+
current_balance { 0 }
|
45
|
+
available_balance { 0 }
|
46
|
+
minimum_balance { 0 }
|
47
|
+
status { :draft }
|
48
|
+
active { true }
|
49
|
+
account_properties { {} }
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
factory :application, class: "Dscf::Banking::Application" do
|
3
|
+
association :user
|
4
|
+
association :virtual_account_product
|
5
|
+
applicant_type { :individual }
|
6
|
+
status { :draft }
|
7
|
+
|
8
|
+
form_data do
|
9
|
+
{
|
10
|
+
first_name: Faker::Name.first_name,
|
11
|
+
last_name: Faker::Name.last_name,
|
12
|
+
date_of_birth: Faker::Date.birthday(min_age: 18, max_age: 80).to_s,
|
13
|
+
nationality: "Ethiopian",
|
14
|
+
phone: Faker::PhoneNumber.phone_number,
|
15
|
+
address: Faker::Address.full_address
|
16
|
+
}
|
17
|
+
end
|
18
|
+
submitted_at { nil }
|
19
|
+
completed_at { nil }
|
20
|
+
rejection_reason { nil }
|
21
|
+
assigned_kyc_officer { nil }
|
22
|
+
assigned_branch_manager { nil }
|
23
|
+
|
24
|
+
trait :business do
|
25
|
+
applicant_type { :business }
|
26
|
+
application_type { :current }
|
27
|
+
form_data do
|
28
|
+
{
|
29
|
+
business_name: Faker::Company.name,
|
30
|
+
tin_number: Faker::Number.number(digits: 10).to_s,
|
31
|
+
business_type: "Limited Company",
|
32
|
+
contact_person: Faker::Name.name,
|
33
|
+
phone: Faker::PhoneNumber.phone_number,
|
34
|
+
address: Faker::Address.full_address
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
trait :submitted do
|
40
|
+
status { :submitted }
|
41
|
+
submitted_at { 1.day.ago }
|
42
|
+
end
|
43
|
+
|
44
|
+
trait :under_review do
|
45
|
+
status { :under_review }
|
46
|
+
submitted_at { 2.days.ago }
|
47
|
+
association :assigned_kyc_officer, factory: [ :user, :dscf_core ]
|
48
|
+
end
|
49
|
+
|
50
|
+
trait :approved do
|
51
|
+
status { :approved }
|
52
|
+
submitted_at { 3.days.ago }
|
53
|
+
association :assigned_kyc_officer, factory: [ :user, :dscf_core ]
|
54
|
+
association :assigned_branch_manager, factory: [ :user, :dscf_core ]
|
55
|
+
end
|
56
|
+
|
57
|
+
trait :rejected do
|
58
|
+
status { :rejected }
|
59
|
+
submitted_at { 3.days.ago }
|
60
|
+
rejection_reason { "Incomplete documentation" }
|
61
|
+
association :assigned_kyc_officer, factory: [ :user, :dscf_core ]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dscf-banking
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eyosiyas Mekbib
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-09-15 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: rails
|
@@ -439,7 +439,9 @@ files:
|
|
439
439
|
- MIT-LICENSE
|
440
440
|
- README.md
|
441
441
|
- Rakefile
|
442
|
+
- app/controllers/dscf/banking/accounts_controller.rb
|
442
443
|
- app/controllers/dscf/banking/application_controller.rb
|
444
|
+
- app/controllers/dscf/banking/applications_controller.rb
|
443
445
|
- app/controllers/dscf/banking/interest_configurations_controller.rb
|
444
446
|
- app/controllers/dscf/banking/interest_rate_tiers_controller.rb
|
445
447
|
- app/controllers/dscf/banking/interest_rate_types_controller.rb
|
@@ -449,6 +451,8 @@ files:
|
|
449
451
|
- app/jobs/dscf/banking/application_job.rb
|
450
452
|
- app/mailers/dscf/banking/application_mailer.rb
|
451
453
|
- app/models/concerns/dscf/banking/auditable.rb
|
454
|
+
- app/models/dscf/banking/account.rb
|
455
|
+
- app/models/dscf/banking/application.rb
|
452
456
|
- app/models/dscf/banking/application_record.rb
|
453
457
|
- app/models/dscf/banking/interest_configuration.rb
|
454
458
|
- app/models/dscf/banking/interest_rate_tier.rb
|
@@ -457,12 +461,15 @@ files:
|
|
457
461
|
- app/models/dscf/banking/product_audit_log.rb
|
458
462
|
- app/models/dscf/banking/product_category.rb
|
459
463
|
- app/models/dscf/banking/virtual_account_product.rb
|
464
|
+
- app/serializers/dscf/banking/account_serializer.rb
|
465
|
+
- app/serializers/dscf/banking/application_serializer.rb
|
460
466
|
- app/serializers/dscf/banking/interest_configuration_serializer.rb
|
461
467
|
- app/serializers/dscf/banking/interest_rate_tier_serializer.rb
|
462
468
|
- app/serializers/dscf/banking/interest_rate_type_serializer.rb
|
463
469
|
- app/serializers/dscf/banking/product_approval_serializer.rb
|
464
470
|
- app/serializers/dscf/banking/product_category_serializer.rb
|
465
471
|
- app/serializers/dscf/banking/virtual_account_product_serializer.rb
|
472
|
+
- app/services/dscf/banking/account_creation_service.rb
|
466
473
|
- config/routes.rb
|
467
474
|
- db/migrate/20250830211002_create_dscf_banking_product_categories.rb
|
468
475
|
- db/migrate/20250830211027_create_dscf_banking_interest_rate_types.rb
|
@@ -473,10 +480,14 @@ files:
|
|
473
480
|
- db/migrate/20250831082356_create_dscf_banking_product_approvals.rb
|
474
481
|
- db/migrate/20250831083907_create_dscf_banking_product_audit_logs.rb
|
475
482
|
- db/migrate/20250831084706_allow_null_virtual_account_product_id_in_product_audit_logs.rb
|
483
|
+
- db/migrate/20250912193134_create_dscf_banking_applications.rb
|
484
|
+
- db/migrate/20250912203527_create_dscf_banking_accounts.rb
|
476
485
|
- lib/dscf/banking.rb
|
477
486
|
- lib/dscf/banking/engine.rb
|
478
487
|
- lib/dscf/banking/version.rb
|
479
488
|
- lib/tasks/dscf/banking_tasks.rake
|
489
|
+
- spec/factories/dscf/banking/accounts.rb
|
490
|
+
- spec/factories/dscf/banking/applications.rb
|
480
491
|
- spec/factories/dscf/banking/interest_configurations.rb
|
481
492
|
- spec/factories/dscf/banking/interest_rate_tiers.rb
|
482
493
|
- spec/factories/dscf/banking/interest_rate_types.rb
|