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