diff --git a/saas/app/controllers/admin/billing_waivers_controller.rb b/saas/app/controllers/admin/billing_waivers_controller.rb new file mode 100644 index 000000000..8895ad399 --- /dev/null +++ b/saas/app/controllers/admin/billing_waivers_controller.rb @@ -0,0 +1,13 @@ +class Admin::BillingWaiversController < AdminController + include Admin::AccountScoped + + def create + @account.comp + redirect_to saas.edit_admin_account_path(@account.external_account_id), notice: "Account comped" + end + + def destroy + @account.uncomp + redirect_to saas.edit_admin_account_path(@account.external_account_id), notice: "Account uncomped" + end +end \ No newline at end of file diff --git a/saas/app/models/account/billing.rb b/saas/app/models/account/billing.rb index 2ef42edb7..5b52dd836 100644 --- a/saas/app/models/account/billing.rb +++ b/saas/app/models/account/billing.rb @@ -3,6 +3,7 @@ module Account::Billing included do has_one :subscription, class_name: "Account::Subscription", dependent: :destroy + has_one :billing_waiver, class_name: "Account::BillingWaiver", dependent: :destroy end def plan @@ -10,10 +11,32 @@ module Account::Billing end def active_subscription - subscription if subscription&.active? + if comped? + comped_subscription + else + subscription if subscription&.active? + end end def subscribed? subscription.present? end + + def comped? + billing_waiver.present? + end + + def comp + create_billing_waiver unless billing_waiver + end + + def uncomp + billing_waiver&.destroy + reload_billing_waiver + end + + private + def comped_subscription + @comped_subscription ||= billing_waiver&.subscription + end end diff --git a/saas/app/models/account/billing_waiver.rb b/saas/app/models/account/billing_waiver.rb new file mode 100644 index 000000000..c1968522a --- /dev/null +++ b/saas/app/models/account/billing_waiver.rb @@ -0,0 +1,7 @@ +class Account::BillingWaiver < SaasRecord + belongs_to :account + + def subscription + @subscription ||= Account::Subscription.new(plan_key: Plan.paid.key) + end +end diff --git a/saas/app/models/account/subscription.rb b/saas/app/models/account/subscription.rb index cc4989343..937d11bcc 100644 --- a/saas/app/models/account/subscription.rb +++ b/saas/app/models/account/subscription.rb @@ -8,7 +8,7 @@ class Account::Subscription < SaasRecord delegate :paid?, to: :plan def plan - Plan.find(plan_key) + @plan ||= Plan.find(plan_key) end def to_be_canceled? diff --git a/saas/app/models/plan.rb b/saas/app/models/plan.rb index 77a2cfbc7..835e94472 100644 --- a/saas/app/models/plan.rb +++ b/saas/app/models/plan.rb @@ -12,11 +12,11 @@ class Plan end def free - @free ||= all.find(&:free?) + @free ||= find(:free_v1) end def paid - @paid ||= all.find(&:paid?) + @paid ||= find(:monthly_v1) end def find(key) diff --git a/saas/app/views/account/settings/_subscription_panel.html.erb b/saas/app/views/account/settings/_subscription_panel.html.erb index 52479f0b1..55fc2f63f 100644 --- a/saas/app/views/account/settings/_subscription_panel.html.erb +++ b/saas/app/views/account/settings/_subscription_panel.html.erb @@ -1,4 +1,4 @@ -<% if Current.user.admin? %> +<% if Current.user.admin? && !Current.account.comped? %>

Subscription

diff --git a/saas/app/views/admin/accounts/edit.html.erb b/saas/app/views/admin/accounts/edit.html.erb index 1a2f90515..49c99773d 100644 --- a/saas/app/views/admin/accounts/edit.html.erb +++ b/saas/app/views/admin/accounts/edit.html.erb @@ -28,6 +28,12 @@ <%= button_to "Reset limits", saas.admin_account_overridden_limits_path(@account.external_account_id), method: :delete, class: "btn btn--negative" %> <% end %> + <% if @account.comped? %> + <%= button_to "Uncomp account", saas.admin_account_billing_waiver_path(@account.external_account_id), method: :delete, class: "btn btn--negative" %> + <% else %> + <%= button_to "Comp account", saas.admin_account_billing_waiver_path(@account.external_account_id), method: :post, class: "btn btn--positive" %> + <% end %> + <%= link_to "Back to accounts", saas.admin_accounts_path, class: "btn btn--plain txt-link" %> diff --git a/saas/config/routes.rb b/saas/config/routes.rb index 7f9d36465..ece9fef17 100644 --- a/saas/config/routes.rb +++ b/saas/config/routes.rb @@ -7,6 +7,7 @@ Fizzy::Saas::Engine.routes.draw do resource :account_search, only: :create resources :accounts do resource :overridden_limits, only: :destroy + resource :billing_waiver, only: [ :create, :destroy ] end end end diff --git a/saas/db/migrate/20251215160000_create_account_billing_waivers.rb b/saas/db/migrate/20251215160000_create_account_billing_waivers.rb new file mode 100644 index 000000000..2cc9023d1 --- /dev/null +++ b/saas/db/migrate/20251215160000_create_account_billing_waivers.rb @@ -0,0 +1,9 @@ +class CreateAccountBillingWaivers < ActiveRecord::Migration[8.2] + def change + create_table :account_billing_waivers, id: :uuid do |t| + t.references :account, null: false, type: :uuid, index: { unique: true } + + t.timestamps + end + end +end \ No newline at end of file diff --git a/saas/db/saas_schema.rb b/saas/db/saas_schema.rb index edfead6ad..15dea28fc 100644 --- a/saas/db/saas_schema.rb +++ b/saas/db/saas_schema.rb @@ -10,7 +10,14 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.2].define(version: 2025_12_15_140000) do +ActiveRecord::Schema[8.2].define(version: 2025_12_15_160000) do + create_table "account_billing_waivers", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.uuid "account_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["account_id"], name: "index_account_billing_waivers_on_account_id", unique: true + end + create_table "account_overridden_limits", id: :uuid, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.uuid "account_id", null: false t.integer "card_count" diff --git a/saas/test/controllers/admin/billing_waivers_controller_test.rb b/saas/test/controllers/admin/billing_waivers_controller_test.rb new file mode 100644 index 000000000..ad2f76721 --- /dev/null +++ b/saas/test/controllers/admin/billing_waivers_controller_test.rb @@ -0,0 +1,32 @@ +require "test_helper" + +class Admin::BillingWaiversControllerTest < ActionDispatch::IntegrationTest + test "staff can comp an account" do + sign_in_as :david + account = accounts(:"37s") + + assert_not account.comped? + + untenanted do + post saas.admin_account_billing_waiver_path(account.external_account_id) + assert_redirected_to saas.edit_admin_account_path(account.external_account_id) + end + + assert account.reload.comped? + end + + test "staff can uncomp an account" do + sign_in_as :david + account = accounts(:"37s") + account.comp + + assert account.comped? + + untenanted do + delete saas.admin_account_billing_waiver_path(account.external_account_id) + assert_redirected_to saas.edit_admin_account_path(account.external_account_id) + end + + assert_not account.reload.comped? + end +end \ No newline at end of file diff --git a/saas/test/models/account/billing_test.rb b/saas/test/models/account/billing_test.rb index 3969f18b4..cdb7c3fa9 100644 --- a/saas/test/models/account/billing_test.rb +++ b/saas/test/models/account/billing_test.rb @@ -2,7 +2,7 @@ require "test_helper" class Account::BillingTest < ActiveSupport::TestCase test "active subscription" do - account = Account.create!(name: "Test") + account = accounts(:initech) # No subscription assert_nil account.active_subscription @@ -15,4 +15,17 @@ class Account::BillingTest < ActiveSupport::TestCase account.subscription.update!(status: "active") assert_equal account.subscription, account.active_subscription end + + test "comped account" do + account = accounts(:"37s") + + assert_not account.comped? + + account.comp + assert account.comped? + + # Calling comp again does not create duplicate + account.comp + assert_equal 1, Account::BillingWaiver.where(account: account).count + end end diff --git a/saas/test/models/account/limited_test.rb b/saas/test/models/account/limited_test.rb index ad982203c..335d6a37c 100644 --- a/saas/test/models/account/limited_test.rb +++ b/saas/test/models/account/limited_test.rb @@ -48,4 +48,29 @@ class Account::LimitedTest < ActiveSupport::TestCase assert account.exceeding_card_limit? assert_equal 1001, account.billed_cards_count end + + test "comped accounts are never limited" do + account = accounts(:initech) + account.update_column(:cards_count, 1_000_000) + + assert account.exceeding_card_limit? + assert account.nearing_plan_cards_limit? + + account.comp + + assert_not account.exceeding_card_limit? + assert_not account.nearing_plan_cards_limit? + end + + test "uncomping an account restores limits" do + account = accounts(:initech) + account.update_column(:cards_count, 1_000_000) + account.comp + + assert_not account.exceeding_card_limit? + + account.uncomp + + assert account.exceeding_card_limit? + end end