├── app
├── models
│ ├── .keep
│ └── spree
│ │ ├── order_decorator.rb
│ │ ├── shipping_method_decorator.rb
│ │ ├── shipping_category_decorator.rb
│ │ ├── shipping
│ │ ├── box.rb
│ │ └── package.rb
│ │ ├── address_decorator.rb
│ │ ├── stock_location_decorator.rb
│ │ └── shipment_decorator.rb
├── assets
│ └── javascripts
│ │ └── admin
│ │ └── working_title
│ │ ├── namespace.js.coffee
│ │ └── order_shipments.js.coffee
├── views
│ └── spree
│ │ ├── user_sessions
│ │ └── new.html.haml
│ │ ├── shared
│ │ ├── _js_onready.html.haml
│ │ └── _user_form.html.erb
│ │ └── admin
│ │ └── orders
│ │ └── shipments
│ │ └── edit.haml
├── overrides
│ └── spree
│ │ ├── admin
│ │ └── shared
│ │ │ └── _order_tabs
│ │ │ └── add_shipment_link.html.erb.deface
│ │ └── layouts
│ │ └── admin
│ │ └── add_js_onready_yielder.html.erb.deface
└── controllers
│ └── spree
│ └── admin
│ └── orders
│ └── shipments_controller.rb
├── config
├── spree.yml
├── initializers
│ ├── devise.rb
│ ├── session_store.rb
│ ├── filter_parameter_logging.rb
│ ├── mime_types.rb
│ ├── backtrace_silencers.rb
│ ├── wrap_parameters.rb
│ ├── secret_token.rb
│ ├── inflections.rb
│ └── spree.rb
├── locales
│ └── en.yml
├── environment.rb
├── boot.rb
├── database.yml
├── application.rb
└── routes.rb
├── db
├── seeds.rb
├── seeds
│ ├── shipping.rb
│ └── shipping
│ │ └── boxes.rb
└── migrate
│ ├── 20140123204218_add_label_printer_name_to_user.rb
│ ├── 20140121231143_add_label_print_jobs.rb
│ ├── 20140121212137_add_shipping_boxes.rb
│ └── 20140121211116_add_shipment_packages.rb
├── lib
├── spree
│ └── permitted_attributes_decorator.rb
└── utilities
│ └── labeler.rb
├── config.ru
├── spec
├── models
│ └── spree
│ │ ├── order_spec.rb
│ │ ├── stock_location_spec.rb
│ │ ├── shipment_spec.rb
│ │ ├── shipping_method_spec.rb
│ │ └── shipping
│ │ ├── box_spec.rb
│ │ └── package_spec.rb
└── spec_helper.rb
├── Rakefile
├── README.md
├── LICENSE.txt
├── data
└── test_label.zpl
└── Gemfile
/app/models/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/config/spree.yml:
--------------------------------------------------------------------------------
1 | ---
2 | version: 2.1.3
3 |
--------------------------------------------------------------------------------
/db/seeds.rb:
--------------------------------------------------------------------------------
1 | load "db/seeds/shipping.rb"
2 |
--------------------------------------------------------------------------------
/config/initializers/devise.rb:
--------------------------------------------------------------------------------
1 | Devise.secret_key = ENV['DEVISE_SECRET_KEY']
2 |
--------------------------------------------------------------------------------
/app/assets/javascripts/admin/working_title/namespace.js.coffee:
--------------------------------------------------------------------------------
1 | window.WorkingTitle ||= {}
2 |
--------------------------------------------------------------------------------
/db/seeds/shipping.rb:
--------------------------------------------------------------------------------
1 | puts "** SHIPPING SEEDS **"
2 |
3 | load "db/seeds/shipping/boxes.rb"
4 |
--------------------------------------------------------------------------------
/app/models/spree/order_decorator.rb:
--------------------------------------------------------------------------------
1 | Spree::Order.class_eval do
2 | has_many :packages, through: :shipments
3 | end
4 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | spree:
3 | order_number: "Order Number"
4 | recently_viewed:
5 | header: "Recently Viewed"
6 |
--------------------------------------------------------------------------------
/lib/spree/permitted_attributes_decorator.rb:
--------------------------------------------------------------------------------
1 | Spree::PermittedAttributes.class_eval do
2 | user_attributes << :label_printer_name
3 | end
4 |
--------------------------------------------------------------------------------
/app/models/spree/shipping_method_decorator.rb:
--------------------------------------------------------------------------------
1 | Spree::ShippingMethod.class_eval do
2 | def fedex?
3 | name.to_s.downcase.include? 'fedex'
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/models/spree/shipping_category_decorator.rb:
--------------------------------------------------------------------------------
1 | Spree::ShippingCategory.class_eval do
2 | def self.default
3 | where("name ilike 'default'").first
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Rails.application
5 |
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the Rails application.
5 | WorkingTitle::Application.initialize!
6 |
--------------------------------------------------------------------------------
/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | WorkingTitle::Application.config.session_store :cookie_store, key: '_working-title_session'
4 |
--------------------------------------------------------------------------------
/db/migrate/20140123204218_add_label_printer_name_to_user.rb:
--------------------------------------------------------------------------------
1 | class AddLabelPrinterNameToUser < ActiveRecord::Migration
2 | def change
3 | add_column :spree_users, :label_printer_name, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 | # Mime::Type.register_alias "text/html", :iphone
6 |
--------------------------------------------------------------------------------
/spec/models/spree/order_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Spree::Order do
4 |
5 | describe "configuration" do
6 | describe "associations" do
7 | it { should have_many(:packages).through(:shipments) }
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require File.expand_path('../config/application', __FILE__)
5 |
6 | WorkingTitle::Application.load_tasks
7 |
--------------------------------------------------------------------------------
/app/models/spree/shipping/box.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | module Shipping
3 | class Box < ActiveRecord::Base
4 | self.table_name = "spree_shipping_boxes"
5 |
6 | has_many :packages
7 |
8 | validates_uniqueness_of :description, case_sensitive: false
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/app/views/spree/user_sessions/new.html.haml:
--------------------------------------------------------------------------------
1 | - if flash[:alert]
2 | .flash.errors= flash[:alert]
3 |
4 | #existing-customer.row
5 | .col-md-6
6 | %h1.la-heading.margin-40= Spree.t(:login_as_existing)
7 | %div{ data: { hook: 'login' } }
8 | = render partial: 'spree/shared/login'
9 |
10 | .col.md-6
--------------------------------------------------------------------------------
/config/boot.rb:
--------------------------------------------------------------------------------
1 | # Set up gems listed in the Gemfile.
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 |
4 | unless %w[staging production].include?(ENV["RAILS_ENV"].to_s.strip)
5 | require 'dotenv'
6 | Dotenv.load
7 | end
8 |
9 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
10 |
--------------------------------------------------------------------------------
/app/overrides/spree/admin/shared/_order_tabs/add_shipment_link.html.erb.deface:
--------------------------------------------------------------------------------
1 |
2 |
>
3 |
4 | Shipping & Labels
5 |
6 |
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # What
2 | This is intended to be a [Spree](https://github.com/spree/spree) extension that pairs with `spree_active_shipping`. I'm not ready to set aside the time to build out a full extension at the moment but I did take a few minutes to extract the relevant code from my existing apps so that I'd have something to point to the next time I bring this up in #spree.
3 |
4 | ## Screenshot
5 | 
6 |
--------------------------------------------------------------------------------
/app/overrides/spree/layouts/admin/add_js_onready_yielder.html.erb.deface:
--------------------------------------------------------------------------------
1 |
3 |
4 | <% content_for :js_onready do %>
5 | console.log("Brutishly shortening admin menu headings");
6 | $("#admin-menu span:contains('onfiguration')").text('Config');
7 | $("#admin-menu span:contains('romotions')").text('Promos');
8 | <% end %>
9 |
10 | <%= render partial: 'spree/shared/js_onready' %>
11 |
--------------------------------------------------------------------------------
/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/app/models/spree/address_decorator.rb:
--------------------------------------------------------------------------------
1 | Spree::Address.class_eval do
2 | def fedex_formatted
3 | {
4 | name: full_name,
5 | company: company,
6 | phone_number: phone,
7 | address: [address1, address2].compact.join(' '),
8 | city: city,
9 | state: state && state.abbr,
10 | postal_code: zipcode,
11 | country_code: country && country.iso,
12 | residential: false, # going commercial to get access to FedEx Ground
13 | }
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/app/models/spree/stock_location_decorator.rb:
--------------------------------------------------------------------------------
1 | Spree::StockLocation.class_eval do
2 | def company
3 | "Working Title"
4 | end
5 |
6 | def fedex_formatted
7 | {
8 | company: company,
9 | phone_number: phone,
10 | address: [address1, address2].compact.join(' '),
11 | city: city,
12 | state: state && state.abbr,
13 | postal_code: zipcode,
14 | country_code: country && country.iso,
15 | residential: !company.blank?,
16 | }
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/app/models/spree/shipment_decorator.rb:
--------------------------------------------------------------------------------
1 | Spree::Shipment.class_eval do
2 | has_many :packages, class_name: "Spree::Shipping::Package"
3 |
4 | accepts_nested_attributes_for :packages, allow_destroy: true,
5 | :reject_if => proc { |attributes| attributes['weight'].to_f <= 0 }
6 |
7 | delegate :fedex?, :to => :shipping_method
8 |
9 | scope :not_shipped, -> { where(arel_table[:state].eq('shipped').not) }
10 | scope :backordered, -> { joins(:inventory_units).
11 | merge(Spree::InventoryUnit.backordered) }
12 | end
13 |
--------------------------------------------------------------------------------
/app/views/spree/shared/_js_onready.html.haml:
--------------------------------------------------------------------------------
1 | :javascript
2 | var console = window.console || { log: function(){} };
3 |
4 | - if content_for?(:js_onready)
5 | - escaped_js = content_for(:js_onready)
6 | - unescaped_js = escaped_js.gsub("&&", "&&")
7 |
8 | :javascript
9 | $(function() {
10 | console.log("** Beginning sitewide :js_onready **");
11 | #{unescaped_js}
12 | console.log("** Sitewide :js_onready ended. **");
13 | });
14 | - else
15 | :javascript
16 | console.log("** No :js_onready code received **")
17 |
--------------------------------------------------------------------------------
/db/migrate/20140121231143_add_label_print_jobs.rb:
--------------------------------------------------------------------------------
1 | class AddLabelPrintJobs < ActiveRecord::Migration
2 | def change
3 | create_table "label_print_jobs", :force => true do |t|
4 | t.string "printer_name", :null => false
5 | t.text "label_plaintext", :null => false
6 | t.datetime "processed_at"
7 | t.datetime "created_at", :null => false
8 | t.datetime "updated_at", :null => false
9 | end
10 |
11 | add_index "label_print_jobs", ["processed_at"], :name => "idx_label_print_jobs_by_processed_at"
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/db/seeds/shipping/boxes.rb:
--------------------------------------------------------------------------------
1 | dimensions = [
2 | ['(7x7x7)', '7x7x7'],
3 | ['(12x12x8)', '12x12x8'],
4 | ['(15x15x8)', '15x15x8'],
5 | ['(24x12x12)', '24x12x12'],
6 | ['(36x12x12)', '36x12x12'],
7 | ['(24x24x12)', '24x24x12'],
8 | ['(48x24x12)', '48x24x12']]
9 |
10 | dimensions.each do |desc, lwh|
11 | l,w,h = lwh.split('x')
12 |
13 | box = Spree::Shipping::Box.find_or_create_by(
14 | description: desc,
15 | length: l,
16 | width: w,
17 | height: h)
18 |
19 | puts "Ensured existence of shipping box #{box.description}"
20 | end
21 |
--------------------------------------------------------------------------------
/spec/models/spree/stock_location_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Spree::StockLocation do
4 | let(:klass) { Spree::StockLocation }
5 |
6 | describe "public instance methods" do
7 | it "should have a :company method returning a string" do
8 | subject.should respond_to(:company)
9 | subject.company.is_a?(String).should be_true
10 | end
11 |
12 | it "should have a :fedex_formatted method that returns a hash" do
13 | subject.should respond_to(:fedex_formatted)
14 | subject.fedex_formatted.is_a?(Hash).should be_true
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/config/initializers/secret_token.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rake secret` to generate a secure secret key.
9 |
10 | # Make sure your secret_key_base is kept private
11 | # if you're sharing your code publicly.
12 | WorkingTitle::Application.config.secret_key_base = 'bananas'
13 |
--------------------------------------------------------------------------------
/db/migrate/20140121212137_add_shipping_boxes.rb:
--------------------------------------------------------------------------------
1 | class AddShippingBoxes < ActiveRecord::Migration
2 | def change
3 | create_table "spree_shipping_boxes", :force => true do |t|
4 | t.string "description", :null => false
5 | t.integer "length", :default => 0, :null => false
6 | t.integer "width", :default => 0, :null => false
7 | t.integer "height", :default => 0, :null => false
8 | t.datetime "created_at", :null => false
9 | t.datetime "updated_at", :null => false
10 | end
11 |
12 | add_index "spree_shipping_boxes", ["description"], :name => "udx_shipping_boxes_on_description", :unique => true
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/config/initializers/spree.rb:
--------------------------------------------------------------------------------
1 | # Configure Spree Preferences
2 | #
3 | # Note: Initializing preferences available within the Admin will overwrite any changes that were made through the user interface when you restart.
4 | # If you would like users to be able to update a setting with the Admin it should NOT be set here.
5 | #
6 | # In order to initialize a setting do:
7 | # config.setting_name = 'new value'
8 | Spree.config do |config|
9 | config.currency = "USD"
10 | config.site_name = "Working Title"
11 | config.track_inventory_levels = true
12 | #config.products_per_page = 48 # prefer multiples of 3
13 | config.orders_per_page = 15 # admin page
14 | end
15 |
16 | Spree.user_class = "Spree::User"
17 |
--------------------------------------------------------------------------------
/db/migrate/20140121211116_add_shipment_packages.rb:
--------------------------------------------------------------------------------
1 | class AddShipmentPackages < ActiveRecord::Migration
2 | def change
3 | create_table "spree_shipping_packages", :force => true do |t|
4 | t.integer "shipment_id"
5 | t.integer "box_id"
6 | t.decimal "weight", :precision => 8, :scale => 2
7 | t.datetime "created_at", :null => false
8 | t.datetime "updated_at", :null => false
9 | t.text "label_zpl"
10 | t.string "tracking_number"
11 | t.integer "shipping_method_id"
12 | end
13 |
14 | add_index :spree_shipping_packages, [:shipment_id], name: :idx_spree_shipping_packages_on_shipment
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/spec/models/spree/shipment_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Spree::Shipment do
4 | describe "decorations" do
5 | describe 'configuration' do
6 | context "associations" do
7 | it { should have_many(:packages) }
8 | end
9 |
10 | context "delegates" do
11 | let(:shipping_method) { double("shipping_method") }
12 |
13 | before { subject.stub(:shipping_method).and_return shipping_method }
14 |
15 | it { should accept_nested_attributes_for(:packages).allow_destroy(true) }
16 |
17 | it "should delegate :fedex? to :shipping_method" do
18 | shipping_method.should_receive :fedex?
19 | subject.fedex?
20 | end
21 |
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/spec/models/spree/shipping_method_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Spree::ShippingMethod do
4 | context "public instance methods" do
5 | describe ".fedex?" do
6 | it "should be true if the downcased name includes 'fedex'" do
7 | subject.stub(:name).and_return('123123fedex324')
8 | subject.fedex?.should be_true
9 |
10 | subject.stub(:name).and_return('FEDEX!!')
11 | subject.fedex?.should be_true
12 | end
13 |
14 | it "should not be true if the downcased name doesn't include 'fedex'" do
15 | subject.stub(:name).and_return('123123f')
16 | subject.fedex?.should be_false
17 |
18 | subject.stub(:name).and_return(nil)
19 | subject.fedex?.should be_false
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | # PG setup:
2 | # createuser working_title --createdb
3 | # rake db:create db:migrate db:seed
4 | # env RAILS_ENV=test rake db:create db:migrate db:seed
5 |
6 | #------------------------------------------------------------
7 | # common
8 | #------------------------------------------------------------
9 |
10 | pg: &pg
11 | adapter: postgresql
12 | encoding: utf8
13 | template: template0
14 | port: 5432
15 | pool: 25
16 |
17 | #------------------------------------------------------------
18 | # environments
19 | #------------------------------------------------------------
20 |
21 | development:
22 | <<: *pg
23 | host: 127.0.0.1
24 | username: working_title
25 | password: working_title
26 | database: working_title_development
27 |
28 | test: &test
29 | <<: *pg
30 | host: 127.0.0.1
31 | username: working_title
32 | password: working_title
33 | database: working_title_test
34 |
--------------------------------------------------------------------------------
/spec/models/spree/shipping/box_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Spree::Shipping::Box do
4 | context "database fields" do
5 | it { should have_db_column(:id).of_type(:integer) }
6 | it { should have_db_column(:description).of_type(:string).with_options(null: false) }
7 | it { should have_db_column(:length).of_type(:integer).with_options(default: 0, null: false) }
8 | it { should have_db_column(:width).of_type(:integer).with_options(default: 0, null: false) }
9 | it { should have_db_column(:height).of_type(:integer).with_options(default: 0, null: false) }
10 | end
11 |
12 | context "database indexes" do
13 | it { should have_db_index(:description).unique(true) }
14 | end
15 |
16 | context "configuration" do
17 | context "validations" do
18 | it { should validate_uniqueness_of(:description).case_insensitive }
19 | end
20 |
21 | describe "associations" do
22 | it { should have_many(:packages) }
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Coroutine, LLC
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/app/views/spree/shared/_user_form.html.erb:
--------------------------------------------------------------------------------
1 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # load rails and other dependencies
2 | #
3 | ENV["RAILS_ENV"] ||= 'test'
4 | require File.expand_path("../../config/environment", __FILE__)
5 | require 'rspec/rails'
6 |
7 | require 'capybara-screenshot/rspec'
8 |
9 | # spree test helpers
10 | require 'spree/testing_support/factories'
11 | require 'spree/testing_support/controller_requests'
12 | require 'spree/testing_support/authorization_helpers'
13 |
14 | # load supporting code
15 | Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
16 |
17 | # configure rspec
18 | RSpec.configure do |config|
19 | Capybara.javascript_driver = :webkit
20 |
21 | config.order = 'random'
22 |
23 | # controller test setup
24 | config.include Spree::TestingSupport::ControllerRequests, type: :controller
25 | config.include Devise::TestHelpers, type: :controller
26 |
27 | # mocks
28 | config.mock_with :rspec
29 |
30 | # transactions
31 | config.use_transactional_fixtures = true
32 | config.use_transactional_examples = false
33 |
34 | # database cleaner
35 | config.before(:suite) { DatabaseCleaner.strategy = :transaction }
36 | config.before(:each) { DatabaseCleaner.start }
37 | config.after(:each) { DatabaseCleaner.clean }
38 |
39 | end
40 |
--------------------------------------------------------------------------------
/app/assets/javascripts/admin/working_title/order_shipments.js.coffee:
--------------------------------------------------------------------------------
1 | WorkingTitle.OrderShipments ||=
2 | initialize: ->
3 | @selectifyForm()
4 | @bindHandlers()
5 | console.log("OrderShipments initialized.")
6 |
7 | selectifyForm: ->
8 | $(".shipment-package-form select").select2 width: "100%"
9 |
10 | preselectMethod: ($container) ->
11 | window.$container = $container
12 | methodId = $container.data().methodId || null
13 | $container.find(".shipping-method-column option[value=#{methodId}]").
14 | last().attr('selected', 'selected')
15 |
16 | handleFieldAdded: (event) ->
17 | container = $(event.target).parents(".shipment-package-form")
18 | WorkingTitle.OrderShipments.preselectMethod(container)
19 | WorkingTitle.OrderShipments.selectifyForm()
20 |
21 | # not printable until it's persisted
22 | $(".icon-print", event.field).hide()
23 |
24 | bindHandlers: ->
25 | $(document).on "nested:fieldAdded", WorkingTitle.OrderShipments.handleFieldAdded
26 |
27 | # api ship endpoint returns JSON that we don't want to see
28 | $(".finalize-shipment-button").bind "ajax:success", ->
29 | location.reload()
30 |
31 | $(".finalize-shipment-button").bind "ajax:failure", ->
32 | alert "Failed to finalize shipment."
33 |
34 | $(".icon-print").bind "ajax:error", (e, data, status, xhr) ->
35 | err = JSON.parse(data.responseText).error
36 | window.errs = err
37 | err = err or "Unexpected shipping error! Check server logs for details."
38 | alert err
39 |
40 | $(".icon-print").bind "ajax:success", (e, data, status, xhr) ->
41 | location.reload()
42 |
--------------------------------------------------------------------------------
/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | require 'rails/all'
4 |
5 | # Require the gems listed in Gemfile, including any gems
6 | # you've limited to :test, :development, or :production.
7 | Bundler.require(:default, Rails.env)
8 |
9 | module WorkingTitle
10 | class Application < Rails::Application
11 |
12 | config.to_prepare do
13 | # Load libs
14 | Dir.glob(File.join(File.dirname(__FILE__), "../lib/**/*.rb")) do |c|
15 | Rails.configuration.cache_classes ? require(c) : load(c)
16 | end
17 |
18 | # Load application's model / class decorators
19 | Dir.glob(File.join(File.dirname(__FILE__), "../app/**/*_decorator*.rb")) do |c|
20 | Rails.configuration.cache_classes ? require(c) : load(c)
21 | end
22 |
23 | # Load application's view overrides
24 | Dir.glob(File.join(File.dirname(__FILE__), "../app/overrides/*.rb")) do |c|
25 | Rails.configuration.cache_classes ? require(c) : load(c)
26 | end
27 | end
28 |
29 | # Settings in config/environments/* take precedence over those specified here.
30 | # Application configuration should go into files in config/initializers
31 | # -- all .rb files in that directory are automatically loaded.
32 |
33 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
34 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
35 | config.time_zone = 'Central Time (US & Canada)'
36 |
37 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
38 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
39 | # config.i18n.default_locale = :de
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/app/controllers/spree/admin/orders/shipments_controller.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | module Admin
3 | module Orders
4 | class ShipmentsController < Spree::Admin::BaseController
5 | expose(:order) { @order }
6 |
7 | before_filter :load_ivars
8 |
9 | rescue_from Utilities::LabelError do |err|
10 | render json: { error: "Error:\n< #{err.message} >" }, status: 422
11 | end
12 |
13 | def edit
14 | end
15 |
16 | def update
17 | unless @shipment.update_attributes(shipment_params)
18 | flash[:error] = @shipment.errors.full_messages.join(" ")
19 | end
20 |
21 | redirect_to edit_admin_order_shipments_url
22 | end
23 |
24 | def print
25 | unless @package.persisted?
26 | return render(
27 | status: 200,
28 | json: { errors: [Spree.t(:cannot_print_unsaved_label)] })
29 | end
30 |
31 | printer_name = spree_current_user.label_printer_name
32 |
33 | @package.print_label!(printer_name: printer_name)
34 | render status: 200,
35 | json: { tracking_code: @package.tracking_number }
36 | end
37 |
38 | def print_product_labels
39 | labels = @order.line_items.map(&:generate_variant_label!)
40 | render json: labels
41 | end
42 |
43 | private
44 | def load_ivars
45 | @order = Order.find_by_number!(params[:order_id], :include => :adjustments)
46 | @shipment = @order.shipments.find_by_number(params[:shipment_number])
47 | @package = @order.packages.find_by_id(params[:package_id])
48 | end
49 |
50 | def shipment_params
51 | params.require(:shipment).permit(
52 | packages_attributes: [:shipping_method_id, :box_id, :weight, :_destroy, :id])
53 | end
54 | end
55 | end
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/app/models/spree/shipping/package.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | module Shipping
3 | class Package < ActiveRecord::Base
4 | after_save :update_shipment_tracking_number
5 |
6 | self.table_name = "spree_shipping_packages"
7 |
8 | belongs_to :shipment, validate: true
9 | belongs_to :shipping_method, validate: true
10 | belongs_to :box, validate: true
11 | has_one :order, through: :shipment
12 |
13 | scope :has_label, -> { where "label_zpl is not null" }
14 |
15 | validates_presence_of :shipping_method_id
16 | validates_presence_of :box_id
17 | validates_presence_of :weight
18 | validates_numericality_of :weight, greater_than: 0
19 |
20 | delegate :fedex?, to: :shipping_method
21 |
22 | delegate :length, to: :box
23 | delegate :width, to: :box
24 | delegate :height, to: :box
25 |
26 | def destination
27 | shipment.address || order.ship_address
28 | end
29 |
30 | def origin
31 | shipment.stock_location
32 | end
33 |
34 | # Endicia requires 4.1 precision
35 | def weight_in_ounces
36 | raw = weight * 16.0
37 | rounded_up = (raw * 10).ceil / 10.0
38 | end
39 |
40 | # Throw away the label ZPL when e.g. shipping method or address has changed
41 | #
42 | def invalidate_label!
43 | self.label_zpl = nil
44 | save!
45 | end
46 |
47 | def print_label!(printer_name: nil)
48 | generate_label!
49 |
50 | # Left out because I didn't write that class:
51 | Utilities::RawPrinter.new(printer_name).print label_zpl
52 | end
53 |
54 | private
55 | def generate_label!
56 | # Don't regenerate a label unless it's nil. Regenerating USPS labels
57 | # costs money.
58 | #
59 |
60 | if label_zpl.nil?
61 | begin
62 | response = ::Utilities::Labeler.new(self).generate
63 | rescue *acceptable_errors => err
64 | handle_label_error! err
65 | end
66 |
67 | self.label_zpl = response[:label]
68 | self.tracking_number = response[:tracking_number]
69 |
70 | save!
71 | end
72 | end
73 |
74 | def handle_label_error!(err)
75 | Rails.logger.warn("** Labeler error: #{err.inspect}")
76 | raise Utilities::LabelError, err.message
77 | end
78 |
79 | def acceptable_errors
80 | [Utilities::LabelError]
81 | end
82 |
83 | def update_shipment_tracking_number
84 | return if tracking_number.blank?
85 |
86 | shipment.tracking = tracking_number
87 | shipment.save! if shipment.changed?
88 | end
89 | end
90 | end
91 | end
92 |
--------------------------------------------------------------------------------
/data/test_label.zpl:
--------------------------------------------------------------------------------
1 | ^XA^CF,0,0,0^PR12^MD30^PW800^POI^CI13^LH0,20
2 | ^FO12,139^GB753,2,2^FS
3 | ^FO12,405^GB777,2,2^FS
4 | ^FO464,8^GB2,129,2^FS
5 | ^FO32,10^AdN,0,0^FWN^FH^FDORIGIN ID:HKAA ^FS
6 | ^FO224,10^AdN,0,0^FWN^FH^FD(901) 754-9100^FS
7 | ^FO32,28^AdN,0,0^FWN^FH^FDSHIPPING DEPT^FS
8 | ^FO32,46^AdN,0,0^FWN^FH^FDLR^FS
9 | ^FO32,64^AdN,0,0^FWN^FH^FD7730 TRINITY RD, SUITE 110^FS
10 | ^FO32,82^AdN,0,0^FWN^FH^FD^FS
11 | ^FO32,100^AdN,0,0^FWN^FH^FDCORDOVA, TN 38018^FS
12 | ^FO32,118^AdN,0,0^FWN^FH^FDUNITED STATES US^FS
13 | ^FO478,46^AdN,0,0^FWN^FH^FDCAD: 8030215/WSXI0100^FS
14 | ^FO8,151^A0N,21,21^FWN^FH^FDTO^FS
15 | ^FO37,233^A0N,38,38^FWN^FH^FD5107 N KENMORE AVE^FS
16 | ^FO37,275^A0N,38,38^FWN^FH^FDAPT 4S^FS
17 | ^FO37,317^A0N,43,40^FWN^FH^FDCHICAGO IL 60640^FS
18 | ^FO28,747^A0N,24,24^FWN^FH^FDTRK#^FS
19 | ^FO28,805^A0N,27,32^FWN^FH^FD^FS
20 | ^FO136,717^A0N,27,36^FWN^FH^FD^FS
21 | ^FO37,149^A0N,38,38^FWN^FH^FDWILL GLYNN^FS
22 | ^FO35,359^A0N,21,21^FWN^FH^FD(773) 968-9811^FS
23 | ^FO37,191^A0N,38,38^FWN^FH^FD^FS
24 | ^FO677,511^GB104,10,10^FS
25 | ^FO677,521^GB10,112,10^FS
26 | ^FO771,521^GB10,112,10^FS
27 | ^FO677,633^GB104,10,10^FS
28 | ^FO652,449^A0N,43,58^FWN^FH^FDFedEx^FS
29 | ^FO708,488^A0N,19,26^FWN^FH^FDExpress^FS
30 | ^FO697,529^A0N,128,137^FWN^FH^FDE^FS
31 | ^FO21,416^BY2,3^BCN,25,N,N,N^FWN^FD75107 N KENMORE AVE^FS
32 | ^FO21,449^BY2,2^B7N,10,5,14^FH^FWN^FH^FD[)>_1E01_1D0260640_1D840_1D05_1D7972760443430201_1DFDE_1D389858480_1D333_1D_1D1/1_1D3.3LB_1DN_1D5107 N Kenmore Ave_1DChicago_1DIL_1DWill Glynn_1E06_1D10ZED006_1D12Z7739689811_1D31Z36064039510025627972760443432019_1D32Z10_1D33Z _1D34Z01_1D14ZApt 4S_1D15Z8030215_1D20Z_1C _1D26Z1518_1C_1D_1E_04^FS
33 | ^FO478,100^AdN,0,0^FWN^FH^FDBILL SENDER^FS
34 | ^FO12,694^GB777,2,2^FS
35 | ^FO494,890^A0N,43,43^FWN^FH^FD^FS
36 | ^FO791,120^AbN,11,7^FWB^FH^FD51AG1/D5E6/1A9E^FS
37 | ^FO95,751^A0N,53,40^FWN^FH^FD7972 7604 4343^FS
38 | ^FO409,700^A0N,51,38^FWN^FH^FB390,,,R,^FD MON - 02 DEC AA^FS
39 | ^FO309,752^A0N,51,38^FWN^FH^FB490,,,R,^FD STANDARD OVERNIGHT^FS
40 | ^FO413,804^A0N,40,40^FWN^FH^FB386,,,R,^FD DSR^FS
41 | ^FO495,846^A0N,44,44^FWN^FH^FB298,,,R,^FD 60640^FS
42 | ^FO574,906^A0N,24,24^FWN^FH^FB120,,,R,^FD IL-US^FS
43 | ^FO695,890^A0N,43,43^FWN^FH^FB100,,,R,^FDORD^FS
44 | ^FO39,932^A0N,27,32^FWN^FH^FD^FS
45 | ^FO75,993^BY3,2^BCN,200,N,N,N,N^FWN^FD>;36064039510025627972760443432019^FS
46 | ^FO28,842^A0N,107,96^FWN^FH^FDSE NBUA ^FS
47 | ^FO790,513^A0N,13,18^FWB^FH^FDJ13201306280126^FS
48 | ^FO478,10^AdN,0,0^FWN^FH^FDSHIP DATE: 29NOV13^FS
49 | ^FO478,28^AdN,0,0^FWN^FH^FDACTWGT: 3.3 LB^FS
50 | ^FO478,64^AdN,0,0^FWN^FH^FDDIMS: 12x12x8 IN^FS
51 | ^FO328,364^AbN,11,7^FWN^FH^FDREF: ^FS
52 | ^FO38,378^AbN,11,7^FWN^FH^FDINV: ^FS
53 | ^FO38,392^AbN,11,7^FWN^FH^FDPO: ORDER 249902^FS
54 | ^FO428,392^AbN,11,7^FWN^FH^FDDEPT: ^FS
55 | ^FO25,768^GB58,1,1^FS
56 | ^FO25,768^GB1,26,1^FS
57 | ^FO83,768^GB1,26,1^FS
58 | ^FO25,794^GB58,1,1^FS
59 | ^FO31,774^AdN,0,0^FWN^FH^FD0201^FS
60 | ^PQ1
61 | ^XZ
62 |
63 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | # Heroku uses latest patch level and blows up if you specify it explicitly
4 | ruby "2.1.1"
5 |
6 | gem "rails", "4.0.3"
7 |
8 | # app server
9 | gem "puma", "~> 2.7.1"
10 |
11 | # database
12 | gem "pg", "~> 0.17.1"
13 |
14 | # View stuff
15 | gem 'bootstrap-sass', "~> 3.0.3.0"
16 | gem "decent_exposure", "~> 2.3.0"
17 | gem "draper", "~> 1.3.0"
18 | gem "font-awesome-sass", "~> 4.0.2"
19 | gem "haml-rails", "~> 0.5.3"
20 | gem "nested_form", "~> 0.3.2"
21 | gem "sass-rails", "~> 4.0.0"
22 |
23 | # Use Uglifier as compressor for JavaScript assets
24 | gem "uglifier", ">= 1.3.0"
25 |
26 | # Use CoffeeScript for .js.coffee assets and views
27 | gem "coffee-rails", "~> 4.0.0"
28 |
29 | # See https://github.com/sstephenson/execjs#readme for more supported runtimes
30 | # gem "therubyracer", platforms: :ruby
31 |
32 | # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
33 | gem "turbolinks"
34 |
35 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
36 | gem "jbuilder", "~> 1.2"
37 |
38 | # spree
39 | gem "spree", github: "spree/spree", branch: "2-1-stable"
40 | #gem "spree_gateway", github: "spree/spree_gateway", branch: "2-1-stable"
41 | #gem "spree_auth_devise", github: "spree/spree_auth_devise", branch: "2-1-stable"
42 |
43 | # shipping / labels
44 | gem "spree_active_shipping", github: "spree/spree_active_shipping", branch: "2-1-stable"
45 | gem "fedex", "~> 3.4.0"
46 |
47 | # local
48 | group :development, :test do
49 | gem "active_record_sampler_platter", "~> 0.1.0"
50 | gem "better_errors", "~> 0.9.0"
51 | gem "binding_of_caller", "~> 0.7.2"
52 | gem "capybara-webkit", "~> 1.1.1"
53 | gem "capybara-screenshot", "~> 0.3.16"
54 | gem "database_cleaner", "~> 1.0.1"
55 | gem "dotenv", "~> 0.9.0"
56 | gem "factory_girl_rails", "~> 4.2.1"
57 | gem "ffaker", "~> 1.22.1"
58 | gem "guard-rspec", "~> 4.0.3"
59 | gem "growl", "~> 1.0.3"
60 | gem "pry", "~> 0.9.12.3"
61 | gem "pry-doc", "~> 0.4.6"
62 | gem "pry-rails", "~> 0.3.2"
63 | gem "rspec-rails", "~> 2.14.0"
64 | gem "rspec-pride", "~> 2.2.0"
65 | gem "shoulda-matchers", "~> 2.1.0"
66 | gem "spring", "~> 1.1.0"
67 | gem "spring-commands-rspec", "~> 1.0.1"
68 | gem "xray-rails", "~> 0.1.6"
69 | end
70 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | WorkingTitle::Application.routes.draw do
2 |
3 | # This line mounts Spree's routes at the root of your application.
4 | # This means, any requests to URLs such as /products, will go to Spree::ProductsController.
5 | # If you would like to change where this engine is mounted, simply change the :at option to something different.
6 | #
7 | # We ask that you don't use the :as option here, as Spree relies on it being the default of "spree"
8 | mount Spree::Core::Engine, :at => '/'
9 | # The priority is based upon order of creation: first created -> highest priority.
10 | # See how all your routes lay out with "rake routes".
11 |
12 | namespace :admin do
13 |
14 | resource :label_printing_config, controller: "label_printing_config", only: [:edit, :update] do
15 | member do
16 | post :generate_new_key
17 | end
18 | end
19 |
20 | resources :orders do
21 | resource :shipment, :controller => "orders/shipments", :only => [:update, :edit, :show, :print] do
22 | member do
23 | post :print
24 | post :print_product_labels
25 | end
26 | end
27 | end
28 |
29 | resources :products do
30 | member do
31 | post :print_label
32 | end
33 | end
34 | end
35 |
36 |
37 | # You can have the root of your site routed with "root"
38 | # root 'welcome#index'
39 |
40 | # Example of regular route:
41 | # get 'products/:id' => 'catalog#view'
42 |
43 | # Example of named route that can be invoked with purchase_url(id: product.id)
44 | # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
45 |
46 | # Example resource route (maps HTTP verbs to controller actions automatically):
47 | # resources :products
48 |
49 | # Example resource route with options:
50 | # resources :products do
51 | # member do
52 | # get 'short'
53 | # post 'toggle'
54 | # end
55 | #
56 | # collection do
57 | # get 'sold'
58 | # end
59 | # end
60 |
61 | # Example resource route with sub-resources:
62 | # resources :products do
63 | # resources :comments, :sales
64 | # resource :seller
65 | # end
66 |
67 | # Example resource route with more complex sub-resources:
68 | # resources :products do
69 | # resources :comments
70 | # resources :sales do
71 | # get 'recent', on: :collection
72 | # end
73 | # end
74 |
75 | # Example resource route with concerns:
76 | # concern :toggleable do
77 | # post 'toggle'
78 | # end
79 | # resources :posts, concerns: :toggleable
80 | # resources :photos, concerns: :toggleable
81 |
82 | # Example resource route within a namespace:
83 | # namespace :admin do
84 | # # Directs /admin/products/* to Admin::ProductsController
85 | # # (app/controllers/admin/products_controller.rb)
86 | # resources :products
87 | # end
88 | end
89 |
90 | Spree::Core::Engine.routes.draw do
91 | namespace :admin do
92 |
93 | resource :label_printing_config, controller: "label_printing_config", only: [:edit, :update] do
94 | member do
95 | post :generate_new_key
96 | end
97 | end
98 |
99 | resources :orders do
100 | resource :shipments, :controller => "orders/shipments", :only => [:update, :edit, :index, :print] do
101 | member do
102 | post :print
103 | post :print_product_labels
104 | end
105 | end
106 | end
107 |
108 | resources :products do
109 | member do
110 | post :print_label
111 | end
112 | end
113 | end
114 | end
115 |
--------------------------------------------------------------------------------
/lib/utilities/labeler.rb:
--------------------------------------------------------------------------------
1 | # Generate ZPLII shipping labels for thermal printers.
2 | # Connects to FedEx and pulls in a plaintext label.
3 | #
4 | # Expects a Spree::Shipping::Package
5 | #
6 | module Utilities
7 | class LabelError < Exception; end
8 |
9 | class Labeler
10 | attr_reader :package
11 | def initialize(pkg=nil)
12 | @package = pkg
13 | end
14 |
15 | # Create a shipping label for a given package. Supports Fedex
16 | #
17 | def generate
18 | if package.fedex?
19 | fedex_generate
20 | else
21 | raise LabelError, "Shipping method does not have a valid label generator!"
22 | end
23 | end
24 |
25 | def label_service_name_for_calculator(calculator)
26 | name = calculator.type.gsub(/^Spree::Calculator::Shipping::/, '')
27 |
28 | mappings = {
29 | 'Fedex::PriorityOvernight' => 'PRIORITY_OVERNIGHT',
30 | 'Fedex::StandardOvernight' => 'STANDARD_OVERNIGHT',
31 | 'Fedex::TwoDay' => 'FEDEX_2_DAY',
32 | 'Fedex::ExpressSaver' => 'FEDEX_EXPRESS_SAVER',
33 | 'Fedex::Ground' => 'FEDEX_GROUND',
34 | 'Fedex::GroundHomeDelivery' => 'GROUND_HOME_DELIVERY',
35 | 'Fedex::InternationalEconomy' => 'INTERNATIONAL_ECONOMY',
36 |
37 | 'Usps::FirstClassMailParcels' => 'First',
38 | 'Usps::PriorityMail' => 'Priority',
39 | 'Usps::ExpressMailInternational' => 'ExpressMailInternational',
40 | }
41 |
42 | mappings.fetch(name)
43 | end
44 |
45 | #######################
46 | # Private Instance Methods
47 | #######################
48 |
49 | private
50 | # Makes recursive labeling a bit easier to test
51 | def companion_labeler
52 | @companion_memo ||= self.class.new
53 | end
54 |
55 | def fedex_generate
56 | begin
57 | result = self.class.fedex_connection.label({
58 | packages: [
59 | {
60 | weight: { value: package.weight, units: "LB" },
61 | dimensions: {
62 | length: package.length,
63 | width: package.width,
64 | height: package.height,
65 | units: "IN"
66 | }
67 | }
68 | ],
69 | recipient: package.destination.fedex_formatted,
70 | shipper: package.origin.fedex_formatted,
71 | label_specification: {
72 | image_type: "ZPLII",
73 | label_stock_type: "STOCK_4X6.75_LEADING_DOC_TAB",
74 | filename: nil
75 | },
76 | service_type: service_name,
77 | })
78 | rescue Exception => err
79 | raise Utilities::LabelError, err.message
80 | end
81 |
82 | {
83 | label: result.image,
84 | tracking_number: result.options[:tracking_number]
85 | }
86 | end
87 |
88 | def service_name
89 | label_service_name_for_calculator(package.shipping_method.calculator)
90 | end
91 |
92 | #######################
93 | # Private Class Methods
94 | #######################
95 |
96 | def self.fedex_connection
97 | @fedex_conn_memo ||= ::Fedex::Shipment.new({
98 | key: Spree::ActiveShipping::Config[:fedex_key],
99 | password: Spree::ActiveShipping::Config[:fedex_password],
100 | meter: Spree::ActiveShipping::Config[:fedex_login],
101 | account_number: Spree::ActiveShipping::Config[:fedex_account],
102 | mode: (test_mode? ? 'test' : 'production')
103 | })
104 | end
105 |
106 |
107 | def self.test_mode?
108 | !!Spree::ActiveShipping::Config[:test_mode]
109 | end
110 | end
111 | end
112 |
--------------------------------------------------------------------------------
/app/views/spree/admin/orders/shipments/edit.haml:
--------------------------------------------------------------------------------
1 | -# Header
2 | - content_for :js_onready do
3 | :plain
4 | WorkingTitle.OrderShipments.initialize()
5 |
6 | = render :partial => 'spree/admin/shared/order_tabs', :locals => { :current => 'Shipment' }
7 |
8 | = content_for :page_title do
9 | %i.icon-arrow-right
10 | Print Labels
11 |
12 | -# Body
13 | = content_for :page_actions do
14 | %li
15 | = button_link_to Spree.t(:back_to_orders_list), admin_orders_path, :icon => 'icon-arrow-left'
16 |
17 | - order.shipments.each do |shipment|
18 | - method_id = shipment.shipping_method.id rescue nil
19 | .shipment-package-form{ data: { method_id: method_id} }
20 | %h4{ align: "center" } Shipment #{shipment.number}
21 | %fieldset.no-border-bottom
22 | %legend{ align: "center" }
23 | #{shipment.shipping_method.name rescue "No shipping method"}
24 | -
25 | to
26 | - printer_name = spree_current_user.label_printer_name || "No printer configured"
27 | = link_to "[#{printer_name}]", edit_user_path(spree_current_user)
28 | - if shipment.ready? and can? :update, shipment
29 | = '-'
30 | - token = spree_current_user.spree_api_key
31 | = link_to 'Ship', ship_api_order_shipment_path(order, shipment, token: token),
32 | method: :put, class: %w[button icon-arrow-right],
33 | remote: true,
34 | class: %w[finalize-shipment-button icon-arrow-right button]
35 |
36 | = nested_form_for shipment, url: { action: "update", shipment_number: shipment.number } do |f|
37 | %fieldset.no-border-top
38 | %table{ id: "packages-#{shipment.number}" }
39 | %thead
40 | %tr
41 | %th.shipping-method-column
42 | Method
43 | %th
44 | Box type
45 | %th
46 | Weight (lbs)
47 | %th
48 | Tracking #
49 |
50 | = f.fields_for :packages, wrapper: false do |package_form|
51 | - obj = package_form.object
52 | %tr.fields
53 | %td.shipping-method-column
54 | - suggested_method = obj.shipping_method || shipment.shipping_method
55 | - if suggested_method
56 | - select_options = options_for_select(Spree::ShippingMethod.all.map { |m| [m.name, m.id] }, suggested_method.id )
57 | - else
58 | - select_options = options_for_select(Spree::ShippingMethod.all.map { |m| [m.name, m.id] })
59 |
60 | = package_form.select(:shipping_method_id, select_options, include_blank: true)
61 | %td
62 | = package_form.select(:box_id, Spree::Shipping::Box.all.map { |b| [b.description, b.id] })
63 | %td.weight-column
64 | = package_form.text_field :weight
65 | %td
66 | - if (trk = obj.tracking_number) && (meth = obj.shipping_method)
67 | - url = meth.build_tracking_url(trk)
68 | = label_tag(:tracking_number, link_to(trk, url))
69 | - else
70 | = label_tag(:tracking_number, 'n/a')
71 | %td.actions
72 | - if obj
73 | - if spree_current_user.label_printer_name.present?
74 | =link_to("", print_admin_order_shipments_path(package_id: obj.id),
75 | method: :post, remote: true, class: %w[icon-print no-text with-tip])
76 | - else
77 | =link_to("", "javascript:alert('#{Spree.t(:no_label_printer_yet)}')",
78 | class: %w[red icon-print no-text with-tip])
79 |
80 | = package_form.link_to_remove("", class: ["delete_item", "icon-trash", "no-text", "with-tip"])
81 |
82 | %span.offset-by-one
83 | = f.link_to_add(" Add a package",
84 | :packages, data: { target: "#packages-#{shipment.number}" }, class: 'icon-plus')
85 |
86 | .form-buttons.filter-actions.actions
87 | %button.button.icon-refresh{ type: 'submit'}
88 | Update
89 | %span.or
90 | =Spree.t(:or)
91 | = link_to_with_icon 'icon-remove', Spree.t('actions.cancel'),
92 | edit_admin_order_path(shipment.order), class: 'button'
93 |
--------------------------------------------------------------------------------
/spec/models/spree/shipping/package_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Spree::Shipping::Package do
4 | context "database fields" do
5 | it { should have_db_column(:id).of_type(:integer) }
6 | it { should have_db_column(:shipment_id).of_type(:integer) }
7 | it { should have_db_column(:shipping_method_id).of_type(:integer) }
8 | it { should have_db_column(:box_id).of_type(:integer) }
9 | it { should have_db_column(:tracking_number).of_type(:string) }
10 | end
11 |
12 | context "database indexes" do
13 | it { should have_db_index(:shipment_id).unique(false) }
14 | end
15 |
16 | context "configuration" do
17 | describe "associations" do
18 | it { should belong_to(:shipment) }
19 | it { should belong_to(:box) }
20 | it { should have_one(:order).through(:shipment) }
21 | end
22 |
23 | describe "validations" do
24 | it { should validate_presence_of :box_id }
25 | it { should validate_presence_of :shipping_method_id }
26 | it { should validate_presence_of :weight }
27 | it { should validate_numericality_of(:weight) }
28 | end
29 | end
30 |
31 | context "public instance methods" do
32 | let(:shipment) { double("shipment").as_null_object }
33 | let(:shipping_method) { double("shipping method").as_null_object }
34 | let(:order) { double("order") }
35 | let(:box) { double("box") }
36 |
37 | before { subject.stub(:shipment).and_return(shipment) }
38 | before { subject.stub(:shipping_method).and_return(shipping_method) }
39 | before { subject.stub(:order).and_return(order) }
40 | before { subject.stub(:box).and_return(box) }
41 |
42 | context "delegates" do
43 | it "should delegate :fedex? to :shipping_method" do
44 | shipping_method.should_receive :fedex?
45 | subject.fedex?
46 | end
47 |
48 | %i[length width height].each do |dimension|
49 | it "should delegate :#{dimension} to :box" do
50 | box.should_receive dimension
51 | subject.send dimension
52 | end
53 | end
54 | end
55 |
56 | describe ".destination" do
57 | after { subject.destination }
58 |
59 | context "shipment has an address" do
60 | before { shipment.stub(:address).and_return(Object.new) }
61 |
62 | it "should return the shipment address" do
63 | shipment.should_receive(:address)
64 | end
65 | end
66 |
67 | context "shipment has no address" do
68 | before { shipment.stub(:address).and_return(nil) }
69 |
70 | it "should try the order :ship_address as a fallback" do
71 | order.should_receive(:ship_address)
72 | end
73 | end
74 | end
75 |
76 | describe ".origin" do
77 | after { subject.origin }
78 |
79 | it "should return the shipment's stock location" do
80 | shipment.should_recive :stock_location
81 | end
82 | end
83 |
84 | describe ".weight_in_ounces" do
85 | context "package has a :weight greater than zero" do
86 | let(:weight) { rand(1..1000) / 10.0 }
87 | before { subject.stub(:weight).and_return weight }
88 |
89 | it "should return :weight * 16.0 rounded up to the nearest 10th" do
90 | raw = weight * 16.0
91 | rounded_up_nearest_tenth = (raw * 10).ceil / 10.0
92 | subject.weight_in_ounces.should == (rounded_up_nearest_tenth)
93 | end
94 | end
95 | end
96 |
97 | describe ".print_label!" do
98 | let(:printer_name) { Faker::Company.bs }
99 | let(:zpl_text) { Faker::Company.bs }
100 | let(:raw_printer) { double("raw printer").as_null_object }
101 |
102 | before { subject.stub :save! }
103 | before { Utilities::RawPrinter.stub(:new) { raw_printer } }
104 | before { subject.stub(:label_zpl).and_return zpl_text }
105 |
106 | after { subject.print_label!(printer_name: printer_name) }
107 |
108 | it "should call :generate_label!" do
109 | subject.should_receive(:generate_label!)
110 | end
111 |
112 | it "should create a new print job with the appropriate label text and printer name" do
113 | raw_printer.should_receive(:print).with(zpl_text)
114 | end
115 | end
116 | end
117 | end
118 |
--------------------------------------------------------------------------------