Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 8 additions & 83 deletions Steepfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,87 +6,12 @@ target :app do
library "date"
library "bigdecimal"

check "app/services/service_result.rb"
check "app/services/service_result_types.rb"
check "app/services/payment_ingestions.rb"
check "app/services/payment_ingestions/ingestion_result.rb"
check "app/services/payment_ingestions/parsers/base.rb"
check "app/services/payment_ingestions/parsers/chase_statement.rb"
check "app/services/payment_ingestions/parsers/venmo.rb"
check "app/services/payment_ingestions/parsers/zelle.rb"
check "app/queries/dashboards/property_summaries_query.rb"
check "app/queries/payment_ingestions/form_data_query.rb"
check "app/queries/payment_ingestions/index_query.rb"
check "app/queries/properties/active_years_query.rb"
check "app/queries/properties/financial_items_query.rb"
check "app/queries/properties/schedule_e_summary_query.rb"
check "app/queries/tenancies/balance_query.rb"
check "app/services/payment_ingestions/confirm_service.rb"
check "app/services/payment_ingestions/ingestion.rb"
check "app/services/payment_ingestions/tenant_resolver.rb"
check "app/services/payment_ingestions/update_service.rb"
check "app/services/payment_ingestions/upload_service.rb"
check "app/services/expenses/save_service.rb"
check "app/services/expenses/tenant_charge_service.rb"
check "app/services/properties/create_service.rb"
check "app/services/rent_terms/change_service.rb"
check "app/services/schedule_e_generator.rb"
check "app/services/tenancies/create_service.rb"
check "app/services/tenancies/update_service.rb"
check "app/services/tenancy_parties/create_service.rb"
check "app/services/tenancy_parties/destroy_service.rb"
check "app/services/tenancy_parties/update_service.rb"
check "app/services/tenant_payments/receipt_pdf_service.rb"
check "app/jobs/application_job.rb"
check "app/jobs/ingest_payment_document_job.rb"
check "app/models/application_record.rb"
check "app/models/current.rb"
check "app/models/expense.rb"
check "app/models/party_alias.rb"
check "app/models/party.rb"
check "app/models/payment_document.rb"
check "app/models/payment_ingestion.rb"
check "app/models/property.rb"
check "app/models/rent_term.rb"
check "app/models/rentable_unit.rb"
check "app/models/scheduled_rent.rb"
check "app/models/session.rb"
check "app/models/tenancy_party.rb"
check "app/models/tenancy.rb"
check "app/models/tenant_charge.rb"
check "app/models/tenant_payment.rb"
check "app/models/user.rb"
check "app/controllers/application_controller.rb"
check "app/controllers/dashboards_controller.rb"
check "app/controllers/expenses_controller.rb"
check "app/controllers/parties_controller.rb"
check "app/controllers/passwords_controller.rb"
check "app/controllers/payment_documents_controller.rb"
check "app/controllers/payment_ingestions_controller.rb"
check "app/controllers/properties_controller.rb"
check "app/controllers/rent_terms_controller.rb"
check "app/controllers/rentable_units_controller.rb"
check "app/controllers/scheduled_rents_controller.rb"
check "app/controllers/sessions_controller.rb"
check "app/controllers/tenancies_controller.rb"
check "app/controllers/tenancy_parties_controller.rb"
check "app/controllers/tenant_charges_controller.rb"
check "app/controllers/tenant_payments_controller.rb"
check "app/mailers/application_mailer.rb"
check "app/mailers/passwords_mailer.rb"
check "app/controllers/concerns/authentication.rb"
check "app/helpers/application_helper.rb"
check "app/helpers/dashboards_helper.rb"
check "app/helpers/expenses_helper.rb"
check "app/helpers/parties_helper.rb"
check "app/helpers/payment_ingestions_helper.rb"
check "app/helpers/properties_helper.rb"
check "app/helpers/rent_payments_helper.rb"
check "app/helpers/rent_terms_helper.rb"
check "app/helpers/rentable_units_helper.rb"
check "app/helpers/scheduled_rents_helper.rb"
check "app/helpers/tenancies_helper.rb"
check "app/helpers/tenancy_parties_helper.rb"
check "app/helpers/utility_payments_helper.rb"
check "app/channels/application_cable/connection.rb"
check "app/channels"
check "app/controllers"
check "app/helpers"
check "app/jobs"
check "app/mailers"
check "app/models"
check "app/queries"
check "app/services"
end
6 changes: 3 additions & 3 deletions app/controllers/tenancies_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ def participants_params
effective_until: p[:effective_until]
}
end
elsif params[:tenancy][:party_ids].present?
party_ids = params[:tenancy][:party_ids]
party_ids.compact_blank.map do |party_id|
elsif (pids = params[:tenancy][:party_ids]).present?
pids.to_a.filter_map do |party_id|
next if party_id.blank?
{ party_id: party_id, role: "tenant" }
end
else
Expand Down
36 changes: 36 additions & 0 deletions app/models/account.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
class Account < ApplicationRecord
belongs_to :user
has_many :postings, dependent: :restrict_with_error

ACCOUNT_TYPES = %w[
asset
liability
equity
income
expense
].freeze

enum :account_type, ACCOUNT_TYPES.index_by(&:itself), prefix: false, validate: true

validates :key, presence: true,
uniqueness: { scope: :user_id, case_sensitive: false },
format: { with: /\A[a-z0-9_]+\z/, message: "must contain only lowercase letters, numbers, and underscores" }
validates :name, presence: true
validates :account_type, presence: true
validate :identity_fields_immutable, on: :update

normalizes :key, with: ->(k) { k.strip.downcase }
normalizes :name, with: ->(n) { n.strip }

def accounting_user
user
end

private

def identity_fields_immutable
errors.add(:user_id, "cannot be changed") if user_id_changed?
errors.add(:key, "cannot be changed") if key_changed?
errors.add(:account_type, "cannot be changed") if account_type_changed?
end
end
4 changes: 4 additions & 0 deletions app/models/expense.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,8 @@ def reimburse_lease_id=(val)
def reimburse_amount
@reimburse_amount.presence || tenant_charge&.amount || amount
end

def accounting_user
property&.user
end
end
42 changes: 42 additions & 0 deletions app/models/journal_entry.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
class JournalEntry < ApplicationRecord
belongs_to :user
belongs_to :reversal_of, class_name: "JournalEntry", optional: true
has_one :reversal, class_name: "JournalEntry", foreign_key: :reversal_of_id, dependent: :restrict_with_error
has_many :postings, dependent: :restrict_with_error

validates :source_type, presence: true
validates :source_id, presence: true, numericality: { only_integer: true, greater_than: 0 }
validates :event_type, presence: true
validates :occurred_on, presence: true
validates :posted_at, presence: true
validates :source_id, uniqueness: {
scope: %i[user_id source_type event_type],
message: "has already been posted for this event"
}
validates :reversal_of_id, uniqueness: {
allow_nil: true,
message: "has already been reversed"
}

before_update :prevent_mutation
before_destroy :prevent_mutation

def reversed?
reversal.present?
end

def reversal?
reversal_of_id.present?
end

def accounting_user
user
end

private

def prevent_mutation
errors.add(:base, "Posted journal entries are immutable")
throw :abort
end
end
5 changes: 5 additions & 0 deletions app/models/party.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class Party < ApplicationRecord
has_many :party_aliases, dependent: :destroy
has_many :tenancy_parties, dependent: :restrict_with_error
has_many :tenancies, through: :tenancy_parties
has_many :accounting_postings, class_name: "Posting", dependent: :restrict_with_error
has_many :payment_ingestions, dependent: :nullify

PARTY_TYPES = %w[
Expand Down Expand Up @@ -34,4 +35,8 @@ def alias_candidate?(alias_name)
!party_aliases.where("LOWER(TRIM(alias_name)) = ?", clean_name).exists?
end
end

def accounting_user
user
end
end
4 changes: 4 additions & 0 deletions app/models/party_alias.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@ class PartyAlias < ApplicationRecord
validates :alias_name, uniqueness: { scope: :party_id, case_sensitive: false }

normalizes :alias_name, with: ->(name) { name.strip }

def accounting_user
party&.user
end
end
4 changes: 4 additions & 0 deletions app/models/payment_document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ class PaymentDocument < ApplicationRecord
success: "success",
failed: "failed"
}

def accounting_user
user
end
end
4 changes: 4 additions & 0 deletions app/models/payment_ingestion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ def attachment_image?
payment_document&.attachment_content_type&.start_with?("image/")
end

def accounting_user
user
end

private

def validate_parse_status
Expand Down
89 changes: 89 additions & 0 deletions app/models/posting.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
class Posting < ApplicationRecord
belongs_to :journal_entry
belongs_to :account
belongs_to :property, optional: true
belongs_to :rentable_unit, optional: true
belongs_to :tenancy, optional: true
belongs_to :party, optional: true

validates :amount_cents, presence: true, numericality: { only_integer: true, other_than: 0 }
validate :account_belongs_to_journal_entry_user
validate :dimensions_belong_to_journal_entry_user
validate :dimension_hierarchy_coherent

before_update :prevent_mutation
before_destroy :prevent_mutation

def debit?
amount_cents.positive?
end

def credit?
amount_cents.negative?
end

def debit_amount
amount_cents.positive? ? amount_cents : nil
end

def credit_amount
amount_cents.negative? ? -amount_cents : nil
end

def accounting_user
journal_entry&.user || account&.user
end

private

def prevent_mutation
errors.add(:base, "Posted journal postings are immutable")
throw :abort
end

def account_belongs_to_journal_entry_user
return unless account && journal_entry&.user_id

if account.user_id != journal_entry.user_id
errors.add(:account, "must belong to the journal entry user")
end
end

def dimensions_belong_to_journal_entry_user
return unless journal_entry

user_id = journal_entry.user_id

if (p = property) && p.user_id != user_id
errors.add(:property, "must belong to the journal entry user")
end

if (u = rentable_unit) && u.property.user_id != user_id
errors.add(:rentable_unit, "must belong to the journal entry user")
end

if (t = tenancy) && t.rentable_unit.property.user_id != user_id
errors.add(:tenancy, "must belong to the journal entry user")
end

if (prt = party) && prt.user_id != user_id
errors.add(:party, "must belong to the journal entry user")
end
end

def dimension_hierarchy_coherent
if (u = rentable_unit) && (p = property) && u.property_id != p.id
errors.add(:property, "does not match rentable unit property")
end

if (t = tenancy)
if (u = rentable_unit) && t.rentable_unit_id != u.id
errors.add(:rentable_unit, "does not match tenancy rentable unit")
end

if (p = property) && t.rentable_unit.property_id != p.id
errors.add(:property, "does not match tenancy property")
end
end
end
end
5 changes: 5 additions & 0 deletions app/models/property.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class Property < ApplicationRecord
has_many :scheduled_rents, through: :tenancies
has_many :tenant_payments, through: :tenancies
has_many :tenant_charges, through: :tenancies
has_many :accounting_postings, class_name: "Posting", dependent: :restrict_with_error

ASSET_TYPES = %w[
single_family
Expand Down Expand Up @@ -37,4 +38,8 @@ def schedule_e_summary(*args, year: nil)
target_year = year || args.first || Date.current.year
Properties::ScheduleESummaryQuery.new(property: self).call(year: target_year)
end

def accounting_user
user
end
end
12 changes: 10 additions & 2 deletions app/models/rent_term.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ def amount

def amount=(val)
if val.present? && val.to_s.strip.present?
self.amount_cents = (BigDecimal(val.to_s) * 100).round rescue nil
begin
self.amount_cents = (BigDecimal(val.to_s) * 100).round
rescue StandardError
self.amount_cents = 0
end
else
self.amount_cents = nil
self.amount_cents = 0
end
end

Expand All @@ -45,6 +49,10 @@ def due_date_for(year, month)
Date.new(year, month, [ due_day, max_days ].min)
end

def accounting_user
tenancy&.property&.user
end

private

def effective_until_after_effective_from
Expand Down
5 changes: 5 additions & 0 deletions app/models/rentable_unit.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class RentableUnit < ApplicationRecord
belongs_to :property
has_many :tenancies, dependent: :restrict_with_error
has_many :accounting_postings, class_name: "Posting", dependent: :restrict_with_error

validates :name, presence: true, uniqueness: { scope: :property_id, case_sensitive: false }
validates :square_footage, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
Expand All @@ -16,4 +17,8 @@ def display_name
def occupied?(as_of = Date.current)
tenancies.any? { |t| t.active?(as_of) }
end

def accounting_user
property&.user
end
end
Loading