diff --git a/guides/shipments/cartons.md b/guides/shipments/cartons.md new file mode 100644 index 00000000000..d442d44017f --- /dev/null +++ b/guides/shipments/cartons.md @@ -0,0 +1,42 @@ +# Cartons + +The `Spree::Carton` model represents how an order was shipped. For stores that +use third-party logistics or complicated warehouse workflows, the shipment +described when the order is confirmed may not be how the _actual_ shipment is +packaged when it leaves its destination. + +## Example of usage + +**A customer orders two t-shirts.** + +Solidus creates one `Spree::Shipment` object, which is used to ship both +t-shirts in the order as one package. + +**The customer is charged for a single shipment.** + +Your t-shirt warehouse receives the shipping instructions for the new order. +However, they realize that one of the t-shirts is currently backordered. They +cannot send both t-shirts in a single package. + +**The warehouse ship the first of two packages, which is the first of two +t-shirts.** + +However, you do not want to charge the customer for a second shipment. Your +inventory was not accurate, and your store is paying for the additional package. + +If you split the shipment into two `Spree::Shipment` objects, the customer would +be charged shipping for both packages. + +To avoid an extra shipping charge, you can instead create additional +`Spree::Carton` objects for every package sent out of the warehouse. This way, +the shipment object stays unique and the order total does not change. + +**The warehouse ships the second of two packages.** + +This second package is associated with the order as another `Spree::Carton` +object. Although it is shipped as a separate package, the customer is not +charged for another shipment. + + diff --git a/guides/shipments/custom-shipping-calculators.md b/guides/shipments/custom-shipping-calculators.md new file mode 100644 index 00000000000..ca88290c466 --- /dev/null +++ b/guides/shipments/custom-shipping-calculators.md @@ -0,0 +1,79 @@ +# Custom shipping calculators + +This article provides context about creating custom shipping calculators in the +case that the provided calculators do not meet your store's needs. + +## Pre-configured calculators + +Before developing a custom calculator, you should make sure that the calculator +you need doesn't already exist in Solidus or one of its extensions. + +Solidus comes with a set of default calculators that account for typical +shipping scenarios: + +- [Flat percent](https://github.com/solidusio/solidus/blob/master/core/app/models/spree/calculator/shipping/flat_percent_item_total.rb) +- [Flat rate (per order)](https://github.com/solidusio/solidus/blob/master/core/app/models/spree/calculator/shipping/flat_rate.rb) +- [Flat rate per package item](https://github.com/solidusio/solidus/blob/master/core/app/models/spree/calculator/shipping/per_item.rb) +- [Flexible rate per package item](https://github.com/solidusio/solidus/blob/master/core/app/models/spree/calculator/shipping/flexi_rate.rb) +- [Price sack](https://github.com/solidusio/solidus/blob/master/core/app/models/spree/calculator/shipping/price_sack.rb) + +If the calculators that come with Solidus are not enough for your needs, you +might want to use an extension like +[`solidus_active_shipping`][solidus-active-shipping] that provides additional +API-based rate calculation functionality for common carriers like UPS, USPS, and +FedEx. Alternatively, you could develop your own custom calculator. + +[solidus-active-shipping]: solidus-active-shipping-extension.md + +## Custom calculator requirements + +A custom calculator should accept a `Spree::Stock::Package` (a `package`) and +return a cost. + +Use the `package.order` method to access the current order's information, and +the `package.contents` methods to access the current package's contents. As a +developer, you should always deal with the `package.contents`. Otherwise, you +may be quoting an entire order when you only want to quote one of many shipments +on an order. + + + +Typically, a calculator uses the following order information: + +- The `Spree::Address` used as the order's shipping address. +- The `Spree::LineItem` objects associated with the order. +- The `Spree::Variant` product information (such as the weight and dimensions) + associated with each line item in the order. + +For an example of a typical calculator, we recommend reading the source code for +Solidus's [stock flat rate calculator][flat-rate-source]. For a more complicated +example of what is possible with custom calculators, see the +[`solidus_active_shipping` base calculator][base-calculator-source]. This +calculator collects enough information about an order to send to a carrier and +get a rate quote back. + +[flat-rate-source]: https://github.com/solidusio/solidus/blob/master/core/app/models/spree/calculator/shipping/flat_rate.rb +[base-calculator-source]: https://github.com/solidusio-contrib/solidus_active_shipping/blob/master/app/models/spree/calculator/shipping/active_shipping/base.rb + +## Use additional product information + +In addition to providing relevant information about shipping addresses and +product variants, you can use information about the product itself to inform a +calculator's results. A product's tax category or shipping category could be +meaningful information for shipping calculations. By default, each package +contains only items in the same shipping category. + +For example, you might want your calculator to handle your product with a +shipping category of "Oversized" differently than it would a product with the +"Default" shipping category. + + + diff --git a/guides/shipments/overview-of-shipments.md b/guides/shipments/overview-of-shipments.md new file mode 100644 index 00000000000..04d095f4fb7 --- /dev/null +++ b/guides/shipments/overview-of-shipments.md @@ -0,0 +1,186 @@ +# Overview of shipments + +Solidus uses a flexible system to calculate shipping. It accommodates the full +range of shipment pricing: from simple [flat +rate](https://en.wikipedia.org/wiki/Flat_rate) calculations to more +complex calculations related to product weight or type, the customer's shipping +address, what shipping method is being used, and so on. + +If your store has complex shipping needs, you may find one of Solidus's existing +shipping extensions, like [`solidus_active_shipping`][solidus-active-shipping] +or [`solidus_shipstation`][solidus-shipstation], useful. Check out [the list of +supported Solidus Extensions](https://extensions.solidus.io). + +This article provides a summary of shipping concepts. If you are interested in +reading about example Solidus shipment setups see +[Shipment setup examples](shipment-setup-examples.md). + + + +[solidus-active-shipping]: solidus-active-shipping-extension.md +[solidus-shipstation]: https://github.com/boomerdigital/solidus_shipstation + +## Shipment attributes + +The `Spree::Shipment` model tracks how items should be delivered to the +customer. Developers may be interested in the following attributes: + +- `number`: The unique identifier for this shipment. It begins with the letter + `H` and ends in an 11-digit number. This number is visible to customers, and + it can be used to find the order (by calling `Spree::Shipment.find_by(number: + H12345678910)`). +- `tracking`: The identifier given for the shipping provider (such as FedEx or + UPS). +- `shipped_at`: The time when the shipment is shipped. +- `state`: The current state of the shipment. See [Shipping + states](#shipping-states) for more information. +- `stock_location_id`: The ID of the stock location where the items for this + shipment are sourced from. +- `adjustment_total`: The sum of the promotion and tax adjustments on the + shipment. +- `additional_tax_total`: The sum of U.S.-style sales taxes on the shipment. +- `promo_total`: The sum of the promotions on the shipment. +- `included_tax_total`: The sum of the VAT-style taxes on the shipment. +- `cost`: The estimated shipping cost (for the selected shipping method). +- `order_id`: The ID for the order that the shipment belongs to. + + + +### Shipping states + +Each shipment is assigned a `state` attribute. Depending on its state, different +actions can be performed on shipments. There are four possible states: + +- `pending`: The shipment has backordered inventory units and/or the order is + not paid for. +- `ready`: The shipment has no backordered inventory units and the order is paid + for. +- `shipped`: The shipment has left the stock location. +- `canceled`: When an order is cancelled, all of its shipments will also be + cancelled. When this happens, all items in the shipment will be restocked. If + an order is "resumed", then the shipment will also be resumed. + +## Core concepts + +To leverage Solidus's shipping system, become familiar with its key concepts: + +- Stock locations +- Shipping methods +- Zones +- Shipping categories +- Shipping calculators +- Shipping rates +- Inventory units +- Cartons + +### Stock locations + +Stock locations represent physical storage locations from which stock is +shipped. + +### Shipping methods + +Shipping methods identify the actual delivery services used to ship the +product. For example: + +- UPS Ground +- UPS One Day +- FedEx 2Day +- FedEx Overnight +- DHL International + +Each shipping method is only applicable to a specific geographic +zone. For example, you wouldn't be able to get a package delivered +internationally using a domestic-only shipping method. You can't ship from +Dallas, USA, to Rio de Janeiro, Brazil, using UPS Ground, which is a United +States-only carrier. + +### Shipping categories + +You can further restrict when shipping methods are available to customers by +using shipping categories. If you assign two products to two different shipping +categories, you could ensure that these items are always sent as separate +shipments. + +For example, if your store can only ship oversized products via a specific +carrier, called "USPS Oversized Parcels", then you could create a shipping +category called "Oversized" for that shipping method, which can then be assigned +to oversized products. + +Shipping categories are created in the admin interface (**Settings -> Shipping +-> Shipping Categories**) and then assigned to products (**Products -> Edit**). + +### Zones + +Zones serve as a mechanism for grouping distinct geographic areas together. + +The shipping address entered during checkout defines the zone (or zones) for the +order. The zone limits the available shipping methods for the order. It also +defines regional taxation rules. + + + +### Shipping calculators + +A shipping calculator is the component responsible for calculating the shipping +rate for each available shipping method. + +Solidus ships with five default shipping calculators: + +- [Flat percent](https://github.com/solidusio/solidus/blob/master/core/app/models/spree/calculator/shipping/flat_percent_item_total.rb) +- [Flat rate (per order)](https://github.com/solidusio/solidus/blob/master/core/app/models/spree/calculator/shipping/flat_rate.rb) +- [Flat rate per package item](https://github.com/solidusio/solidus/blob/master/core/app/models/spree/calculator/shipping/per_item.rb) +- [Flexible rate per package item](https://github.com/solidusio/solidus/blob/master/core/app/models/spree/calculator/shipping/flexi_rate.rb) +- [Price sack](https://github.com/solidusio/solidus/blob/master/core/app/models/spree/calculator/shipping/price_sack.rb) + (which offers variable shipping cost that depends on order total) + +If you want to estimate shipping rates using carrier APIs, You can use an +extension like [`solidus_active_shipping`][solidus-active-shipping]. Or, if you +have other complex needs, you can create a [custom shipping +calculators][custom-shipping-calculators] for more information. + +[custom-shipping-calculators]: custom-shipping-calculators.md + +### Shipping rates + +For each shipment, a `Spree::ShippingRate` object is created for each of your +store's shipping methods. These objects represent the cost of the shipment as +calculated by each shipping method's calculator. + +| `Spree::ShippingRate` | 1 | 2 | 3 | Description | +|-----------------------|-------|-------|-------|-----------------------------------------------------| +| `shipment_id` | 1 | 1 | 1 | All of these rates are for one shipment | +| `shipping_method_id` | 1 | 2 | 3 | Each available shipping method | +| `selected` | false | true | false | Set to `true` for the selected shipping method only | +| `cost` | $0.50 | $1.75 | $1.25 | The shipment's shipping rate | + +Once the shipping method has been chosen, the matching `Spree::ShippingRate`'s +`selected` key becomes `true`. Only one shipping rate can be `selected` for each +shipment. + +### Inventory units + +A `Spree::InventoryUnit` is created for each item in a shipment. It tracks +whether the item has shipped, what product variant the item is, and what order +and shipment the item is associated with. + + + +### Cartons + +The `Spree::Carton` model represents how an order was shipped. For stores that +use third-party logistics or complicated warehouse workflows, the shipment +described when the order is confirmed may not be how the _actual_ shipment is +packaged when it leaves its destination. + +For more information, see the [Cartons](cartons.md) article. diff --git a/guides/shipments/shipment-setup-examples.md b/guides/shipments/shipment-setup-examples.md new file mode 100644 index 00000000000..0ce5c2b0340 --- /dev/null +++ b/guides/shipments/shipment-setup-examples.md @@ -0,0 +1,171 @@ +# Shipment setup examples + +Your store's shipping setup may range from a simple, singular shipping rate to +a set of more specific shipping categories and rates. + +## Simple setup + +In this example setup, you run a store that sells t-shirts to the United States +and Europe and ship from a single location. You work with two carriers: + +- USPS Ground (to the US) +- FedEx (to Europe) + +Because you only sell t-shirts, you can easily anticipate the shipping costs of +any order. Instead of using live shipping calculator to estimate each order's +shipping costs, you offer a flat rate to your customers and pay the carriers +directly. + +Your pricing is as follows: + +- **For USPS shipments**: $5 for the first item. Then, $2 for each additional + item. (Flexible rate per package item.) +- **For FedEx shipments**: $3 for each item, regardless of the quantity. (Flat + rate per package.) + +To achieve this setup you need the following configuration: + +- **One shipping category**: In this example, you only sell type of product + (t-shirts), so you only need to define one default shipping category. Each + product would use this shipping category. +- **One stock location**: At least one stock location is required. You are + shipping all items from the same location, so you can use the default stock + location. +- **Two shipping methods**: In this case, USPS and FedEx. +- **Two zones**: Because your shipping methods are bound to specific + geographical regions, you can assign one zone for the United States and + another for Europe. This way, customers from either region cannot + mistakenly choose the wrong shipping method for their region. + +For this example, the shipping methods should be configured as follows: + +| Name | Zones | Base calculator | +|--------------|-----------|-----------------------------------------| +| USPS Ground | `US` | Flexible rate per package item ($5, $2) | +| FedEx | `EU_VAT` | Flat rate per package ($3) | + +### Summary + +- Any shipment to a United States address is automatically charged for USPS + Ground shipping. +- Any shipment to a European address is automatically charged for FedEx + shipping. +- The customer does not need to pick a shipping method. +- The customer can see the shipping rates for the shipping method they have been + assigned. +- If the customer is outside of the United States or Europe they will not be + able to complete their order. + +## Advanced setup + +In this example setup, you run a store that sells t-shirts, mugs, and kayaks. +You only sell within the United States, and you ship orders from two different +stock locations (New York City, and Los Angeles). + +### Zones + +Because you only sell within the United States, you only need to set up a single +zone for the United States. + +If a customer tried to enter a shipping address that is outside of the United +States, they will not be able to complete their order. + +### Stock locations + +You need to set up two stock locations, which will help you keep track of your +inventory and prepare for shipments. + +In some cases, multiple shipments might be required for a single order. For +example, if one product in the order is only available from your Los Angeles +location, while all the other items are only available from the New York City +location. + +### Carriers and shipping methods + +You use three different carriers: FedEx, UPS, and USPS. You use +[`solidus_active_shipping`][solidus-active-shipping-repo] to get shipping +estimates from all three carriers. (When you have the +`solidus_active_shipping` extension installed and set up, you can select a +specific shipping estimate calculator for any supported shipping method.) + +In order to get estimates, you require developer access to those shipping +services. (This article does not go into detail about using +`solidus_active_shipping`. See [its +documentation][solidus-active-shipping-readme] for more information about +usage.) + +[solidus-active-shipping-repo]: https://github.com/solidusio-contrib/solidus_active_shipping +[solidus-active-shipping-readme]: https://github.com/solidusio-contrib/solidus_active_shipping/blob/master/README.md + +Note that three carriers does not mean you will only be setting up three +shipping methods. Oversized items require a different shipping method, even +though they use the same carrier. + +In this case you will set up four shipping methods: FedEx, UPS, USPS, and an +additional FedEx shipping method specifically for oversized items. + +### Shipping categories + +Your store's products can be classified into three shipping categories: + +- **Default**: For any product that can be shipped using your regular shipping + methods. +- **Oversized**: For large, heavy items like kayaks that have special shipping + needs. + +For most products, you just use the default shipping category. This means that +the products can be shipped by any carrier. Products that are assigned to the +**Oversized** shipping category can only be shipped via FedEx. + +However, because the items are oversized and have special shipping requirements, +you need to create an additional shipping method that better takes into account +how an order with oversized items should be handled. For example, how should a +customer be charged when they order multiple kayaks? + +### Shipping configuration + +Now, we have detailed the shipping configuration required in this example. To +summarize the information above: + +- **One zone**: The United States. +- **Two stock locations**: Now York City and Los Angeles. +- **Two shipping categories**: Default and Oversized. +- **Four shipping methods**: FedEx, UPS, and USPS, and a FedEx variant for + oversized items. + +Because you will use the `solidus_active_shipping` extension to create shipping +estimates, you do not to set your own shipping rates. + +When you set up each shipping location, you can choose which shipping categories +are available to each method: + +| | Default | Oversized | +|-----------------------|---------|-----------| +| FedEx | Yes | No | +| FedEx (Oversized) | No | Yes | +| UPS | Yes | No | +| USPS | Yes | No | + +Now, you have ensured that any time that one of your oversized kayak products +are ordered, the customer can be charged reasonably for special oversized +shipping specifically—while regular products can be delivered at normal rate. + +You have determined that the "FedEx 2 Day Freight" method that FedEx offers +gives you and your customers the best value for kayak shipments. When you set up +your "FedEx (Oversized)" shipping method, you can use "FedEx 2 Day Freight" as +the base calculator thanks to the `solidus_active_shipping` extension. + +### Summary + +- The customer can pick from a FedEx, UPS, or USPS shipping method for regular + items like t-shirts and mugs. +- The customer who orders a kayak is assigned your special oversized shipping + method. +- All of your shipping charges are automatically estimated using the + `solidus_active_shipping` extension and your developer accounts with FedEx, + UPS, and USPS. +- The customer can see the shipping rates for the shipping method they have + chosen or been assigned. +- If the customer is outside of the United States they will not be able to + complete their order. + diff --git a/guides/shipments/shipping-method-filters.md b/guides/shipments/shipping-method-filters.md new file mode 100644 index 00000000000..1cc753cf07a --- /dev/null +++ b/guides/shipments/shipping-method-filters.md @@ -0,0 +1,72 @@ +# Shipping method filters + +While shipping method filters occur automatically, your store may require +additional shipping method filtering. + +## Default behavior + +Shipping methods are automatically filtered by zone. This means that once the +customer provides a shipping address during checkout, the shipping methods that +the customer can choose from are limited to the shipping methods available in +their region. + +For example, if your store does not have a zone that includes the United +Kingdom, then a customer cannot complete the checkout process if their shipping +address is within the United Kingdom. + +## Shipping method availability + +The base `Spree::Calculator`'s `available?` method returns `true` by default. + + + + +You can see the `shipping_methods` being filtered out in the +`Spree::Stock::Estimator` class below: + +```ruby +class Spree::Stock::Estimator + def shipping_methods(package) + shipping_methods = package.shipping_methods + shipping_methods.delete_if { |ship_method| !ship_method.calculator.available?(package.contents) } + shipping_methods.delete_if { |ship_method| !ship_method.include?(order.ship_address) } + shipping_methods.delete_if { |ship_method| !(ship_method.calculator.preferences[:currency].nil? || ship_method.calculator.preferences[:currency] == currency) } + shipping_methods + end +end +``` + + + +## Add additional criteria to filter by + +Consider a custom "USPS Bogus First Class International" delivery service. This +service is only available if the shipment weighs less than 10lbs. + +We want to ensure that "USPS Bogus First Class International" shipping is not +available when orders weigh more than 10lbs. So, we want to set our shipping +calculator's `available?` method to `false` if the collective weight of an +order's line items are greater than 10lbs: + +```ruby +class Calculator::Usps::FirstClassMailParcels < Calculator::Usps::Base + def self.description + "USPS Bogus First Class International" + end + + def available?(package) + multiplier = 1.3 + weight = order.line_items.inject(0) do |weight, line_item| + weight + (line_item.variant.weight ? (line_item.quantity * line_item.variant.weight * multiplier) : 0) + end + # If weight in oz > 13, then "USPS Bogus First Class International" is not available for the order + weight > 13 ? false : true + end +end +``` diff --git a/guides/shipments/solidus-active-shipping-extension.md b/guides/shipments/solidus-active-shipping-extension.md new file mode 100644 index 00000000000..b8e96f7cdca --- /dev/null +++ b/guides/shipments/solidus-active-shipping-extension.md @@ -0,0 +1,160 @@ +# `solidus_active_shipping` extension + +The [`solidus_active_shipping`][repo] extension wraps Shopify's popular +[`active_shipping`](http://shopify.github.io/active_shipping/) gem to interface +with carrier APIs (including USPS, FedEx, and UPS). This extension provides +Solidus-compatible shipping calculators for the delivery services offered by +supported carriers. This means that you can offer your customers accurate +shipping estimates for their orders before checkout. + +[repo]: https://github.com/solidusio-contrib/solidus_active_shipping + +Throughout this article, we will demonstrate usage of the +`solidus_active_shipping` extension with USPS delivery services. The other +carriers supported by this extension would follow a similar pattern. + +## Install `solidus_active_shipping` + +Follow the installation instructions provided by the `solidus_active_shipping` +extension.You can get more detailed information on installation from [the +extension's documentation][readme]. + +[readme]: https://github.com/solidusio-contrib/solidus_active_shipping/blob/master/README.md + +Now, you will be able to use any of the pre-configured shipping calculators that +have been built into the extension. + +## Add required configuration variables for web services + +In addition to installing the extension, we need to authenticate with any +developer accounts we have with carriers. Administrators can add authentication +keys from the `/admin/active_shipping_settings` page (**Settings -> Stores -> +Active Shipping Settings** in the admin). + +You can also set the keys from any initializer in your application. For example, +you could create a new file, `config/initializers/active_shipping.rb`, with the +contents: + +```ruby +Rails.application.config.after_initialize do + Spree::ActiveShipping::Config.set(:usps_login => "your-developer-key") +end +``` + +However, note that after the application has been initialized, any changes made +in the admin will override the initial value. Then, if the application is +restarted, the value in the initializer would overwrite the value set in the +admin again. + + + +## Add optional configuration variables + +You may also want to add shipping settings specifically for your store. The +following settings are available: + +- Weight units +- Unit multiplier (for unit conversion) +- Default product weight +- Handling fee +- Maximum weight per package + +You can configure these settings from the `admin/active_shipping_settings` page +(**Settings -> Store -> Active Shipping Settings** in the admin) or from any +initializer in your application. For example, you could create a new file, +`config/initializers/active_shipping.rb`, with the contents: + +```ruby +Rails.application.config.after_initialize do + Spree::ActiveShipping::Config[:default_weight] = 3.0 +end +``` + +See [the extension's documentation][readme] for more information about available +configuration settings. + +## Add shipping methods + +When you set up a new shipping method (**Settings -> Shipping -> Shipping +Methods** in the admin), such as "USPS Media Mail", you can choose the +corresponding base calculator, "USPS Media Mail Parcel", from the list of +available calculators. + +Now, once an order has been assigned a shipping method, a shipping estimate can +be provided to the customer before checkout. + +If the delivery service you wish to use is not built into the extension (for +example, a delivery service called "USPS Bogus First Class International"), it +can be easily added as an additional calculator. See [Add additional shipping +calculators](#add-additional-shipping-calculators) for more information. + +## Access pre-configured calculators + +The `solidus_active_shipping` extension comes with pre-configured shipping +calculators. Administrators can access these calculators when adding new +shipping methods by picking them from the "Base Calculator" drop-down menu. + +You can extend or override the pre-configured calculator classes, such as +`Spree::Calculator::Shipping::Usps::GlobalExpressGuaranteed`. + +You can see all of the extension's included shipping calculators in the list +of [the extension's shipping models][models]. + +[models]: https://github.com/solidusio-contrib/solidus_active_shipping/tree/master/app/models/spree/calculator/shipping + +## Add additional shipping calculators + +Additional delivery services that are not pre-configured as a calculator in the +extension can be easily added. + +### Inherit from an existing service's base class + +For example, you need to a delivery service called "Bogus First Class +International" from USPS, you can add a new calculator class that inherits from +the `Spree::Calculator::Shipping::Usps::Base` class: + +```ruby +module Spree + module Calculator::Shipping + module Usps + class BogusFirstClassInternational < Spree::Calculator::Shipping::Usps::Base + self.description + "USPS Bogus First Class International" + end + end + end + end +end +``` + +Unlike shipping calculators that you write yourself, these calculators inherit +from the existing superclasses built into `solidus_active_shipping` and do not +require a `compute` instance method that returns a shipping amount. + +### Match the delivery service's provided name + +The string returned by the `description` method must match the name of the USPS +delivery service _exactly_. To determine the exact spelling, you should examine +what the USPS API returns. + + + +### Register the new calculator + +Finally, register the calculator you added. In extensions, this is accomplished +with the `activate` method: + +```ruby +def activate + Spree::Calculator::Shipping::Usps::BogusFirstClassInternational.register +end +``` + diff --git a/guides/shipments/split-shipments.md b/guides/shipments/split-shipments.md new file mode 100644 index 00000000000..91ecabd660a --- /dev/null +++ b/guides/shipments/split-shipments.md @@ -0,0 +1,114 @@ +# Split shipments + +Solidus supports split shipments out of the box. This feature addresses the +needs of complex Solidus stores who require detailed inventory management, and +sophisticated shipping and warehouse logic. It also allows you to manage +shipments from multiple stock locations. + +[`Spree::Stock::SimpleCoordinator`][simple-coordinator] contains all of the +business logic for how stock should be packaged. If your store requires a +specialized flow for handling split shipments, the simple coordinator should +provide a good starting point for customizations. + + + +## Creating proposed shipments + +An order's shipments are determined by +[`Spree::Stock::SimpleCoordinator`][simple-coordinator] while the +`Spree::Order`'s' state is set to `delivery`. This occurs before the customer +has completed their order at checkout. + +The `SimpleCoordinator` takes an order and builds as many shipments as are +necessary to fulfill it. + +[simple-coordinator]: https://github.com/solidusio/solidus/blob/master/core/app/models/spree/stock/simple_coordinator.rb + +The simple coordinator performs a number of tasks in order to create shipment +proposals: + +1. The coordinator checks the availability of the ordered items. +2. Inventory is allocated from available stock to the current order. +3. It splits the order into logical packages based on stock locations and + inventory at those locations. +4. Each package generates a new `Spree::Shipment` object that is associated with + the current order. +5. It estimates the shipping rates for each shipment. + +After the proposed shipments have been determined, the customer can continue the +checkout process and take the order from the `delivery` state to the `payment` +state. + +## Splitters + +In order to split shipments, Solidus runs a series of splitters in sequence. The +first splitter in the sequence takes the array of packages from the order, +splits the order into packages according to its rules, then passes the packages +on to the next splitter in the sequence. + +For each generated shipment, a shipping method can be assigned. + +### Default splitters + +Solidus comes with three built-in splitters: + +- [Backordered splitter][backordered-splitter]: Splits an order based on the + amount of inventory on hand at each stock location. +- [Shipping category splitter][shipping-category-splitter]: Splits an order into + shipments based on a product's shipping categories. This means that each + package only has items that belongs to the same shipping category. +- [Weight splitter][weight-splitter]: Splits an order into shipments based on a + weight threshold. This means that each shipment has a maximum weight: if a new + item is added to the order and it causes a package to go over the weight + threshold, a new shipment is created. Each shipment needs to weigh less than + the threshold. You can set the weight threshold by changing the + `Spree::Stock::Splitter::Weight.threshold` value in an initializer. (It + defaults to `150`.) + +[backordered-splitter]: https://github.com/solidusio/solidus/blob/master/core/app/models/spree/stock/splitter/backordered.rb +[shipping-category-splitter]: https://github.com/solidusio/solidus/blob/master/core/app/models/spree/stock/splitter/shipping_category.rb +[weight-splitter]: https://github.com/solidusio/solidus/blob/master/core/app/models/spree/stock/splitter/weight.rb + +#### Custom splitters + +Note that splitters can be customized. Create you own splitter by inheriting the +`Spree::Stock::Splitter::Base` class. + +For an example of a simple splitter, take a look at Solidus's [weight +splitter][weight-splitter]. This splitter ensures that items that weigh +more than `150` are split into their own shipment. + +After you create your splitter, you need to add it to the array of splitters +that Solidus uses. To do this, add the following to your +`config/initializers/spree.rb` file: + +```ruby +Rails.application.config.spree.stock_splitters << Spree::Stock::Splitter::CustomSplitter +``` + +You can also override the splitters used in Solidus, rearrange them, or +otherwise customize them from the `config/initializers/spree.rb`: + +```ruby +Rails.application.config.spree.stock_splitters = [ + Spree::Stock::Splitter::CustomSplitter, + Spree::Stock::Splitter::ShippingCategory +] +``` + +If you want to add different splitters for each of your `Spree::StockLocation`s, +you can decorate the `Spree::Stock::SimpleCoordinator` class and override the +`splitters` method. + +#### Turn off split shipments + +If you don't want to split packages in any case, you can set the +`config.spree.stock_splitters` option to an empty array: + +```ruby +Rails.application.config.spree.stock_splitters = [] +``` diff --git a/guides/shipments/user-interface-for-shipments.md b/guides/shipments/user-interface-for-shipments.md new file mode 100644 index 00000000000..db68922d3f2 --- /dev/null +++ b/guides/shipments/user-interface-for-shipments.md @@ -0,0 +1,63 @@ +# User interface for shipments + +This article explores important elements of the user interface for shipments. +While Solidus's user interface is entirely customizable, this article is +modelled after a typical Solidus installation, which would use the standard UI +elements present in the `solidus_frontend` and `solidus_backend` gems. + +## Customer-facing UI + +In a typical Solidus store, there is no mention of shipping until checkout. + +After the customer enters the shipping address for their order, the UI displays +any shipping methods that are available for the current order. (Every time that +the order is changed or updated, the available shipping methods would also be +updated.) + +For more information on how shipping methods are filtered, see [Shipping method +filters](shipping-method-filters.html.markdown). + +The customer must choose a shipping method for each shipment before proceeding +to the next stage of the checkout process. + +Once they have reached the order confirmation page, the shipping cost is shown +and included in the order total. + + + +## Administrator-facing UI + +Shipment objects (`Spree::Shipment`) are created during checkout. Multiple +shipments can be associated with a single order. + +Shipments do not have their own dedicated part of the admin UI. Shipments are +viewable and editable from within an order. + +In the admin dashboard, the administrator can update each shipment's shipping +cost and add tracking codes when editing an order. They can also perform other +tasks, such as splitting a single shipment into multiple shipments. They can do +all of this from the "Shipments" tab of any order. + +### Shipping instructions + +The optional configuration setting `Spree::Config[:shipping_instructions]` +controls whether customers can add additional shipping instructions for store +administrators. If enabled, the customer can provide shipping instructions +during checkout. + +If your store uses the `solidus_frontend` gem, the shipping instructions can be +provided [during the checkout's delivery step][shipping-instructions-source]. + +By default, this setting is set to `false`. + +If an order has any shipping instructions attached, the instructions are +displayed in the admin when you edit an order (from the `/admin/orders` URL) on +the "Shipment" tab. + +Note that shipping instructions are attached to **the order** and not just an +individual shipment. + +[shipping-instructions-source]: https://github.com/solidusio/solidus/blob/master/frontend/app/views/spree/checkout/_delivery.html.erb#L91-L96 + diff --git a/guides/shipping.md b/guides/shipping.md deleted file mode 100644 index ffd7eed1ed6..00000000000 --- a/guides/shipping.md +++ /dev/null @@ -1,504 +0,0 @@ -# Shipping - -This guide explains how Solidus represents shipping options and how it calculates expected costs, and shows how you can configure the system with your own shipping methods. After reading it you should know: - -* how shipments and shipping are implemented in Solidus -* how to specify your shipping structure -* how split shipments work -* how to configure products for special shipping treatment -* how to capture shipping instructions - -Solidus uses a very flexible and effective system to calculate shipping, accommodating the full range of shipment pricing: from simple flat rate to complex product-type- and weight-dependent calculations. - -The Shipment model is used to track how items are delivered to the buyer. - -Shipments have the following attributes: - -* `number`: The unique identifier for this shipment. It begins with the letter H and ends in an 11-digit number. This number is shown to the users, and can be used to find the order by calling `Spree::Shipment.find_by(number: number)`. -* `tracking`: The identifier given for the shipping provider (i.e. FedEx, UPS, etc). -* `shipped_at`: The time when the shipment was shipped. -* `state`: The current state of the shipment. -* `stock_location_id`: The ID of the Stock Location where the items for this shipment will be sourced from. -* Other attributes likely of interest to developers: - * `adjustment_total` - * `additional_tax_total` - * `promo_total` - * `included_tax_total` - * `cost` - * `order_id` - -**Needed:** shipment process flow diagram - -An explanation of the different shipment states: - -* `pending`: The shipment has backordered inventory units and/or the order is not paid for. -* `ready`: The shipment has *no* backordered inventory units and the order is paid for. -* `shipped`: The shipment is on its way to the buyer. -* `canceled`: When an order is cancelled, all of its shipments will also be cancelled. When this happens, all items in the shipment will be restocked. If an order is "resumed", then the shipment will also be resumed. - -Explaining each piece of the shipment world inside of Solidus separately and how each piece fits together can be a cumbersome task. Fortunately, using a few simple examples makes it much easier to grasp. In that spirit, the examples are shown first in this guide. - -## Definitions, Design, & Functionality - -To properly leverage Solidus' shipping system's flexibility you must understand a few key concepts: - -* Stock Locations -* Shipping Methods -* Zones -* Shipping Categories -* Calculators (through Shipping Rates) - -### Stock Locations -Stock locations represent physical storage locations from which stock is shipped. - -### Shipping Methods - -Shipping methods identify the actual services or carriers used to ship the product. For example: - -* UPS Ground -* UPS One Day -* FedEx 2Day -* FedEx Overnight -* DHL International - -Each shipping method is only applicable to a specific geographic **Zone**. For example, you wouldn't be able to get a package delivered internationally using a domestic-only shipping method. You can't ship from Dallas, USA to Rio de Janeiro, Brazil using UPS Ground (a US-only carrier). - -If you are using shipping categories, these can be used to qualify or disqualify a given shipping method. - -**Note**: *Shipping methods can have multiple shipping categories assigned to them. This allows the shipping methods available to an order to be determined by the shipping categories of the items in a shipment.* - -### Zones - -Zones serve as a mechanism for grouping geographic areas together into a single entity. You can read all about how to configure and use Zones in the [Zones Guide](addresses#zones). - -The Shipping Address entered during checkout will define the zone the customer is in and limit the Shipping Methods available to him. - -### Shipping Categories - -Shipping Categories are useful if you sell products whose shipping pricing vary depending on the type of product (TVs and Mugs, for instance) or the handling of the product (frigile or large). Shipping categories can be assigned when editing a product. - -**Note:** *For simple setups, where shipping for all products is priced the same (ie. T-shirt-only shop), all products would be assigned to the default shipping category for the store.* - -Some examples of Shipping Categories would be: - -* Light (for lightweight items like stickers) -* Regular -* Heavy (for items over a certain weight) - -Shipping Categories are created in the admin interface (Settings -> Shipping -> Shipping Categories) and then assigned to products (Products -> Edit). - -During checkout, the shipping categories of the products in your order will determine which calculator will be used to price its shipping for each Shipping Method. - -### Shipping Calculators - -A Calculator is the component responsible for calculating the shipping price for each available Shipping Method. - -Solidus ships with 5 default Calculators: - -* Flat percent -* Flat rate (per order) -* Flat rate per package item -* Flexible rate per package item -* Price sack - -Flexible rate is defined as a flat rate for the first product, plus a different flat rate for each additional product. - -You can define your own calculator if you have more complex needs. In that case, check out the Calculators Guide (*this guide has not yet been ported from Spree as of this edit*). - -## Examples - -### Simple Setup - -Consider you sell T-shirts to the US and Europe and ship from a single location, and you work with 2 carriers: - -* USPS Ground (to US) -* FedEx (to EU) - -and their pricing is as follow: - -* USPS charges $5 for one T-shirt and $2 for each additional one -* FedEx charges $10 each, regardless of the quantity - -To achieve this setup you need the following configuration: - -* Shipping Categories: All your products are the same, so you only need to define one default shipping category. Each of your products would then need to be assigned to this shipping category. -* 1 Stock Location: You are shipping all items from the same location, so you can use the default. -* 2 Shipping Methods (Configuration->Shipping Methods) as follows: - -|Name|Zone|Calculator| -|---:|---:|---:| -|USPS Ground|US|Flexi Rate($5,$2)| -|FedEx|EU_VAT|FlatRate-per-item($10)| - -With the above configuration, a customer shipping to the US would see the USPS Ground shipping option presented to them at checkout, while a customer shipping to the EU would see the FedEx option. Shipping rate would be calculatd at checkout according to the calculator rules. - - -### Advanced Setup - -Consider you sell products to a single zone (US) and you ship from 2 locations (Stock Locations): - -* New York -* Los Angeles - -and you work with 3 carriers (Shipping Methods): - -* FedEx -* DHL -* US postal service - -and your products can be classified into 3 Shipping Categories: - -* Light -* Regular -* Heavy - -and their pricing is as follow: - -FedEx charges: - -* $10 for all light items regardless of how many you have -* $2 per regular item -* $20 for the first heavy item and $15 for each additional one - -DHL charges: - -* $5 per item if it's light or regular -* $50 per item if it's heavy - -USPS charges: - -* $8 per item if it's light or regular -* $20 per item if it's heavy - -To achieve this setup you need the following configuration: - -* 4 Shipping Categories: Default, Light, Regular and Heavy -* 3 Shipping Methods (Settings -> Shipping -> Shipping Methods): FedEx, DHL, USPS -* 2 Stock Locations (Settings -> Shipping -> Stock Locations): New York, Los Angeles - -|S. Category / S. Method|DHL|FedEx|USPS| -|---:|---:|---:|---:| -|Light|Per Item ($5)|Flat Rate ($10)|Per Item ($8)| -|Regular|Per Item ($5)|Per Item ($2)|Per Item ($8)| -|Heavy|Per Item ($50)|Flexi Rate($20,$15)|Per Item ($20)| - -## UI - -### What the Customer Sees - -In the standard system, there is no mention of shipping until the checkout phase. - -After entering a shipping address, the system displays the available shipping options and their costs for each shipment in the order. Only the shipping options whose zones include the _shipping_ address are presented. - -The customer must choose a shipping method for each shipment before proceeding to the next stage. At the confirmation step, the shipping cost will be shown and included in the order's total. - -**Note:** *You can enable collection of extra _shipping instructions_ by setting the option `Spree::Config.shipping_instructions` to `true`. This is set to `false` by default. See [Shipping Instructions](#shipping-instructions) below.* - -### What the Order's Administrator Sees - -**Shipment** objects are created during checkout for an order. Initially each records just the shipping method and the order it applies to. The administrator can update the record with the actual shipping cost and a tracking code, and may also (once only) confirm the dispatch. This confirmation causes a shipping date to be set as the time of confirmation. - -## Advanced Shipping Methods - -Solidus comes with a set of calculators that should fit most of the shipping situations that may arise. If the calculators that come with Solidus are not enough for your needs, you might want to use an extension - if one exists to meet your needs - or create a custom one. - -### Extensions - -There are a few Solidus extensions which provide additional shipping capabilities. See the [Solidus Extension List](http://extensions.solidus.io/) for the latest information. - -### Writing Your Own - -For more detailed information, check out the section on [Calculators](calculators). - -Your calculator should accept an array of `LineItem` objects and return a cost. It can look at any reachable data, but typically uses the address, the order and the information from variants which are contained in the line_items. - -### Product & Variant Configuration - -Store administrators can assign products to specific Shipping Categories or include extra information in variants to enable custom calculators to determine results. Weight and dimension information can also be used in the calculator. - -## Shipping Instructions - -The option `Spree::Config[:shipping_instructions]` controls collection of additional shipping instructions. This is turned off (set to `false`) by default. If an order has any shipping instructions attached, they will be shown in an order's shipment admin page and can also be edited at that stage. Observe that instructions are currently attached to the _order_ and not to actual _shipments_. - -## The Active Shipping Extension - -The solidus_active_shipping extension harnesses the active_shipping gem to interface with carrier APIs such as USPS, Fedex and UPS, ultimately providing Solidus-compatible calculators for the different delivery services of those carriers. - -To install the solidus_active_shipping extension add the following to your Gemfile: - -```ruby -gem 'solidus_active_shipping' -gem 'active_shipping', :git => 'git://github.com/Shopify/active_shipping.git' -``` - -and run `bundle install` from the command line. - -As an example of how to use the [solidus_active_shipping extension](https://github.com/solidusio-contrib/solidus_active_shipping) we'll demonstrate how to configure it to work with the USPS API. The other carriers follow a very similar pattern. - -For each USPS delivery service you want to offer (e.g. "USPS Media Mail"), you will need to create a `ShippingMethod` with a descriptive name (Settings -> Shipping -> Shipping Methods) and a `Calculator` (registered in the `active_shipping` extension) that ties the delivery service and the shipping method together. - -### Default Calculators - -The `solidus_active_shipping` extension comes with several pre-configured calculators out of the box. For example, here are the ones provided for the USPS carrier: - -```ruby -def activate - [ - #... calculators for Fedex and UPS not shown ... - Calculator::Usps::MediaMail, - Calculator::Usps::ExpressMail, - Calculator::Usps::PriorityMail, - Calculator::Usps::PriorityMailSmallFlatRateBox, - Calculator::Usps::PriorityMailRegularMediumFlatRateBoxes, - Calculator::Usps::PriorityMailLargeFlatRateBox - ].each(&:register) -end -``` - -Each USPS delivery service you want to make available at checkout has to be associated with a corresponding shipping method. Which shipping methods are made available at checkout is ultimately determined by the zone of the customer's shipping address. The USPS' basic shipping categories are domestic and international, so we'll set up zones to mimic this distinction. We need to set up two zones then - a domestic one, consisting of the USA and its territories; and an international one, consisting of all other countries. - -With zones in place, we can now start adding some shipping methods through the admin panel. The only other essential requirement to calculate the shipping total at checkout is that each product and variant be assigned a weight. - -The `spree_active_shipping` gem needs some configuration variables set in order to consume the carrier web services. - -```ruby - # these can be set in an initializer in your site extension - Spree::ActiveShipping::Config.set(:usps_login => "YOUR_USPS_LOGIN") - Spree::ActiveShipping::Config.set(:fedex_login => "YOUR_FEDEX_LOGIN") - Spree::ActiveShipping::Config.set(:fedex_password => "YOUR_FEDEX_PASSWORD") - Spree::ActiveShipping::Config.set(:fedex_account => "YOUR_FEDEX_ACCOUNT") - Spree::ActiveShipping::Config.set(:fedex_key => "YOUR_FEDEX_KEY") -``` - -### Adding Additional Calculators - -Additional delivery services that are not pre-configured as a calculator in the `spree_active_shipping` extension can be easily added. Say, for example, you need First Class International Parcels via the US Postal Service. - -First, create a calculator class that inherits from `Calculator::Usps::Base` and implements a description class method: - -```ruby -class Calculator::Usps::FirstClassMailInternationalParcels < Calculator::Usps::Base - def self.description - "USPS First-Class Mail International Package" - end -end -``` - -**Note:** *unlike calculators that you write yourself, these calculators do not have to implement a `compute` instance method that returns a shipping amount. The superclasses take care of that requirement.* - -There is one gotcha to bear in mind: the string returned by the `description` method must _exactly_ match the name of the USPS delivery service. To determine the exact spelling of the delivery service, you'll need to examine what gets returned from the API: - -```ruby -class Calculator::ActiveShipping < Calculator - def compute(line_items) - #.... - rates = retrieve_rates(origin, destination, packages(order)) - # the key of this hash is the name you need to match - # raise rates.inspect - - return nil unless rates - rate = rates[self.description].to_f + (Spree::ActiveShipping::Config[:handling_fee].to_f || 0.0) - return nil unless rate - # divide by 100 since active_shipping rates are expressed as cents - - return rate/100.0 - end - - def retrieve_rates(origin, destination, packages) - #.... - # carrier is an instance of ActiveMerchant::Shipping::USPS - response = carrier.find_rates(origin, destination, packages) - # turn this beastly array into a nice little hash - h = Hash[*response.rates.collect { |rate| [rate.service_name, rate.price] }.flatten] - #.... - end -end -``` - -As you can see in the code above, the `solidus_active_shipping` gem returns an array of services with their corresponding prices, which the `retrieve_rates` method converts into a hash. Below is what would get returned for an order with an international destination: - -```ruby -{ - "USPS Priority Mail International Flat Rate Envelope"=>1345, - "USPS First-Class Mail International Large Envelope"=>376, - "USPS USPS GXG Envelopes"=>4295, - "USPS Express Mail International Flat Rate Envelope"=>2895, - "USPS First-Class Mail International Package"=>396, - "USPS Priority Mail International Medium Flat Rate Box"=>4345, - "USPS Priority Mail International"=>2800, - "USPS Priority Mail International Large Flat Rate Box"=>5595, - "USPS Global Express Guaranteed Non-Document Non-Rectangular"=>4295, - "USPS Global Express Guaranteed Non-Document Rectangular"=>4295, - "USPS Global Express Guaranteed (GXG)"=>4295, - "USPS Express Mail International"=>2895, - "USPS Priority Mail International Small Flat Rate Box"=>1345 -} -``` - -From all of the viable shipping services in this hash, the `compute` method selects the one that matches the description of the calculator. At this point, an optional flat handling fee (set via preferences) can be added: - -```ruby -rate = rates[self.description].to_f + (Spree::ActiveShipping::Config[:handling_fee].to_f || 0.0) -``` - -Finally, don't forget to register the calculator you added. In extensions, this is accomplished with the `activate` method: - -```ruby -def activate - Calculator::Usps::FirstClassMailInternationalParcels.register -end -``` - -## Filtering Shipping Methods On Criteria Other Than the Zone - -Ordinarily, it is the zone of the shipping address that determines which shipping methods are displayed to a customer at checkout. Here is how the availability of a shipping method is determined: - -```ruby -class Spree::Stock::Estimator - def shipping_methods(package) - shipping_methods = package.shipping_methods - shipping_methods.delete_if { |ship_method| !ship_method.calculator.available?(package.contents) } - shipping_methods.delete_if { |ship_method| !ship_method.include?(order.ship_address) } - shipping_methods.delete_if { |ship_method| !(ship_method.calculator.preferences[:currency].nil? || ship_method.calculator.preferences[:currency] == currency) } - shipping_methods - end -end -``` - -Unless overridden, the calculator's `available?` method returns `true` by default. It is, therefore, the zone of the destination address that filters out the shipping methods in most cases. However, in some circumstances it may be necessary to filter out additional shipping methods. - -Consider the case of the USPS First Class domestic shipping service, which is not offered if the weight of the package is greater than 13oz. Even though the USPS API does not return the option for First Class in this instance, First Class will appear as an option in the checkout view with an unfortunate value of 0, since it has been set as a Shipping Method. - -To ensure that First Class shipping is not available for orders that weigh more than 13oz, the calculator's `available?` method must be overridden as follows: - -```ruby -class Calculator::Usps::FirstClassMailParcels < Calculator::Usps::Base - def self.description - "USPS First-Class Mail Parcel" - end - - def available?(order) - multiplier = 1.3 - weight = order.line_items.inject(0) do |weight, line_item| - weight + (line_item.variant.weight ? (line_item.quantity * line_item.variant.weight * multiplier) : 0) - end - #if weight in ounces > 13, then First Class Mail is not available for the order - weight > 13 ? false : true - end -end -``` - -## Split Shipments - -### Introduction - -Split shipments is a feature that addresses the needs of complex Solidus stores that require sophisticated shipping and warehouse logic. This includes detailed inventory management and allows for shipping from multiple locations. - -**_Spre Guide had an image here_** - -### Creating Proposed Shipments - -This section steps through the basics of what is involved in determining shipments for an order. There are a lot of pieces that make up this process. They are explained in detail in the [Components of Split Shipments](#components-of-split-shipments) section of this guide. - -The process of determining shipments for an order is triggered by calling `create_proposed_shipments` on an `Order` object while transitioning to the `delivery` state during checkout. This process will first delete any existing shipments for an order and then determine the possible shipments available for that order. - -`create_proposed_shipments` will initially call `Spree::Stock::Coordinator.new(@order).packages`. This will return an array of packages. In order to determine which items belong in which package when they are being built, Spree uses an object called a `Splitter`, described in more detail [below](#the-packer). - -After obtaining the array of available packages, they are converted to shipments on the order object. Shipping rates are determined and inventory units are created during this process as well. - -At this point, the checkout process can continue to the delivery step. - -## Components of Split Shipments - -This section describes the four main components that power split shipments: [The Coordinator](#the-coordinator), [The Packer](#the-packer), [The Prioritizer](#the-prioritizer), and [The Estimator](#the-estimator). - -### The Coordinator - -The `Spree::Stock::Coordinator` is the starting point for determining shipments when calling `create_proposed_shipments` on an order. Its job is to go through each `StockLocation` available and determine what can be shipped from that location. - -The `Spree::Stock::Coordinator` will ultimately return an array of packages which can then be easily converted into shipments for an order by calling `to_shipment` on them. - -### The Packer - -A `Spree::Stock::Packer` object is an important part of the `create_proposed_shipments` process. Its job is to determine possible packages for a given StockLocation and order. It uses rules defined in classes known as `Splitters` to determine what packages can be shipped from a `StockLocation`. - -For example, we may have two splitters for a stock location. One splitter has a rule that any order weighing more than 50lbs should be shipped in a separate package from items weighing less. Our other splitter is a catch-all for any item weighing less than 50lbs. So, given one item in an order weighing 60lbs and two items weighing less, the Packer would use the rules defined in our splitters to come up with two separate packages: one containing the single 60lb item, the other containing our other two items. - -#### Default Splitters - -Solidus comes with two default splitters which are run in sequence. This means that the first splitter takes the packages array from the order, and each subsequent splitter takes the output of the splitter that came before it. - -Let's take a look at what the default splitters do: - -* **Shipping Category Splitter**: Splits an order into packages based on items' shipping categories. This means that each package will only have items that all belong to the same shipping category. -* **Weight Splitter**: Splits an order into packages based on a weight threshold. This means that each package has a mass weight. If a new item is added to the order and it causes a package to go over the weight threshold, a new package will be created so that all packages weigh less than the threshold. You can set the weight threshold by changing `Spree::Stock::Splitter::Weight.threshold` (defaults to `150`) in an initializer. - -#### Custom Splitters - -Note that splitters can be customized, and creating your own can be done with relative ease. By inheriting from `Spree::Stock::Splitter::Base`, you can create your own splitter. - -For an example of a simple splitter, take a look at Solidus' [weight based splitter](https://github.com/solidusio/solidus/blob/master/core/app/models/spree/stock/splitter/weight.rb). This splitter pulls items with a weight greater than 150 into their own shipment. - -After creating your splitter, you need to add it to the array of splitters Solidus -uses. To do this, add the following to your application's solidus initializer -`solidus.rb` file: - -```ruby -Rails.application.config.spree.stock_splitters << Spree::Stock::Splitter::CustomSplitter -``` - -You can also completely override the splitters used in Solidus, rearrange them, etc. -To do this, add the following to your `solidus.rb` file: - -```ruby -Rails.application.config.spree.stock_splitters = [ - Spree::Stock::Splitter::CustomSplitter, - Spree::Stock::Splitter::ShippingCategory -] -``` - -Or if you don't want to split packages just set the option above to an empty -array. e.g. a store with the following configuration in solidus.rb won't have any -package splitted. - -```ruby -Rails.application.config.spree.stock_splitters = [] -``` - -If you want to add different splitters for each `StockLocation`, you need to decorate the `Spree::Stock::Coordinator` class and override the `splitters` method. - -### The Prioritizer - -A `Spree::Stock::Prioritizer` object will decide which `StockLocation` should ship which package from an order. The prioritizer will attempt to come up with the best shipping situation available to the user. - -By default, the prioritizer will first select packages where the items are on hand. Then it will try to find packages where items are backordered. During this process, the `Spree::Stock::Adjuster` is also used to ensure each package has the correct number of items. - -The prioritizer is also a customization point. If you want to customize which packages should take priority for the order during this process, you can override the `sort_packages` method in `Spree::Stock::Prioritizer`. - -#### Customizing the Adjuster - -The `Adjuster` visits each package in an order and ensures the correct number of items are in each package. To customize this functionality, you need to do two things: - -* Subclass the [Spree::Stock::Adjuster](https://github.com/solidusio/solidus/blob/master/core/app/models/spree/stock/adjuster.rb) class and override the the `adjust` method to get the desired functionality. -* Decorate the `Spree::Stock::Coordinator` and override the `prioritize_packages` method, passing in your custom adjuster class to the `Prioritizer` initializer. For example, if our adjuster was called `Spree::Stock::CustomAdjuster`, we would do the following: - -```ruby -Spree::Stock::Coordinator.class_eval do - def prioritize_packages(packages) - prioritizer = Prioritizer.new(order, packages, Spree::Stock::CustomAdjuster) - prioritizer.prioritized_packages - end -end -``` - -### The Estimator - -The `Spree::Stock::Estimator` loops through the packages created by the packer in order to calculate and attach shipping rates to them. This information is then returned to the user so they can select shipments for their order and complete the checkout process. - - - -## Documentation ToDo and notes -* This guide was adapted from the original spree guide: https://github.com/spree/spree-guides/blob/master/content/developer/core/shipments.md -* There were diagrams and screen shots in the original Spree docs that were dated. It would be nice to add some diagrams and screenshots to this doc. -* The examples of implementing custom calculators, shipping splitters, etc have been reviewed and seem accurate, but have not yet been validated in Solidus directly. -