> ## Documentation Index
> Fetch the complete documentation index at: https://spreecommerce.org/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Calculators

## Overview

### Calculator Model Diagram

```mermaid theme={"theme":"night-owl"}
erDiagram
    Calculator {
        string type
        string calculable_type
        text preferences
    }

    TaxRate {
        string name
        decimal amount
        boolean included_in_price
    }

    ShippingMethod {
        string name
        string display_on
    }

    PromotionAction {
        string type
    }

    Adjustment {
        decimal amount
        string label
    }

    TaxRate ||--|| Calculator : "has one"
    ShippingMethod ||--|| Calculator : "has one"
    PromotionAction ||--|| Calculator : "has one"
    TaxRate ||--o{ Adjustment : "creates"
    ShippingMethod ||--o{ ShippingRate : "calculates"
    PromotionAction ||--o{ Adjustment : "creates"
```

**Key relationships:**

* **Calculator** computes amounts for various features
* Used by **[Tax Rates](/developer/core-concepts/taxes)** to calculate tax amounts
* Used by **[Shipping Methods](/developer/core-concepts/shipments)** to calculate shipping costs
* Used by **[Promotion Actions](/developer/core-concepts/promotions)** to calculate discounts
* Calculators store [preferences (rates, percentages, etc.)](/developer/customization/model-preferences) for their calculations

Spree makes extensive use of the `Spree::Calculator` model and there are several subclasses provided to deal with various types of calculations flat rate, percentage discount, sales tax, VAT, etc. All calculators extend the `Spree::Calculator` class and must provide the following methods:

```ruby theme={"theme":"night-owl"}
def self.description
  # Human readable description of the calculator
end

def compute(object=nil)
  # Returns the value after performing the required calculation
end
```

Calculators link to a `calculable` object, which are typically one of `Spree::ShippingMethod`, `Spree::TaxRate`, or `Spree::Promotion::Actions::CreateAdjustment`. These three classes use the `Spree::CalculatedAdjustments` module described below to provide an easy way to calculate adjustments for their objects.

## Available Calculators

The following are descriptions of the currently available calculators in Spree. If you would like to add your own, please see the [Creating a New Calculator](#creating-a-new-calculator) section.

### Default Tax

For information about this calculator, please read the [Taxes](/developer/core-concepts/taxes) guide.

### Flat Percent Per Item Total

This calculator has one preference: `flat_percent` and can be set like this:

```ruby theme={"theme":"night-owl"}
calculator.preferred_flat_percent = 10
```

This calculator takes an order and calculates an amount using this calculation:

```ruby theme={"theme":"night-owl"}
[item total] x [flat percentage]
```

For example, if an order had an item total of `$31` and the calculator was configured to have a flat percent amount of `10`, the discount would be `$3.10`, because `$31 x 10% = $3.10`.

### Flat Rate

This calculator can be used to provide a flat rate discount.

This calculator has two preferences: `amount` and `currency`. These can be set like this:

```ruby theme={"theme":"night-owl"}
calculator.preferred_amount = 10
calculator.preferred_currency = "USD"
```

The currency for this calculator is used to check to see if a shipping method is available for an order. If an order's currency does not match the shipping method's currency, then that shipping method will not be displayed on the frontend.

This calculator can take any object and will return simply the preferred amount.

### Flexi Rate

This calculator is typically used for promotional discounts when you want a specific discount for the first product, and then subsequent discounts for other products, up to a certain amount.

This calculator takes three preferences:

* `first_item`: The discounted price of the first items.
* `additional_item`: The discounted price of subsequent items.
* `max_items`: The maximum number of items this discount applies to.

The calculator computes based on this:

```text theme={"theme":"night-owl"}
[first item discount] + (([items_count*] - 1) x [additional item discount])
```

* up to the `max_items`

Thus, if you have ten items in your shopping cart, your `first_item` preference is set to `$10`, your `additional_items` preference is set to `$5`, and your `max_items` preference is set to `4`, the total discount would be `$25`:

* `$10` for the first item
* `$5` for each of the `3` subsequent items: `$5 \* 3 = $15`
* `$0` for the remaining `6` items

### Per Item

The Per Item calculator (`Spree::Calculator::Shipping::PerItem`) is a shipping calculator that charges a flat amount for every item in a shipment.

This calculator takes two preferences:

* `amount`: The flat amount charged per item.
* `currency`: The currency for this calculator.

It computes a flat rate per item by multiplying the `amount` preference by the total item quantity in the shipment package:

```text theme={"theme":"night-owl"}
[amount] x [total item quantity in package]
```

For example, with an `amount` of `5` and a package containing `3` items in total, the calculator computes an amount of `15` (`5 x 3`).

### Percent Per Item

The Percent Per Item calculator (`Spree::Calculator::PercentOnLineItem`) applies a percentage discount to a single line item. It takes two preferences:

* `percent`: The percentage to apply to the line item's amount.
* `apply_only_on_full_priced_items`: When enabled, skips line items that are already on sale.

For each line item, the calculator computes `line item amount` x `percent` / `100`, capped at the line item's amount so a promotion adjustment never pushes the total negative.

For example, a `$30` line item at `10%` yields a `$3` discount (`$30 x 10% = $3`).

### Price Sack

The Price Sack calculator is useful for when you want to provide a discount for an order which is over a certain price. The calculator has four preferences:

* `minimal_amount`: The minimum amount for the line items total to trigger the calculator.
* `discount_amount`: The amount to discount from the order if the line items total is equal to or greater than the `minimal_amount`.
* `normal_amount`: The amount to discount from the order if the line items total is less than the `minimal_amount`.
* `currency`: The currency for this calculator. Defaults to the store currency

Suppose you have a Price Sack calculator with a `minimal_amount` preference of `$50`, a `normal_amount` preference of `$2`, and a `discount_amount` of `$5`. An order with a line items total of `$60` would result in a discount of `$5` for the whole order. An order of `$20` would result in a discount of `$2`.

## Creating a New Calculator

To create a new calculator for Spree, you need to do two things. The first is to inherit from the `Spree::Calculator` class and define `description` and `compute` methods on that class:

```ruby theme={"theme":"night-owl"}
class CustomCalculator < Spree::Calculator
  def self.description
    # Human readable description of the calculator
  end

  def compute(object=nil)
    # Returns the value after performing the required calculation
  end
end
```

If you are creating a new calculator for shipping methods, please be aware that you need to inherit from `Spree::ShippingCalculator` instead, and define a `compute_package` method:

```ruby theme={"theme":"night-owl"}
class CustomCalculator < Spree::ShippingCalculator
  def self.description
    # Human readable description of the calculator
  end

  def compute_package(package)
    # Returns the value after performing the required calculation
  end
end
```

The second thing is to register this calculator as a tax, shipping, or promotion adjustment calculator by calling code like this at the end of `config/initializers/spree.rb` inside your application `config` variable defined for brevity:

<Tabs>
  <Tab title="Spree 5.2+">
    ```ruby config/initializers/spree.rb theme={"theme":"night-owl"}
    Rails.application.config.after_initialize do
      Spree.calculators.tax_rates << CustomCalculator
      Spree.calculators.shipping_methods << CustomCalculator
      Spree.calculators.promotion_actions_create_adjustments << CustomCalculator
    end
    ```
  </Tab>

  <Tab title="Spree 5.1 and below">
    ```ruby config/initializers/spree.rb theme={"theme":"night-owl"}
    Rails.application.config.spree.calculators.tax_rates << CustomCalculator
    Rails.application.config.spree.calculators.shipping_methods << CustomCalculator
    Rails.application.config.spree.calculators.promotion_actions_create_adjustments << CustomCalculator
    ```
  </Tab>
</Tabs>

For example if your calculator is placed in `app/models/spree/calculator/shipping/my_own_calculator.rb` you should call:

<Tabs>
  <Tab title="Spree 5.2+">
    ```ruby config/initializers/spree.rb theme={"theme":"night-owl"}
    Rails.application.config.after_initialize do
      Spree.calculators.shipping_methods << Spree::Calculator::Shipping::MyOwnCalculator
    end
    ```
  </Tab>

  <Tab title="Spree 5.1 and below">
    ```ruby config/initializers/spree.rb theme={"theme":"night-owl"}
    Rails.application.config.spree.calculators.shipping_methods << Spree::Calculator::Shipping::MyOwnCalculator
    ```
  </Tab>
</Tabs>

### Determining Availability

By default, all shipping method calculators are available at all times. If you wish to make this dependent on something from the order, you can re-define the `available?` method inside your calculator:

```ruby app/models/custom_calculator.rb theme={"theme":"night-owl"}
class CustomCalculator < Spree::Calculator
  def available?(object)
    object.currency == "USD"
  end
end
```

## Calculated Adjustments

If you wish to use Spree's calculator functionality for your own application, you can include the `Spree::CalculatedAdjustments` module into a model of your choosing.

```ruby app/models/plan.rb theme={"theme":"night-owl"}
class Plan < ActiveRecord::Base
  include Spree::CalculatedAdjustments
end
```

To have calculators available for this class, you will need to register them. `Spree.calculators` is a fixed-member struct (`SpreeCalculators`, defined in `spree/core/lib/spree/core/engine.rb`) that exposes only the four built-in buckets:

* `shipping_methods`
* `tax_rates`
* `promotion_actions_create_adjustments`
* `promotion_actions_create_item_adjustments`

`Plan.calculators` internally calls `Spree.calculators.send(:plans)` (the tableized model name), so both registration and lookup raise `NoMethodError` until you extend the struct to add a matching `plans` member. Ruby structs cannot gain members at runtime, so this means redefining the `SpreeCalculators` struct (or reassigning `Spree.calculators` to an object that responds to `plans`) before you register anything onto it.

Once the struct exposes a `plans` member you can register calculators:

<Tabs>
  <Tab title="Spree 5.2+">
    ```ruby config/initializers/spree.rb theme={"theme":"night-owl"}
    Rails.application.config.after_initialize do
      Spree.calculators.plans << CustomCalculator
    end
    ```
  </Tab>

  <Tab title="Spree 5.1 and below">
    ```ruby config/initializers/spree.rb theme={"theme":"night-owl"}
    Rails.application.config.spree.calculators.plans << CustomCalculator
    ```
  </Tab>
</Tabs>

Then you can access these calculators by calling this method:

```ruby theme={"theme":"night-owl"}
Plan.calculators
```

Using this method, you can then display the calculators as you please. Each object for this new class will need to have a calculator associated so that adjustments can be calculated on them.

`Spree::CalculatedAdjustments` provides a `has_one :calculator` association (with `accepts_nested_attributes_for` and a presence validation), delegates `compute` to that calculator, exposes a `with_calculator` scope, `calculator_type` / `calculator_type=` accessors, and the `Plan.calculators` registry shown above. To work out what the calculator would compute an amount to be, call `compute` on an instance:

```ruby theme={"theme":"night-owl"}
plan.compute(<calculable object>)
```

The module does not define `create_adjustment`, `update_adjustment`, or `compute_amount`. If you also need to build adjustments, include `Spree::AdjustmentSource`, which adds `create_adjustment(order, adjustable, included = false)`. That method calls a `compute_amount` you define on the including model (as `Spree::TaxRate` and the `Spree::Promotion::Actions` classes do). There is no `update_adjustment` method in core.

## Related Documentation

* [Adjustments](/developer/core-concepts/adjustments) — the records calculators compute amounts for.
* [Dependencies](/developer/customization/dependencies) — swap out calculators via the Dependencies system.
