Last active
November 28, 2019 21:33
-
-
Save joegaudet/1b8e1ac9a069f500c10c3ee0e8887862 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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