bscf-core 0.4.97 → 0.4.99
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/bscf/core/application_controller.rb +2 -6
- data/app/models/bscf/core/loan.rb +28 -0
- data/app/models/bscf/core/loan_repayment.rb +45 -0
- data/app/models/bscf/core/virtual_account.rb +26 -2
- data/app/policies/application_policy.rb +22 -0
- data/db/migrate/20250720123239_create_bscf_core_loans.rb +16 -0
- data/db/migrate/20250720131241_create_bscf_core_loan_repayments.rb +12 -0
- data/db/migrate/20250720184059_nullable_occupation_source_of_funds.rb +6 -0
- data/lib/bscf/core/version.rb +1 -1
- data/spec/factories/bscf/core/loan_repayments.rb +9 -0
- data/spec/factories/bscf/core/loans.rb +12 -0
- metadata +25 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b0455d1f4a5bcc686f33479c012b2bc9ec78ed743de0d101134a0c7a814011d1
|
4
|
+
data.tar.gz: b5082962af2d1679daf511986d30c6c6f9d3e25c818615c7674b63f64dfae2d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c64b3a1a1d4e87ac64919c8948f5f9acf7c97b2cd0fb0410ef894f13e887ca71490e9bd7f5f7e9978ab55b46316bc701f4a37dcf30d2e47f8eb60e39a2603399
|
7
|
+
data.tar.gz: ad4848e51ba7b6220417f52f57172df8187bd1a3a70af91a64cb47617c932e771dab61844633bb1fc70dba950adf51126e27332977aaf0261a7d135772e81ee9
|
@@ -1,18 +1,14 @@
|
|
1
1
|
module Bscf
|
2
2
|
module Core
|
3
3
|
class ApplicationController < ActionController::API
|
4
|
+
include Pundit::Authorization
|
5
|
+
|
4
6
|
private
|
5
7
|
|
6
8
|
def is_authenticated
|
7
9
|
render json: { error: "Not authenticated" }, status: :unauthorized unless current_user
|
8
10
|
end
|
9
11
|
|
10
|
-
def is_allowed
|
11
|
-
user_role = UserRole.find_by(user: current_user)
|
12
|
-
role = Role.find(user_role.role_id)
|
13
|
-
render json: { error: "Not authorized" }, status: :ok unless role.name == "User" || role.name == "Admin"
|
14
|
-
end
|
15
|
-
|
16
12
|
def current_user
|
17
13
|
return @current_user if defined?(@current_user)
|
18
14
|
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Bscf::Core
|
2
|
+
class Loan < ApplicationRecord
|
3
|
+
belongs_to :virtual_account, class_name: "Bscf::Core::VirtualAccount"
|
4
|
+
belongs_to :disbursement_transaction, class_name: "Bscf::Core::VirtualAccountTransaction"
|
5
|
+
|
6
|
+
enum :status, { disbursed: 0, late: 1, paid: 2 }
|
7
|
+
|
8
|
+
validates :principal_amount, :interest_amount, :unpaid_balance, presence: true, numericality: { greater_than_or_equal_to: 0 }
|
9
|
+
validates :status, :due_date, presence: true
|
10
|
+
validates :status, inclusion: { in: statuses.keys }
|
11
|
+
|
12
|
+
validate :paid_at_requires_zero_balance_and_paid_status
|
13
|
+
validate :paid_status_requires_zero_balance
|
14
|
+
|
15
|
+
def paid_at_requires_zero_balance_and_paid_status
|
16
|
+
if paid_at.present?
|
17
|
+
errors.add(:paid_at, "can only be set if loan is marked as paid") if status != "paid"
|
18
|
+
errors.add(:paid_at, "can only be set if unpaid balance is zero") if unpaid_balance.to_f > 0
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def paid_status_requires_zero_balance
|
23
|
+
if status == "paid" && unpaid_balance.to_f > 0
|
24
|
+
errors.add(:status, "can only be set to paid if unpaid balance is zero")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Bscf::Core
|
2
|
+
class LoanRepayment < ApplicationRecord
|
3
|
+
belongs_to :loan, class_name: "Bscf::Core::Loan"
|
4
|
+
belongs_to :repayment_transaction, class_name: "Bscf::Core::VirtualAccountTransaction"
|
5
|
+
|
6
|
+
validates :amount, presence: true, numericality: { greater_than: 0 }
|
7
|
+
validates :payment_date, presence: true
|
8
|
+
validate :amount_cannot_exceed_unpaid_balance
|
9
|
+
validate :loan_cannot_be_already_paid
|
10
|
+
|
11
|
+
after_create :apply_repayment_to_loan
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def apply_repayment_to_loan
|
16
|
+
loan.with_lock do
|
17
|
+
loan.unpaid_balance -= amount
|
18
|
+
loan.unpaid_balance = 0 if loan.unpaid_balance.negative?
|
19
|
+
|
20
|
+
if loan.unpaid_balance.zero?
|
21
|
+
loan.status = "paid"
|
22
|
+
loan.paid_at = payment_date
|
23
|
+
end
|
24
|
+
|
25
|
+
loan.save!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def amount_cannot_exceed_unpaid_balance
|
30
|
+
return if loan.blank? || amount.blank?
|
31
|
+
|
32
|
+
if amount > loan.unpaid_balance
|
33
|
+
errors.add(:amount, "cannot be greater than the loan's unpaid balance")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def loan_cannot_be_already_paid
|
38
|
+
return if loan.blank?
|
39
|
+
|
40
|
+
if loan.paid?
|
41
|
+
errors.add(:loan, "is already marked as paid")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -76,9 +76,33 @@ module Bscf
|
|
76
76
|
def generate_account_number
|
77
77
|
return if account_number.present?
|
78
78
|
|
79
|
-
|
79
|
+
last_account = self.class.maximum(:account_number)
|
80
|
+
last_seq = last_account ? last_account[-6..-1].to_i : 0
|
80
81
|
seq = (last_seq + 1).to_s.rjust(6, "0")
|
81
|
-
|
82
|
+
|
83
|
+
branch_part = branch_code.to_s.rjust(3, "0")[0..2]
|
84
|
+
product_part = map_product_scheme_to_digit(product_scheme)
|
85
|
+
voucher_part = map_voucher_type_to_digit(voucher_type)
|
86
|
+
|
87
|
+
self.account_number = "#{branch_part}#{product_part}#{voucher_part}#{seq}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def map_product_scheme_to_digit(scheme)
|
91
|
+
case scheme
|
92
|
+
when "SAVINGS" then "1"
|
93
|
+
when "CURRENT" then "2"
|
94
|
+
when "LOAN" then "3"
|
95
|
+
else "0"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def map_voucher_type_to_digit(type)
|
100
|
+
case type
|
101
|
+
when "REGULAR" then "1"
|
102
|
+
when "SPECIAL" then "2"
|
103
|
+
when "TEMPORARY" then "3"
|
104
|
+
else "0"
|
105
|
+
end
|
82
106
|
end
|
83
107
|
|
84
108
|
def transfer_to!(to_account, amount)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class ApplicationPolicy
|
2
|
+
attr_reader :user, :record
|
3
|
+
|
4
|
+
def initialize(user, record)
|
5
|
+
@user = user
|
6
|
+
@record = record
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def admin?
|
12
|
+
user.user_roles.any? { |ur| ur.role.name == "Admin" }
|
13
|
+
end
|
14
|
+
|
15
|
+
def driver?
|
16
|
+
user.user_roles.any? { |ur| ur.role.name == "Driver" }
|
17
|
+
end
|
18
|
+
|
19
|
+
def user_role?
|
20
|
+
user.user_roles.any? { |ur| ur.role.name == "User" }
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class CreateBscfCoreLoans < ActiveRecord::Migration[8.0]
|
2
|
+
def change
|
3
|
+
create_table :bscf_core_loans do |t|
|
4
|
+
t.references :virtual_account, null: false, foreign_key: { to_table: :bscf_core_virtual_accounts }
|
5
|
+
t.references :disbursement_transaction, null: false, foreign_key: { to_table: :bscf_core_virtual_account_transactions }
|
6
|
+
t.float :principal_amount, null: false, default: 0.0
|
7
|
+
t.float :interest_amount, null: false, default: 0.0
|
8
|
+
t.float :unpaid_balance, null: false, default: 0.0
|
9
|
+
t.integer :status, null: false, default: 0
|
10
|
+
t.date :due_date, null: false, default: Date.today + 15.days
|
11
|
+
t.date :paid_at, null: true
|
12
|
+
|
13
|
+
t.timestamps
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class CreateBscfCoreLoanRepayments < ActiveRecord::Migration[8.0]
|
2
|
+
def change
|
3
|
+
create_table :bscf_core_loan_repayments do |t|
|
4
|
+
t.references :loan, null: false, foreign_key: { to_table: :bscf_core_loans }
|
5
|
+
t.references :repayment_transaction, null: false, foreign_key: { to_table: :bscf_core_virtual_account_transactions }
|
6
|
+
t.float :amount, null: false
|
7
|
+
t.date :payment_date, null: false, default: Date.today
|
8
|
+
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/bscf/core/version.rb
CHANGED
@@ -0,0 +1,12 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
factory :loan, class: 'Bscf::Core::Loan' do
|
3
|
+
association :virtual_account
|
4
|
+
association :disbursement_transaction, factory: :virtual_account_transaction
|
5
|
+
|
6
|
+
principal_amount { 200.5 }
|
7
|
+
interest_amount { 10.5 }
|
8
|
+
unpaid_balance { 210.5 }
|
9
|
+
status { 0 }
|
10
|
+
due_date { Date.today + 15.days }
|
11
|
+
end
|
12
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bscf-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.99
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Asrat
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 2025-07-23 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: active_model_serializers
|
@@ -147,6 +147,20 @@ dependencies:
|
|
147
147
|
- - ">="
|
148
148
|
- !ruby/object:Gem::Version
|
149
149
|
version: '0'
|
150
|
+
- !ruby/object:Gem::Dependency
|
151
|
+
name: pundit
|
152
|
+
requirement: !ruby/object:Gem::Requirement
|
153
|
+
requirements:
|
154
|
+
- - ">="
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
version: '0'
|
157
|
+
type: :runtime
|
158
|
+
prerelease: false
|
159
|
+
version_requirements: !ruby/object:Gem::Requirement
|
160
|
+
requirements:
|
161
|
+
- - ">="
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
version: '0'
|
150
164
|
- !ruby/object:Gem::Dependency
|
151
165
|
name: database_cleaner-active_record
|
152
166
|
requirement: !ruby/object:Gem::Requirement
|
@@ -296,6 +310,8 @@ files:
|
|
296
310
|
- app/models/bscf/core/delivery_order_item.rb
|
297
311
|
- app/models/bscf/core/invoice.rb
|
298
312
|
- app/models/bscf/core/invoice_item.rb
|
313
|
+
- app/models/bscf/core/loan.rb
|
314
|
+
- app/models/bscf/core/loan_repayment.rb
|
299
315
|
- app/models/bscf/core/marketplace_listing.rb
|
300
316
|
- app/models/bscf/core/order.rb
|
301
317
|
- app/models/bscf/core/order_item.rb
|
@@ -314,6 +330,7 @@ files:
|
|
314
330
|
- app/models/bscf/core/virtual_account_transaction.rb
|
315
331
|
- app/models/bscf/core/voucher.rb
|
316
332
|
- app/models/bscf/core/wholesaler_product.rb
|
333
|
+
- app/policies/application_policy.rb
|
317
334
|
- app/services/bscf/core/gebeta_maps_service.rb
|
318
335
|
- app/services/bscf/core/token_service.rb
|
319
336
|
- app/services/bscf/core/transaction_service.rb
|
@@ -364,6 +381,9 @@ files:
|
|
364
381
|
- db/migrate/20250624062746_rename_business_id_to_user_id_in_bscf_core_business_documents.rb
|
365
382
|
- db/migrate/20250624122210_add_document_type_to_business_documents.rb
|
366
383
|
- db/migrate/20250714201747_add_optimized_route_to_delivery_order.rb
|
384
|
+
- db/migrate/20250720123239_create_bscf_core_loans.rb
|
385
|
+
- db/migrate/20250720131241_create_bscf_core_loan_repayments.rb
|
386
|
+
- db/migrate/20250720184059_nullable_occupation_source_of_funds.rb
|
367
387
|
- lib/bscf/core.rb
|
368
388
|
- lib/bscf/core/engine.rb
|
369
389
|
- lib/bscf/core/version.rb
|
@@ -379,6 +399,8 @@ files:
|
|
379
399
|
- spec/factories/bscf/core/delivery_orders.rb
|
380
400
|
- spec/factories/bscf/core/invoice_items.rb
|
381
401
|
- spec/factories/bscf/core/invoices.rb
|
402
|
+
- spec/factories/bscf/core/loan_repayments.rb
|
403
|
+
- spec/factories/bscf/core/loans.rb
|
382
404
|
- spec/factories/bscf/core/marketplace_listings.rb
|
383
405
|
- spec/factories/bscf/core/order_items.rb
|
384
406
|
- spec/factories/bscf/core/orders.rb
|
@@ -418,7 +440,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
418
440
|
- !ruby/object:Gem::Version
|
419
441
|
version: '0'
|
420
442
|
requirements: []
|
421
|
-
rubygems_version: 3.6.
|
443
|
+
rubygems_version: 3.6.2
|
422
444
|
specification_version: 4
|
423
445
|
summary: An Engine for Supply Chain Financing
|
424
446
|
test_files: []
|