scottohara/loot

View on GitHub
app/models/transaction.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
# Copyright (c) 2016 Scott O'Hara, oharagroup.net
# frozen_string_literal: true

# Transaction
class Transaction < ApplicationRecord
    validates :transaction_type, presence: true, inclusion: {in: %w[Basic Split Transfer Payslip LoanRepayment Sub Subtransfer SecurityTransfer SecurityHolding SecurityInvestment Dividend]}
    has_one :flag, class_name: 'TransactionFlag', dependent: :destroy, autosave: true, inverse_of: :trx

    include ::Categorisable

    class << self
        include ::Transactable

        def class_for(type)
            "#{type}Transaction".constantize
        end

        def types_for(account_type)
            (account_type.eql?('investment') && %w[SecurityTransfer SecurityHolding SecurityInvestment Dividend]) || %w[Basic Split Transfer Payslip LoanRepayment]
        end

        # Transactable concern expects a transactions association, so just return self
        def transactions
            self
        end

        def for_ledger(opts)
            joins(
                [
                    'LEFT OUTER JOIN transaction_accounts ON transaction_accounts.transaction_id = transactions.id',
                    'LEFT OUTER JOIN transaction_splits ON transaction_splits.transaction_id = transactions.id',
                    'LEFT OUTER JOIN transaction_headers ON transaction_headers.transaction_id = transactions.id OR transaction_headers.transaction_id = transaction_splits.parent_id',
                    'LEFT OUTER JOIN transaction_categories ON transaction_categories.transaction_id = transactions.id'
                ]
            )
                .where('transactions.transaction_type != \'Subtransfer\'')
                .for_query opts
        end

        def for_closing_balance(opts)
            joins(
                [
                    'JOIN transaction_accounts ON transaction_accounts.transaction_id = transactions.id',
                    'JOIN transaction_headers ON transaction_headers.transaction_id = transactions.id'
                ]
            )
                .for_query opts
        end

        def for_basic_closing_balance(opts)
            joins(
                [
                    'JOIN transaction_categories ON transaction_categories.transaction_id = transactions.id',
                    'LEFT OUTER JOIN transaction_splits ON transaction_splits.transaction_id = transactions.id',
                    'JOIN transaction_headers ON transaction_headers.transaction_id = transactions.id OR transaction_headers.transaction_id = transaction_splits.parent_id'
                ]
            )
                .for_query opts
        end

        def for_query(opts)
            term = opts[:query].downcase

            case term
            when 'is:flagged' then joins 'JOIN transaction_flags as is_flagged ON is_flagged.transaction_id = transactions.id'
            else where 'LOWER(transactions.memo) LIKE ?', "%#{term}%"
            end
        end

        def opening_balance
            0
        end

        def account_type
            nil
        end

        def create_from_json(json)
            # id included for the case where we destroy & recreate on transaction type change
            s = new id: json[:id], memo: json['memo']
            s.build_flag flag_type: json['flag_type'], memo: json['flag'] unless json['flag_type'].nil? && json['flag'].nil?
            s
        end
    end

    def as_subclass
        becomes self.class.class_for transaction_type
    end

    def update_from_json(json)
        self.memo = json['memo']
        if json['flag_type'].nil? && json['flag'].nil?
            unless flag.nil?
                flag.destroy!
                self.flag = nil
            end
        else
            flag.nil? ? build_flag(flag_type: json['flag_type'], memo: json['flag']) : flag.assign_attributes(flag_type: json['flag_type'], memo: json['flag'])
        end
        self
    end

    def as_json(_options = {})
        {
            id:,
            transaction_type:,
            memo:,
            flag_type: (flag.present? && flag.flag_type) || nil,
            flag: (flag.present? && flag.memo) || nil
        }
    end
end