Pricing Rules
Complete guide to pricing rules in the Pricing Engine.
Overview
Pricing rules control automated price adjustments based on various conditions. Each rule can increase or decrease prices by percentage or fixed amounts, with global limits enforced at the restaurant level.
Rule Model
Location: app/models/pricing_rule.rb
Attributes
| Field | Type | Description |
|---|---|---|
restaurant_id | String | Restaurant identifier (required) |
name | String | Rule name (required) |
rule_type | String | Type of rule (required, see Rule Types) |
priority | Integer | Execution priority (0+, higher = earlier) |
active | Boolean | Whether rule is enabled (default: false) |
requires_approval | Boolean | Whether changes need approval (default: false) |
start_date | Date | When rule becomes valid |
end_date | Date | When rule expires (null if is_no_end_date = true) |
is_no_end_date | Boolean | Rule never expires (default: false) |
apply_to_all_packages | Boolean | Apply to all packages (default: true) |
date_type | String | Date format type (for specific_date_time rules) |
Rule Types
Defined in: RULE_TYPES constant
| Type | Status | Description |
|---|---|---|
specific_date_time | ✅ Active | Date & time based pricing |
demand_capacity | 🔜 Future | Capacity-based dynamic pricing |
performance_based | 🔜 Future | Performance metric rules |
advance_last_minute | 🔜 Future | Booking window rules |
Current Implementation: Only specific_date_time is fully implemented.
Date Types
Defined in: DATE_TYPES constant (for specific_date_time rules)
| Type | Description |
|---|---|
specific_date | Only date fields (start_date, end_date) |
specific_date_time | Both date and time fields |
Associations
# Conditions for date/time based pricing
has_many :date_time_conditions, dependent: :destroy
# Package associations
has_many :pricing_rules_packages, dependent: :destroy
# Change history
has_many :pricing_rule_histories, dependent: :nullify
# Through association
has_many :condition_items, through: :date_time_conditions
Validations
Required Fields
restaurant_id: Must be presentname: Must be presentrule_type: Must be present and one of RULE_TYPESpriority: Must be integer >= 0
Optional Validations
date_type: Must be one of DATE_TYPES if providedend_date: Must be after start_date if both presentend_time: Must be after start_time if both present- Global pricing limits: All date_time_conditions must respect restaurant’s global limits
Scopes
Status Scopes
PricingRule.active # Active rules only
PricingRule.inactive # Inactive rules only
Restaurant Scopes
PricingRule.for_restaurant('rest_123')
PricingRule.active_for_restaurant('rest_123') # Active + currently valid
Rule Type Scopes
PricingRule.by_rule_type('specific_date_time')
PricingRule.time_based # specific_date_time rules
PricingRule.demand_based # demand_capacity rules
PricingRule.performance_based # performance_based rules
PricingRule.advance_booking # advance_last_minute rules
Date Range Scopes
PricingRule.currently_valid # Valid today
PricingRule.valid_on_date('2025-01-15') # Valid on specific date
Other Scopes
PricingRule.apply_to_all # Rules for all packages
PricingRule.by_priority # Ordered by priority DESC, created_at DESC
Priority System
Rules are evaluated in priority order (highest first):
- Higher priority = Executed first
- Tie-breaking: Newer rules (created_at DESC)
- Range: 0 to unlimited (integer)
- Default: No default, must be specified
Example:
# Executed first (highest priority)
PricingRule.create!(priority: 100, name: "Holiday Special")
# Executed second
PricingRule.create!(priority: 50, name: "Weekend Discount")
# Executed last (lowest priority)
PricingRule.create!(priority: 10, name: "Regular Hours")
Approval Flow
Field: requires_approval (Boolean)
true: Price changes need approval before applyingfalse: Price changes apply immediately (default)
Note: Approval is at the rule level, not condition level.
Nested Attributes
Rules accept nested attributes for date_time_conditions:
accepts_nested_attributes_for :date_time_conditions,
allow_destroy: true,
reject_if: :all_blank
Usage:
{
"pricing_rule": {
"name": "Weekend Boost",
"date_time_conditions_attributes": [
{
"date_type": "day_of_week",
"days_of_week": [0, 6],
"action_type": "increase",
"value_type": "percentage",
"value": 1500
}
]
}
}
Cache Invalidation
Callback: after_commit :invalidate_repository_cache
Automatically invalidates Redis cache after:
- Create
- Update
- Destroy
Ensures fresh data in PricingRuleRepository.
Global Pricing Limits
Validation: validate_global_pricing_limits
All date_time_conditions are validated against restaurant’s global pricing settings:
- Increase limit: Cannot exceed
price_increase_limit_value - Decrease limit: Cannot exceed
price_decrease_limit_value - Unit matching: Value type must match limit type (percentage/fixed_amount)
See: 007-GLOBAL-PRICING-SETTINGS.md
Example Usage
Create Weekend Rule
rule = PricingRule.create!(
restaurant_id: 'rest_123',
name: 'Weekend Special',
rule_type: 'specific_date_time',
priority: 50,
active: false,
requires_approval: true,
start_date: Date.current,
is_no_end_date: true,
apply_to_all_packages: true
)
Query Active Rules
# All active rules for restaurant, ordered by priority
rules = PricingRule
.active_for_restaurant('rest_123')
.by_priority
rules.each do |rule|
puts "#{rule.name} (Priority: #{rule.priority})"
end
Check Rule Validity
rule = PricingRule.find(123)
# Is rule currently valid?
valid = rule.start_date <= Date.current &&
(rule.is_no_end_date || rule.end_date >= Date.current)
API Endpoints
See: 004-API-ENDPOINTS.md
GET /api/partner/v1/pricing_rules- List rulesPOST /api/partner/v1/pricing_rules- Create rulePATCH /api/partner/v1/pricing_rules/:id- Update ruleDELETE /api/partner/v1/pricing_rules/:id- Delete rulePATCH /api/partner/v1/pricing_rules/:id/activate- ActivatePATCH /api/partner/v1/pricing_rules/:id/deactivate- DeactivatePOST /api/partner/v1/pricing_rules/:id/clone- Clone rule
Related Documentation
- 006-DATE-TIME-CONDITIONS.md - Date/time condition details
- 007-GLOBAL-PRICING-SETTINGS.md - Global limits
- 008-ENUM-REFERENCE.md - All enum values