dscf-banking 0.3.6 → 0.3.7
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 +6 -12
- data/app/controllers/dscf/banking/documents_controller.rb +4 -4
- data/app/controllers/dscf/banking/interest_configurations_controller.rb +3 -5
- data/app/controllers/dscf/banking/transactions_controller.rb +1 -1
- data/app/controllers/dscf/banking/virtual_account_products_controller.rb +4 -4
- data/app/controllers/dscf/banking/vouchers_controller.rb +133 -0
- data/app/models/dscf/banking/account.rb +4 -1
- data/app/models/dscf/banking/interest_configuration.rb +8 -117
- data/app/models/dscf/banking/virtual_account_product.rb +1 -2
- data/app/models/dscf/banking/voucher.rb +76 -0
- data/app/models/dscf/banking/voucher_redemption.rb +27 -0
- data/app/serializers/dscf/banking/account_serializer.rb +3 -2
- data/app/serializers/dscf/banking/document_serializer.rb +3 -9
- data/app/serializers/dscf/banking/interest_configuration_serializer.rb +0 -1
- data/app/serializers/dscf/banking/voucher_redemption_serializer.rb +10 -0
- data/app/serializers/dscf/banking/voucher_serializer.rb +20 -0
- data/app/services/dscf/banking/voucher_service.rb +317 -0
- data/config/routes.rb +1 -0
- data/db/migrate/20260210120000_create_dscf_banking_vouchers.rb +23 -0
- data/db/migrate/20260210120010_create_dscf_banking_voucher_redemptions.rb +16 -0
- data/db/seeds.rb +30 -49
- data/lib/dscf/banking/version.rb +1 -1
- data/spec/factories/dscf/banking/accounts.rb +2 -0
- data/spec/factories/dscf/banking/interest_configurations.rb +14 -42
- data/spec/factories/dscf/banking/interest_rate_types.rb +1 -1
- data/spec/factories/dscf/banking/voucher_redemptions.rb +9 -0
- data/spec/factories/dscf/banking/vouchers.rb +14 -0
- metadata +14 -19
- data/db/seeds/interest_rate_types.rb +0 -38
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1d9aebfb57294bdeee3240fe4079565174e97a172b44aaf93c4d93f3be94573d
|
|
4
|
+
data.tar.gz: f7acfa8939c35b2ce39f99a787772712ee886be3ed89326247948edf6f4b8a9e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 714c5cdb80e79dea3fd95842f9ef2ddb5e1f39c49af9f08ffee40625157ec2147fb0676430316885f8c28534811982a48ada4c471aef0c91a338d9c3fc068b5a
|
|
7
|
+
data.tar.gz: b1b6079ab0ad3d70f2c9c1076f70b06f6cefe57c118a1fa001710319a29054808bede0b95dfada63702968a6310ca74190d7013444c72889c7a1eca8ac034255
|
|
@@ -30,18 +30,12 @@ module Dscf::Banking
|
|
|
30
30
|
|
|
31
31
|
def full_name
|
|
32
32
|
account = Dscf::Banking::Account.find_by(account_number: params[:id])
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
else
|
|
40
|
-
render json: { success: false, error: "User profile not found" }, status: :not_found
|
|
41
|
-
end
|
|
42
|
-
else
|
|
43
|
-
render json: { success: false, error: "Account not found" }, status: :not_found
|
|
44
|
-
end
|
|
33
|
+
return render json: { success: false, error: "Account not found" }, status: :not_found unless account
|
|
34
|
+
|
|
35
|
+
full_name = account.name.to_s.strip
|
|
36
|
+
full_name = "Unnamed" if full_name.empty?
|
|
37
|
+
|
|
38
|
+
render json: { success: true, data: { full_name: full_name } }
|
|
45
39
|
end
|
|
46
40
|
|
|
47
41
|
def show
|
|
@@ -8,7 +8,7 @@ module Dscf::Banking
|
|
|
8
8
|
super do
|
|
9
9
|
documents = @application.documents
|
|
10
10
|
options = { each_serializer: DocumentSerializer }
|
|
11
|
-
[
|
|
11
|
+
[documents, options]
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
|
|
@@ -16,17 +16,17 @@ module Dscf::Banking
|
|
|
16
16
|
super do
|
|
17
17
|
document = @application.documents.find(params[:id])
|
|
18
18
|
options = { serializer: DocumentSerializer }
|
|
19
|
-
[
|
|
19
|
+
[document, options]
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def create
|
|
24
24
|
document = @application.documents.build(model_params)
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
if document.save
|
|
27
27
|
# Attach file if provided
|
|
28
28
|
document.files.attach(params[:document][:file]) if params[:document][:file].present?
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
render json: {
|
|
31
31
|
success: true,
|
|
32
32
|
data: DocumentSerializer.new(document).as_json
|
|
@@ -24,7 +24,7 @@ module Dscf::Banking
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def eager_loaded_associations
|
|
27
|
-
[ :virtual_account_product, :interest_rate_type
|
|
27
|
+
[ :virtual_account_product, :interest_rate_type ]
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def allowed_order_columns
|
|
@@ -33,10 +33,8 @@ module Dscf::Banking
|
|
|
33
33
|
|
|
34
34
|
def default_serializer_includes
|
|
35
35
|
{
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
create: [ :virtual_account_product, :interest_rate_type, :interest_rate_tiers ],
|
|
39
|
-
update: [ :virtual_account_product, :interest_rate_type, :interest_rate_tiers ]
|
|
36
|
+
virtual_account_product: {},
|
|
37
|
+
interest_rate_type: {}
|
|
40
38
|
}
|
|
41
39
|
end
|
|
42
40
|
end
|
|
@@ -83,10 +83,10 @@ module Dscf::Banking
|
|
|
83
83
|
|
|
84
84
|
def default_serializer_includes
|
|
85
85
|
{
|
|
86
|
-
index: [
|
|
87
|
-
show: [
|
|
88
|
-
create: [
|
|
89
|
-
update: [
|
|
86
|
+
index: [:product_category, :created_by, :approved_by, reviews: { reviewed_by: :user_profile }],
|
|
87
|
+
show: [:product_category, :created_by, :approved_by, reviews: { reviewed_by: :user_profile }],
|
|
88
|
+
create: [:reviews],
|
|
89
|
+
update: [:product_category, :created_by, :approved_by, reviews: { reviewed_by: :user_profile }]
|
|
90
90
|
}
|
|
91
91
|
end
|
|
92
92
|
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
module Dscf
|
|
2
|
+
module Banking
|
|
3
|
+
class VouchersController < ApplicationController
|
|
4
|
+
include Dscf::Core::Common
|
|
5
|
+
|
|
6
|
+
def create
|
|
7
|
+
parent_account = Dscf::Banking::Account.find_by(account_number: model_params[:parent_account_number])
|
|
8
|
+
|
|
9
|
+
unless parent_account
|
|
10
|
+
return render_error(errors: "Parent account not found", status: :not_found)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
service = Dscf::Banking::VoucherService.new
|
|
14
|
+
service.generate_voucher(
|
|
15
|
+
parent_account: parent_account,
|
|
16
|
+
amount: model_params[:amount],
|
|
17
|
+
recipient_mobile: model_params[:recipient_mobile],
|
|
18
|
+
recipient_name: model_params[:recipient_name],
|
|
19
|
+
message: model_params[:message],
|
|
20
|
+
expires_at: model_params[:expires_at]
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
if service.success?
|
|
24
|
+
render_success(data: service.voucher, serializer_options: { include: default_serializer_includes[:default] }, status: :created)
|
|
25
|
+
else
|
|
26
|
+
render_error(errors: service.errors, status: :unprocessable_entity)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def redeem
|
|
31
|
+
service = Dscf::Banking::VoucherService.new
|
|
32
|
+
destination_account = nil
|
|
33
|
+
|
|
34
|
+
if redeem_params[:destination_account_number].present?
|
|
35
|
+
destination_account = Dscf::Banking::Account.find_by(account_number: redeem_params[:destination_account_number])
|
|
36
|
+
unless destination_account
|
|
37
|
+
return render_error(errors: "Destination account not found", status: :not_found)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
service.redeem_voucher(
|
|
42
|
+
code: redeem_params[:code] || params[:code],
|
|
43
|
+
amount: redeem_params[:amount],
|
|
44
|
+
recipient_mobile: redeem_params[:recipient_mobile],
|
|
45
|
+
destination_account: destination_account
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
if service.success?
|
|
49
|
+
render_success(
|
|
50
|
+
data: {
|
|
51
|
+
voucher: service.voucher,
|
|
52
|
+
redemption: service.redemption
|
|
53
|
+
},
|
|
54
|
+
serializer_options: {
|
|
55
|
+
voucher: { include: default_serializer_includes[:default] },
|
|
56
|
+
redemption: { include: [ :destination_account ] }
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
else
|
|
60
|
+
render_error(errors: service.errors, status: :unprocessable_entity)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def show
|
|
65
|
+
voucher = Dscf::Banking::Voucher.find_by(code: params[:id].to_s.strip.upcase)
|
|
66
|
+
return render_error(errors: "Voucher not found", status: :not_found) unless voucher
|
|
67
|
+
|
|
68
|
+
if voucher.active? && voucher.expired?
|
|
69
|
+
Dscf::Banking::VoucherService.new.expire_voucher(voucher)
|
|
70
|
+
voucher.reload
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
render_success(data: voucher, serializer_options: { include: default_serializer_includes[:default] })
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def cancel
|
|
77
|
+
service = Dscf::Banking::VoucherService.new
|
|
78
|
+
service.cancel_voucher(code: params[:code])
|
|
79
|
+
|
|
80
|
+
if service.success?
|
|
81
|
+
render_success(data: service.voucher, serializer_options: { include: default_serializer_includes[:default] })
|
|
82
|
+
else
|
|
83
|
+
render_error(errors: service.errors, status: :unprocessable_entity)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def account_vouchers
|
|
88
|
+
account = Dscf::Banking::Account.find(params[:account_id])
|
|
89
|
+
vouchers = account.vouchers.order(created_at: :desc)
|
|
90
|
+
|
|
91
|
+
render_success(data: vouchers, serializer_options: { include: default_serializer_includes[:default] })
|
|
92
|
+
rescue ActiveRecord::RecordNotFound
|
|
93
|
+
render_error(errors: "Account not found", status: :not_found)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def model_params
|
|
99
|
+
params.require(:voucher).permit(
|
|
100
|
+
:parent_account_number,
|
|
101
|
+
:amount,
|
|
102
|
+
:recipient_mobile,
|
|
103
|
+
:recipient_name,
|
|
104
|
+
:message,
|
|
105
|
+
:expires_at
|
|
106
|
+
)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def redeem_params
|
|
110
|
+
params.require(:redemption).permit(
|
|
111
|
+
:code,
|
|
112
|
+
:amount,
|
|
113
|
+
:recipient_mobile,
|
|
114
|
+
:destination_account_number
|
|
115
|
+
)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def eager_loaded_associations
|
|
119
|
+
[]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def allowed_order_columns
|
|
123
|
+
%w[id code status amount remaining_amount recipient_mobile created_at updated_at]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def default_serializer_includes
|
|
127
|
+
{
|
|
128
|
+
default: [ :parent_account, :redemptions ]
|
|
129
|
+
}
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -3,6 +3,9 @@ module Dscf::Banking
|
|
|
3
3
|
belongs_to :virtual_account_product, optional: true
|
|
4
4
|
belongs_to :application, optional: true
|
|
5
5
|
|
|
6
|
+
has_many :vouchers, class_name: "Dscf::Banking::Voucher", foreign_key: :parent_account_id, dependent: :restrict_with_error
|
|
7
|
+
has_many :voucher_redemptions, through: :vouchers, class_name: "Dscf::Banking::VoucherRedemption"
|
|
8
|
+
|
|
6
9
|
enum :status, {
|
|
7
10
|
draft: 0,
|
|
8
11
|
pending_activation: 1,
|
|
@@ -16,7 +19,7 @@ module Dscf::Banking
|
|
|
16
19
|
validates :name, presence: true
|
|
17
20
|
validates :currency, presence: true
|
|
18
21
|
validates :current_balance, :available_balance, :minimum_balance,
|
|
19
|
-
|
|
22
|
+
numericality: { greater_than_or_equal_to: 0 }
|
|
20
23
|
validates :system_account, inclusion: { in: [ true, false ] }
|
|
21
24
|
validate :system_account_associations
|
|
22
25
|
|
|
@@ -2,25 +2,19 @@ module Dscf::Banking
|
|
|
2
2
|
class InterestConfiguration < ApplicationRecord
|
|
3
3
|
belongs_to :interest_rate_type, class_name: "Dscf::Banking::InterestRateType"
|
|
4
4
|
belongs_to :virtual_account_product, class_name: "Dscf::Banking::VirtualAccountProduct"
|
|
5
|
-
has_many :interest_rate_tiers, class_name: "Dscf::Banking::InterestRateTier",
|
|
5
|
+
has_many :interest_rate_tiers, class_name: "Dscf::Banking::InterestRateTier", dependent: :destroy
|
|
6
6
|
|
|
7
|
-
validates :annual_interest_rate, presence: true, numericality: { greater_than: 0, less_than_or_equal_to: 1 }
|
|
8
|
-
validates :annual_interest_rate, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 1 }, if: :interest_free?, allow_nil: true
|
|
7
|
+
validates :annual_interest_rate, presence: true, numericality: { greater_than: 0, less_than_or_equal_to: 1 }
|
|
9
8
|
validates :income_tax_rate, presence: true, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 1 }
|
|
10
9
|
validates :minimum_balance_for_interest, presence: true, numericality: { greater_than_or_equal_to: 0 }
|
|
11
|
-
validates :calculation_method, presence: true
|
|
12
|
-
validates :interest_basis, presence: true, unless: :interest_free?
|
|
13
|
-
validates :accrual_frequency, presence: true, unless: :interest_free?
|
|
14
|
-
validates :rounding_rule, presence: true, unless: :interest_free?
|
|
15
|
-
validates :calculation_timing, presence: true, unless: :interest_free?
|
|
16
|
-
validates :is_active, inclusion: { in: [ true, false ] }
|
|
10
|
+
validates :calculation_method, presence: true
|
|
17
11
|
|
|
12
|
+
validates :interest_basis, presence: true
|
|
13
|
+
validates :accrual_frequency, presence: true
|
|
14
|
+
validates :rounding_rule, presence: true
|
|
15
|
+
validates :calculation_timing, presence: true
|
|
16
|
+
validates :is_active, inclusion: { in: [ true, false ] }
|
|
18
17
|
validate :promotional_dates_consistency
|
|
19
|
-
validate :rate_type_specific_validations
|
|
20
|
-
validate :tiers_only_for_variable_rate
|
|
21
|
-
validate :interest_rate_type_requirements
|
|
22
|
-
|
|
23
|
-
before_validation :set_interest_basis_from_accrual_frequency
|
|
24
18
|
|
|
25
19
|
enum :calculation_method, {
|
|
26
20
|
simple: 0,
|
|
@@ -63,33 +57,6 @@ module Dscf::Banking
|
|
|
63
57
|
scope :promotional, -> { where.not(promotional_start_date: nil, promotional_end_date: nil) }
|
|
64
58
|
scope :current_promotional, -> { where("promotional_start_date <= ? AND promotional_end_date >= ?", Date.current, Date.current) }
|
|
65
59
|
|
|
66
|
-
def self.ransackable_attributes(auth_object = nil)
|
|
67
|
-
%w[id virtual_account_product_id interest_rate_type_id annual_interest_rate income_tax_rate
|
|
68
|
-
minimum_balance_for_interest calculation_method compounding_period interest_basis
|
|
69
|
-
accrual_frequency rounding_rule promotional_start_date promotional_end_date is_active
|
|
70
|
-
created_at updated_at]
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def self.ransackable_associations(auth_object = nil)
|
|
74
|
-
%w[virtual_account_product interest_rate_type interest_rate_tiers]
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def interest_free?
|
|
78
|
-
interest_rate_type&.code == "INTEREST_FREE"
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def fixed_rate?
|
|
82
|
-
interest_rate_type&.code == "FIXED"
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def variable_rate?
|
|
86
|
-
interest_rate_type&.code == "VARIABLE"
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def promotional_rate?
|
|
90
|
-
interest_rate_type&.code == "PROMOTIONAL"
|
|
91
|
-
end
|
|
92
|
-
|
|
93
60
|
private
|
|
94
61
|
|
|
95
62
|
def promotional_dates_consistency
|
|
@@ -114,81 +81,5 @@ module Dscf::Banking
|
|
|
114
81
|
errors.add(:calculation_day_of_month, "must be specified for annual accrual") if calculation_day_of_month.blank?
|
|
115
82
|
end
|
|
116
83
|
end
|
|
117
|
-
|
|
118
|
-
def rate_type_specific_validations
|
|
119
|
-
return if interest_rate_type.blank?
|
|
120
|
-
|
|
121
|
-
case interest_rate_type.code
|
|
122
|
-
when "INTEREST_FREE"
|
|
123
|
-
validate_interest_free
|
|
124
|
-
when "FIXED"
|
|
125
|
-
validate_fixed_rate
|
|
126
|
-
when "VARIABLE"
|
|
127
|
-
validate_variable_rate
|
|
128
|
-
when "PROMOTIONAL"
|
|
129
|
-
validate_promotional_rate
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def validate_interest_free
|
|
134
|
-
if annual_interest_rate.present? && annual_interest_rate > 0
|
|
135
|
-
errors.add(:annual_interest_rate, "must be 0 for interest-free accounts")
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def validate_fixed_rate
|
|
140
|
-
if annual_interest_rate.blank? || annual_interest_rate <= 0
|
|
141
|
-
errors.add(:annual_interest_rate, "must be greater than 0 for fixed rate")
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
def validate_variable_rate
|
|
146
|
-
if annual_interest_rate.blank? || annual_interest_rate <= 0
|
|
147
|
-
errors.add(:annual_interest_rate, "must be greater than 0 for variable rate")
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
def validate_promotional_rate
|
|
152
|
-
if promotional_start_date.blank?
|
|
153
|
-
errors.add(:promotional_start_date, "is required for promotional rate")
|
|
154
|
-
end
|
|
155
|
-
if promotional_end_date.blank?
|
|
156
|
-
errors.add(:promotional_end_date, "is required for promotional rate")
|
|
157
|
-
end
|
|
158
|
-
if annual_interest_rate.blank? || annual_interest_rate <= 0
|
|
159
|
-
errors.add(:annual_interest_rate, "must be greater than 0 for promotional rate")
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
def tiers_only_for_variable_rate
|
|
164
|
-
return if interest_rate_type.blank?
|
|
165
|
-
return unless persisted? || interest_rate_tiers.loaded?
|
|
166
|
-
|
|
167
|
-
if !variable_rate? && interest_rate_tiers.any?
|
|
168
|
-
errors.add(:base, "Interest rate tiers can only be used with variable rate type")
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def interest_rate_type_requirements
|
|
173
|
-
return if interest_rate_type.blank?
|
|
174
|
-
return unless persisted?
|
|
175
|
-
|
|
176
|
-
if variable_rate? && interest_rate_tiers.empty?
|
|
177
|
-
errors.add(:base, "Variable rate type requires at least one interest rate tier")
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def set_interest_basis_from_accrual_frequency
|
|
182
|
-
return if accrual_frequency.blank?
|
|
183
|
-
return if interest_free?
|
|
184
|
-
return if interest_basis.present?
|
|
185
|
-
|
|
186
|
-
case accrual_frequency
|
|
187
|
-
when "daily"
|
|
188
|
-
self.interest_basis = :actual_365
|
|
189
|
-
when "monthly", "quarterly", "annually"
|
|
190
|
-
self.interest_basis = :thirty_360
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
84
|
end
|
|
194
85
|
end
|
|
@@ -19,7 +19,6 @@ module Dscf::Banking
|
|
|
19
19
|
|
|
20
20
|
has_many :product_approvals, class_name: "Dscf::Banking::ProductApproval", dependent: :destroy
|
|
21
21
|
has_many :product_audit_logs, class_name: "Dscf::Banking::ProductAuditLog", dependent: :destroy
|
|
22
|
-
has_many :interest_configurations, class_name: "Dscf::Banking::InterestConfiguration", dependent: :destroy
|
|
23
22
|
|
|
24
23
|
validates :product_code, presence: true, uniqueness: { case_sensitive: true }, length: { maximum: 50 }
|
|
25
24
|
validates :product_name, presence: true, length: { maximum: 200 }
|
|
@@ -43,7 +42,7 @@ module Dscf::Banking
|
|
|
43
42
|
end
|
|
44
43
|
|
|
45
44
|
def self.ransackable_associations(auth_object = nil)
|
|
46
|
-
%w[product_category created_by approved_by reviews
|
|
45
|
+
%w[product_category created_by approved_by reviews]
|
|
47
46
|
end
|
|
48
47
|
|
|
49
48
|
before_validation :strip_whitespace
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module Dscf
|
|
2
|
+
module Banking
|
|
3
|
+
class Voucher < ApplicationRecord
|
|
4
|
+
self.table_name = "dscf_banking_vouchers"
|
|
5
|
+
|
|
6
|
+
belongs_to :parent_account, class_name: "Dscf::Banking::Account"
|
|
7
|
+
has_many :redemptions, class_name: "Dscf::Banking::VoucherRedemption", dependent: :restrict_with_error
|
|
8
|
+
|
|
9
|
+
enum :status, {
|
|
10
|
+
pending: 0,
|
|
11
|
+
active: 1,
|
|
12
|
+
redeemed: 2,
|
|
13
|
+
expired: 3,
|
|
14
|
+
cancelled: 4
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
validates :parent_account, presence: true
|
|
18
|
+
validates :amount, presence: true, numericality: { greater_than: 0 }
|
|
19
|
+
validates :remaining_amount, presence: true, numericality: { greater_than_or_equal_to: 0 }
|
|
20
|
+
validates :currency, presence: true
|
|
21
|
+
validates :code, presence: true, uniqueness: { case_sensitive: false }
|
|
22
|
+
validates :recipient_mobile, presence: true
|
|
23
|
+
validate :remaining_amount_not_exceed_amount
|
|
24
|
+
validate :expires_at_in_future, if: -> { expires_at.present? }
|
|
25
|
+
|
|
26
|
+
before_validation :set_defaults, on: :create
|
|
27
|
+
before_validation :normalize_code, if: -> { code.present? }
|
|
28
|
+
|
|
29
|
+
scope :by_status, ->(status) { where(status: status) }
|
|
30
|
+
scope :active_now, -> { where(status: :active).where("expires_at IS NULL OR expires_at > ?", Time.current) }
|
|
31
|
+
scope :expired_now, -> { where(status: :active).where("expires_at IS NOT NULL AND expires_at <= ?", Time.current) }
|
|
32
|
+
|
|
33
|
+
def expired?
|
|
34
|
+
expires_at.present? && expires_at <= Time.current
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def fully_redeemed?
|
|
38
|
+
remaining_amount.to_f.zero?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def mark_expired!
|
|
42
|
+
update!(status: :expired) if expired? && !expired_status?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def expired_status?
|
|
46
|
+
status == "expired"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def set_defaults
|
|
52
|
+
self.currency ||= parent_account&.currency
|
|
53
|
+
self.remaining_amount = amount if remaining_amount.nil? && amount
|
|
54
|
+
self.status ||= :pending
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def normalize_code
|
|
58
|
+
self.code = code.to_s.strip.upcase
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def remaining_amount_not_exceed_amount
|
|
62
|
+
return if amount.nil? || remaining_amount.nil?
|
|
63
|
+
|
|
64
|
+
if remaining_amount > amount
|
|
65
|
+
errors.add(:remaining_amount, "cannot exceed voucher amount")
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def expires_at_in_future
|
|
70
|
+
if expires_at <= Time.current
|
|
71
|
+
errors.add(:expires_at, "must be in the future")
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Dscf
|
|
2
|
+
module Banking
|
|
3
|
+
class VoucherRedemption < ApplicationRecord
|
|
4
|
+
self.table_name = "dscf_banking_voucher_redemptions"
|
|
5
|
+
|
|
6
|
+
belongs_to :voucher, class_name: "Dscf::Banking::Voucher"
|
|
7
|
+
belongs_to :destination_account, class_name: "Dscf::Banking::Account", optional: true
|
|
8
|
+
|
|
9
|
+
validates :voucher, presence: true
|
|
10
|
+
validates :amount, presence: true, numericality: { greater_than: 0 }
|
|
11
|
+
validates :recipient_mobile, presence: true
|
|
12
|
+
validates :currency, presence: true
|
|
13
|
+
validates :redeemed_at, presence: true
|
|
14
|
+
|
|
15
|
+
before_validation :set_defaults, on: :create
|
|
16
|
+
|
|
17
|
+
scope :by_voucher, ->(voucher_id) { where(voucher_id: voucher_id) }
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def set_defaults
|
|
22
|
+
self.currency ||= voucher&.currency
|
|
23
|
+
self.recipient_mobile ||= voucher&.recipient_mobile
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
module Dscf::Banking
|
|
2
2
|
class AccountSerializer < ActiveModel::Serializer
|
|
3
3
|
attributes :id, :account_number, :name, :status, :activation_date, :closure_date,
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
:account_properties, :current_balance, :available_balance, :minimum_balance,
|
|
5
|
+
:currency, :active, :created_at, :updated_at
|
|
6
6
|
|
|
7
7
|
belongs_to :virtual_account_product, serializer: VirtualAccountProductSerializer
|
|
8
8
|
|
|
9
9
|
attribute :application_id
|
|
10
|
+
attribute :system_account
|
|
10
11
|
end
|
|
11
12
|
end
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
module Dscf::Banking
|
|
2
2
|
class DocumentSerializer < ActiveModel::Serializer
|
|
3
|
-
attributes :id, :document_type, :is_verified, :verified_at, :metadata, :file_urls, :created_at, :updated_at
|
|
4
|
-
:documentable_type, :documentable_id, :documentable
|
|
3
|
+
attributes :id, :document_type, :is_verified, :verified_at, :metadata, :file_urls, :created_at, :updated_at
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
{
|
|
8
|
-
type: object.documentable_type,
|
|
9
|
-
id: object.documentable_id
|
|
10
|
-
}
|
|
11
|
-
end
|
|
5
|
+
belongs_to :documentable, polymorphic: true
|
|
12
6
|
|
|
13
7
|
def file_urls
|
|
14
8
|
return [] unless object.files.attached?
|
|
@@ -22,7 +16,7 @@ module Dscf::Banking
|
|
|
22
16
|
object.files.map(&:url)
|
|
23
17
|
end
|
|
24
18
|
end
|
|
25
|
-
rescue
|
|
19
|
+
rescue
|
|
26
20
|
[]
|
|
27
21
|
end
|
|
28
22
|
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module Dscf
|
|
2
|
+
module Banking
|
|
3
|
+
class VoucherRedemptionSerializer < ActiveModel::Serializer
|
|
4
|
+
attributes :id, :voucher_id, :destination_account_id, :amount, :currency,
|
|
5
|
+
:recipient_mobile, :redeemed_at, :created_at, :updated_at
|
|
6
|
+
|
|
7
|
+
belongs_to :destination_account, serializer: AccountSerializer
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Dscf
|
|
2
|
+
module Banking
|
|
3
|
+
class VoucherSerializer < ActiveModel::Serializer
|
|
4
|
+
attributes :id, :parent_account_id, :code, :status, :amount, :remaining_amount,
|
|
5
|
+
:currency, :recipient_mobile, :recipient_name, :message, :expires_at,
|
|
6
|
+
:redeemed_at, :created_at, :updated_at
|
|
7
|
+
|
|
8
|
+
attribute :amount do
|
|
9
|
+
object.amount.to_f
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attribute :remaining_amount do
|
|
13
|
+
object.remaining_amount.to_f
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
belongs_to :parent_account, serializer: AccountSerializer
|
|
17
|
+
has_many :redemptions, serializer: VoucherRedemptionSerializer
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|