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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca5f351bc1c4d0ef3f5682b4b01524be4b79366267c313badd649185ba197118
4
- data.tar.gz: 257e06eed096459bc7f7479abfe00620692fe7e534f1b327ecab8056b75d07ce
3
+ metadata.gz: b0455d1f4a5bcc686f33479c012b2bc9ec78ed743de0d101134a0c7a814011d1
4
+ data.tar.gz: b5082962af2d1679daf511986d30c6c6f9d3e25c818615c7674b63f64dfae2d8
5
5
  SHA512:
6
- metadata.gz: dc857844cd4f14fdc8ac0d96be4b9eb06ac2513aef4b649c011dc4595387a4d63047fa4066ac85ec5cfbc9730264cb4709b4caf5a58d9c9cdff6b744c5ba0447
7
- data.tar.gz: fc8944ef60c284da3bf9489a8f4dec0afdb75d732f167cc7831f9a64aeb76da8c8f95cb8dd1f9916da8841796f7223c43ed13aeb8c4a796124470c163d44025d
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
- last_seq = self.class.maximum(:account_number).to_s[-6..-1].to_i
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
- self.account_number = "#{branch_code}#{product_scheme}#{voucher_type}#{seq}"
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
@@ -0,0 +1,6 @@
1
+ class NullableOccupationSourceOfFunds < ActiveRecord::Migration[8.0]
2
+ def change
3
+ change_column_null :bscf_core_user_profiles, :occupation, true
4
+ change_column_null :bscf_core_user_profiles, :source_of_funds, true
5
+ end
6
+ end
@@ -1,5 +1,5 @@
1
1
  module Bscf
2
2
  module Core
3
- VERSION = "0.4.97"
3
+ VERSION = "0.4.99"
4
4
  end
5
5
  end
@@ -0,0 +1,9 @@
1
+ FactoryBot.define do
2
+ factory :loan_repayment, class: 'Bscf::Core::LoanRepayment' do
3
+ association :loan, factory: :loan
4
+ association :repayment_transaction, factory: :virtual_account_transaction
5
+
6
+ amount { 1.5 }
7
+ payment_date { Date.today }
8
+ end
9
+ end
@@ -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.97
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: 1980-01-02 00:00:00.000000000 Z
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.9
443
+ rubygems_version: 3.6.2
422
444
  specification_version: 4
423
445
  summary: An Engine for Supply Chain Financing
424
446
  test_files: []