Skip to content

Commit

Permalink
[#162409] [Shared dev] Daily rate price policies updates (#4682)
Browse files Browse the repository at this point in the history
* Add price policy daily usage rate and subsidy

* Handle daily usage rates submittion

* Add constants for Instrument pricing modes

* Create and use .negative-number for price policies
  • Loading branch information
joaquinco authored Oct 28, 2024
1 parent f68f865 commit 2807d30
Show file tree
Hide file tree
Showing 20 changed files with 371 additions and 53 deletions.
21 changes: 19 additions & 2 deletions app/assets/stylesheets/nucore.scss
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,14 @@ th.order-note {

td.currency,
th.currency,
th.hourly_rate {
th.hourly_rate,
th.daily_rate {
text-align: right;
}
td.daily_rate {
width: 120px;
}

.currency-input input {
width: 7em;
}
Expand Down Expand Up @@ -282,7 +287,6 @@ th.hourly_rate {
}
}

.price-policy-table input[type=text],
.half-width {
width: 50%;
}
Expand All @@ -291,6 +295,15 @@ th.hourly_rate {
width: 100%;
}

.negative-number::before {
content: '';
width: 0;
display: inline-block;
overflow: visible;
position: relative;
left: -10px;
}

/* Price policies */

p.per-minute {
Expand All @@ -305,6 +318,10 @@ p.per-minute-show {
margin-top: 0;
}

.price-policy-table input[type=text] {
width: 80%;
}

/* Account Fields */
.order_account, .order_detail_account {
select {
Expand Down
8 changes: 6 additions & 2 deletions app/helpers/price_policies_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@ def charge_for_options(instrument)

def display_usage_rate(price_group, price_policy)
param_for_price_group(price_group, :usage_rate) ||
number_to_currency(price_policy.hourly_usage_rate, unit: "", delimiter: "")
display_rate(price_policy.hourly_usage_rate)
end

def display_usage_subsidy(price_group, price_policy)
param_for_price_group(price_group, :usage_subsidy) ||
number_to_currency(price_policy.hourly_usage_subsidy, unit: "", delimiter: "")
display_rate(price_policy.hourly_usage_subsidy)
end

def display_rate(value)
number_to_currency(value, unit: "", delimiter: "")
end

private
Expand Down
39 changes: 37 additions & 2 deletions app/models/concerns/price_policies/usage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,25 @@ module Usage
extend ActiveSupport::Concern

included do
validates :usage_rate, presence: true, unless: :restrict_purchase?
validates :usage_rate, :usage_subsidy, :minimum_cost, numericality: { allow_blank: true, greater_than_or_equal_to: 0 }
validates :usage_rate,
presence: true,
unless: -> { restrict_purchase? || daily_booking? }
validates :usage_rate_daily,
presence: true,
unless: -> { restrict_purchase? || !daily_booking? }

validates(
:usage_rate,
:usage_rate_daily,
:usage_subsidy,
:usage_subsidy_daily,
:minimum_cost,
numericality: { allow_blank: true, greater_than_or_equal_to: 0 }
)

validate :daily_subsidy_less_than_rate, if: :daily_booking?

before_validation :sanitize_usage_rate
end

def has_rate?
Expand Down Expand Up @@ -41,6 +58,10 @@ def subsidized_hourly_usage_cost
hourly_usage_rate - hourly_usage_subsidy
end

def subsidized_daily_usage_cost
usage_rate_daily - usage_subsidy_daily
end

def minimum_cost_subsidy
return unless has_minimum_cost?
minimum_cost * subsidy_ratio
Expand All @@ -66,6 +87,20 @@ def subsidy_field
:usage_subsidy
end

def daily_subsidy_less_than_rate
return if usage_subsidy_daily.blank? || usage_rate_daily.blank?
return if usage_subsidy_daily <= usage_rate_daily

errors.add(:usage_subsidy_daily, :subsidy_greater_than_cost)
end

def sanitize_usage_rate
if usage_rate_daily_changed? && daily_booking?
self.usage_rate = nil
self.usage_subsidy = nil
end
end

end

end
15 changes: 10 additions & 5 deletions app/models/instrument.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,21 @@

class Instrument < Product

module Pricing
SCHEDULE_RULE = "Schedule Rule"
SCHEDULE_DAILY = "Schedule Rule (Daily Booking only)"
DURATION = "Duration"
end

include Products::RelaySupport
include Products::ScheduleRuleSupport
include Products::SchedulingSupport
include EmailListAttribute

RESERVE_INTERVALS = [1, 5, 10, 15, 30, 60].freeze
SCHEDULE_RULE_DAILY_BOOKING = "Schedule Rule (Daily Booking only)"
PRICING_MODES = ["Schedule Rule", "Duration"].tap do |pricing_modes|
PRICING_MODES = [Pricing::SCHEDULE_RULE, Pricing::DURATION].tap do |pricing_modes|
if SettingsHelper.feature_on?(:show_daily_rate_option)
pricing_modes.insert(1, SCHEDULE_RULE_DAILY_BOOKING)
pricing_modes.insert(1, Pricing::SCHEDULE_DAILY)
end
end.freeze

Expand Down Expand Up @@ -107,11 +112,11 @@ def blackout_reservations(date)
end

def duration_pricing_mode?
pricing_mode == "Duration"
pricing_mode == Pricing::DURATION
end

def daily_booking?
pricing_mode == SCHEDULE_RULE_DAILY_BOOKING
pricing_mode == Pricing::SCHEDULE_DAILY
end

private
Expand Down
4 changes: 4 additions & 0 deletions app/models/price_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ def note_option
Settings.price_policy_note_options.include?(note) ? note : "Other"
end

def daily_booking?
product&.daily_booking?
end

private

# TODO: Refactor
Expand Down
20 changes: 12 additions & 8 deletions app/services/price_policy_updater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,23 @@ def permitted_params
[
:can_purchase,
:usage_rate,
:usage_rate_daily,
:usage_subsidy,
:usage_subsidy_daily,
:minimum_cost,
:cancellation_cost,
:unit_cost,
:unit_subsidy,
duration_rates_attributes: [
:id,
:subsidy,
:rate,
:price_policy_id,
:min_duration_hours,
:_destroy
]
{
duration_rates_attributes: [
:id,
:subsidy,
:rate,
:price_policy_id,
:min_duration_hours,
:_destroy,
],
},
].tap do |attributes|
attributes << :full_price_cancellation if SettingsHelper.feature_on?(:charge_full_price_on_cancellation)
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
= render "time_based_price_policies/price_policy_fields", f: f, cancellation: true, minimum_cost: true, charge_for_collection: charge_for_options(@price_policies.first.product)
= render("time_based_price_policies/price_policy_fields",
f: f, cancellation: !@product.daily_booking?, minimum_cost: !@product.daily_booking?,
charge_for_collection: charge_for_options(@price_policies.first.product))
6 changes: 5 additions & 1 deletion app/views/instrument_price_policies/_table.html.haml
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
= render "time_based_price_policies/table", price_policies: price_policies, url_date: url_date, cancellation: true, minimum_cost: true, product: product
= render "time_based_price_policies/table",
price_policies: price_policies, url_date: url_date,
cancellation: !@product.daily_booking?,
minimum_cost: !@product.daily_booking?,
product: product
19 changes: 14 additions & 5 deletions app/views/time_based_price_policies/_adjustment_row.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,17 @@

= pp.hidden_field :full_price_cancellation, class: "js--fullCancellationCost", readonly: true

%td
= pp.hidden_field :usage_rate, value: display_usage_rate(price_group, price_policy),
class: "js--usageRate"
= pp.label :usage_subsidy, t("price_policies.adjustment"), class: "normal-weight"
= "- #{pp.text_field :usage_subsidy, value: display_usage_subsidy(price_group, price_policy), size: 8, class: "usage_adjustment"}"
- if @product.daily_booking?
%td.daily_rate
= pp.hidden_field :usage_rate_daily, value: price_policy.usage_rate_daily, class: "js--usageRate"

= pp.label :usage_subsidy_daily, t("price_policies.adjustment"), class: "normal-weight"
%span.negative-number
= pp.text_field :usage_subsidy_daily, value: display_rate(price_policy.usage_subsidy_daily)
- else
%td
= pp.hidden_field :usage_rate, value: display_usage_rate(price_group, price_policy), class: "js--usageRate"

= pp.label :usage_subsidy, t("price_policies.adjustment"), class: "normal-weight"
%span.negative-number
= pp.text_field :usage_subsidy, value: display_usage_subsidy(price_group, price_policy), size: 8, class: "usage_adjustment"
18 changes: 12 additions & 6 deletions app/views/time_based_price_policies/_amount_row.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@
= price_policy.class.human_attribute_name(:full_price_cancellation)
= tooltip_icon "fa fa-question-circle-o", t("price_policies.charge_full_price_on_cancellation_hint")

%td
= pp.label :usage_rate, t("price_policies.rate"), class: "normal-weight"
= pp.text_field :usage_rate, value: display_usage_rate(price_group, price_policy),
size: 8,
class: "#{price_group.master_internal? ? 'master_usage_cost' : ''} usage_rate",
data: { target: ".js--usageRate" }
- if @product.daily_booking?
%td.daily_rate
= pp.label :usage_rate_daily, t("price_policies.rate"), class: "normal-weight"
= pp.text_field :usage_rate_daily, value: display_rate(price_policy.usage_rate_daily),
size: 8, data: { target: ".js--usageRate" }
- else
%td
= pp.label :usage_rate, t("price_policies.rate"), class: "normal-weight"
= pp.text_field :usage_rate, value: display_usage_rate(price_group, price_policy),
size: 8,
class: "#{price_group.master_internal? ? 'master_usage_cost' : ''} usage_rate",
data: { target: ".js--usageRate" }
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
%th{ scope: "col" }= t(".step_1")
%th{ scope: "col" }= t(".step_2")
%th{ scope: "col" }= t(".step_3")
- else
- elsif @product.daily_booking?
%th.daily_rate{ scope: "col" }= PricePolicy.human_attribute_name(:usage_rate_daily)
- else
%th.hourly_rate{ scope: "col" }= t("activerecord.attributes.price_policy.hourly_usage_rate")
%tbody
Expand All @@ -43,7 +45,6 @@
%td
- if local_assigns[:cancellation]
%td
- if @product.duration_pricing_mode?
%th{ scope: "col" }
= label :default_min_duration, t(".rate_start"), class: "normal-weight"
= number_field :default_min_duration, "default", value: 0, readonly: true, disabled:true, class: "half-width"
Expand Down Expand Up @@ -81,7 +82,7 @@
- else
%td
= dr.label :subsidy, t(".adjustment"), class: "normal-weight"
%span="-"
= dr.text_field :subsidy, value: number_to_currency(dr.object.hourly_subsidy, unit: "", delimiter: ""), size: 8, class: "usage_adjustment"
%span.negative-number
= dr.text_field :subsidy, value: number_to_currency(dr.object.hourly_subsidy, unit: "", delimiter: ""), size: 8, class: "usage_adjustment"
= dr.hidden_field :min_duration_hours
= dr.hidden_field :rate, class: "js--hiddenRate"
16 changes: 12 additions & 4 deletions app/views/time_based_price_policies/_table.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
%th.currency{ scope: "col" }= t("time_based_price_policies.table.step_1")
%th.currency{ scope: "col" }= t("time_based_price_policies.table.step_2")
%th.currency{ scope: "col" }= t("time_based_price_policies.table.step_3")
- else
- elsif @product.daily_booking?
%th.daily_rate{ scope: "col" }= PricePolicy.human_attribute_name(:usage_rate_daily)
- else
%th.hourly_rate{ scope: "col" }= t("activerecord.attributes.price_policy.hourly_usage_rate")

%tbody
Expand Down Expand Up @@ -58,7 +60,7 @@
%td.currency
.rate= number_to_currency price_policy.minimum_cost
- if price_policy.has_minimum_cost? && price_policy.has_subsidy?
.subsidy= "- #{number_to_currency price_policy.minimum_cost_subsidy}"
.subsidy.negative-number= number_to_currency price_policy.minimum_cost_subsidy
%strong= "= #{number_to_currency price_policy.subsidized_minimum_cost}"

- if local_assigns[:cancellation]
Expand All @@ -71,21 +73,27 @@
- if price_policy.has_rate?
.rate= number_to_currency price_policy.hourly_usage_rate
- if price_policy.has_subsidy?
.subsidy= "- #{number_to_currency price_policy.hourly_usage_subsidy}"
.subsidy.negative-number= number_to_currency price_policy.hourly_usage_subsidy
%strong= "= #{number_to_currency price_policy.subsidized_hourly_usage_cost}"

%p.per-minute-show
- price_per_minute = (price_policy.subsidized_hourly_usage_cost / 60)
= number_to_currency price_per_minute, precision: 4
\/ minute
- elsif price_policy.usage_rate_daily.present?
.rate= number_to_currency price_policy.usage_rate_daily
- if price_policy.usage_subsidy_daily.present?
.subsidy.negative-number= number_to_currency price_policy.usage_subsidy_daily
%strong= "= #{number_to_currency price_policy.subsidized_daily_usage_cost}"

- price_policy.duration_rates.sorted.each do |duration_rate|
%td.currency
- per_minute = nil
- if duration_rate.rate.present?
.rate= number_to_currency duration_rate.hourly_rate
- per_minute = duration_rate.hourly_rate / 60
- if duration_rate.subsidy.present?
.subsidy= "- #{number_to_currency duration_rate.hourly_subsidy}"
.subsidy.negative-number= number_to_currency duration_rate.hourly_subsidy
%strong= "= #{number_to_currency duration_rate.subsidized_hourly_cost}"
- per_minute = duration_rate.subsidized_hourly_cost / 60

Expand Down
1 change: 1 addition & 0 deletions config/locales/en.models.yml
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ en:
can_purchase: Can Purchase?
minimum_cost: Minimum Cost
hourly_usage_rate: Rate Per Hour
usage_rate_daily: Rate Per Day
cancellation_cost: Reservation Cost
full_price_cancellation: Full reservation cost
unit_cost: Unit Cost
Expand Down
1 change: 1 addition & 0 deletions config/locales/views/admin/en.price_policies.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ en:
edit: "Edit Price Policies"
remove: "Remove Price Policies"
time_based_price_policies:
problem: "There are errors in this policy:"
table:
stepped_rates_title: Stepped Billing Rates
rates_title: Billing Rates
Expand Down
10 changes: 10 additions & 0 deletions db/migrate/20241022135335_add_price_policy_usage_rate_daily.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

class AddPricePolicyUsageRateDaily < ActiveRecord::Migration[7.0]
def change
with_options(precision: 10, scale: 2) do
add_column :price_policies, :usage_rate_daily, :decimal
add_column :price_policies, :usage_subsidy_daily, :decimal
end
end
end
4 changes: 3 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.0].define(version: 2024_10_15_214235) do
ActiveRecord::Schema[7.0].define(version: 2024_10_22_135335) do
create_table "account_facility_joins", id: :integer, charset: "utf8mb3", force: :cascade do |t|
t.integer "facility_id", null: false
t.integer "account_id", null: false
Expand Down Expand Up @@ -534,6 +534,8 @@
t.boolean "full_price_cancellation", default: false, null: false
t.string "note", limit: 256
t.integer "created_by_id"
t.decimal "usage_rate_daily", precision: 10, scale: 2
t.decimal "usage_subsidy_daily", precision: 10, scale: 2
t.index ["created_by_id"], name: "index_price_policies_on_created_by_id"
t.index ["price_group_id"], name: "fk_rails_74aa223960"
t.index ["product_id"], name: "index_price_policies_on_product_id"
Expand Down
Loading

0 comments on commit 2807d30

Please sign in to comment.