Files
Dena/app/models/adjustment.rb
2026-08-13 19:50:53 +03:30

162 lines
5.2 KiB
Ruby

# encoding: UTF-8
# frozen_string_literal: true
class Adjustment < ApplicationRecord
# == Constants ============================================================
include AASM
include AASM::Locking
CATEGORIES = %w[asset_registration investment minting_token
balance_anomaly misc refund compensation
incentive bank_fees bank_interest minor].freeze
# == Attributes ===========================================================
enum category: CATEGORIES
enum state: { pending: 1, accepted: 2, rejected: 3 }
# == Relationships ========================================================
belongs_to :currency
belongs_to :creator, class_name: :Member, required: true
belongs_to :validator, class_name: :Member
# Define has_one relation with Operations::{Asset,Expense,Liability,Revenue}.
::Operations::Account::TYPES.each do |op_t|
has_one op_t.to_sym,
class_name: "::Operations::#{op_t.to_s.camelize}",
as: :reference
end
# == Validations ==========================================================
validates :validator, presence: { unless: :pending? }
validates :category, inclusion: { in: CATEGORIES }
validates :currency_id, inclusion: { in: ->(_) { Currency.codes } }
validate do
errors.add(:base, 'invalidates accounting equation') unless Operations.validate_accounting_equation(fetch_operations)
end
validate on: :create do
errors.add(:prebuild_operations, 'are invalid') unless prebuild_operations.map(&:valid?).all?(true)
end
# == Extensions ===========================================================
aasm column: :state, enum: true, whiny_transitions: false do
state :pending, initial: true
state :accepted
state :rejected
event :accept do
transitions from: :pending, to: :accepted, after: :assign_validator do
guard do
prebuild_operations.map(&:valid?).all?(true)
end
after do
prebuild_operations.map(&:save!)
Operations.update_legacy_balance(liability)
end
end
end
event :reject do
transitions from: :pending, to: :rejected, after: :assign_validator
end
end
# Custom ransackers.
ransacker :state, formatter: proc { |v| states[v] } do |parent|
parent.table[:state]
end
ransacker :category, formatter: proc { |v| categories[v] } do |parent|
parent.table[:category]
end
# == Scopes ===============================================================
# == Callbacks ============================================================
# == Instance Methods =====================================================
def assign_validator(validator:)
update!(validator: validator)
end
def fetch_operations
if pending? || rejected?
prebuild_operations
elsif accepted?
load_operations
end
end
def load_operations
operations = %i[asset liability revenue expense].map do |op_type|
"Operations::#{op_type.capitalize}".constantize.find_by(reference: self)
end
operations.compact
end
%i[asset liability revenue expense].each do |op_type|
define_method("fetch_#{op_type}") do
fetch_operations.find { |op| op.is_a?("Operations::#{op_type.capitalize}".constantize) }
end
end
def prebuild_operations
account_number_hash = Operations.split_account_number(account_number: receiving_account_number)
currency_id = account_number_hash[:currency_id]
code = account_number_hash[:code]
member = Member.find_by(uid: account_number_hash[:member_uid]) if account_number_hash.key?(:member_uid)
amount > 0 ? credit = amount : debit = -amount
klass = Operations.klass_for(code: code)
params = {
currency_id: currency_id.downcase,
code: asset_account_code,
debit: debit.to_d,
credit: credit.to_d,
reference: self
}
asset = Operations::Asset.new(params)
# For expense we need swap debit and credit values due to:
# asset - liabilities = revenue - expense
params.merge!(credit: debit.to_d, debit: credit.to_d) if klass == Operations::Expense
params.merge!(member_id: member.id) if member.present? && klass.column_names.include?('member_id')
receiving_operation = klass.new(params.merge(code: code))
[asset, receiving_operation]
end
end
# == Schema Information
# Schema version: 20190830082950
#
# Table name: adjustments
#
# id :bigint not null, primary key
# reason :string(255) not null
# description :text(65535) not null
# creator_id :bigint not null
# validator_id :bigint
# amount :decimal(32, 16) not null
# asset_account_code :integer unsigned, not null
# receiving_account_number :string(64) not null
# currency_id :string(255) not null
# category :integer not null
# state :integer not null
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_adjustments_on_currency_id (currency_id)
# index_adjustments_on_currency_id_and_state (currency_id,state)
#