defmodule ExCheckout.Package do @moduledoc """ Defines the struct for storing a `Package`, which is then passed along with an origin and destination address for shipping estimates. A `description` is optional, as it may or may not be used with various carriers. For USPS, a package has a `container` string which can be one of the pre-defined USPS containers. Do not pass a `weight` parameter. Instead, pass in a list of `:items` with a weight parameter on each of these. The weight on the package will be the sum of the weights of each of these. Same for `:monetary_value`. `:description` can optionally be passed in. Otherwise, it will be generated by joining the descriptions of each of the items. ExCheckout.Package.package(%{length: 8, width: 8, height: 8, items: [ %{weight: 1, monetary_value: 100, description: "A"}, %{weight: 2, monetary_value: 200, description: "B"} ]}) # => %Package{weight: 3, monetary_value: 300, description: "A, B", ...} """ alias ExCheckout.Item, as: Item @enforce_keys [:length, :width, :height, :weight, :items, :monetary_value, :description] @fields ~w(length width height weight girth container insurance monetary_value description items)a defstruct @fields @typep flat_rate_container() :: %{ name: String.t(), rate: integer(), length: number(), width: number(), height: number() } @type t() :: %__MODULE__{ length: number(), width: number(), height: number(), weight: number(), monetary_value: number(), girth: nil | number(), container: nil | atom() | String.t(), insurance: nil | number(), description: nil | String.t(), items: [Item.t()] } @doc """ Builds and returns a `Package`. Use this instead of directly initializing the struct. """ @spec new(map()) :: t() def new(attrs) do items = case attrs do %{items: [_ | _] = items} -> Enum.map(items, &Item.new/1) _ -> [] end weight = items |> Enum.filter(&is_number(&1.weight)) |> Enum.reduce(0, &(&1.weight + &2)) monetary_value = items |> Enum.filter(&is_number(&1.monetary_value)) |> Enum.reduce(0, &(&1.monetary_value + &2)) description = case attrs do %{description: d} when is_binary(d) and d != "" -> d _ -> items |> Enum.filter(&is_binary(&1.description)) |> Enum.map(&String.normalize(&1.description, :nfc)) |> Enum.join(", ") end attrs = attrs |> Map.merge(%{ items: items, weight: weight, monetary_value: monetary_value, description: description }) |> Map.take(@fields) struct(__MODULE__, attrs) end @doc """ Returns a map of predefined containers for use with USPS. These can be passed to `package.container` for fetching rates. """ @spec usps_containers() :: %{atom() => String.t()} def usps_containers() do %{ box_large: "Lg Flat Rate Box", box_medium: "Md Flat Rate Box", box_small: "Sm Flat Rate Box", envelope: "Flat Rate Envelope", envelope_gift_card: "Gift Card Flat Rate Envelope", envelope_legal: "Legal Flat Rate Envelope", envelope_padded: "Padded Flat Rate Envelope", envelope_small: "Sm Flat Rate Envelope", envelope_window: "Window Flat Rate Envelope", nonrectangular: "Nonrectangular", rectangular: "Rectangular", variable: "Variable" } end @doc """ Returns a map of flat rate USPS containers, along with their string description and flat shipping rate (in cents). """ @spec usps_flat_rate_containers() :: %{atom() => flat_rate_container()} def usps_flat_rate_containers() do %{ envelope: %{name: "Flat Rate Envelope", rate: 665, length: 12.5, height: 9.5, width: 0}, envelope_gift_card: %{ name: "Gift Card Flat Rate Envelope", rate: 665, length: 10, height: 7, width: 0 }, envelope_window: %{ name: "Window Flat Rate Envelope", rate: 665, length: 10, height: 5, width: 0 }, envelope_small: %{name: "Sm Flat Rate Envelope", rate: 665, length: 10, height: 6, width: 0}, envelope_legal: %{ name: "Legal Flat Rate Envelope", rate: 695, length: 15, height: 9.5, width: 0 }, envelope_padded: %{ name: "Padded Flat Rate Envelope", rate: 720, length: 12.5, height: 9.5, width: 0 }, box_small: %{ name: "Sm Flat Rate Box", rate: 715, length: 8.6875, height: 5.4375, width: 1.75 }, box_medium: %{name: "Md Flat Rate Box", rate: 1360, length: 11.25, height: 8.75, width: 6}, box_large: %{name: "Lg Flat Rate Box", rate: 1885, length: 12.25, height: 12.25, width: 6} } end end