Global Pricing Settings
Restaurant-level pricing limits that constrain all pricing rules.
Overview
Global pricing settings define maximum price adjustments allowed for a restaurant. All pricing rules must respect these limits.
Key Concepts:
- One setting per restaurant (1:1 relationship)
- Auto-creates with defaults on first GET
- Controls increase AND decrease limits separately
- Enforced during pricing rule validation
Model
Location: app/models/global_pricing_setting.rb
Attributes
| Field | Type | Description | Default |
|---|---|---|---|
restaurant_id | String | Restaurant identifier (unique) | Required |
price_increase_limit_type | String | Limit type for increases | "percentage" |
price_increase_limit_value | Decimal | Max increase (decimal % or cents) | 50.0 (50%) |
price_decrease_limit_type | String | Limit type for decreases | "percentage" |
price_decrease_limit_value | Decimal | Max decrease (decimal % or cents) | 50.0 (50%) |
Limit Types
Defined in: LIMIT_TYPES constant
| Value | Description | Storage Unit |
|---|---|---|
percentage | Percentage-based limit | Decimal percentage (50.0 = 50%) |
fixed_amount | Fixed amount limit | Decimal amount (100.0 = 100 THB) |
Value Storage Format
IMPORTANT: The API accepts basis points for percentages, but converts them to decimal format for storage:
API Input → Database Storage:
- Percentage (API): Basis points (5000) → Stored as: Decimal (50.0)
- Fixed Amount (API): Cents (10000) → Stored as: Decimal (100.0)
Conversion Rules:
- Percentage values >= 100 are treated as basis points and divided by 100
- Percentage values < 100 are treated as already in decimal format
- Fixed amount values are stored as-is (in cents/minor currency units)
Examples:
API Input: 5000 (basis points) → Stored: 50.0 (decimal %)
API Input: 50.0 (decimal) → Stored: 50.0 (decimal %)
API Input: 2500 (basis points) → Stored: 25.0 (decimal %)
Fixed: 10000 (cents) → Stored: 10000.0 (cents)
Why this matters:
- Ensures compatibility with internal percentage calculations
- Prevents confusion when comparing with pricing rule values (which use basis points internally)
- Automatic conversion in model setters handles both formats
Validations
restaurant_id: Must be present and uniqueprice_increase_limit_type: Must bepercentageorfixed_amount(if present)price_decrease_limit_type: Must bepercentageorfixed_amount(if present)price_increase_limit_value: Must be >= 0 (if present)price_decrease_limit_value: Must be >= 0 (if present)
Auto-Create Behavior
Controller: app/controllers/api/partner/v1/global_pricing_settings_controller.rb
When you GET a setting that doesn’t exist, it’s automatically created with defaults:
GlobalPricingSetting.find_or_create_by!(restaurant_id: restaurant_id) do |setting|
setting.price_increase_limit_type = "percentage"
setting.price_increase_limit_value = 50.0 # 50% as decimal (or 5000 basis points - auto-converted)
setting.price_decrease_limit_type = "percentage"
setting.price_decrease_limit_value = 50.0 # 50% as decimal (or 5000 basis points - auto-converted)
end
Result: Every restaurant gets default 50% limits on first access.
Note: The model automatically converts basis points to decimal percentages when saving.
API Endpoints
Base Route: Singular resource pattern
resource :global_pricing_setting, only: [:show, :update]
GET Setting (with Auto-Create)
Endpoint: GET /api/partner/v1/global_pricing_setting
Headers:
Authorization: Bearer {token}X-Restaurant-Id: {restaurant_id}(required)
Response: 200 OK
{
"id": 1,
"restaurant_id": "rest_123",
"price_increase_limit_type": "percentage",
"price_increase_limit_value": "50.0",
"price_decrease_limit_type": "percentage",
"price_decrease_limit_value": "50.0",
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
Note:
- If setting doesn’t exist, it’s created with defaults automatically
- Values are returned as decimal strings (50.0 = 50%)
UPDATE Setting
Endpoint: PATCH /api/partner/v1/global_pricing_setting
Headers:
Authorization: Bearer {token}X-Restaurant-Id: {restaurant_id}(required)
Request Body:
{
"global_pricing_setting": {
"price_increase_limit_type": "percentage",
"price_increase_limit_value": 2500,
"price_decrease_limit_type": "fixed_amount",
"price_decrease_limit_value": 10000
}
}
Value Formats:
- For percentage type: Send basis points (2500 = 25%, will be converted to 25.0)
- For fixed_amount type: Send cents (10000 = 100 THB)
Response: 200 OK (same format as GET)
Restaurant Context
Settings are accessed per restaurant via:
-
Header (recommended):
X-Restaurant-Id: rest_123 -
Query parameter (fallback):
?restaurant_id=rest_123
Implementation: determine_target_restaurants(require_single: true) helper from BaseController
Enforcement
Limits are enforced when:
- Creating a pricing rule
- Updating a pricing rule
Validation Location: app/models/pricing_rule.rb
validate :validate_global_pricing_limits,
if: -> { restaurant_id.present? && date_time_conditions.any? }
Validation Logic
For each date_time_condition:
- Check increase: If action_type = “increase”, value must not exceed increase limit
- Check decrease: If action_type = “decrease”, value must not exceed decrease limit
- Type matching: Condition’s value_type must match global limit type
Example Error:
{
"errors": [
"Condition 1: increase value 10000 exceeds global limit of 5000 basis points"
]
}
Class Methods
for_restaurant
Find or initialize settings for a restaurant:
setting = GlobalPricingSetting.for_restaurant('rest_123')
Returns existing or new (unsaved) instance.
Instance Methods
price_increase_within_limit?
Check if price increase is within global limit:
setting.price_increase_within_limit?(10000, 12000)
# => true if 20% increase is within limit
price_decrease_within_limit?
Check if price decrease is within global limit:
setting.price_decrease_within_limit?(10000, 8000)
# => true if 20% decrease is within limit
Example Usage
Get Setting (First Time)
curl -X GET \
https://price-engine.hh-engineering.my.id/api/partner/v1/global_pricing_setting \
-H "Authorization: Bearer {token}" \
-H "X-Restaurant-Id: rest_123"
Response: Auto-created with 50% defaults
Update to 25% Limits
curl -X PATCH \
https://price-engine.hh-engineering.my.id/api/partner/v1/global_pricing_setting \
-H "Authorization: Bearer {token}" \
-H "X-Restaurant-Id: rest_123" \
-H "Content-Type: application/json" \
-d '{
"global_pricing_setting": {
"price_increase_limit_value": 2500,
"price_decrease_limit_value": 2500
}
}'
Update to Mixed Types
curl -X PATCH \
https://price-engine.hh-engineering.my.id/api/partner/v1/global_pricing_setting \
-H "Authorization: Bearer {token}" \
-H "X-Restaurant-Id: rest_123" \
-H "Content-Type: application/json" \
-d '{
"global_pricing_setting": {
"price_increase_limit_type": "percentage",
"price_increase_limit_value": 3000,
"price_decrease_limit_type": "fixed_amount",
"price_decrease_limit_value": 5000000
}
}'
Singular Resource Pattern
Route Definition:
resource :global_pricing_setting, only: [:show, :update]
URLs:
GET /api/partner/v1/global_pricing_setting(no ID in URL)PATCH /api/partner/v1/global_pricing_setting(no ID in URL)
Rationale: One setting per restaurant, identified by X-Restaurant-Id header, not by URL parameter.
Related Documentation
- 005-PRICING-RULES.md - How rules are validated
- 006-DATE-TIME-CONDITIONS.md - Condition values
- 004-API-ENDPOINTS.md - Complete API reference
- 008-ENUM-REFERENCE.md - Enum values