Billing


require "not_activerecord"
require "dependor/shorty"

#removed configuration options for clarity

create_table :school_billing_licenses, id: false do |t|
  t.string    :id
  t.integer   :school_id
  t.integer   :pupil_id
  t.datetime  :activated_at
  t.string    :native_language
  t.string    :learning_language
  t.string    :school_year_id
  t.datetime  :deactivated_at
end

create_table :school_billing_subscriptions, id: false do |t|
  t.string    :id
  t.integer   :school_id
  t.string    :native_language
  t.string    :learning_language
  t.string    :school_year_id
  t.integer   :bought_licenses
  t.integer   :used_licenses
  t.decimal   :price
end

create_table :school_billing_purchases, id: false do |t|
  t.string    :id
  t.integer   :school_id
  t.string    :native_language
  t.string    :learning_language
  t.string    :school_year_id
  t.integer   :bought_licenses
  t.decimal   :price
  t.datetime  :purchased_at
end

class Billing < ActiveRecord::Base
  class Subscription < ActiveRecord::Base
    include SchoolYearSerializer
    extend  NotActiveRecord

    does_not_belong_to :school

    def dictionary_name
      s_("School|lang_" + learning_language)
    end

    def overused?
      used_licenses > bought_licenses
    end

    def cancelable?
      bought_licenses.nonzero?
    end
  end

  class License < ActiveRecord::Base
    include SchoolYearSerializer
    extend  NotActiveRecord

    does_not_belong_to :school
    does_not_belong_to :pupil
  end

  class Purchase < ActiveRecord::Base
    include SchoolYearSerializer
    extend  NotActiveRecord

    does_not_belong_to :school
  end

  class SubscriptionPriceCalculator
    takes :school_year, :bought_licenses, :now

    def price
      raise ArgumentError if months_to_pay >= 13
      raise ArgumentError if months_to_pay <= 0

      price = price_per_license * bought_licenses * BigDecimal.new(months_to_pay) / BigDecimal.new(12)
      price.round(2, BigDecimal::ROUND_HALF_UP)
    end

    private

    def months_to_pay
      if now < school_year.starts_at
        12
      else
        (school_year.ends_at.year * 12 + school_year.ends_at.month) - (now.year * 12 + now.month) + 1
      end
    end

    def price_per_license
      BigDecimal.new("2.5")
    end
  end

  self.table_name = "schools"

  has_many :subscriptions, class_name: "::School::Billing::Subscription", foreign_key: "school_id", autosave: true
  has_many :licenses,      class_name: "::School::Billing::License",      foreign_key: "school_id", autosave: true
  has_many :purchases,     class_name: "::School::Billing::Purchase",     foreign_key: "school_id", autosave: true

  def used_licenses(pupil_ids, native_language, learning_languages, school_year, at_time)
    pupil_ids = Array.wrap(pupil_ids)
    Array.wrap(learning_languages).each do |learning_language|
      s = subscription_for(native_language, learning_language, school_year)
      s.used_licenses += pupil_ids.size
      pupil_ids.each do |pupil_id|
        licenses.build do |l|
          l.id                = SecureRandom.uuid
          l.pupil_id          = pupil_id
          l.activated_at      = at_time
          l.native_language   = native_language
          l.learning_language = learning_language
          l.school_year       = school_year
        end
      end
    end
  end

  def terminated_licenses(pupil_ids, native_language, learning_languages, school_year, at_time)
    pupil_ids = Array.wrap(pupil_ids)
    Array.wrap(learning_languages).each do |learning_language|
      s = subscription_for(native_language, learning_language, school_year)
      s.used_licenses -= pupil_ids.size
      pupil_ids.each do |pupil_id|
        license = licenses.find do |l|
          l.pupil_id == pupil_id &&
          l.native_language == native_language &&
          l.learning_language == learning_language &&
          l.school_year == school_year &&
          l.deactivated_at.nil?
        end
        license.deactivated_at = at_time
      end
    end
  end

  def subscriptions_for(native_language, learning_languages, school_years)
    Array.wrap(learning_languages).map do |ll|
      Array.wrap(school_years).map do |sy|
        subscriptions.find do |s|
          s.native_language == native_language &&
          s.learning_language == ll &&
          s.school_year == sy
        end || subscriptions.build do |s|
          s.id                = SecureRandom.uuid
          s.native_language   = native_language
          s.learning_language = ll
          s.school_year       = sy
          s.used_licenses     = 0
          s.bought_licenses   = 0
          s.price             = 0
        end
      end
    end.flatten
  end

  def subscription_for(native_language, learning_language, school_year)
    subscriptions_for(native_language, learning_language, school_year).first
  end

  PurchasingNotEnoughLicenses = Class.new(StandardError)

  def buy_subscription(native_language, learning_language, school_year, bought_licenses, at)
    s = subscription_for(native_language, learning_language, school_year)
    raise PurchasingNotEnoughLicenses if s.bought_licenses + bought_licenses < 40
    price = SubscriptionPriceCalculator.new(school_year, bought_licenses, at).price

    purchase = purchases.build do |p|
      p.id                = SecureRandom.uuid
      p.native_language   = native_language
      p.learning_language = learning_language
      p.school_year       = school_year
      p.bought_licenses   = bought_licenses
      p.price             = price
      p.purchased_at      = at
    end

    s.bought_licenses += purchase.bought_licenses
    s.price += purchase.price
    s
  end

  def cancel_subscription(native_language, learning_language, school_year, at)
    s = subscription_for(native_language, learning_language, school_year)
    purchases.build do |p|
      p.id                = SecureRandom.uuid
      p.native_language   = native_language
      p.learning_language = learning_language
      p.school_year       = school_year
      p.bought_licenses   = -s.bought_licenses
      p.price             = s.price * BigDecimal.new(-1)
      p.purchased_at      = at
    end

    s = subscription_for(native_language, learning_language, school_year)
    s.bought_licenses = 0
    s.price = 0
    s
  end

  # some domain methods removed
end
          

Subscribe to our newsletter to get more content like this