Skip to content

Instantly share code, notes, and snippets.

@joegaudet
Last active November 28, 2019 21:33
Show Gist options
  • Select an option

  • Save joegaudet/1b8e1ac9a069f500c10c3ee0e8887862 to your computer and use it in GitHub Desktop.

Select an option

Save joegaudet/1b8e1ac9a069f500c10c3ee0e8887862 to your computer and use it in GitHub Desktop.
module Accounting
module Interfaces
module Invoiceable
extend ActiveSupport::Concern
included do
# Quack like a duck
attribute :can_invoice?, :boolean, default: true
attribute :invoice, Accounting::Invoice, default: -> { raise NotImplementedError, "invoice is not defined on #{self}" }
attribute :currency, :string, default: -> { raise NotImplementedError, "currency is not defined on #{self}" }
attribute :identifier, :string, default: -> { raise NotImplementedError, "identifier is not defined on #{self}" }
attribute :ordered_items, :string, default: -> { raise NotImplementedError, "food_items is not defined on #{self}" }
attribute :fees, :string, default: -> { raise NotImplementedError, "fees is not defined on #{self}" }
attribute :discounts, :string, default: -> { raise NotImplementedError, "discounts is not defined on #{self}" }
attribute :payments, :string, default: -> { raise NotImplementedError, "payments is not defined on #{self}" }
end
end
end
end
class Accounting::LedgerItem < ApplicationRecord
include ActiveRecord::TypedCollectionProxy::Support
acts_as_ledger_item
belongs_to :recipient, polymorphic: true
belongs_to :sender, polymorphic: true
has_many :line_items
default_scope { includes(:line_items) }
collection_proxy :ordered_items, :line_items, Accounting::LineItems::OrderedItem
collection_proxy :discounts, :line_items, Accounting::LineItems::Discount
collection_proxy :fees, :line_items, Accounting::LineItems::Fee
collection_proxy :payments, :line_items, Accounting::LineItems::Payment
# # specific line items
attribute :status, :string, default: 'open'
attribute :issue_date, :date, default: Date.current
attribute :period_start, :date, default: Date.current
def reset!
line_items.delete_all
end
def discounts_total
discounts.sum(:total_amount)
end
def payments_total
discounts.sum(:total_amount)
end
def fees_total
fees.sum(:total_amount)
end
def food_total
food.sum(:total_amount)
end
def paid?
status == 'closed'
end
state_machine :status, initial: :open do
event(:close) { transition :open => :closed }
event(:cancel) { transition :open => :cancelled }
end
# The invoicing gem we use rather than using language features interrogates the table
# for subclasses meaning that as the table grows in size, any invoicing ops slow down
def self.known_subclasses(_table = table_name, _type_column = inheritance_column)
self.descendants
end
def save!
validate!
self.class.import [self], recursive: true
self
end
end
module Accounting
module Commands
class ProcessInvoice < ::Commands::Command
dependency :save, ::Commands::SaveRecord
# @param [Accouting::Invoiceable] invoiceable
def call(invoiceable, due_date: Time.now)
raise ArgumentError, "#{invoiceable} cannot be invoiced." unless (invoiceable.can_invoice? rescue false)
invoice = invoiceable.invoice || Accounting::Invoice.new(
# recipient GOM / Client
recipient: invoiceable,
currency: invoiceable.currency,
identifier: invoiceable.identifier,
due_date: due_date,
)
invoice.reset!
invoiceable.ordered_items do |order_item|
invoice.ordered_items.build(
quantity: order_item.quantity,
description: item.description,
net_amount: item.client_total_dollars,
# TODO
# tax_amount: item.client_total_dollars,
accounting_code: item.client_accounting_code
)
end
invoiceable.fees do |fee|
invoice.fees.build(
description: fee.description,
# TODO compute
net_amount: -fee.amount,
# TODO Figure this out
# tax_amount: item.client_total_dollars,
accounting_code: item.accounting_code
)
end
# TODO include promo discount
invoiceable.discounts do |discount|
invoice.discounts.build(
description: discount.description,
# TODO Compute
net_amount: -0,
# TODO Figure this out
# tax_amount: item.client_total_dollars,
accounting_code: item.accounting_code
)
end
invoiceable.payments do |payment|
invoice.payments.build(
description: payment.description,
# TODO Compute
net_amount: 0,
# TODO Figure this out
# tax_amount: item.client_total_dollars,
accounting_code: item.accounting_code
)
end
save.(invoice)
invoice
end
end
end
end
# This class allows you to define a subset of a AR Collection by class
# and add / iterate over it.
#
# Usage
#
# class Foo < ApplicationRecord
# include ActiveRecord::TypedCollectionProxy::Support
#
# has_many :bars
#
# collection_proxy :bazes, :bars, Baz
# end
#
# f = Foo.new
# b = f.bazes.build()
# f.bars.include? b
#
module ActiveRecord
class TypedCollectionProxy
delegate *Array.public_instance_methods, to: :array
def initialize(base, attribute, type)
@base = base
@attribute = attribute
@type = type
end
def build(attribute = {})
@base.send(@attribute) << @type.new(attribute)
end
def array
@base.send(@attribute).select { |li| li.type.safe_constantize == @type }
end
module Support
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def collection_proxy(name, attribute, type)
self.define_method(name) do
TypedCollectionProxy.new(self, attribute, type)
end
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment