From 7f52b2823f2e7525e706236fd7a2c2a139775efe Mon Sep 17 00:00:00 2001 From: "soroush.asadi" Date: Wed, 3 Jun 2026 00:58:49 +0330 Subject: [PATCH] =?UTF-8?q?feat(plans):=20Stage=203a=20=E2=80=94=20enforce?= =?UTF-8?q?=20tables=20cap=20from=20the=20DB=20catalog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PlanLimitChecker already enforces orders/customers/branches/SMS from the admin-editable catalog (GetLimitsAsync). Add the tables cap the same way (POST /api/cafes/{cafeId}/tables → MaxTables), so Free's 6-table limit is both enforced and admin-editable. Terminals/categories/report-history already enforce the correct matrix values via PlanLimits defaults; routing them through the catalog for editability + the watermark/styling/review-reply feature gates are the remaining S3 items. 86 tests pass. --- src/Meezi.API/Services/PlanLimitChecker.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Meezi.API/Services/PlanLimitChecker.cs b/src/Meezi.API/Services/PlanLimitChecker.cs index 041506e..afd2011 100644 --- a/src/Meezi.API/Services/PlanLimitChecker.cs +++ b/src/Meezi.API/Services/PlanLimitChecker.cs @@ -99,6 +99,21 @@ public class PlanLimitChecker : IPlanLimitChecker return (false, "PLAN_LIMIT_REACHED", "Branch limit reached for your plan. Please upgrade."); } + var tablesPath = $"/api/cafes/{cafeId}/tables"; + if (path.StartsWith(tablesPath, StringComparison.OrdinalIgnoreCase) && + (path.Equals(tablesPath, StringComparison.OrdinalIgnoreCase) || + path.Equals($"{tablesPath}/", StringComparison.OrdinalIgnoreCase))) + { + var limitsTables = await _platformCatalog.GetLimitsAsync(tier, cancellationToken); + var maxTables = limitsTables.MaxTables; + if (maxTables != int.MaxValue) + { + var tableCount = await _db.Tables.CountAsync(t => t.CafeId == cafeId, cancellationToken); + if (tableCount >= maxTables) + return (false, "PLAN_LIMIT_REACHED", "Table limit reached for your plan. Please upgrade."); + } + } + var smsCampaignPath = $"/api/cafes/{cafeId}/sms/campaign"; if (path.Equals(smsCampaignPath, StringComparison.OrdinalIgnoreCase) || path.Equals($"{smsCampaignPath}/", StringComparison.OrdinalIgnoreCase))