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