No commit activity in last 3 years
No release in over 3 years
DSL for creating packing slips and general shipping materials
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 1.3
>= 0

Runtime

>= 0
 Project Readme

Shipping Materials

Shipping Materials provides a simple DSL for grouping and sorting a collection of orders and their items and creating print materials for them. So far this includes packing slips and CSVs for label makers.

Installation

$ gem install shipping_materials

Dependencies

wkhtmltopdf if used for PDF generation. The call to it is made as a linux command therefore this functionality will not work on Windows.

gzip is used for the zip functionality.

Usage

Setup

There is a little bit of configuration you are going to want to do first and that is to add a save_path.

  ShippingMaterials.config do |config|
    config.save_path = 'local/save/path'
  end

If you would like to use S3, add the following:

  ShippingMaterials.config do |config|
    config.s3_bucket            = 'bucket.domain.com'
    config.s3_access_key_id     = ENV['AWS_ACCESS_KEY']
    config.s3_secret_access_key = ENV['AWS_SECRET_ACCESS_KEY']
  end

The Packager

The DSL is provided via the ShippingMaterials::Packager class.

  packager = ShippingMaterials::Packager.new

The Packager's #package method takes a collection of objects of the same type.

  orders = Order.where(state: 'placed')

  packager.package orders do
    # ...
  end

Because we are creating shipping materials here, at the very least, it is assumed you are going to want packing slips. You may specify a global template with the #html or #pdf methods:

  packager.package orders do
    pdf 'path/to/template.erb'
  end

Now, at the simplest level, we can start breaking these objects down into groups.

  packager.package orders do
    pdf 'path/to/template.erb'

    group 'Canadian Standard Post' do
      filter {
        ship_method == 'std' && country == 'CA'
      }
    end
    
    group 'United States UPS Expedited' do
      filter {
        ship_method == 'UPSexp' && country == 'US'
      }
    end
    
    group 'International World Ship' do
      filter {
        ship_method == 'UPS' && !%w[ US CA ].include?(country)
      }
    end
  end

PDFs (one per group) will now be created. With groups named as above, you can expect the file names 'CanadianStandardPost.pdf', 'UnitedStatesUPSExpedite.pdf' and 'InternationalWorldShip.pdf'.

Templating

Shipping Materials uses Tilt therefore there are a number of options available. Templates are evaluated within the context of the order array. A sample ERB template would look like this:

  <html>
    <body>
      <% self.each do |order| %>
        <p><%= order.number %>
        <div>
        <% order.line_items.each do |li| %>
          <p><%= li.desc %>: $<%= li.price %> x <%= li.qty %> = <%= li.total %></p>
        <% end %>
        </div>
      <% end %>
    </body>
  </html>

Each group will produce one PDF.

CSV (for shipping labels)

Any label printer I know -- as well as things like UPS Worldship -- use CSVs, so Shipping Materials provides a little CSV templating DSL. This is provided via the Group#csv method.

The #csv method takes a block which exposes the #row method. #row takes a hash or an array and may be called multiple times.

Here is an example with hashes:

  group 'Canadian Standard Post' do
    csv(headers: true) {
      row 'Code'         => 'Q',
          'Order Number' => :number,
          'Name'         => [ :shipping_address, :name ]
           # ...
          'Country'      => [ :shipping_address, :country, :iso ]
          
      row line_items: [ 'H', :id, :name, :quantity, :price ]
    }
  end

In this example, the first call to row is evaluated in the context of each order. Symbols are called as methods whereas string values are kept as-is. In order to chain method calls, use an array of symbols as a value. For example, [ :shipping_address, :country, :iso ] will call order.shipping_method.country.iso.

Passing headers: true to the csv method will use the keys from the first call to row (if it is a hash) as the headers for the CSV.

As demonstrated in the second call to row, you can evalute your row in the context of your line items (or other one-to-many relationship) using its method name as a key.

CSV Callbacks

Right now an if callback can be provided to the row method. This is useful when calling row multiple times and you don't always want all of them to render.

  group 'Canadian Standard Post' do
    csv(headers: true) {
      row 'Code'         => 'Q'
           # ...

      row({ line_items: [ 'H', :id, :name, :quantity, :price ] },
          if: proc {|o| o.lines_items.size > 1 })
    }
  end

Sorting

While most sorting should probably be done at the query level, Shipping Materials provides a sorting DSL for more complex sorts. For example:

  packager.package orders do
    pdf 'path/to/template.erb'
      
    sort do
      # put orders containing only Vinyl at the top
      rule {
        line_items.select {|li| type != 'Vinyl' }.any?
      }
      
      # next come orders that have both Vinyl and CDs and nothing else
      rule {
        line_items.select {|li| %w[ Vinyl CD ].include?(li.type) }.uniq.size == 2
      }
      
      # get the picture?
    end
      
    group # ...
  end

A merge sort will be performed sorting the orders within each group according to each rule in the order they are defined.

While it is definitely recommended to sort line items at the query level, you can operate in the context of line items by passing the name of the association to the sort method (ie: your association doesn't have to be called "line_items" specifically):

  sort(:line_items) do
    rule { type == 'Vinyl' }
    rule { type == 'CD' }
    rule { type == 'Cassette' }
    rule { type == '8-Track' }
  end

This will sort your line items within each packing slip.

Documentation

Other than this README, there is no documentation yet. There are a few other experimental and volatile features not mentioned here. They are certainly going to change soon.

Contributing

This is my first foray into the world of library authoring. I welcome any and all advice and pull requests with open arms, but for the love of whoever or whatever you believe in: please follow these guidelines when writing your commit messages.

A note about the tests

I am still learning to test properly. Tests are passing but some of them are writing to the filesystem. These wrongs will be righted in due time.