Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

FieldTypeDescription
restaurant_idStringRestaurant identifier (required)
nameStringRule name (required)
rule_typeStringType of rule (required, see Rule Types)
priorityIntegerExecution priority (0+, higher = earlier)
activeBooleanWhether rule is enabled (default: false)
requires_approvalBooleanWhether changes need approval (default: false)
start_dateDateWhen rule becomes valid
end_dateDateWhen rule expires (null if is_no_end_date = true)
is_no_end_dateBooleanRule never expires (default: false)
apply_to_all_packagesBooleanApply to all packages (default: true)
date_typeStringDate format type (for specific_date_time rules)

Rule Types

Defined in: RULE_TYPES constant

TypeStatusDescription
specific_date_time✅ ActiveDate & time based pricing
demand_capacity🔜 FutureCapacity-based dynamic pricing
performance_based🔜 FuturePerformance metric rules
advance_last_minute🔜 FutureBooking window rules

Current Implementation: Only specific_date_time is fully implemented.

Date Types

Defined in: DATE_TYPES constant (for specific_date_time rules)

TypeDescription
specific_dateOnly date fields (start_date, end_date)
specific_date_timeBoth 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 present
  • name: Must be present
  • rule_type: Must be present and one of RULE_TYPES
  • priority: Must be integer >= 0

Optional Validations

  • date_type: Must be one of DATE_TYPES if provided
  • end_date: Must be after start_date if both present
  • end_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):

  1. Higher priority = Executed first
  2. Tie-breaking: Newer rules (created_at DESC)
  3. Range: 0 to unlimited (integer)
  4. 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 applying
  • false: 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 rules
  • POST /api/partner/v1/pricing_rules - Create rule
  • PATCH /api/partner/v1/pricing_rules/:id - Update rule
  • DELETE /api/partner/v1/pricing_rules/:id - Delete rule
  • PATCH /api/partner/v1/pricing_rules/:id/activate - Activate
  • PATCH /api/partner/v1/pricing_rules/:id/deactivate - Deactivate
  • POST /api/partner/v1/pricing_rules/:id/clone - Clone rule