dscf-banking 0.1.0

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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +8 -0
  5. data/app/controllers/dscf/banking/application_controller.rb +6 -0
  6. data/app/controllers/dscf/banking/interest_configurations_controller.rb +41 -0
  7. data/app/controllers/dscf/banking/interest_rate_tiers_controller.rb +30 -0
  8. data/app/controllers/dscf/banking/interest_rate_types_controller.rb +27 -0
  9. data/app/controllers/dscf/banking/product_approvals_controller.rb +36 -0
  10. data/app/controllers/dscf/banking/product_categories_controller.rb +27 -0
  11. data/app/controllers/dscf/banking/virtual_account_products_controller.rb +74 -0
  12. data/app/jobs/dscf/banking/application_job.rb +6 -0
  13. data/app/mailers/dscf/banking/application_mailer.rb +8 -0
  14. data/app/models/concerns/dscf/banking/auditable.rb +123 -0
  15. data/app/models/dscf/banking/application_record.rb +7 -0
  16. data/app/models/dscf/banking/interest_configuration.rb +85 -0
  17. data/app/models/dscf/banking/interest_rate_tier.rb +24 -0
  18. data/app/models/dscf/banking/interest_rate_type.rb +22 -0
  19. data/app/models/dscf/banking/product_approval.rb +23 -0
  20. data/app/models/dscf/banking/product_audit_log.rb +46 -0
  21. data/app/models/dscf/banking/product_category.rb +22 -0
  22. data/app/models/dscf/banking/virtual_account_product.rb +50 -0
  23. data/app/serializers/dscf/banking/interest_configuration_serializer.rb +11 -0
  24. data/app/serializers/dscf/banking/interest_rate_tier_serializer.rb +8 -0
  25. data/app/serializers/dscf/banking/interest_rate_type_serializer.rb +5 -0
  26. data/app/serializers/dscf/banking/product_approval_serializer.rb +11 -0
  27. data/app/serializers/dscf/banking/product_category_serializer.rb +5 -0
  28. data/app/serializers/dscf/banking/virtual_account_product_serializer.rb +10 -0
  29. data/config/routes.rb +12 -0
  30. data/db/migrate/20250830211002_create_dscf_banking_product_categories.rb +14 -0
  31. data/db/migrate/20250830211027_create_dscf_banking_interest_rate_types.rb +17 -0
  32. data/db/migrate/20250831063605_add_code_and_remove_fields_from_interest_rate_types.rb +12 -0
  33. data/db/migrate/20250831064917_create_dscf_banking_virtual_account_products.rb +26 -0
  34. data/db/migrate/20250831072627_create_dscf_banking_interest_configurations.rb +26 -0
  35. data/db/migrate/20250831080745_create_dscf_banking_interest_rate_tiers.rb +18 -0
  36. data/db/migrate/20250831082356_create_dscf_banking_product_approvals.rb +22 -0
  37. data/db/migrate/20250831083907_create_dscf_banking_product_audit_logs.rb +18 -0
  38. data/db/migrate/20250831084706_allow_null_virtual_account_product_id_in_product_audit_logs.rb +5 -0
  39. data/lib/dscf/banking/engine.rb +24 -0
  40. data/lib/dscf/banking/version.rb +5 -0
  41. data/lib/dscf/banking.rb +10 -0
  42. data/lib/tasks/dscf/banking_tasks.rake +4 -0
  43. data/spec/factories/dscf/banking/interest_configurations.rb +36 -0
  44. data/spec/factories/dscf/banking/interest_rate_tiers.rb +34 -0
  45. data/spec/factories/dscf/banking/interest_rate_types.rb +31 -0
  46. data/spec/factories/dscf/banking/product_approvals.rb +40 -0
  47. data/spec/factories/dscf/banking/product_audit_logs.rb +34 -0
  48. data/spec/factories/dscf/banking/product_categories.rb +26 -0
  49. data/spec/factories/dscf/banking/virtual_account_products.rb +46 -0
  50. metadata +510 -0
@@ -0,0 +1,22 @@
1
+ module Dscf::Banking
2
+ class ProductCategory < ApplicationRecord
3
+ self.table_name = "dscf_banking_product_categories"
4
+
5
+ validates :name, presence: true, uniqueness: true, length: { maximum: 100 }
6
+ validates :description, length: { maximum: 1000 }
7
+ validates :is_active, inclusion: { in: [ true, false ] }
8
+
9
+ scope :active, -> { where(is_active: true) }
10
+ scope :inactive, -> { where(is_active: false) }
11
+ scope :by_name, ->(name) { where("name ILIKE ?", "%#{name}%") }
12
+
13
+ before_validation :strip_whitespace
14
+
15
+ private
16
+
17
+ def strip_whitespace
18
+ self.name = name&.strip
19
+ self.description = description&.strip
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,50 @@
1
+ module Dscf::Banking
2
+ class VirtualAccountProduct < ApplicationRecord
3
+ include Auditable
4
+
5
+ self.table_name = "dscf_banking_virtual_account_products"
6
+
7
+ enum :status, {
8
+ draft: 0,
9
+ pending_approval: 1,
10
+ approved: 2,
11
+ rejected: 3
12
+ }
13
+
14
+ belongs_to :product_category, class_name: "Dscf::Banking::ProductCategory", optional: true
15
+ belongs_to :created_by, class_name: "Dscf::Core::User"
16
+ belongs_to :approved_by, class_name: "Dscf::Core::User", optional: true
17
+
18
+ has_many :product_approvals, class_name: "Dscf::Banking::ProductApproval", dependent: :destroy
19
+ has_many :product_audit_logs, class_name: "Dscf::Banking::ProductAuditLog", dependent: :destroy
20
+
21
+ validates :product_code, presence: true, uniqueness: { case_sensitive: true }, length: { maximum: 50 }
22
+ validates :product_name, presence: true, length: { maximum: 200 }
23
+ validates :description, length: { maximum: 1000 }
24
+ validates :document_reference, length: { maximum: 100 }
25
+ validates :rejection_reason, length: { maximum: 500 }
26
+ validates :is_active, inclusion: { in: [ true, false ] }
27
+ validates :status, presence: true
28
+ validates :approved_by, presence: true, if: -> { status == "approved" }
29
+
30
+ scope :active, -> { where(is_active: true) }
31
+ scope :inactive, -> { where(is_active: false) }
32
+ scope :by_status, ->(status) { where(status: status) }
33
+ scope :by_product_code, ->(code) { where(product_code: code) }
34
+ scope :by_product_name, ->(name) { where("product_name ILIKE ?", "%#{name}%") }
35
+ scope :by_category, ->(category_id) { where(product_category_id: category_id) }
36
+ scope :created_by_user, ->(user_id) { where(created_by_id: user_id) }
37
+
38
+ before_validation :strip_whitespace
39
+
40
+ private
41
+
42
+ def strip_whitespace
43
+ self.product_code = product_code&.strip
44
+ self.product_name = product_name&.strip
45
+ self.description = description&.strip
46
+ self.document_reference = document_reference&.strip
47
+ self.rejection_reason = rejection_reason&.strip
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,11 @@
1
+ module Dscf::Banking
2
+ class InterestConfigurationSerializer < ActiveModel::Serializer
3
+ attributes :id, :virtual_account_product_id, :interest_rate_type_id, :annual_interest_rate,
4
+ :income_tax_rate, :minimum_balance_for_interest, :calculation_method, :compounding_period,
5
+ :interest_basis, :accrual_frequency, :rounding_rule, :calculation_timing,
6
+ :promotional_start_date, :promotional_end_date, :is_active, :created_at, :updated_at
7
+
8
+ belongs_to :virtual_account_product, serializer: VirtualAccountProductSerializer
9
+ belongs_to :interest_rate_type, serializer: InterestRateTypeSerializer
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ module Dscf::Banking
2
+ class InterestRateTierSerializer < ActiveModel::Serializer
3
+ attributes :id, :tier_order, :balance_min, :balance_max, :interest_rate, :description,
4
+ :created_at, :updated_at
5
+
6
+ belongs_to :interest_config, serializer: InterestConfigurationSerializer
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module Dscf::Banking
2
+ class InterestRateTypeSerializer < ActiveModel::Serializer
3
+ attributes :id, :code, :name, :description, :created_at, :updated_at
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ module Dscf::Banking
2
+ class ProductApprovalSerializer < ActiveModel::Serializer
3
+ attributes :id, :virtual_account_product_id, :submitted_by_id, :submitted_at,
4
+ :reviewed_by_id, :reviewed_at, :status, :comments, :approval_level,
5
+ :created_at, :updated_at
6
+
7
+ belongs_to :virtual_account_product, serializer: VirtualAccountProductSerializer
8
+ belongs_to :submitted_by
9
+ belongs_to :reviewed_by
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ module Dscf::Banking
2
+ class ProductCategorySerializer < ActiveModel::Serializer
3
+ attributes :id, :name, :description, :is_active, :created_at, :updated_at
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ module Dscf::Banking
2
+ class VirtualAccountProductSerializer < ActiveModel::Serializer
3
+ attributes :id, :product_code, :product_name, :description, :document_reference,
4
+ :status, :rejection_reason, :is_active, :created_at, :updated_at
5
+
6
+ belongs_to :product_category, serializer: ProductCategorySerializer
7
+ belongs_to :created_by
8
+ belongs_to :approved_by
9
+ end
10
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,12 @@
1
+ Dscf::Banking::Engine.routes.draw do
2
+ resources :product_categories
3
+ resources :interest_rate_types
4
+ resources :virtual_account_products do
5
+ member do
6
+ get :audit_logs
7
+ end
8
+ end
9
+ resources :interest_configurations
10
+ resources :interest_rate_tiers
11
+ resources :product_approvals
12
+ end
@@ -0,0 +1,14 @@
1
+ class CreateDscfBankingProductCategories < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :dscf_banking_product_categories do |t|
4
+ t.string :name, null: false, limit: 100
5
+ t.text :description
6
+ t.boolean :is_active, null: false, default: true
7
+
8
+ t.timestamps
9
+ end
10
+
11
+ add_index :dscf_banking_product_categories, :name, unique: true
12
+ add_index :dscf_banking_product_categories, :is_active
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ class CreateDscfBankingInterestRateTypes < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :dscf_banking_interest_rate_types do |t|
4
+ t.string :name, null: false, limit: 100
5
+ t.text :description
6
+ t.string :calculation_method, null: false, limit: 50
7
+ t.string :compounding_period, limit: 50
8
+ t.boolean :is_active, null: false, default: true
9
+
10
+ t.timestamps
11
+ end
12
+
13
+ add_index :dscf_banking_interest_rate_types, :name, unique: true
14
+ add_index :dscf_banking_interest_rate_types, :calculation_method
15
+ add_index :dscf_banking_interest_rate_types, :is_active
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ class AddCodeAndRemoveFieldsFromInterestRateTypes < ActiveRecord::Migration[8.0]
2
+ def change
3
+ add_column :dscf_banking_interest_rate_types, :code, :string, limit: 20, null: false
4
+ add_index :dscf_banking_interest_rate_types, :code, unique: true, name: "idx_interest_rate_types_code"
5
+
6
+ remove_column :dscf_banking_interest_rate_types, :calculation_method, :string
7
+ remove_column :dscf_banking_interest_rate_types, :compounding_period, :string
8
+ remove_column :dscf_banking_interest_rate_types, :is_active, :boolean
9
+
10
+ change_column :dscf_banking_interest_rate_types, :name, :string, limit: 50, null: false
11
+ end
12
+ end
@@ -0,0 +1,26 @@
1
+ class CreateDscfBankingVirtualAccountProducts < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :dscf_banking_virtual_account_products do |t|
4
+ t.string :product_code, limit: 50, null: false
5
+ t.string :product_name, limit: 200, null: false
6
+ t.references :product_category, null: true, foreign_key: { to_table: :dscf_banking_product_categories }
7
+ t.text :description
8
+ t.string :document_reference, limit: 100
9
+ t.integer :status, default: 0
10
+ t.references :created_by, null: false, foreign_key: { to_table: :dscf_core_users }
11
+ t.references :approved_by, null: true, foreign_key: { to_table: :dscf_core_users }
12
+ t.timestamp :approved_at
13
+ t.text :rejection_reason
14
+ t.boolean :is_active, default: true
15
+
16
+ t.timestamps
17
+ end
18
+
19
+ add_index :dscf_banking_virtual_account_products, :product_code, unique: true, name: "idx_products_code"
20
+ add_index :dscf_banking_virtual_account_products, :status, name: "idx_products_status"
21
+ add_index :dscf_banking_virtual_account_products, :product_category_id, name: "idx_products_category"
22
+ add_index :dscf_banking_virtual_account_products, :created_by_id, name: "idx_products_created_by"
23
+ add_index :dscf_banking_virtual_account_products, :approved_by_id, name: "idx_products_approved_by"
24
+ add_index :dscf_banking_virtual_account_products, :is_active, name: "idx_products_active"
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ class CreateDscfBankingInterestConfigurations < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :dscf_banking_interest_configurations do |t|
4
+ t.references :virtual_account_product, null: false, foreign_key: { to_table: :dscf_banking_virtual_account_products }
5
+ t.references :interest_rate_type, null: false, foreign_key: { to_table: :dscf_banking_interest_rate_types }
6
+ t.decimal :annual_interest_rate, precision: 5, scale: 4
7
+ t.decimal :income_tax_rate, precision: 5, scale: 4, default: 0.05
8
+ t.decimal :minimum_balance_for_interest, precision: 18, scale: 2, default: 0
9
+ t.date :promotional_start_date
10
+ t.date :promotional_end_date
11
+ t.integer :calculation_method
12
+ t.integer :compounding_period
13
+ t.integer :interest_basis
14
+ t.integer :accrual_frequency
15
+ t.integer :rounding_rule
16
+ t.datetime :calculation_timing
17
+ t.boolean :is_active, default: true
18
+
19
+ t.timestamps
20
+ end
21
+
22
+ add_index :dscf_banking_interest_configurations, :virtual_account_product_id, name: 'idx_interest_config_product'
23
+ add_index :dscf_banking_interest_configurations, :interest_rate_type_id, name: 'idx_interest_config_type'
24
+ add_index :dscf_banking_interest_configurations, :is_active, name: 'idx_interest_config_active'
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ class CreateDscfBankingInterestRateTiers < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :dscf_banking_interest_rate_tiers do |t|
4
+ t.references :interest_config, null: false, foreign_key: { to_table: :dscf_banking_interest_configurations }
5
+ t.integer :tier_order, null: false
6
+ t.decimal :balance_min, precision: 18, scale: 2, null: false
7
+ t.decimal :balance_max, precision: 18, scale: 2
8
+ t.decimal :interest_rate, precision: 5, scale: 4, null: false
9
+ t.string :description, limit: 200
10
+
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :dscf_banking_interest_rate_tiers, [ :interest_config_id, :tier_order ], unique: true, name: "unique_tier_order"
15
+ add_index :dscf_banking_interest_rate_tiers, :interest_config_id, name: "idx_rate_tiers_config"
16
+ add_index :dscf_banking_interest_rate_tiers, [ :interest_config_id, :tier_order ], name: "idx_rate_tiers_order"
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ class CreateDscfBankingProductApprovals < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :dscf_banking_product_approvals do |t|
4
+ t.references :virtual_account_product, null: false, foreign_key: { to_table: :dscf_banking_virtual_account_products }
5
+ t.references :submitted_by, null: false, foreign_key: { to_table: :dscf_core_users }
6
+ t.timestamp :submitted_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
7
+ t.references :reviewed_by, null: true, foreign_key: { to_table: :dscf_core_users }
8
+ t.timestamp :reviewed_at
9
+ t.integer :status, null: false, default: 0
10
+ t.text :comments
11
+ t.integer :approval_level, default: 1
12
+
13
+ t.timestamps null: false, default: -> { "CURRENT_TIMESTAMP" }
14
+ end
15
+
16
+ add_index :dscf_banking_product_approvals, :virtual_account_product_id, name: "idx_approvals_product"
17
+ add_index :dscf_banking_product_approvals, :status, name: "idx_approvals_status"
18
+ add_index :dscf_banking_product_approvals, :submitted_by_id, name: "idx_approvals_submitted_by"
19
+ add_index :dscf_banking_product_approvals, :reviewed_by_id, name: "idx_approvals_reviewed_by"
20
+ add_index :dscf_banking_product_approvals, :submitted_at, name: "idx_approvals_submitted_at"
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ class CreateDscfBankingProductAuditLogs < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :dscf_banking_product_audit_logs do |t|
4
+ t.references :virtual_account_product, null: false, foreign_key: { to_table: :dscf_banking_virtual_account_products }
5
+ t.references :changed_by, null: false, foreign_key: { to_table: :dscf_core_users }
6
+ t.integer :change_type, null: false
7
+ t.jsonb :old_values
8
+ t.jsonb :new_values
9
+ t.text :change_description
10
+ t.timestamps null: false, default: -> { 'CURRENT_TIMESTAMP' }
11
+ end
12
+
13
+ add_index :dscf_banking_product_audit_logs, :virtual_account_product_id, name: 'idx_audit_product'
14
+ add_index :dscf_banking_product_audit_logs, :changed_by_id, name: 'idx_audit_user'
15
+ add_index :dscf_banking_product_audit_logs, :created_at, name: 'idx_audit_created_at'
16
+ add_index :dscf_banking_product_audit_logs, :change_type, name: 'idx_audit_change_type'
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ class AllowNullVirtualAccountProductIdInProductAuditLogs < ActiveRecord::Migration[8.0]
2
+ def change
3
+ change_column_null :dscf_banking_product_audit_logs, :virtual_account_product_id, true
4
+ end
5
+ end
@@ -0,0 +1,24 @@
1
+ module Dscf
2
+ module Banking
3
+ class Engine < ::Rails::Engine
4
+ isolate_namespace Dscf::Banking
5
+ config.generators.api_only = true
6
+
7
+ config.generators do |g|
8
+ g.test_framework :rspec, routing_specs: false
9
+ g.fixture_replacement :factory_bot
10
+ g.factory_bot dir: "spec/factories"
11
+ end
12
+
13
+ initializer "bscf_banking.factories", after: "factory_bot.set_factory_paths" do
14
+ FactoryBot.definition_file_paths << File.expand_path("../../../spec/factories", __dir__) if defined?(FactoryBot)
15
+ end
16
+
17
+ initializer :append_migrations do |app|
18
+ app.config.paths["db/migrate"].concat(config.paths["db/migrate"].expanded) unless app.root.to_s.match(root.to_s + File::SEPARATOR)
19
+ end
20
+
21
+ require "active_storage/engine"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ module Dscf
2
+ module Banking
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ require "dscf/banking/version"
2
+ require "dscf/banking/engine"
3
+ require "dscf/core"
4
+
5
+
6
+ module Dscf
7
+ module Banking
8
+ # Your code goes here...
9
+ end
10
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :dscf_banking do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,36 @@
1
+ FactoryBot.define do
2
+ factory :interest_configuration, class: "Dscf::Banking::InterestConfiguration" do
3
+ association :virtual_account_product, factory: :virtual_account_product
4
+ association :interest_rate_type, factory: :interest_rate_type
5
+
6
+ annual_interest_rate { Faker::Number.decimal(l_digits: 1, r_digits: 4).clamp(0.0001, 1.0) }
7
+ income_tax_rate { Faker::Number.decimal(l_digits: 1, r_digits: 4).clamp(0.0, 1.0) }
8
+ minimum_balance_for_interest { Faker::Number.decimal(l_digits: 4, r_digits: 2).clamp(0.0, 999999.99) }
9
+ calculation_method { [ 0, 1 ].sample }
10
+ compounding_period { [ 0, 1, 2, 3, 4 ].sample }
11
+ interest_basis { [ 0, 1, 2 ].sample }
12
+ accrual_frequency { [ 0, 1, 2, 3, 4 ].sample }
13
+ rounding_rule { [ 0, 1, 2 ].sample }
14
+ calculation_timing { Faker::Time.between(from: Time.current.beginning_of_day, to: Time.current.end_of_day) }
15
+ is_active { true }
16
+
17
+ trait :inactive do
18
+ is_active { false }
19
+ end
20
+
21
+ trait :promotional do
22
+ promotional_start_date { Faker::Date.between(from: 30.days.ago, to: Date.current) }
23
+ promotional_end_date { Faker::Date.between(from: Date.current, to: 30.days.from_now) }
24
+ end
25
+
26
+ trait :simple_interest do
27
+ calculation_method { 0 }
28
+ compounding_period { nil }
29
+ end
30
+
31
+ trait :compound_interest do
32
+ calculation_method { 1 }
33
+ compounding_period { [ 0, 1, 2, 3, 4 ].sample }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,34 @@
1
+ FactoryBot.define do
2
+ factory :interest_rate_tier, class: "Dscf::Banking::InterestRateTier" do
3
+ association :interest_config, factory: :interest_configuration
4
+ sequence(:tier_order) { |n| n }
5
+ balance_min { 1000.00 }
6
+ balance_max { 5000.00 }
7
+ interest_rate { Faker::Number.decimal(l_digits: 1, r_digits: 4).clamp(0.0001, 0.9999) }
8
+ description { Faker::Lorem.sentence(word_count: 5) }
9
+
10
+ trait :first_tier do
11
+ tier_order { 1 }
12
+ balance_min { 0 }
13
+ balance_max { 10000 }
14
+ interest_rate { 0.0250 }
15
+ description { "First tier for balances up to $10,000" }
16
+ end
17
+
18
+ trait :second_tier do
19
+ tier_order { 2 }
20
+ balance_min { 10000 }
21
+ balance_max { 50000 }
22
+ interest_rate { 0.0350 }
23
+ description { "Second tier for balances $10,000 to $50,000" }
24
+ end
25
+
26
+ trait :unlimited_tier do
27
+ tier_order { 3 }
28
+ balance_min { 50000 }
29
+ balance_max { nil }
30
+ interest_rate { 0.0450 }
31
+ description { "Unlimited tier for balances over $50,000" }
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ FactoryBot.define do
2
+ factory :interest_rate_type, class: "Dscf::Banking::InterestRateType" do
3
+ sequence(:code) { |n| "RATE#{n.to_s.rjust(3, '0')}" }
4
+ sequence(:name) { |n| "Interest Rate Type #{n}" }
5
+ description { Faker::Lorem.paragraph(sentence_count: 2) }
6
+
7
+ trait :fixed_rate do
8
+ code { "FIXED" }
9
+ name { "Fixed Rate" }
10
+ description { "Fixed annual interest rate" }
11
+ end
12
+
13
+ trait :variable_rate do
14
+ code { "VARIABLE" }
15
+ name { "Variable Rate" }
16
+ description { "Tiered rates based on balance" }
17
+ end
18
+
19
+ trait :promotional_rate do
20
+ code { "PROMOTIONAL" }
21
+ name { "Promotional Rate" }
22
+ description { "Special rate for limited period" }
23
+ end
24
+
25
+ trait :interest_free do
26
+ code { "INTEREST_FREE" }
27
+ name { "Interest Free" }
28
+ description { "No interest applicable" }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,40 @@
1
+ FactoryBot.define do
2
+ factory :product_approval, class: "Dscf::Banking::ProductApproval" do
3
+ association :virtual_account_product, factory: :virtual_account_product
4
+ association :submitted_by, factory: :user
5
+ submitted_at { Faker::Time.backward(days: 30) }
6
+ reviewed_by { nil }
7
+ reviewed_at { nil }
8
+ status { 0 }
9
+ comments { Faker::Lorem.paragraph(sentence_count: 2) }
10
+ approval_level { 1 }
11
+
12
+ trait :approved do
13
+ status { 1 }
14
+ association :reviewed_by, factory: :user
15
+ reviewed_at { Faker::Time.backward(days: 7) }
16
+ comments { "Approval granted after thorough review" }
17
+ end
18
+
19
+ trait :rejected do
20
+ status { 2 }
21
+ association :reviewed_by, factory: :user
22
+ reviewed_at { Faker::Time.backward(days: 7) }
23
+ comments { "Rejected due to insufficient documentation" }
24
+ end
25
+
26
+ trait :under_review do
27
+ status { 3 }
28
+ association :reviewed_by, factory: :user
29
+ comments { "Currently under review by compliance team" }
30
+ end
31
+
32
+ trait :level_2 do
33
+ approval_level { 2 }
34
+ end
35
+
36
+ trait :level_3 do
37
+ approval_level { 3 }
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,34 @@
1
+ FactoryBot.define do
2
+ factory :product_audit_log, class: 'Dscf::Banking::ProductAuditLog' do
3
+ association :virtual_account_product, factory: :virtual_account_product
4
+ association :changed_by, factory: :user
5
+ change_type { :updated }
6
+ old_values { { name: Faker::Commerce.product_name, is_active: false } }
7
+ new_values { { name: Faker::Commerce.product_name, is_active: true } }
8
+ change_description { Faker::Lorem.sentence }
9
+
10
+ trait :created do
11
+ change_type { :created }
12
+ old_values { nil }
13
+ new_values { { name: Faker::Commerce.product_name, is_active: true } }
14
+ end
15
+
16
+ trait :deleted do
17
+ change_type { :deleted }
18
+ old_values { { name: Faker::Commerce.product_name, is_active: true } }
19
+ new_values { nil }
20
+ end
21
+
22
+ trait :approved do
23
+ change_type { :approved }
24
+ old_values { { status: 'pending_approval' } }
25
+ new_values { { status: 'approved' } }
26
+ end
27
+
28
+ trait :rejected do
29
+ change_type { :rejected }
30
+ old_values { { status: 'pending_approval' } }
31
+ new_values { { status: 'rejected' } }
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,26 @@
1
+ FactoryBot.define do
2
+ factory :product_category, class: "Dscf::Banking::ProductCategory" do
3
+ sequence(:name) { |n| "#{Faker::Bank.name} Category #{n}" }
4
+ description { Faker::Lorem.paragraph(sentence_count: 2) }
5
+ is_active { true }
6
+
7
+ trait :inactive do
8
+ is_active { false }
9
+ end
10
+
11
+ trait :savings do
12
+ name { "Savings Account" }
13
+ description { "Traditional savings account with competitive interest rates" }
14
+ end
15
+
16
+ trait :checking do
17
+ name { "Checking Account" }
18
+ description { "Everyday banking account for transactions and payments" }
19
+ end
20
+
21
+ trait :loan do
22
+ name { "Personal Loan" }
23
+ description { "Flexible personal loans for various financial needs" }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,46 @@
1
+ FactoryBot.define do
2
+ factory :virtual_account_product, class: "Dscf::Banking::VirtualAccountProduct" do
3
+ sequence(:product_code) { |n| "VAP#{n.to_s.rjust(3, '0')}" }
4
+ product_name { Faker::Bank.name + " Virtual Account" }
5
+ association :product_category, factory: :product_category
6
+ description { Faker::Lorem.paragraph(sentence_count: 3) }
7
+ document_reference { "DOC-#{Faker::Alphanumeric.alphanumeric(number: 8).upcase}" }
8
+ status { :draft }
9
+ association :created_by, factory: :user
10
+ approved_by { nil }
11
+ approved_at { nil }
12
+ rejection_reason { nil }
13
+ is_active { true }
14
+
15
+ trait :pending_approval do
16
+ status { :pending_approval }
17
+ end
18
+
19
+ trait :approved do
20
+ status { :approved }
21
+ association :approved_by, factory: :user
22
+ approved_at { Faker::Time.backward(days: 7) }
23
+ end
24
+
25
+ trait :rejected do
26
+ status { :rejected }
27
+ association :approved_by, factory: :user
28
+ approved_at { Faker::Time.backward(days: 7) }
29
+ rejection_reason { Faker::Lorem.sentence(word_count: 10) }
30
+ end
31
+
32
+ trait :inactive do
33
+ is_active { false }
34
+ end
35
+
36
+ trait :savings_product do
37
+ product_name { "Premium Savings Virtual Account" }
38
+ description { "High-yield savings account with virtual account features" }
39
+ end
40
+
41
+ trait :business_product do
42
+ product_name { "Business Virtual Account" }
43
+ description { "Comprehensive virtual account solution for businesses" }
44
+ end
45
+ end
46
+ end