diff --git a/saas/app/controllers/admin/accounts_controller.rb b/saas/app/controllers/admin/accounts_controller.rb
index ba6200d1b..26a2b9c82 100644
--- a/saas/app/controllers/admin/accounts_controller.rb
+++ b/saas/app/controllers/admin/accounts_controller.rb
@@ -12,7 +12,7 @@ class Admin::AccountsController < AdminController
end
def update
- @account.override_limits(card_count: overridden_card_count_param)
+ @account.override_limits(**overridden_limits_params.to_h.symbolize_keys)
redirect_to saas.edit_admin_account_path(@account.external_account_id), notice: "Account limits updated"
end
@@ -21,7 +21,7 @@ class Admin::AccountsController < AdminController
@account = Account.find_by!(external_account_id: params[:id])
end
- def overridden_card_count_param
- params[:account][:overridden_card_count].to_i
+ def overridden_limits_params
+ params.expect(account: [ :card_count, :bytes_used ])
end
end
diff --git a/saas/app/models/account/limited.rb b/saas/app/models/account/limited.rb
index f7eb3cbc1..b185815e4 100644
--- a/saas/app/models/account/limited.rb
+++ b/saas/app/models/account/limited.rb
@@ -6,15 +6,20 @@ module Account::Limited
end
NEAR_CARD_LIMIT_THRESHOLD = 100
+ NEAR_STORAGE_LIMIT_THRESHOLD = 500.megabytes
- def override_limits(card_count:)
- (overridden_limits || build_overridden_limits).update!(card_count:)
+ def override_limits(card_count: nil, bytes_used: nil)
+ (overridden_limits || build_overridden_limits).update!(card_count:, bytes_used:)
end
def billed_cards_count
overridden_limits&.card_count || cards_count
end
+ def billed_bytes_used
+ overridden_limits&.bytes_used || bytes_used
+ end
+
def nearing_plan_cards_limit?
plan.limit_cards? && remaining_cards_count < NEAR_CARD_LIMIT_THRESHOLD
end
@@ -23,6 +28,22 @@ module Account::Limited
plan.limit_cards? && billed_cards_count > plan.card_limit
end
+ def nearing_plan_storage_limit?
+ remaining_storage < NEAR_STORAGE_LIMIT_THRESHOLD
+ end
+
+ def exceeding_storage_limit?
+ billed_bytes_used > plan.storage_limit
+ end
+
+ def nearing_limits?
+ nearing_plan_cards_limit? || nearing_plan_storage_limit?
+ end
+
+ def exceeding_limits?
+ exceeding_card_limit? || exceeding_storage_limit?
+ end
+
def reset_overridden_limits
overridden_limits&.destroy
reload_overridden_limits
@@ -32,4 +53,8 @@ module Account::Limited
def remaining_cards_count
plan.card_limit - billed_cards_count
end
+
+ def remaining_storage
+ plan.storage_limit - billed_bytes_used
+ end
end
diff --git a/saas/app/views/admin/accounts/edit.html.erb b/saas/app/views/admin/accounts/edit.html.erb
index 49c99773d..950ecc761 100644
--- a/saas/app/views/admin/accounts/edit.html.erb
+++ b/saas/app/views/admin/accounts/edit.html.erb
@@ -11,14 +11,21 @@
- <%= form.label :overridden_card_count, "Override card count", class: "font-weight-bold" %>
-
- <%= form.number_field :overridden_card_count, value: @account.overridden_limits&.card_count, class: "input full-width" %>
-
+
+ <%= form.label :card_count, "Override card count", class: "font-weight-bold" %>
+ <%= form.number_field :card_count, value: @account.overridden_limits&.card_count, class: "input" %>
+
+
+
+ <%= form.label :bytes_used, "Override bytes used", class: "font-weight-bold" %>
+ <%= form.number_field :bytes_used, value: @account.overridden_limits&.bytes_used, class: "input" %>
diff --git a/saas/db/migrate/20251216000000_add_bytes_used_to_account_overridden_limits.rb b/saas/db/migrate/20251216000000_add_bytes_used_to_account_overridden_limits.rb
new file mode 100644
index 000000000..85adad44a
--- /dev/null
+++ b/saas/db/migrate/20251216000000_add_bytes_used_to_account_overridden_limits.rb
@@ -0,0 +1,5 @@
+class AddBytesUsedToAccountOverriddenLimits < ActiveRecord::Migration[8.2]
+ def change
+ add_column :account_overridden_limits, :bytes_used, :bigint
+ end
+end
diff --git a/saas/db/saas_schema.rb b/saas/db/saas_schema.rb
index 59c78f4cb..7e9b80b92 100644
--- a/saas/db/saas_schema.rb
+++ b/saas/db/saas_schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[8.2].define(version: 2025_12_15_170000) do
+ActiveRecord::Schema[8.2].define(version: 2025_12_16_000000) 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
@@ -20,6 +20,7 @@ ActiveRecord::Schema[8.2].define(version: 2025_12_15_170000) do
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.bigint "bytes_used"
t.integer "card_count"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
diff --git a/saas/test/models/account/limited_test.rb b/saas/test/models/account/limited_test.rb
index 335d6a37c..dafca0c31 100644
--- a/saas/test/models/account/limited_test.rb
+++ b/saas/test/models/account/limited_test.rb
@@ -73,4 +73,45 @@ class Account::LimitedTest < ActiveSupport::TestCase
assert account.exceeding_card_limit?
end
+
+ test "detect nearing storage limit" do
+ # Paid plans have large storage limits
+ accounts(:"37s").update_column(:bytes_used, 4.gigabytes)
+ assert_not accounts(:"37s").nearing_plan_storage_limit?
+
+ # Free plan not near limit
+ accounts(:initech).update_column(:bytes_used, 400.megabytes)
+ assert_not accounts(:initech).nearing_plan_storage_limit?
+
+ # Free plan near limit
+ accounts(:initech).update_column(:bytes_used, 600.megabytes)
+ assert accounts(:initech).nearing_plan_storage_limit?
+ end
+
+ test "detect exceeding storage limit" do
+ # Free plan under limit
+ accounts(:initech).update_column(:bytes_used, 900.megabytes)
+ assert_not accounts(:initech).exceeding_storage_limit?
+
+ # Free plan over limit
+ accounts(:initech).update_column(:bytes_used, 1.1.gigabytes)
+ assert accounts(:initech).exceeding_storage_limit?
+ end
+
+ test "override bytes_used limits" do
+ account = accounts(:initech)
+ account.update_column(:bytes_used, 1.1.gigabytes)
+
+ assert account.exceeding_storage_limit?
+ assert_equal 1.1.gigabytes, account.billed_bytes_used
+
+ account.override_limits bytes_used: 500.megabytes
+ assert_not account.exceeding_storage_limit?
+ assert_equal 500.megabytes, account.billed_bytes_used
+ assert_equal 1.1.gigabytes, account.bytes_used # original unchanged
+
+ account.reset_overridden_limits
+ assert account.exceeding_storage_limit?
+ assert_equal 1.1.gigabytes, account.billed_bytes_used
+ end
end