effective_learndash 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/admin/course_fee_histories_controller.rb +23 -0
- data/app/datatables/admin/course_fee_histories_datatable.rb +26 -0
- data/app/datatables/admin/effective_learndash_courses_datatable.rb +1 -2
- data/app/models/concerns/effective_learndash_course_registration.rb +0 -3
- data/app/models/effective/course_fee_history.rb +54 -0
- data/app/models/effective/learndash_course.rb +61 -7
- data/app/views/admin/course_fee_histories/_form.html.haml +25 -0
- data/app/views/admin/learndash_courses/_form.html.haml +10 -0
- data/app/views/admin/learndash_courses/_form_learndash_course.html.haml +4 -8
- data/config/routes.rb +4 -0
- data/db/migrate/101_create_effective_learndash.rb +18 -2
- data/lib/effective_learndash/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1f0eb60514e8eeffec5acb15be3ec18871d32deb63a220e4423798be3eb137d0
|
|
4
|
+
data.tar.gz: 99f7ddc61aeae6e835383237d64714f2de0865f6fe219f6437fa845ac43aa534
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0fe3018d26e29a3a2dbc1fd8c7863e120396d78d953562b7aa877dbbfc72aa59cd43dbb427b775a813789c89521849145d28c6ff97aec6cefbd8dfa3c997257b
|
|
7
|
+
data.tar.gz: 9c50f0c9b581b3e72ed59aad4d7d03d5dca525223369f1e33f6cbac867f775981b412b46a901aaa199d12907da660cf72159610d6875302a20f26c6e90322dd8
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Admin
|
|
2
|
+
class CourseFeeHistoriesController < ApplicationController
|
|
3
|
+
before_action(:authenticate_user!) if defined?(Devise)
|
|
4
|
+
before_action { EffectiveResources.authorize!(self, :admin, :effective_learndash) }
|
|
5
|
+
|
|
6
|
+
include Effective::CrudController
|
|
7
|
+
|
|
8
|
+
# Inline datatable create/update/destroy do an in-place row swap and never re-render the
|
|
9
|
+
# price-timeline warnings shown on the course page. When triggered from an inline datatable,
|
|
10
|
+
# redirect back so the whole page reloads and the warnings recompute.
|
|
11
|
+
on(:save, redirect: -> { :back if params[:_datatable_id].present? })
|
|
12
|
+
on(:destroy, redirect: -> { :back if params[:_datatable_id].present? })
|
|
13
|
+
|
|
14
|
+
# Duplicating closes the current prices at year-end and opens next year's copy.
|
|
15
|
+
on(:duplicate, redirect: -> { :back },
|
|
16
|
+
success: -> { "Copied #{resource.learndash_course}'s prices forward — set next year's prices on the new history." })
|
|
17
|
+
|
|
18
|
+
def permitted_params
|
|
19
|
+
params.require(:effective_course_fee_history).permit!
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Admin
|
|
2
|
+
class CourseFeeHistoriesDatatable < Effective::Datatable
|
|
3
|
+
datatable do
|
|
4
|
+
order :start_on, :desc
|
|
5
|
+
|
|
6
|
+
col :id, visible: false
|
|
7
|
+
col :created_at, visible: false
|
|
8
|
+
col :updated_at, visible: false
|
|
9
|
+
|
|
10
|
+
col :learndash_course, search: Effective::LearndashCourse.sorted
|
|
11
|
+
|
|
12
|
+
col :start_on
|
|
13
|
+
col :end_on, label: 'End on<br>(blank = current)'
|
|
14
|
+
|
|
15
|
+
col :regular_fee, as: :price
|
|
16
|
+
col :member_fee, as: :price
|
|
17
|
+
|
|
18
|
+
actions_col
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
collection do
|
|
22
|
+
Effective::CourseFeeHistory.deep.all
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -16,8 +16,7 @@ module Admin
|
|
|
16
16
|
col :learndash_users, visible: false
|
|
17
17
|
|
|
18
18
|
col :can_register
|
|
19
|
-
|
|
20
|
-
col :member_price, as: :price
|
|
19
|
+
# Prices now live on date-ranged CourseFeeHistory records — see Admin::CourseFeeHistoriesDatatable.
|
|
21
20
|
|
|
22
21
|
actions_col
|
|
23
22
|
end
|
|
@@ -49,9 +49,6 @@ module EffectiveLearndashCourseRegistration
|
|
|
49
49
|
has_many :course_registrants, -> { order(:id) }, class_name: 'Effective::CourseRegistrant', inverse_of: :course_registration, dependent: :destroy
|
|
50
50
|
accepts_nested_attributes_for :course_registrants, reject_if: :all_blank, allow_destroy: true
|
|
51
51
|
|
|
52
|
-
has_many :orders, -> { order(:id) }, as: :parent, class_name: 'Effective::Order', dependent: :nullify
|
|
53
|
-
accepts_nested_attributes_for :orders
|
|
54
|
-
|
|
55
52
|
effective_resource do
|
|
56
53
|
# Acts as Statused
|
|
57
54
|
status :string, permitted: false
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Effective
|
|
2
|
+
# A dated set of prices for a learndash course.
|
|
3
|
+
#
|
|
4
|
+
# Prices used to live in flat columns (regular_price, member_price) directly on
|
|
5
|
+
# learndash_courses. They now live here so a price change can be scheduled ahead of
|
|
6
|
+
# time: each CourseFeeHistory covers a date range (start_on..end_on), and the price
|
|
7
|
+
# for a given date is read from whichever history covers it.
|
|
8
|
+
#
|
|
9
|
+
# There should always be exactly one open-ended (end_on: nil) history per course — the
|
|
10
|
+
# currently active prices. Read them via LearndashCourse#prices.
|
|
11
|
+
class CourseFeeHistory < ActiveRecord::Base
|
|
12
|
+
belongs_to :learndash_course, class_name: 'Effective::LearndashCourse'
|
|
13
|
+
|
|
14
|
+
log_changes(to: :learndash_course) if respond_to?(:log_changes)
|
|
15
|
+
|
|
16
|
+
effective_resource do
|
|
17
|
+
start_on :date
|
|
18
|
+
end_on :date
|
|
19
|
+
|
|
20
|
+
regular_fee :integer # Price to applicants or new users
|
|
21
|
+
member_fee :integer # Price to existing members
|
|
22
|
+
|
|
23
|
+
timestamps
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
scope :deep, -> { includes(:learndash_course) }
|
|
27
|
+
scope :sorted, -> { order(start_on: :desc) }
|
|
28
|
+
|
|
29
|
+
validates :start_on, presence: true
|
|
30
|
+
validates :end_on, comparison: { greater_than: :start_on }, allow_nil: true
|
|
31
|
+
|
|
32
|
+
# Price presence used to be validated on the course; it moved here with the prices.
|
|
33
|
+
with_options(if: -> { learndash_course&.can_register? }) do
|
|
34
|
+
validates :regular_fee, presence: true
|
|
35
|
+
validates :member_fee, presence: true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_s
|
|
39
|
+
"#{learndash_course} Fees #{start_on&.year}#{" - #{end_on.year}" if end_on.present?}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Schedule next year's prices: close the current (open-ended) prices at the end of this
|
|
43
|
+
# year and open a copy for next year, ready to have its new prices set.
|
|
44
|
+
def duplicate!
|
|
45
|
+
transaction do
|
|
46
|
+
copy = dup
|
|
47
|
+
copy.assign_attributes(start_on: (Time.zone.now + 1.year).beginning_of_year, end_on: nil)
|
|
48
|
+
copy.save!
|
|
49
|
+
|
|
50
|
+
update!(end_on: Time.zone.now.end_of_year)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -3,6 +3,10 @@ module Effective
|
|
|
3
3
|
has_many :learndash_enrollments
|
|
4
4
|
has_many :learndash_users, through: :learndash_enrollments
|
|
5
5
|
|
|
6
|
+
# Dated prices. The open-ended (end_on: nil) history holds the current prices. Read via #prices.
|
|
7
|
+
has_many :course_fee_histories, -> { order(start_on: :desc) },
|
|
8
|
+
class_name: 'Effective::CourseFeeHistory', dependent: :destroy
|
|
9
|
+
|
|
6
10
|
log_changes if respond_to?(:log_changes)
|
|
7
11
|
|
|
8
12
|
# rich_text_body - Used by the select step
|
|
@@ -31,9 +35,10 @@ module Effective
|
|
|
31
35
|
# For course purchases
|
|
32
36
|
can_register :boolean
|
|
33
37
|
|
|
34
|
-
# Pricing
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
# Pricing now lives on dated course_fee_histories, read via #prices / #regular_price /
|
|
39
|
+
# #member_price. These columns still exist in the database (a later migration drops them).
|
|
40
|
+
# regular_price :integer
|
|
41
|
+
# member_price :integer
|
|
37
42
|
|
|
38
43
|
qb_item_name :string
|
|
39
44
|
tax_exempt :boolean
|
|
@@ -80,10 +85,7 @@ module Effective
|
|
|
80
85
|
validates :status, presence: true
|
|
81
86
|
validates :title, presence: true
|
|
82
87
|
|
|
83
|
-
|
|
84
|
-
validates :regular_price, presence: true
|
|
85
|
-
validates :member_price, presence: true
|
|
86
|
-
end
|
|
88
|
+
# Price presence now lives on CourseFeeHistory (validated when can_register?).
|
|
87
89
|
|
|
88
90
|
# Syncs all courses
|
|
89
91
|
def self.refresh!
|
|
@@ -110,5 +112,57 @@ module Effective
|
|
|
110
112
|
false
|
|
111
113
|
end
|
|
112
114
|
|
|
115
|
+
# The CourseFeeHistory (price list) in effect on the given date — today by default.
|
|
116
|
+
# Prices used to live in flat columns here; read them off this record now:
|
|
117
|
+
#
|
|
118
|
+
# course.prices.regular_fee # price right now
|
|
119
|
+
# course.prices(date: date).member_fee # price in effect on date
|
|
120
|
+
#
|
|
121
|
+
# Raises if no history covers the date so misconfiguration surfaces loudly.
|
|
122
|
+
def prices(date: nil)
|
|
123
|
+
date = (date || Time.zone.now).to_date
|
|
124
|
+
|
|
125
|
+
# course_fee_histories is ordered start_on desc — pick the newest window covering the
|
|
126
|
+
# date. end_on nil makes an endless range (start_on..), i.e. the current prices.
|
|
127
|
+
course_fee_histories.find { |history| (history.start_on..history.end_on).cover?(date) } ||
|
|
128
|
+
raise("No course_fee_history prices available for #{self} on #{date.strftime('%F')}, add a course fee history covering that date")
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Backwards-compatible readers so existing call sites keep working after prices moved to
|
|
132
|
+
# dated histories. These override the retired flat columns.
|
|
133
|
+
def regular_price(date: nil)
|
|
134
|
+
prices(date: date).regular_fee
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def member_price(date: nil)
|
|
138
|
+
prices(date: date).member_fee
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Non-blocking warnings about the price timeline, shown on the admin edit screen. Prices
|
|
142
|
+
# should form one continuous timeline with a single open-ended (current) history.
|
|
143
|
+
def course_fee_histories_warnings
|
|
144
|
+
histories = course_fee_histories.sort_by(&:start_on)
|
|
145
|
+
return ['No prices have been set — add a course fee history.'] if histories.empty?
|
|
146
|
+
|
|
147
|
+
warnings = []
|
|
148
|
+
|
|
149
|
+
current = histories.select { |history| history.end_on.blank? }
|
|
150
|
+
warnings << 'There are no current prices — the most recent history should have no end date.' if current.empty?
|
|
151
|
+
warnings << 'There is more than one current history (more than one with no end date).' if current.size > 1
|
|
152
|
+
|
|
153
|
+
# Each history should pick up the day after the previous one ends — no gaps, no overlaps.
|
|
154
|
+
histories.each_cons(2) do |earlier, later|
|
|
155
|
+
next if earlier.end_on.blank? # an open-ended history in the middle is flagged above
|
|
156
|
+
|
|
157
|
+
if later.start_on > earlier.end_on + 1.day
|
|
158
|
+
warnings << "Gap in prices between #{earlier.end_on} and #{later.start_on}."
|
|
159
|
+
elsif later.start_on <= earlier.end_on
|
|
160
|
+
warnings << "Overlapping prices around #{later.start_on}."
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
warnings
|
|
165
|
+
end
|
|
166
|
+
|
|
113
167
|
end
|
|
114
168
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
= effective_form_with(model: [:admin, course_fee_history], engine: true) do |f|
|
|
2
|
+
- course = course_fee_history.learndash_course
|
|
3
|
+
|
|
4
|
+
- if course.present?
|
|
5
|
+
= f.hidden_field :learndash_course_id
|
|
6
|
+
= f.static_field :learndash_course, label: 'Course' do
|
|
7
|
+
= course.to_s
|
|
8
|
+
- else
|
|
9
|
+
= f.select :learndash_course_id, Effective::LearndashCourse.sorted, label: 'Course'
|
|
10
|
+
|
|
11
|
+
%p These prices take effect on the start date, and apply until the next history begins. Leave the end date blank for the current, ongoing prices.
|
|
12
|
+
|
|
13
|
+
.row
|
|
14
|
+
.col-lg-4= f.date_field :start_on
|
|
15
|
+
-# date_linked: false stops effective_bootstrap auto-filling end_on from start_on — blank means ongoing.
|
|
16
|
+
.col-lg-4= f.date_field :end_on, date_linked: false, hint: 'leave blank for the current, ongoing prices'
|
|
17
|
+
|
|
18
|
+
%h2 Pricing
|
|
19
|
+
%p Charge the following amounts during course registration. Enter $0 for free.
|
|
20
|
+
|
|
21
|
+
.row
|
|
22
|
+
.col-lg-4= f.price_field :regular_fee, label: 'Price to applicants or new users'
|
|
23
|
+
.col-lg-4= f.price_field :member_fee, label: 'Price to existing members'
|
|
24
|
+
|
|
25
|
+
= effective_submit(f)
|
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
= render 'admin/learndash_courses/form_learndash_course', learndash_course: learndash_course
|
|
4
4
|
|
|
5
5
|
- if learndash_course.persisted?
|
|
6
|
+
= tab 'Pricing' do
|
|
7
|
+
%h2 Pricing
|
|
8
|
+
%p Prices are kept in date-ranged histories so they can be scheduled to change over time. The history with no end date holds the current, ongoing prices.
|
|
9
|
+
|
|
10
|
+
- learndash_course.course_fee_histories_warnings.each do |warning|
|
|
11
|
+
.alert.alert-warning= warning
|
|
12
|
+
|
|
13
|
+
- datatable = Admin::CourseFeeHistoriesDatatable.new(learndash_course: learndash_course)
|
|
14
|
+
= render_inline_datatable(datatable)
|
|
15
|
+
|
|
6
16
|
= tab 'Enrollments' do
|
|
7
17
|
%h2 Enrollments
|
|
8
18
|
%p Click the New button from the below table to enroll a user into this course.
|
|
@@ -15,14 +15,10 @@
|
|
|
15
15
|
|
|
16
16
|
= f.show_if :can_register, true do
|
|
17
17
|
= card('Pricing Information') do
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
.col-lg-3= f.price_field :regular_price, label: 'Price to applicants or new users'
|
|
23
|
-
|
|
24
|
-
.row
|
|
25
|
-
.col-lg-3= f.price_field :member_price, label: 'Price to existing members'
|
|
18
|
+
- if f.object.persisted?
|
|
19
|
+
%p.text-muted Prices are set on dated histories — see the Pricing tab.
|
|
20
|
+
- else
|
|
21
|
+
%p.text-muted Save this course, then set its prices on the Pricing tab.
|
|
26
22
|
|
|
27
23
|
= f.check_box :tax_exempt
|
|
28
24
|
|
data/config/routes.rb
CHANGED
|
@@ -31,6 +31,10 @@ EffectiveLearndash::Engine.routes.draw do
|
|
|
31
31
|
get :refresh, on: :collection
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
resources :course_fee_histories, except: [:show] do
|
|
35
|
+
post :duplicate, on: :member
|
|
36
|
+
end
|
|
37
|
+
|
|
34
38
|
resources :course_registrants, only: [:index]
|
|
35
39
|
resources :course_registrations, only: [:index, :show]
|
|
36
40
|
end
|
|
@@ -31,8 +31,7 @@ class CreateEffectiveLearndash < ActiveRecord::Migration[6.0]
|
|
|
31
31
|
# Course Purchases
|
|
32
32
|
t.boolean :can_register, default: false
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
t.integer :member_price
|
|
34
|
+
# Prices live on dated course_fee_histories (see below), not flat columns.
|
|
36
35
|
|
|
37
36
|
t.string :qb_item_name
|
|
38
37
|
t.boolean :tax_exempt, default: false
|
|
@@ -40,6 +39,23 @@ class CreateEffectiveLearndash < ActiveRecord::Migration[6.0]
|
|
|
40
39
|
t.timestamps
|
|
41
40
|
end
|
|
42
41
|
|
|
42
|
+
# Dated prices for a learndash course. The open-ended (end_on: nil) row holds the
|
|
43
|
+
# current, ongoing prices; earlier rows are scheduled/expired price lists.
|
|
44
|
+
create_table :course_fee_histories do |t|
|
|
45
|
+
t.integer :learndash_course_id
|
|
46
|
+
|
|
47
|
+
t.date :start_on
|
|
48
|
+
t.date :end_on
|
|
49
|
+
|
|
50
|
+
t.integer :regular_fee
|
|
51
|
+
t.integer :member_fee
|
|
52
|
+
|
|
53
|
+
t.datetime :updated_at
|
|
54
|
+
t.datetime :created_at
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
add_index :course_fee_histories, :learndash_course_id
|
|
58
|
+
|
|
43
59
|
create_table :learndash_enrollments do |t|
|
|
44
60
|
t.integer :owner_id
|
|
45
61
|
t.string :owner_type
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: effective_learndash
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Code and Effect
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-07-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -179,12 +179,14 @@ files:
|
|
|
179
179
|
- app/assets/javascripts/effective_learndash/base.js
|
|
180
180
|
- app/assets/stylesheets/effective_learndash.scss
|
|
181
181
|
- app/assets/stylesheets/effective_learndash/base.scss
|
|
182
|
+
- app/controllers/admin/course_fee_histories_controller.rb
|
|
182
183
|
- app/controllers/admin/course_registrations_controller.rb
|
|
183
184
|
- app/controllers/admin/learndash_courses_controller.rb
|
|
184
185
|
- app/controllers/admin/learndash_enrollments_controller.rb
|
|
185
186
|
- app/controllers/admin/learndash_users_controller.rb
|
|
186
187
|
- app/controllers/effective/course_registrations_controller.rb
|
|
187
188
|
- app/controllers/effective/learndash_courses_controller.rb
|
|
189
|
+
- app/datatables/admin/course_fee_histories_datatable.rb
|
|
188
190
|
- app/datatables/admin/effective_course_registrations_datatable.rb
|
|
189
191
|
- app/datatables/admin/effective_learndash_courses_datatable.rb
|
|
190
192
|
- app/datatables/admin/effective_learndash_enrollments_datatable.rb
|
|
@@ -196,12 +198,14 @@ files:
|
|
|
196
198
|
- app/helpers/effective_learndash_helper.rb
|
|
197
199
|
- app/models/concerns/effective_learndash_course_registration.rb
|
|
198
200
|
- app/models/concerns/effective_learndash_owner.rb
|
|
201
|
+
- app/models/effective/course_fee_history.rb
|
|
199
202
|
- app/models/effective/course_registrant.rb
|
|
200
203
|
- app/models/effective/course_registration.rb
|
|
201
204
|
- app/models/effective/learndash_api.rb
|
|
202
205
|
- app/models/effective/learndash_course.rb
|
|
203
206
|
- app/models/effective/learndash_enrollment.rb
|
|
204
207
|
- app/models/effective/learndash_user.rb
|
|
208
|
+
- app/views/admin/course_fee_histories/_form.html.haml
|
|
205
209
|
- app/views/admin/learndash_courses/_form.html.haml
|
|
206
210
|
- app/views/admin/learndash_courses/_form_course_registration_content.html.haml
|
|
207
211
|
- app/views/admin/learndash_courses/_form_learndash_course.html.haml
|