Skip to content

Instantly share code, notes, and snippets.

@dachinat
Created December 3, 2021 17:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dachinat/2ee8e4ff0d43065cf41e3c15863995fd to your computer and use it in GitHub Desktop.
Save dachinat/2ee8e4ff0d43065cf41e3c15863995fd to your computer and use it in GitHub Desktop.
Invoice model from one of my projects
class Invoice < ApplicationRecord
# Constants
# Includes
# include AccountScope
include AASM
aasm column: "status", requires_lock: true do
state :pending, initial: true
state :final, :paid
event :finish, after: [:send_reminder, :auto_pay] do
transitions from: :pending, to: :final
end
event :pay do
transitions from: :final, to: :paid
end
end
# Serialization
# Associations
belongs_to :user
has_many :invoice_items
# Delegations
# Callbacks
after_create :set_invoice_number
after_update :notify_if_final
# Validations
validates :name, presence: true, allow_nil: true
validates :start_date, presence: true, date: true
validates :end_date, presence: true, date: true
validates :total, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 999999.9999 }
validates :status, length: { minimum: 1, maximum: 16 }, allow_blank: false
validates :invoice_number, presence: true, length: { minimum: 1, maximum: 32 }, allow_nil: true
validates :due_date, date: true, allow_nil: true
validates :discount_pct, numericality: { greater_than: 1, less_than: 100 }, allow_nil: true
# Named Scopes
scope :for_user, ->(user) { where(user: user) }
scope :for_month, ->(date_or_time) { where(start_date: date_or_time.to_date.beginning_of_month) }
scope :current, ->() { for_month(Time.zone.today) }
scope :pending_state, ->() { where(status: "pending") }
scope :past, ->() { where("created_at < ?", Date.current.beginning_of_month) }
# Class Methods
def self.user_current_invoice(user)
for_user(user).current.first || create_user_month_invoice(user, Time.zone.today)
end
def self.create_user_month_invoice(user, start_date)
create!(
start_date: start_date.to_date.beginning_of_month,
end_date: start_date.to_date.end_of_month,
user: user
)
end
def self.ensure_current_invoices
NovaVm.not_deleted.each do |nova_vm|
nova_vm.ensure_invoice
end
end
# Instance Methods
def bill(description = nil)
Billing::InvoicePayment.create!(
user: self.user,
amount: self.total,
description: description,
data: { invoice_id: self.id }
)
end
def calculated_total(force_calc = false)
if force_calc || total.zero?
discounted_sum
else
total
end
end
def discount_amount
items_sum - discounted_sum
end
def items_sum
invoice_items.map(&:calculated_total).sum
end
protected
def discounted_sum
items_sum * (1 - discount_pct.to_f / 100)
end
private
def set_invoice_number
invoice_number = Time.zone.now.strftime("%Y%m").to_s + "-" + sprintf("%05d", self.id).to_s
update(
name: "Invoice: #{invoice_number}",
invoice_number: invoice_number
)
end
def send_reminder
InvoiceMailer.to_user_final(self).deliver
Notification.create(notify_type: "invoice_final", actor: self.user, user: self.user, target: self)
end
def notify_if_final
send_reminder if self.status == "final" && self.status_was != "final"
end
def auto_pay
return unless Billing::AUTOPAY && Billing.balance_for_user(self.user) >= self.total
bill("Automated payment for invoice #{self.invoice_number} in amount of #{self.total}")
self.pay!
InvoiceMailer.to_user_auto_paid(self).deliver
Notification.create(notify_type: "invoice_auto_paid", actor: self.user, user: self.user, target: self)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment