├── .autotest ├── test ├── integration │ └── spree │ │ ├── admin │ │ ├── untitled file │ │ └── wholesalers_test.rb │ │ └── wholesalers_test.rb ├── dummy_hooks │ ├── templates │ │ ├── admin │ │ │ ├── all.js │ │ │ └── all.css │ │ └── store │ │ │ ├── all.js │ │ │ ├── all.css │ │ │ └── screen.css │ ├── after_migrate.rb.sample │ └── before_migrate.rb ├── fixtures │ └── spree │ │ ├── roles.yml │ │ ├── wholesalers.yml │ │ ├── addresses.yml │ │ ├── orders.yml │ │ ├── variants.yml │ │ ├── states.yml │ │ ├── products.yml │ │ └── countries.yml ├── support │ ├── helper_methods.rb │ └── integration_case.rb ├── unit │ └── spree │ │ ├── roles_test.rb │ │ ├── product_test.rb │ │ ├── order_test.rb │ │ └── wholesaler_test.rb ├── test_helper.rb └── factories.rb ├── Gemfile ├── app ├── views │ └── spree │ │ ├── admin │ │ ├── hooks │ │ │ ├── _wholesale_tab.html.erb │ │ │ ├── _admin_orders_index_rows.html.erb │ │ │ ├── _admin_orders_index_headers.html.erb │ │ │ ├── _admin_orders_index_search.html.erb │ │ │ └── _product_form_right.html.erb │ │ ├── wholesalers │ │ │ ├── _user_options.html.erb │ │ │ ├── new.html.erb │ │ │ ├── edit.html.erb │ │ │ ├── show.html.erb │ │ │ ├── _form.html.erb │ │ │ └── index.html.erb │ │ └── variants │ │ │ └── _form.html.erb │ │ ├── hooks │ │ ├── _cart_item_price.html.erb │ │ ├── _cart_item_total.html.erb │ │ ├── _wholesale_customer_id.html.erb │ │ ├── _products_wholesale_price.html.erb │ │ ├── _wholesale_customer.html.erb │ │ ├── _product_wholesale_price.html.erb │ │ └── _wholesale_payment_options.html.erb │ │ ├── shared │ │ ├── _store_menu.html.erb │ │ ├── _wholesale_static_content.html.erb │ │ └── _address_form.html.erb │ │ ├── wholesalers │ │ ├── edit.html.erb │ │ ├── new.html.erb │ │ ├── index.html.erb │ │ ├── show.html.erb │ │ └── _fields.html.erb │ │ ├── orders │ │ └── show.html.erb │ │ └── checkout │ │ └── _summary.html.erb ├── application_helper_decorator.rb ├── assets │ ├── stylesheets │ │ ├── store │ │ │ └── wholesale.css │ │ └── admin │ │ │ └── wholesalers.css │ └── javascripts │ │ └── wholesaler_address.js ├── overrides │ ├── product_wholesale_price.rb │ ├── products_wholesale_price.rb │ ├── cart_item_total.rb │ ├── admin │ │ ├── admin-wholesale-tab.rb │ │ ├── admin-orders-search.rb │ │ ├── admin_product_form_right.rb │ │ ├── admin-order-show-buttons.rb │ │ ├── admin-orders-rows.rb │ │ └── admin-orders-headers.rb │ ├── wholesale-my-orders.rb │ ├── cart_item_price.rb │ ├── id_inside_cart_form.rb │ └── checkout_payment_step.rb ├── models │ └── spree │ │ ├── variant_decorator.rb │ │ ├── line_item_decorator.rb │ │ ├── user_decorator.rb │ │ ├── product_decorator.rb │ │ ├── spree_current_order_decorator.rb │ │ ├── order_decorator.rb │ │ └── wholesaler.rb └── controllers │ └── spree │ ├── admin │ ├── base_controller_decorator.rb │ └── wholesalers_controller.rb │ ├── checkout_controller_decorator.rb │ └── wholesalers_controller.rb ├── lib ├── spree_wholesale │ ├── version.rb │ └── engine.rb ├── spree_wholesale.rb ├── tasks │ └── spree_wholesale.rake └── generators │ └── spree_wholesale │ └── install_generator.rb ├── .gitignore ├── .travis.yml ├── Versionfile ├── config ├── routes.rb └── locales │ └── en.yml ├── Rakefile ├── db └── migrate │ └── 20120113164801_install_spree_wholesale.rb ├── spree_wholesale.gemspec ├── LICENSE └── README.md /.autotest: -------------------------------------------------------------------------------- 1 | require "autotest/growl" 2 | -------------------------------------------------------------------------------- /test/integration/spree/admin/untitled file: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | gemspec 3 | -------------------------------------------------------------------------------- /test/dummy_hooks/templates/admin/all.js: -------------------------------------------------------------------------------- 1 | //= require admin/spree_core 2 | -------------------------------------------------------------------------------- /test/dummy_hooks/templates/store/all.js: -------------------------------------------------------------------------------- 1 | //= require store/spree_core 2 | -------------------------------------------------------------------------------- /app/views/spree/admin/hooks/_wholesale_tab.html.erb: -------------------------------------------------------------------------------- 1 | <%= tab :wholesalers %> 2 | -------------------------------------------------------------------------------- /app/views/spree/hooks/_cart_item_price.html.erb: -------------------------------------------------------------------------------- 1 | <%= format_price(line_item.price) %> 2 | -------------------------------------------------------------------------------- /test/dummy_hooks/templates/admin/all.css: -------------------------------------------------------------------------------- 1 | /* 2 | *= require admin/spree_core 3 | */ 4 | -------------------------------------------------------------------------------- /test/dummy_hooks/templates/store/all.css: -------------------------------------------------------------------------------- 1 | /* 2 | *= require store/spree_core 3 | */ 4 | -------------------------------------------------------------------------------- /lib/spree_wholesale/version.rb: -------------------------------------------------------------------------------- 1 | module SpreeWholesale 2 | VERSION = "0.70.3" 3 | end 4 | -------------------------------------------------------------------------------- /app/views/spree/admin/hooks/_admin_orders_index_rows.html.erb: -------------------------------------------------------------------------------- 1 | <%= order.is_wholesale? ? 'yes' : 'no' %> 2 | -------------------------------------------------------------------------------- /app/views/spree/admin/hooks/_admin_orders_index_headers.html.erb: -------------------------------------------------------------------------------- 1 | <%= sort_link @search, :wholesale, t("wholesale") %> 2 | -------------------------------------------------------------------------------- /test/fixtures/spree/roles.yml: -------------------------------------------------------------------------------- 1 | admin: 2 | name: admin 3 | 4 | user: 5 | name: user 6 | 7 | wholesaler: 8 | name: wholesaler 9 | -------------------------------------------------------------------------------- /app/views/spree/hooks/_cart_item_total.html.erb: -------------------------------------------------------------------------------- 1 | <%= format_price(line_item.price * line_item.quantity) unless line_item.quantity.nil? %> 2 | -------------------------------------------------------------------------------- /app/application_helper_decorator.rb: -------------------------------------------------------------------------------- 1 | ApplicationHelper.module_eval do 2 | 3 | def wholesaler_signed_in? 4 | current_user && current_user.has_role?("wholesaler") 5 | end 6 | 7 | end 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | \#* 2 | *~ 3 | .#* 4 | .DS_Store 5 | .idea 6 | .project 7 | coverage/ 8 | Gemfile.lock 9 | pkg/*.gem 10 | test/dummy_hooks/after_migrate.rb 11 | test/dummy 12 | tmp/**/* 13 | *.swp 14 | -------------------------------------------------------------------------------- /lib/spree_wholesale.rb: -------------------------------------------------------------------------------- 1 | require "spree_core" 2 | require "spree_auth" 3 | require "spree_sample" unless Rails.env == "production" 4 | 5 | require "spree_wholesale/engine" 6 | require "spree_wholesale/engine" 7 | -------------------------------------------------------------------------------- /test/dummy_hooks/after_migrate.rb.sample: -------------------------------------------------------------------------------- 1 | # assume wholesale prices 2 | rake "db:migrate db:seed spree_sample:load spree_wholesale:create_role spree_wholesale:assume_wholesale_prices", :env => "development" 3 | -------------------------------------------------------------------------------- /app/views/spree/shared/_store_menu.html.erb: -------------------------------------------------------------------------------- 1 |
  • <%= link_to t("home") , root_path %>
  • 2 |
  • <%= link_to t("wholesalers") , spree.wholesalers_path %>
  • 3 |
  • <%= link_to_cart %>
  • 4 | 5 | -------------------------------------------------------------------------------- /app/views/spree/admin/hooks/_admin_orders_index_search.html.erb: -------------------------------------------------------------------------------- 1 |

    2 | <%= f.check_box :wholesale_eq, { :style => "vertical-align:middle;" } %> 3 | 6 |

    7 | -------------------------------------------------------------------------------- /app/assets/stylesheets/store/wholesale.css: -------------------------------------------------------------------------------- 1 | span.price_msrp, 2 | span.price_wholesale { 3 | display: block; 4 | } 5 | span.price_msrp { 6 | color: #c00e0e; 7 | } 8 | span.price_wholesale { 9 | color: #68962c; 10 | } 11 | -------------------------------------------------------------------------------- /app/views/spree/hooks/_wholesale_customer_id.html.erb: -------------------------------------------------------------------------------- 1 | <% order ||= @order %> 2 | <% if order && order.is_wholesale? %> 3 |

    <%= t('wholesale_customer', { :id => order.user_id }) %>

    4 | <% end %> 5 | -------------------------------------------------------------------------------- /app/views/spree/hooks/_products_wholesale_price.html.erb: -------------------------------------------------------------------------------- 1 | <% if current_user && current_user.has_role?(:wholesaler) %> 2 | <% if product.is_wholesaleable? %> 3 |
    <%= number_to_currency(product.wholesale_price) %>
    4 | <% end %> 5 | <% end %> 6 | -------------------------------------------------------------------------------- /app/overrides/product_wholesale_price.rb: -------------------------------------------------------------------------------- 1 | Deface::Override.new( 2 | :virtual_path => 'spree/products/_cart_form', 3 | :name => 'product_wholesale_price', 4 | :insert_bottom => "#product-price", 5 | :partial => "spree/hooks/product_wholesale_price", 6 | :disabled => false 7 | ) 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_script: 2 | - "sh -e /etc/init.d/xvfb start" 3 | - "bundle exec dummier" 4 | 5 | script: "DISPLAY=:99.0 bundle exec rake" 6 | 7 | branches: 8 | only: 9 | - master 10 | 11 | rvm: 12 | - 1.8.7 13 | - 1.9.2 14 | - 1.9.3 15 | - ree 16 | - rbx 17 | -------------------------------------------------------------------------------- /app/views/spree/admin/wholesalers/_user_options.html.erb: -------------------------------------------------------------------------------- 1 | <% wholesaler ||= @wholesaler %> 2 | <% if wholesaler.active? %> 3 | <%= link_to "deny", reject_admin_wholesaler_path(wholesaler) %> 4 | <% else %> 5 | <%= link_to "approve", approve_admin_wholesaler_path(wholesaler) %> 6 | <% end %> 7 | -------------------------------------------------------------------------------- /test/support/helper_methods.rb: -------------------------------------------------------------------------------- 1 | module HelperMethods 2 | 3 | def random_email 4 | random_token.split("").insert(random_token.length / 2, "@").push(".com").join("") 5 | end 6 | 7 | def random_token 8 | Digest::SHA1.hexdigest((Time.now.to_i * rand).to_s) 9 | end 10 | 11 | end 12 | -------------------------------------------------------------------------------- /app/overrides/products_wholesale_price.rb: -------------------------------------------------------------------------------- 1 | Deface::Override.new( 2 | :virtual_path => 'spree/shared/_products', 3 | :name => 'products_wholesale_price', 4 | :insert_after => "[data-hook='products_list_item'] .price.selling", 5 | :partial => "spree/hooks/products_wholesale_price", 6 | :disabled => false 7 | ) 8 | -------------------------------------------------------------------------------- /app/models/spree/variant_decorator.rb: -------------------------------------------------------------------------------- 1 | Spree::Variant.instance_eval do 2 | attr_accessible :wholesale_price 3 | end 4 | 5 | Spree::Variant.class_eval do 6 | scope :wholesales, where("spree_variants.wholesale_price > 0") 7 | 8 | def is_wholesaleable? 9 | 0 < wholesale_price 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /test/unit/spree/roles_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Spree::RolesTest < ActiveSupport::TestCase 4 | 5 | should "find or create wholesale role" do 6 | role = Spree::Role.find_or_create_by_name("wholesaler") 7 | assert !role.nil?, "Wholesale role does not exist." 8 | end 9 | 10 | end 11 | -------------------------------------------------------------------------------- /Versionfile: -------------------------------------------------------------------------------- 1 | "1.2.x" => { :branch => "master" } 2 | "1.1.x" => { :branch => "1.1.x" } 3 | "1.0.x" => { :branch => "1.0.x" } 4 | "0.70.x" => { :branch => "0.70.x" } 5 | "0.60.x" => { :branch => "0.60.x" } 6 | "0.50.x" => { :branch => "0.50.x" } 7 | "0.40.x" => { :version => "0.40.2.2" } 8 | "0.30.x" => { :version => "0.40.2.2" } 9 | -------------------------------------------------------------------------------- /app/controllers/spree/admin/base_controller_decorator.rb: -------------------------------------------------------------------------------- 1 | [Spree::BaseController, Spree::Admin::BaseController].each do |controller| 2 | 3 | controller.class_eval do 4 | 5 | def default_country 6 | @default_country ||= Spree::Country.find Spree::Config[:default_country_id] 7 | end 8 | 9 | end 10 | 11 | end 12 | -------------------------------------------------------------------------------- /app/views/spree/hooks/_wholesale_customer.html.erb: -------------------------------------------------------------------------------- 1 | <% if @user.wholesaler? %> 2 |

    Wholesaler

    3 |

    <%= t('wholesale_customer', { :id => @user.id }) %>

    4 |

    <%= t('wholesale_customer_since', { :time => @user.wholesaler.created_at }) %>

    5 | <% end %> 6 | -------------------------------------------------------------------------------- /app/overrides/cart_item_total.rb: -------------------------------------------------------------------------------- 1 | #replace :cart_item_total, 'hooks/cart_item_total' 2 | Deface::Override.new(:virtual_path => 'spree/orders/_line_item', 3 | :name => 'cart_item_total', 4 | :replace_contents => "[data-hook='cart_item_total'], #cart_item_total[data-hook]", 5 | :partial => "spree/hooks/cart_item_total", 6 | :disabled => false) 7 | -------------------------------------------------------------------------------- /app/overrides/admin/admin-wholesale-tab.rb: -------------------------------------------------------------------------------- 1 | # insert_after :admin_tabs, 'admin/hooks/wholesale_tab' 2 | Deface::Override.new( 3 | :virtual_path => "spree/layouts/admin", 4 | :name => "admin-wholesale-tab", 5 | :insert_bottom => "[data-hook='admin_tabs']", 6 | :partial => "spree/admin/hooks/wholesale_tab", 7 | :disabled => false 8 | ) 9 | -------------------------------------------------------------------------------- /app/overrides/wholesale-my-orders.rb: -------------------------------------------------------------------------------- 1 | #insert_before :account_my_orders, 'hooks/wholesale_customer' 2 | Deface::Override.new(:virtual_path => 'spree/users/show', 3 | :name => 'wholesale-my-orders', 4 | :insert_before => "[data-hook='account_my_orders'], #account_my_orders[data-hook]", 5 | :partial => "spree/hooks/wholesale_customer", 6 | :disabled => false) 7 | -------------------------------------------------------------------------------- /app/views/spree/admin/wholesalers/new.html.erb: -------------------------------------------------------------------------------- 1 |

    <%= t("new_wholesaler") %>

    2 | 3 | <%= form_for [:admin, @wholesaler], :html => { :class => "wholesaler-form" } do |f| %> 4 | <%= render "spree/shared/error_messages", :target => @wholesaler %> 5 | <%= render "form", :form => f %> 6 | <%= render "spree/admin/shared/new_resource_links" %> 7 | <% end %> 8 | -------------------------------------------------------------------------------- /app/views/spree/wholesalers/edit.html.erb: -------------------------------------------------------------------------------- 1 |

    <%= t('wholesale_update') %>

    2 | 3 | <%= form_for @wholesaler do |form| %> 4 | <%= render "spree/shared/error_messages", :target => @wholesaler %> 5 | <%= render 'fields', :form => form %> 6 | 7 |

    8 | <%= form.submit t("update"), :class => 'button' %> 9 |

    10 | <% end %> 11 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Spree::Core::Engine.routes.draw do 2 | 3 | get "/wholesaler/states" => "spree/wholesaler_states#index" 4 | 5 | resources :wholesalers 6 | 7 | namespace(:admin) do 8 | 9 | resources :wholesalers do 10 | member do 11 | get :approve 12 | get :reject 13 | end 14 | end 15 | 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /test/fixtures/spree/wholesalers.yml: -------------------------------------------------------------------------------- 1 | new_wholesaler: 2 | company: Test Company, 3 | buyer_contact: Mr Contacter 4 | manager_contact: Mr Manager 5 | phone: 555-555-5555 6 | fax: 555-555-5555 ext 1 7 | resale_number: 123456789 8 | taxid: 555-55-5555 9 | web_address: testcompany.com 10 | terms: Credit Card 11 | notes: What a guy! 12 | -------------------------------------------------------------------------------- /app/views/spree/admin/wholesalers/edit.html.erb: -------------------------------------------------------------------------------- 1 |

    <%= t("editing_wholesaler") %>

    2 | 3 | <%= form_for [:admin, @wholesaler], :html => { :class => "wholesaler-form" } do |f| %> 4 | <%= render "spree/shared/error_messages", :target => @wholesaler %> 5 | <%= render "form", :form => f %> 6 | <%= render "spree/admin/shared/edit_resource_links" %> 7 | <% end %> 8 | -------------------------------------------------------------------------------- /app/overrides/cart_item_price.rb: -------------------------------------------------------------------------------- 1 | #replace :cart_item_price, 'hooks/cart_item_price' 2 | Deface::Override.new( 3 | :virtual_path => 'spree/orders/_line_item', 4 | :name => 'cart_item_price', 5 | :replace_contents => "[data-hook='cart_item_price'], #cart_item_price[data-hook]", 6 | :partial => "spree/hooks/cart_item_price", 7 | :disabled => false 8 | ) 9 | -------------------------------------------------------------------------------- /app/views/spree/wholesalers/new.html.erb: -------------------------------------------------------------------------------- 1 |

    <%= t('wholesale_signup') %>

    2 | 3 | <%= form_for @wholesaler do |form| %> 4 | <%= render "spree/shared/error_messages", :target => @wholesaler %> 5 | <%= render 'fields', :form => form %> 6 | 7 |

    8 | <%= form.submit t("wholesale_apply"), :class => 'button' %> 9 |

    10 | <% end %> 11 | -------------------------------------------------------------------------------- /app/views/spree/admin/hooks/_product_form_right.html.erb: -------------------------------------------------------------------------------- 1 | <%= f.field_container :wholesale_price do %> 2 | <%= f.label :wholesale_price, t(".master_wholesale_price")%> *
    3 | <%= f.text_field :wholesale_price, :value => number_with_precision(@product.wholesale_price, :precision => 2) %> 4 | <%= f.error_message_on :wholesale_price %> 5 | <% end %> 6 | -------------------------------------------------------------------------------- /app/overrides/admin/admin-orders-search.rb: -------------------------------------------------------------------------------- 1 | #insert_after :admin_orders_index_search, 'admin/hooks/admin_orders_index_search' 2 | Deface::Override.new(:virtual_path => 'spree/admin/orders/index', 3 | :name => 'admin-orders-search', 4 | :insert_before => "[data-hook='admin_orders_index_search_buttons']", 5 | :partial => "spree/admin/hooks/admin_orders_index_search", 6 | :disabled => false) 7 | -------------------------------------------------------------------------------- /app/overrides/id_inside_cart_form.rb: -------------------------------------------------------------------------------- 1 | #insert_before :inside_cart_form, 'hooks/wholesale_customer_id' 2 | Deface::Override.new( 3 | :virtual_path => 'spree/orders/edit', 4 | :name => 'id_inside_cartform', 5 | :insert_before => "[data-hook='inside_cart_form'], #inside_cart_form[data-hook]", 6 | :partial => "spree/hooks/wholesale_customer_id", 7 | :disabled => false 8 | ) 9 | -------------------------------------------------------------------------------- /app/models/spree/line_item_decorator.rb: -------------------------------------------------------------------------------- 1 | Spree::LineItem.class_eval do 2 | 3 | delegate_belongs_to :variant, :wholesale_price 4 | delegate_belongs_to :variant, :is_wholesaleable? 5 | 6 | def copy_price 7 | 8 | self.price = (order.is_wholesale? && variant.is_wholesaleable? ? variant.wholesale_price : variant.price) if variant && self.price.nil? 9 | 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /app/overrides/admin/admin_product_form_right.rb: -------------------------------------------------------------------------------- 1 | #replace :admin_product_form_right, 'admin/hooks/product_form_right' 2 | Deface::Override.new( 3 | :virtual_path => 'spree/admin/products/_form', 4 | :name => 'admin_product_form_right', 5 | :insert_top => "[data-hook='admin_product_form_right']", 6 | :partial => "spree/admin/hooks/product_form_right", 7 | :disabled => false 8 | ) 9 | -------------------------------------------------------------------------------- /app/models/spree/user_decorator.rb: -------------------------------------------------------------------------------- 1 | Spree::User.instance_eval do 2 | 3 | has_one :wholesaler, :class_name => "Spree::Wholesaler" 4 | 5 | scope :wholesale, lambda { includes(:roles).where("spree_roles.name" => "wholesaler") } 6 | 7 | 8 | end 9 | 10 | Spree::User.class_eval do 11 | 12 | def wholesaler? 13 | has_role?("wholesaler") && !wholesaler.nil? 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /app/overrides/admin/admin-order-show-buttons.rb: -------------------------------------------------------------------------------- 1 | #insert_after :admin_order_show_buttons, 'hooks/wholesale_customer_id' 2 | Deface::Override.new(:virtual_path => 'spree/orders/show', 3 | :name => 'admin-order-show-buttons', 4 | :insert_after => "[data-hook='admin_order_show_buttons'], #admin_order_show_buttons[data-hook]", 5 | :partial => "spree/hooks/wholesale_customer_id", 6 | :disabled => false) 7 | -------------------------------------------------------------------------------- /app/overrides/admin/admin-orders-rows.rb: -------------------------------------------------------------------------------- 1 | # insert_after :admin_orders_index_rows, 'admin/hooks/admin_orders_index_rows' 2 | Deface::Override.new(:virtual_path => 'spree/admin/orders/index', 3 | :name => 'admin-orders-rows', 4 | :insert_bottom => "[data-hook='admin_orders_index_rows'], #admin_orders_index_rows[data-hook]", 5 | :partial => "spree/admin/hooks/admin_orders_index_rows", 6 | :disabled => false) 7 | -------------------------------------------------------------------------------- /app/overrides/checkout_payment_step.rb: -------------------------------------------------------------------------------- 1 | #insert_before :checkout_payment_step, 'hooks/wholesale_payment_options' 2 | Deface::Override.new( 3 | :virtual_path => 'spree/checkout/_payment', 4 | :name => 'checkout_payment_step', 5 | :insert_before => "[data-hook='checkout_payment_step'], #customer_payment_step[data-hook]", 6 | :partial => "spree/hooks/wholesale_payment_options", 7 | :disabled => false 8 | ) 9 | -------------------------------------------------------------------------------- /app/views/spree/hooks/_product_wholesale_price.html.erb: -------------------------------------------------------------------------------- 1 | <% if current_user && current_user.has_role?(:wholesaler) %> 2 | <% if @product.try(:wholesale_price) && 0 < @product.try(:wholesale_price) %> 3 |
    <%= t(:wholesale_price) %>
    4 |
    <%= number_to_currency(@product.wholesale_price) %>
    5 | <% end %> 6 | <% end %> 7 | -------------------------------------------------------------------------------- /app/models/spree/product_decorator.rb: -------------------------------------------------------------------------------- 1 | Spree::Product.instance_eval do 2 | 3 | delegate_belongs_to :master, :wholesale_price if Spree::Variant.table_exists? && Spree::Variant.column_names.include?("wholesale_price") 4 | 5 | attr_accessible :wholesale_price 6 | 7 | end 8 | 9 | Spree::Product.class_eval do 10 | 11 | def is_wholesaleable? 12 | 0 < master.wholesale_price 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /app/overrides/admin/admin-orders-headers.rb: -------------------------------------------------------------------------------- 1 | #insert_after :admin_orders_index_headers, 'admin/hooks/admin_orders_index_headers' 2 | Deface::Override.new(:virtual_path => 'spree/admin/orders/index', 3 | :name => 'admin-orders-headers', 4 | :insert_bottom => "[data-hook='admin_orders_index_headers'], #admin_orders_index_headers[data-hook]", 5 | :partial => "spree/admin/hooks/admin_orders_index_headers", 6 | :disabled => false) 7 | -------------------------------------------------------------------------------- /app/assets/stylesheets/admin/wholesalers.css: -------------------------------------------------------------------------------- 1 | .wholesaler-form input[type=text], 2 | .wholesaler-form select { 3 | font-size: 1.1em; 4 | font-weight: normal; 5 | padding: 2px 3px; 6 | } 7 | 8 | input.required, select.required { 9 | color: #222; 10 | } 11 | 12 | .leftie, .rightie { 13 | display: block; 14 | width: 49%; 15 | } 16 | .leftie { 17 | float: left; 18 | } 19 | .rightie { 20 | float: right; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /test/fixtures/spree/addresses.yml: -------------------------------------------------------------------------------- 1 | billing: 2 | firstname: Bill 3 | lastname: Billerman 4 | address1: 1027 State St 5 | address2: 6 | city: Santa Barbara 7 | state_id: 889445952 8 | zipcode: 16804 9 | country_id: 214 10 | phone: 555-555-1027 11 | 12 | 13 | shipping: 14 | firstname: Boxy 15 | lastname: Shipperman 16 | address1: 1000 State St 17 | address2: 18 | city: Santa Barbara 19 | state_id: 889445952 20 | zipcode: 16804 21 | country_id: 214 22 | phone: 555-555-5555 23 | 24 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | require 'rubygems' 3 | begin 4 | require 'bundler/setup' 5 | rescue LoadError 6 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 7 | end 8 | 9 | require 'rake' 10 | require 'rake/testtask' 11 | #require 'rake/rdoctask' 12 | 13 | Bundler::GemHelper.install_tasks 14 | 15 | Rake::TestTask.new(:test) do |t| 16 | t.libs << 'lib' 17 | t.libs << 'test' 18 | t.pattern = 'test/**/*_test.rb' 19 | t.verbose = false 20 | end 21 | 22 | task :default => :test 23 | -------------------------------------------------------------------------------- /test/unit/spree/product_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Spree::ProductTest < ActiveSupport::TestCase 4 | 5 | fixtures :products 6 | 7 | should "respond to wolesale price" do 8 | product = products(:wholesale) 9 | assert product.master.respond_to?(:wholesale_price), "Product is not wholesale ready. Try running `rake db:migrate` first." 10 | end 11 | 12 | should "be wholesaleble" do 13 | product = products(:wholesale) 14 | assert product.is_wholesaleable? 15 | end 16 | 17 | 18 | end 19 | -------------------------------------------------------------------------------- /app/views/spree/orders/show.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <%= link_to t('back_to_store'), products_path %> 3 | 4 | <%= render 'spree/hooks/wholesale_customer_id' %> 5 | 6 | <% if params.has_key? :checkout_complete %> 7 |

    8 |

    <%= t('thank_you_for_your_order') %>

    9 | <% else %> 10 | <%= link_to t('my_account'), account_path if current_user%> 11 | <% end %> 12 |

    13 | 14 | <%= render :partial => 'shared/order_details', :locals => {:order => @order} %> 15 | 16 |
    17 | <%= link_to t('back_to_store'), products_path %> 18 |
    19 | -------------------------------------------------------------------------------- /app/views/spree/wholesalers/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= render 'spree/shared/wholesale_static_content' %> 2 | 3 |
    4 |

    <%= t('wholesale_apply') %>

    5 |

    Maecenas quis tortor arcu. Vivamus rutrum nunc non neque consectetur quis placerat neque lobortis. Nam vestibulum, arcu sodales feugiat consectetur, nisl orci bibendum elit, eu euismod magna sapien ut nibh.

    6 | <%= link_to 'Apply Now!', spree.new_wholesaler_path, :class => 'button' %> 7 |
    8 | 9 |
    10 |

    <%= t('wholesale_login') %>

    11 | <%= render 'spree/shared/login' %> 12 |
    13 | -------------------------------------------------------------------------------- /test/dummy_hooks/before_migrate.rb: -------------------------------------------------------------------------------- 1 | run "rake spree:install:migrations" 2 | run "rake spree_auth:install:migrations" 3 | 4 | # Mount the Spree::Core routes 5 | insert_into_file File.join('config', 'routes.rb'), :after => "Application.routes.draw do\n" do 6 | " # Mount Spree's routes\n mount Spree::Core::Engine, :at => '/'\n" 7 | end 8 | 9 | # remove all stylesheets except core 10 | %w(admin store).each do |ns| 11 | template "#{ns}/all.js", "app/assets/javascripts/#{ns}/all.js", :force => true 12 | template "#{ns}/all.css", "app/assets/stylesheets/#{ns}/all.css", :force => true 13 | end 14 | 15 | # Fix sass load error by using the converted css file 16 | template "store/screen.css", "app/assets/stylesheets/store/screen.css" 17 | 18 | # Install spree_wholesale 19 | run "rails g spree_wholesale:install" 20 | -------------------------------------------------------------------------------- /app/controllers/spree/checkout_controller_decorator.rb: -------------------------------------------------------------------------------- 1 | Spree::CheckoutController.instance_eval do 2 | 3 | before_filter :get_addresses 4 | before_filter :remove_payments_attributes 5 | end 6 | 7 | Spree::CheckoutController.class_eval do 8 | 9 | def get_addresses 10 | return unless current_user && current_user.wholesaler? && !current_user.wholesaler.nil? 11 | @order.bill_address = current_user.wholesaler.bill_address 12 | @order.ship_address = current_user.wholesaler.ship_address 13 | end 14 | 15 | def remove_payments_attributes 16 | if @order.is_wholesale? && @order.payment? && @order.wholesaler.terms != "Credit Card" && params[:order_pay_at] == "later" 17 | params.delete :payment_source if params[:payment_source].present? 18 | params[:order].delete :payments_attributes if params[:order][:payments_attributes].present? 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/views/spree/checkout/_summary.html.erb: -------------------------------------------------------------------------------- 1 |

    <%= t(:order_summary) %>

    2 | <% if order.is_wholesale? %> 3 | <%= render 'spree/hooks/wholesale_customer_id' %> 4 | <% end %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | <% order.adjustments.each do |adjustment| %> 14 | 15 | 16 | 17 | 18 | <% end %> 19 | 20 | 21 | 22 | 23 | 24 | 25 |
    <%= t('item_total') %>:<%= number_to_currency order.item_total %>
    <%= adjustment.label %>: <%= number_to_currency adjustment.amount %>
    <%= t('order_total') %>:<%= number_to_currency @order.total %>
    26 | -------------------------------------------------------------------------------- /lib/tasks/spree_wholesale.rake: -------------------------------------------------------------------------------- 1 | namespace :spree_wholesale do 2 | def load_environment 3 | puts "Loading environment..." 4 | require File.join(Rails.root, 'config', 'environment') 5 | end 6 | 7 | desc "Creates wholesale role" 8 | task :create_role do 9 | load_environment 10 | 11 | name = "wholesaler" 12 | role = Spree::Role.find_by_name(name) rescue nil 13 | if role 14 | puts "Role exists!" 15 | else 16 | role = Spree::Role.create(:name => name) 17 | puts "Role created!" 18 | end 19 | 20 | puts role.inspect 21 | end 22 | 23 | desc "Assumes 50% wholesale discount for each variant" 24 | task :assume_wholesale_prices do 25 | load_environment 26 | 27 | Spree::Variant.all.each do |variant| 28 | price = variant.price * 0.5 29 | ActiveRecord::Base.connection.execute("UPDATE spree_variants SET wholesale_price = #{price} WHERE id = #{variant.id}") 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/generators/spree_wholesale/install_generator.rb: -------------------------------------------------------------------------------- 1 | module SpreeWholesale 2 | module Generators 3 | class InstallGenerator < Rails::Generators::Base 4 | 5 | class_option :skip_migrations, :type => :boolean, :default => false, :description => "Skips the `run_migrations` step" 6 | 7 | desc "Installs required stylesheets, javascripts and migrations for spree_wholesale" 8 | 9 | def add_javascripts 10 | append_file "app/assets/javascripts/store/all.js", "//= require wholesaler_address\n" 11 | append_file "app/assets/javascripts/admin/all.js", "//= require wholesaler_address\n" 12 | end 13 | 14 | def add_stylesheets 15 | inject_into_file "app/assets/stylesheets/store/all.css", " *= require store/wholesale\n", :before => /\*\//, :verbose => true 16 | inject_into_file "app/assets/stylesheets/admin/all.css", " *= require admin/wholesalers\n", :before => /\*\//, :verbose => true 17 | end 18 | 19 | def install_migrations 20 | rake "spree_wholesale:install:migrations" 21 | end 22 | 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /db/migrate/20120113164801_install_spree_wholesale.rb: -------------------------------------------------------------------------------- 1 | class InstallSpreeWholesale < ActiveRecord::Migration 2 | 3 | def self.up 4 | create_table :spree_wholesalers do |t| 5 | t.references :user 6 | t.integer :billing_address_id 7 | t.integer :shipping_address_id 8 | t.string :company 9 | t.string :buyer_contact 10 | t.string :manager_contact 11 | t.string :phone 12 | t.string :fax 13 | t.string :resale_number 14 | t.string :taxid 15 | t.string :web_address 16 | t.string :terms 17 | t.string :alternate_email 18 | t.text :notes 19 | t.timestamps 20 | end 21 | add_index :spree_wholesalers, [:billing_address_id, :shipping_address_id], :name => "wholesalers_addresses" 22 | add_column :spree_orders, :wholesale, :boolean, :default => false 23 | add_column :spree_variants, :wholesale_price, :decimal, :precision => 8, :scale => 2, :null => false, :default => 0 24 | end 25 | 26 | def self.down 27 | remove_column :spree_variants, :wholesale_price 28 | remove_column :spree_orders, :wholesale 29 | drop_table :spree_wholesalers 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /app/views/spree/wholesalers/show.html.erb: -------------------------------------------------------------------------------- 1 |

    <%= t("wholesaler_account") %>

    2 | 3 |
    4 | 5 |
    Company:
    6 |

    <%= @wholesaler.company %>

    7 | 8 |
    Buyer_contact:
    9 |

    <%= @wholesaler.buyer_contact %>

    10 | 11 |
    Manager_contact:
    12 |

    <%= @wholesaler.manager_contact %>

    13 | 14 |
    Phone:
    15 |

    <%= @wholesaler.phone %>

    16 | 17 |
    Fax:
    18 |

    <%= @wholesaler.fax %>

    19 | 20 |
    Resale_number:
    21 |

    <%= @wholesaler.resale_number %>

    22 | 23 |
    Taxid:
    24 |

    <%= @wholesaler.taxid %>

    25 | 26 |
    Web_address:
    27 |

    <%= @wholesaler.web_address %>

    28 | 29 |
    Terms:
    30 |

    <%= @wholesaler.terms %>

    31 | 32 |
    Notes:
    33 |

    <%= simple_format @wholesaler.notes %>

    34 | 35 |
    36 | 37 |
    38 | 39 |
    40 |

    <%= t("bill_address") %>

    41 | <%= render 'spree/admin/shared/address', :address => @wholesaler.bill_address %> 42 |
    43 |
    44 |

    <%= t("ship_address") %>

    45 | <%= render 'spree/admin/shared/address', :address => @wholesaler.ship_address %> 46 |
    47 |
    48 | 49 |
    50 | -------------------------------------------------------------------------------- /lib/spree_wholesale/engine.rb: -------------------------------------------------------------------------------- 1 | module SpreeWholesale 2 | class Engine < Rails::Engine 3 | 4 | engine_name "spree_wholesale" 5 | 6 | config.autoload_paths += %W(#{config.root}/lib) 7 | 8 | class WholesalerAbility 9 | include CanCan::Ability 10 | 11 | def initialize(user) 12 | user ||= User.new 13 | can :index, Spree::Wholesaler 14 | can :new, Spree::Wholesaler 15 | can :create, Spree::Wholesaler 16 | can :read, Spree::Wholesaler do |resource| 17 | resource.user == user || user.has_role?(:admin) 18 | end 19 | can :update, Spree::Wholesaler do |resource| 20 | resource.user == user || user.has_role?(:admin) 21 | end 22 | 23 | end 24 | end 25 | 26 | def self.activate 27 | Dir.glob(File.join(File.dirname(__FILE__), "../../app/**/*_decorator*.rb")) do |c| 28 | Rails.application.config.cache_classes ? require(c) : load(c) 29 | end 30 | 31 | Dir.glob(File.join(File.dirname(__FILE__), "../../app/overrides/**/*.rb")) do |c| 32 | Rails.application.config.cache_classes ? require(c) : load(c) 33 | end 34 | 35 | Spree::Ability.register_ability(WholesalerAbility) 36 | end 37 | 38 | config.to_prepare &method(:activate).to_proc 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /app/views/spree/shared/_wholesale_static_content.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |

    <%= t('wholesalers') %>

    3 | 4 |

    Aenean facilisis nulla vitae urna tincidunt congue sed ut dui. Morbi malesuada nulla nec purus convallis consequat. Vivamus id mollis quam. Morbi ac commodo nulla. In condimentum orci id nisl volutpat bibendum. Quisque commodo hendrerit lorem quis egestas. Maecenas quis tortor arcu. Vivamus rutrum nunc non neque consectetur quis placerat neque lobortis. Nam vestibulum, arcu sodales feugiat consectetur, nisl orci bibendum elit, eu euismod magna sapien ut nibh. Donec semper quam scelerisque tortor dictum gravida. In hac habitasse platea dictumst. Nam pulvinar, odio sed rhoncus suscipit, sem diam ultrices mauris, eu consequat purus metus eu velit. Proin metus odio, aliquam eget molestie nec, gravida ut sapien. Phasellus quis est sed turpis sollicitudin venenatis sed eu odio. Praesent eget neque eu eros interdum malesuada non vel leo. Sed fringilla porta ligula egestas tincidunt. Nullam risus magna, ornare vitae varius eget, scelerisque a libero. Morbi eu porttitor ipsum. Nullam lorem nisi, posuere quis volutpat eget, luctus nec massa. Pellentesque aliquam lacinia tellus sit amet bibendum. Ut posuere justo in enim pretium scelerisque. Etiam ornare vehicula euismod. Vestibulum at risus augue. Sed non semper dolor. Sed fringilla consequat.

    5 |
    6 | -------------------------------------------------------------------------------- /spree_wholesale.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "spree_wholesale/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "spree_wholesale" 7 | s.version = SpreeWholesale::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ["Spencer Steffen"] 10 | s.email = ["spencer@citrusme.com"] 11 | s.homepage = "https://github.com/citrus/spree_wholesale" 12 | s.summary = %q{Wholesale accounts for Spree Commerce.} 13 | s.description = %q{Spree Wholesale adds a wholesale_price field to variants and allows users with a "wholesaler" role to access these prices.} 14 | 15 | s.files = `git ls-files`.split("\n") 16 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 17 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 18 | 19 | s.require_paths = ["lib"] 20 | 21 | s.add_dependency('spree_core', '~> 1.1.0.beta') 22 | s.add_dependency('spree_auth', '~> 1.1.0.beta') 23 | 24 | s.add_development_dependency('spree_sample', '~> 1.1.0') 25 | s.add_development_dependency('shoulda', '~> 3.0.0') 26 | s.add_development_dependency('dummier', '~> 0.3.2') 27 | s.add_development_dependency('factory_girl', '~> 2.6.0') 28 | s.add_development_dependency('capybara', '~> 1.1.2') 29 | s.add_development_dependency('sqlite3', '~> 1.3.4') 30 | #s.add_development_dependency('simplecov', '~> 0.6.1') 31 | 32 | end 33 | -------------------------------------------------------------------------------- /app/models/spree/spree_current_order_decorator.rb: -------------------------------------------------------------------------------- 1 | Spree::Core::CurrentOrder.module_eval do 2 | 3 | # Associate the new order with the currently authenticated user before saving 4 | def before_save_new_order 5 | @current_order.user ||= current_user 6 | @current_order.wholesale = current_user.wholesaler? if current_user 7 | end 8 | 9 | def after_save_new_order 10 | # make sure the user has permission to access the order (if they are a guest) 11 | return if current_user 12 | session[:access_token] = @current_order.token 13 | end 14 | 15 | # The current incomplete order from the session for use in cart and during checkout 16 | def current_order(create_order_if_necessary = false) 17 | return @current_order if @current_order 18 | @current_order ||= Spree::Order.find_by_id(session[:order_id], :include => :adjustments) 19 | if create_order_if_necessary and (@current_order.nil? or @current_order.completed?) 20 | @current_order = Spree::Order.new 21 | before_save_new_order 22 | @current_order.save! 23 | after_save_new_order 24 | end 25 | 26 | if current_user && @current_order 27 | if current_user.wholesaler? && !@current_order.is_wholesale? 28 | @current_order.to_wholesale! 29 | elsif !current_user.wholesaler? && @current_order.is_wholesale? 30 | @current_order.to_fullsale! 31 | end 32 | end 33 | 34 | session[:order_id] = @current_order ? @current_order.id : nil 35 | @current_order 36 | end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /test/unit/spree/order_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Spree::OrderTest < ActiveSupport::TestCase 4 | 5 | PRICE = BigDecimal.new("19.99") 6 | WHOLESALE = BigDecimal.new("18.00") 7 | 8 | fixtures :orders, :products 9 | 10 | def setup 11 | @wholesaler = Factory.create(:wholesaler) 12 | end 13 | 14 | should "respond to wholesale" do 15 | order = Spree::Order.new 16 | assert order.respond_to?(:wholesale) 17 | end 18 | 19 | should "be wholesale" do 20 | order = Factory.create(:order, :user => @wholesaler.user, :wholesale => true) 21 | assert order.is_wholesale? 22 | end 23 | 24 | should "convert to wholesale" do 25 | order = Factory.create(:order, :user => Factory.create(:user)) 26 | assert !order.is_wholesale? 27 | assert !order.to_wholesale! 28 | assert !order.is_wholesale? 29 | order.user.wholesaler = @wholesaler 30 | assert order.to_wholesale! 31 | assert order.is_wholesale? 32 | end 33 | 34 | should "get regular price" do 35 | order = Factory.create(:order) 36 | assert_equal 0, order.item_total 37 | order.add_variant(products(:wholesale).master) 38 | order.update! 39 | assert_equal PRICE, order.item_total 40 | end 41 | 42 | should "get wholesale price" do 43 | order = Factory.create(:order, :user => @wholesaler.user, :wholesale => true) 44 | assert_equal 0, order.item_total 45 | order.add_variant(products(:wholesale).master) 46 | order.update! 47 | assert_equal WHOLESALE, order.item_total 48 | end 49 | 50 | end 51 | -------------------------------------------------------------------------------- /app/controllers/spree/wholesalers_controller.rb: -------------------------------------------------------------------------------- 1 | class Spree::WholesalersController < Spree::BaseController 2 | respond_to :html, :xml 3 | 4 | def index 5 | end 6 | 7 | def show 8 | @wholesaler = Spree::Wholesaler.find(params[:id]) 9 | respond_with(@wholesaler) 10 | end 11 | 12 | def new 13 | @wholesaler = Spree::Wholesaler.new 14 | @wholesaler.build_user 15 | @wholesaler.bill_address = Spree::Address.default 16 | @wholesaler.ship_address = Spree::Address.default 17 | respond_with(@wholesaler) 18 | end 19 | 20 | def create 21 | @wholesaler = Spree::Wholesaler.new(params[:wholesaler]) 22 | if @wholesaler.save 23 | flash[:notice] = I18n.t('spree.wholesaler.signup_success') 24 | redirect_to spree.wholesalers_path 25 | else 26 | flash[:error] = I18n.t('spree.wholesaler.signup_failed') 27 | render :action => "new" 28 | end 29 | end 30 | 31 | def edit 32 | @wholesaler = Spree::Wholesaler.find(params[:id]) 33 | respond_with(@wholesaler) 34 | end 35 | 36 | def update 37 | @wholesaler = Spree::Wholesaler.find(params[:id]) 38 | 39 | if @wholesaler.update_attributes(params[:wholesaler]) 40 | flash[:notice] = I18n.t('spree.wholesaler.update_success') 41 | else 42 | flash[:error] = I18n.t('spree.wholesaler.update_failed') 43 | end 44 | respond_with(@wholesaler) 45 | end 46 | 47 | def destroy 48 | @wholesaler = Spree::Wholesaler.find(params[:id]) 49 | @wholesaler.destroy 50 | flash[:notice] = I18n.t('spree.wholesaler.destroy_success') 51 | respond_with(@wholesaler) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /app/views/spree/admin/variants/_form.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 | <% @product.options.each do |option| %> 5 |

    6 | <%= label :new_variant, option.option_type.presentation %>
    7 | <% if @variant.new_record? %> 8 | <%= select(:new_variant, option.option_type.presentation, 9 | option.option_type.option_values.collect {|ov| [ ov.presentation, ov.id ] }, 10 | {}) 11 | %> 12 | <% else %> 13 | <% opt = @variant.option_values.detect {|o| o.option_type == option.option_type }.presentation %> 14 | <%= text_field(:new_variant, option.option_type.presentation, :value => opt, :disabled => 'disabled') %> 15 | <% end %> 16 |

    17 | <% end %> 18 | 19 |

    <%= f.label :sku, t("sku") %>
    20 | <%= f.text_field :sku %>

    21 | 22 |

    <%= f.label :price, t("price") %>:
    23 | <%= f.text_field :price, :value => number_with_precision(@variant.price, :precision => 2) %>

    24 | 25 |

    <%= f.label :wholesale_price, t(".wholesale_price") %>:
    26 | <%= f.text_field :wholesale_price, :value => number_with_precision(@variant.wholesale_price, :precision => 2) %>

    27 | 28 |

    <%= f.label :cost_price, t("cost_price") %>:
    29 | <%= f.text_field :cost_price, :value => number_with_precision(@variant.cost_price, :precision => 2) %>

    30 | 31 | <% if Spree::Config[:track_inventory_levels] %> 32 |

    <%= f.label :on_hand, t("on_hand") %>:
    33 | <%= f.text_field :on_hand %>

    34 | <% end %> 35 |
    36 |
    37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Spencer Steffen and Citrus Media Group. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of Citrus Media Group nor the names of its 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /test/fixtures/spree/orders.yml: -------------------------------------------------------------------------------- 1 | incomplete: 2 | id: 1069267039 3 | user_id: 1057526560 4 | number: R107687410 5 | item_total: 29.01 6 | total: 29.01 7 | created_at: 2011-01-12 03:07:39 8 | updated_at: 2011-01-12 03:07:39 9 | state: cart 10 | adjustment_total: 0.00 11 | credit_total: 0.00 12 | completed_at: NULL 13 | bill_address_id: NULL 14 | ship_address_id: NULL 15 | payment_total: 0.00 16 | shipping_method_id: NULL 17 | shipment_state: NULL 18 | payment_state: 'balance_due' 19 | email: 'spree@example.com' 20 | special_instructions: NULL 21 | wholesale: 0 22 | 23 | complete: 24 | id: 1069267040 25 | user_id: 1057526560 26 | number: R107687411 27 | item_total: 29.01 28 | total: 29.01 29 | created_at: 2011-01-12 03:07:39 30 | updated_at: 2011-01-12 03:07:39 31 | state: cart 32 | adjustment_total: 0.00 33 | credit_total: 0.00 34 | completed_at: NULL 35 | bill_address_id: NULL 36 | ship_address_id: NULL 37 | payment_total: 0.00 38 | shipping_method_id: NULL 39 | shipment_state: NULL 40 | payment_state: 'balance_due' 41 | email: 'spree@example.com' 42 | special_instructions: NULL 43 | wholesale: 0 44 | 45 | wholesale: 46 | id: 1069267041 47 | user_id: 1 48 | number: R107687412 49 | item_total: 18.00 50 | total: 18.00 51 | created_at: 2011-01-12 03:07:39 52 | updated_at: 2011-01-12 03:07:39 53 | state: complete 54 | adjustment_total: 0.00 55 | credit_total: 0.00 56 | completed_at: 2011-01-12 03:10:48 57 | bill_address_id: NULL 58 | ship_address_id: NULL 59 | payment_total: 0.00 60 | shipping_method_id: NULL 61 | shipment_state: NULL 62 | payment_state: 'balance_due' 63 | email: 'spree@example.com' 64 | special_instructions: NULL 65 | wholesale: 1 66 | 67 | -------------------------------------------------------------------------------- /app/views/spree/admin/wholesalers/show.html.erb: -------------------------------------------------------------------------------- 1 |

    <%= t("wholesaler_account") %>

    2 | 3 |
    4 | 5 |
    Company:
    6 |

    <%= @wholesaler.company %>

    7 | 8 |
    Buyer_contact:
    9 |

    <%= @wholesaler.buyer_contact %>

    10 | 11 |
    Manager_contact:
    12 |

    <%= @wholesaler.manager_contact %>

    13 | 14 |
    Phone:
    15 |

    <%= @wholesaler.phone %>

    16 | 17 |
    Fax:
    18 |

    <%= @wholesaler.fax %>

    19 | 20 |
    Resale_number:
    21 |

    <%= @wholesaler.resale_number %>

    22 | 23 |
    Taxid:
    24 |

    <%= @wholesaler.taxid %>

    25 | 26 |
    Web_address:
    27 |

    <%= @wholesaler.web_address %>

    28 | 29 |
    Terms:
    30 |

    <%= @wholesaler.terms %>

    31 | 32 |
    Notes:
    33 |

    <%= simple_format @wholesaler.notes %>

    34 | 35 |
    36 | 37 |
    38 | 39 |
    40 |

    <%= t("bill_address") %>

    41 | <%= render 'spree/admin/shared/address', :address => @wholesaler.bill_address %> 42 |
    43 |
    44 |

    <%= t("ship_address") %>

    45 | <%= render 'spree/admin/shared/address', :address => @wholesaler.ship_address %> 46 |
    47 | 48 |
    49 |

    <%= t("user_details") %>

    50 |

    51 | ID: <%= @wholesaler.user.id %> 52 |
    53 | Email: <%= @wholesaler.user.email %> 54 |
    55 | <%= link_to "view user", admin_user_path(@wholesaler.user) %> / 56 | <%= link_to "edit user", edit_admin_user_path(@wholesaler.user) %> / 57 | <%= render "user_options", :user => @wholesaler.user %> 58 |

    59 |
    60 | 61 |
    62 | 63 |
    64 | 65 |

    66 | <%= link_to_edit @wholesaler %> <%= t('or') %> 67 | <%= link_to t('back'), collection_url %> 68 |

    69 | -------------------------------------------------------------------------------- /app/views/spree/hooks/_wholesale_payment_options.html.erb: -------------------------------------------------------------------------------- 1 | <% order = (@order || order) %> 2 | <% if order.is_wholesale? %> 3 | 4 |
    5 | 6 |

    Your wholesale terms are <%= @order.user.wholesaler.terms %> which means 7 | <% if @order.user.wholesaler.terms == "Credit Card" %> 8 | that you'll have to pay for this order now.

    9 | <% else %> 10 | that you can pay for this order now or 11 | <% if @order.user.wholesaler.terms == "COD" %> 12 | when it arrives. 13 | <% else %> 14 | at the end of your <%= @order.user.wholesaler.terms.sub("Net ", "") %> day cycle. 15 | <% end %> 16 |

    17 |

    18 | <%= radio_button_tag "order_pay_at", "now" %> 19 | <%= label_tag "order_pay_at_now", "Pay Now" %> 20 |   21 | <%= radio_button_tag "order_pay_at", "later" %> 22 | <%= label_tag "order_pay_at_later", "Pay Later" %> 23 |

    24 | <% content_for :head do %> 25 | 39 | <% end %> 40 | <% end %> 41 |
    42 | 43 |
    44 | 45 | <% end %> 46 | -------------------------------------------------------------------------------- /test/unit/spree/wholesaler_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Spree::WholesalerTest < ActiveSupport::TestCase 4 | 5 | def setup 6 | # nada 7 | end 8 | 9 | should belong_to(:user) 10 | should belong_to(:bill_address) 11 | should belong_to(:ship_address) 12 | 13 | should validate_presence_of(:company) 14 | should validate_presence_of(:buyer_contact) 15 | should validate_presence_of(:manager_contact) 16 | should validate_presence_of(:phone) 17 | should validate_presence_of(:taxid) 18 | 19 | context "with a new wholesaler" do 20 | 21 | setup do 22 | @wholesaler = Factory.build(:wholesaler, :user => nil, :ship_address => nil, :bill_address => nil) 23 | end 24 | 25 | should "create valid wholesaler" do 26 | @wholesaler.user = Factory.create(:wholesale_user) 27 | @wholesaler.bill_address = @wholesaler.ship_address = Factory.create(:address) 28 | assert @wholesaler.valid? 29 | assert @wholesaler.save 30 | end 31 | 32 | end 33 | 34 | context "with an existing wholesaler" do 35 | 36 | setup do 37 | @wholesaler = Factory.create(:wholesaler, 38 | :user => Factory.create(:wholesale_user), 39 | :bill_address => Factory.create(:address), 40 | :ship_address => Factory.create(:address) 41 | ) 42 | end 43 | 44 | should "activate" do 45 | @wholesaler.roles = [Spree::Role.find_by_name("user")] 46 | 47 | assert !@wholesaler.active? 48 | @wholesaler.activate! 49 | assert @wholesaler.active? 50 | end 51 | 52 | should "deactivate" do 53 | @wholesaler.roles = [Spree::Role.find_by_name("wholesaler")] 54 | 55 | assert @wholesaler.active? 56 | @wholesaler.deactivate! 57 | assert !@wholesaler.active? 58 | end 59 | 60 | end 61 | 62 | 63 | #should "activate and deactivate" do 64 | # 65 | # @wholesaler.deactivate! 66 | # assert !@wholesaler.active? 67 | #end 68 | 69 | end 70 | -------------------------------------------------------------------------------- /app/models/spree/order_decorator.rb: -------------------------------------------------------------------------------- 1 | Spree::Order.class_eval do 2 | 3 | def wholesale 4 | read_attribute(:wholesale) && !wholesaler.nil? 5 | end 6 | 7 | def wholesaler 8 | user && user.wholesaler 9 | end 10 | 11 | def is_wholesale? 12 | wholesale 13 | end 14 | 15 | def set_line_item_prices(use_price=:price) 16 | line_items.includes(:variant).each do |line_item| 17 | line_item.price = line_item.variant.send(use_price) 18 | line_item.save 19 | end 20 | end 21 | 22 | def to_fullsale! 23 | self.wholesale = false 24 | set_line_item_prices(:price) 25 | update! 26 | save 27 | end 28 | 29 | def to_wholesale! 30 | return false unless user.wholesaler.present? 31 | self.wholesale = true 32 | set_line_item_prices(:wholesale_price) 33 | update! 34 | save 35 | end 36 | 37 | def add_variant(variant, quantity = 1) 38 | current_item = contains?(variant) 39 | if current_item 40 | current_item.quantity += quantity 41 | current_item.save 42 | else 43 | current_item = Spree::LineItem.new(:quantity => quantity) 44 | current_item.variant = variant 45 | current_item.price = is_wholesale? ? variant.wholesale_price : variant.price 46 | self.line_items << current_item 47 | end 48 | 49 | # populate line_items attributes for additional_fields entries 50 | # that have populate => [:line_item] 51 | # Spree::Variant.additional_fields.select{|f| !f[:populate].nil? && f[:populate].include?(:line_item) }.each do |field| 52 | # value = "" 53 | 54 | # if field[:only].nil? || field[:only].include?(:variant) 55 | # value = variant.send(field[:name].gsub(" ", "_").downcase) 56 | # elsif field[:only].include?(:product) 57 | # value = variant.product.send(field[:name].gsub(" ", "_").downcase) 58 | # end 59 | # current_item.update_attribute(field[:name].gsub(" ", "_").downcase, value) 60 | # end 61 | 62 | current_item 63 | end 64 | 65 | end 66 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # Configure Rails Envinronment 2 | ENV["RAILS_ENV"] = "test" 3 | 4 | env = File.expand_path("../dummy/config/environment.rb", __FILE__) 5 | if File.exists?(env) 6 | require env 7 | else 8 | raise LoadError, "Please create the dummy app before running tests. Try running `bundle exec dummier`" 9 | end 10 | 11 | require 'rails/test_help' 12 | require 'shoulda' 13 | require 'factory_girl' 14 | require 'capybara/rails' 15 | 16 | # Load support files 17 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } 18 | 19 | include HelperMethods 20 | 21 | ActionMailer::Base.delivery_method = :test 22 | ActionMailer::Base.perform_deliveries = false 23 | ActionMailer::Base.default_url_options[:host] = "example.com" 24 | 25 | Rails.backtrace_cleaner.remove_silencers! 26 | 27 | # Configure capybara for integration testing 28 | Capybara.default_driver = :selenium 29 | Capybara.default_selector = :css 30 | 31 | # Run any available migration 32 | ActiveRecord::Migrator.migrate File.expand_path("../dummy/db/migrate/", __FILE__) 33 | 34 | module FixturesHelper 35 | 36 | extend ActiveSupport::Concern 37 | 38 | included do 39 | self.fixture_path = File.expand_path('../fixtures/spree', __FILE__) 40 | set_fixture_class :addresses => Spree::Address 41 | set_fixture_class :countries => Spree::Country 42 | set_fixture_class :orders => Spree::Order 43 | set_fixture_class :products => Spree::Product 44 | set_fixture_class :roles => Spree::Role 45 | set_fixture_class :states => Spree::State 46 | set_fixture_class :variants => Spree::Variant 47 | set_fixture_class :wholesalers => Spree::Wholesaler 48 | fixtures :all 49 | end 50 | 51 | end 52 | 53 | class ActionController::TestCase 54 | include Devise::TestHelpers 55 | include FixturesHelper 56 | end 57 | 58 | class ActiveSupport::TestCase 59 | include FixturesHelper 60 | end 61 | 62 | # Default to US 63 | Spree::Config.set(:default_country_id => 214) 64 | FactoryGirl.reload 65 | -------------------------------------------------------------------------------- /app/views/spree/wholesalers/_fields.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :head do %> 2 | 3 | <% end %> 4 | 5 | 6 |
    7 | 8 |
    9 | User 10 | <%= form.fields_for :user do |uf| %> 11 | <%= render 'spree/shared/user_form', :f => uf %> 12 | <% end %> 13 |
    14 | 15 |
    16 | Company Information 17 | 18 |

    19 | <%= form.label :company %>
    20 | <%= form.text_field :company, :class => 'required' %>* 21 |

    22 |

    23 | <%= form.label :buyer_contact %>
    24 | <%= form.text_field :buyer_contact, :class => 'required' %>* 25 |

    26 |

    27 | <%= form.label :manager_contact %>
    28 | <%= form.text_field :manager_contact, :class => 'required' %>* 29 |

    30 |

    31 | <%= form.label :phone %>
    32 | <%= form.text_field :phone, :class => 'required' %>* 33 |

    34 |

    35 | <%= form.label :fax %>
    36 | <%= form.text_field :fax %> 37 |

    38 |

    39 | <%= form.label :resale_number %>
    40 | <%= form.text_field :resale_number %> 41 |

    42 |

    43 | <%= form.label :taxid %>
    44 | <%= form.text_field :taxid, :class => 'required' %>* 45 |

    46 |

    47 | <%= form.label :web_address %>
    48 | <%= form.text_field :web_address %> 49 |

    50 |

    51 | <%= form.label :terms %>
    52 | <%= form.select :terms, Spree::Wholesaler.term_options, :class => 'required' %>* 53 |

    54 |

    55 | <%= form.label :notes %>
    56 | <%= form.text_area :notes %> 57 |

    58 |
    59 |
    60 | 61 |
    62 | <%= render "spree/shared/address_form", :form => form %> 63 |
    64 | -------------------------------------------------------------------------------- /app/models/spree/wholesaler.rb: -------------------------------------------------------------------------------- 1 | class Spree::Wholesaler < ActiveRecord::Base 2 | partial_updates = false 3 | 4 | attr_accessible :bill_address_attributes, :ship_address_attributes, :user_attributes, 5 | :ship_address, :bill_address, 6 | :company, :buyer_contact, :manager_contact, :phone, :fax, :resale_number, 7 | :taxid, :web_address, :terms, :notes, :use_billing 8 | 9 | belongs_to :user, :class_name => "Spree::User" 10 | belongs_to :bill_address, :foreign_key => "billing_address_id", :class_name => "Spree::Address", :dependent => :destroy 11 | belongs_to :ship_address, :foreign_key => "shipping_address_id", :class_name => "Spree::Address", :dependent => :destroy 12 | 13 | accepts_nested_attributes_for :bill_address 14 | accepts_nested_attributes_for :ship_address 15 | accepts_nested_attributes_for :user 16 | 17 | attr_accessor :use_billing 18 | before_validation :clone_billing_address, :if => "@use_billing" 19 | validates :company, :buyer_contact, :manager_contact, :phone, :taxid, :presence => true 20 | 21 | delegate_belongs_to :user, :roles 22 | delegate_belongs_to :user, :email 23 | 24 | def activate! 25 | get_wholesale_role 26 | return false if user.roles.include?(@role) 27 | user.roles << @role 28 | user.save 29 | end 30 | 31 | def deactivate! 32 | get_wholesale_role 33 | return false unless user.roles.include?(@role) 34 | user.roles.delete(@role) 35 | user.save 36 | end 37 | 38 | def active? 39 | user && user.has_role?("wholesaler") 40 | end 41 | 42 | def self.term_options 43 | %(Net 10, Net 15, Net 30, COD, Credit Card).split(", ") 44 | end 45 | 46 | private 47 | 48 | def get_wholesale_role 49 | @role = Spree::Role.find_or_create_by_name("wholesaler") 50 | end 51 | 52 | def clone_billing_address 53 | if bill_address and self.ship_address.nil? 54 | self.ship_address = bill_address.clone 55 | else 56 | self.ship_address.attributes = bill_address.attributes.except("id", "updated_at", "created_at") 57 | end 58 | true 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /test/factories.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | 3 | factory :state, :class => Spree::State do 4 | name "California" 5 | abbr "CA" 6 | country { Spree::Country.find_by_iso("US") || Factory.create(:country) } 7 | end 8 | 9 | factory :country, :class => Spree::Country do 10 | name "United States" 11 | iso3 "USA" 12 | iso "US" 13 | iso_name { name.upcase } 14 | numcode 840 15 | end 16 | 17 | factory :address, :class => Spree::Address do 18 | firstname "Addy" 19 | lastname "Streetston" 20 | address1 { "#{100 + rand(1000)} State St" } 21 | city "Santa Barbara" 22 | phone "555-555-5555" 23 | zipcode "93101" 24 | state { Spree::State.find_by_name("California") || Factory.create(:state) } 25 | country { Spree::Country.find_by_name("United States") || Factory.create(:country) } 26 | end 27 | 28 | 29 | factory :wholesaler, :class => Spree::Wholesaler do 30 | company "Test Company" 31 | buyer_contact "Mr Contacter" 32 | manager_contact "Mr Manager" 33 | phone "555-555-5555" 34 | fax "555-555-5555 ext 1" 35 | resale_number "123456789" 36 | taxid "555-55-5555" 37 | web_address "testcompany.com" 38 | terms "Credit Card" 39 | notes "What a guy!" 40 | user { Spree::User.wholesale.last || Factory.create(:wholesale_user) } 41 | bill_address { Factory.create(:address) } 42 | ship_address { Factory.create(:address) } 43 | end 44 | 45 | 46 | factory :user, :class => Spree::User do 47 | email { random_email } 48 | password "spree123" 49 | password_confirmation "spree123" 50 | roles { [Spree::Role.find_or_create_by_name("user")] } 51 | end 52 | 53 | factory :admin_user, :parent => :user do 54 | roles { [Spree::Role.find_or_create_by_name("admin")] } 55 | end 56 | 57 | factory :wholesale_user, :parent => :user do 58 | roles { [Spree::Role.find_or_create_by_name("wholesaler")] } 59 | #wholesaler { Factory.create(:wholesaler) } 60 | end 61 | 62 | factory :order, :class => Spree::Order do 63 | user { Spree::User.last || Factory.create(:user) } 64 | state 'cart' 65 | payment_state 'balance_due' 66 | email { random_email } 67 | wholesale false 68 | end 69 | 70 | #factory :wholesale_order, :parent => :order do 71 | # #user { Factory.create(:wholesale_user) } 72 | # wholesale true 73 | #end 74 | 75 | end 76 | -------------------------------------------------------------------------------- /app/assets/javascripts/wholesaler_address.js: -------------------------------------------------------------------------------- 1 | (function($){ 2 | $(document).ready(function(){ 3 | if($('.wholesaler-form, .new_wholesaler').is('*')){ 4 | 5 | // $('.wholesaler-form, .new_wholesaler').validate(); 6 | 7 | var get_states = function(region){ 8 | country = $('p#' + region + 'country' + ' span#' + region + 'country :only-child').val(); 9 | return state_mapper[country]; 10 | } 11 | 12 | var update_state = function(region) { 13 | states = get_states(region); 14 | 15 | state_select = $('p#' + region + 'state select'); 16 | state_input = $('p#' + region + 'state input'); 17 | 18 | if(states) { 19 | selected = state_select.val(); 20 | state_select.html(''); 21 | states_with_blank = [["",""]].concat(states); 22 | $.each(states_with_blank, function(pos,id_nm) { 23 | var opt = $(document.createElement('option')) 24 | .attr('value', id_nm[0]) 25 | .html(id_nm[1]); 26 | if(selected==id_nm[0]){ 27 | opt.prop("selected", true); 28 | } 29 | state_select.append(opt); 30 | }); 31 | state_select.prop("disabled", false).show(); 32 | state_input.hide().prop("disabled", true); 33 | 34 | } else { 35 | state_input.prop("disabled", false).show(); 36 | state_select.hide().prop("disabled", true); 37 | } 38 | 39 | }; 40 | 41 | $('p#bcountry select').change(function() { update_state('b'); }); 42 | $('p#scountry select').change(function() { update_state('s'); }); 43 | update_state('b'); 44 | update_state('s'); 45 | 46 | $('input#wholesaler_use_billing').click(function() { 47 | if($(this).is(':checked')) { 48 | $('#shipping .inner').hide(); 49 | $('#shipping .inner input, #shipping .inner select').prop("disabled", true); 50 | } else { 51 | $('#shipping .inner').show(); 52 | $('#shipping .inner input, #shipping .inner select').prop("disabled", false); 53 | //only want to enable relevant field 54 | if(get_states('s')){ 55 | $('span#sstate input').hide().prop("disabled", true); 56 | }else{ 57 | $('span#sstate select').hide().prop("disabled", true); 58 | } 59 | 60 | } 61 | }).triggerHandler('click'); 62 | 63 | } 64 | 65 | }); 66 | })(jQuery); 67 | -------------------------------------------------------------------------------- /app/views/spree/admin/wholesalers/_form.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :head do %> 2 | 3 | <% end %> 4 | 5 |
    6 |
    7 | Company Information 8 | 9 |

    10 | <%= form.label :company %>
    11 | <%= form.text_field :company, :class => 'required' %>* 12 |

    13 |

    14 | <%= form.label :buyer_contact %>
    15 | <%= form.text_field :buyer_contact, :class => 'required' %>* 16 |

    17 |

    18 | <%= form.label :manager_contact %>
    19 | <%= form.text_field :manager_contact, :class => 'required' %>* 20 |

    21 |

    22 | <%= form.label :phone %>
    23 | <%= form.text_field :phone, :class => 'required' %>* 24 |

    25 |

    26 | <%= form.label :fax %>
    27 | <%= form.text_field :fax %> 28 |

    29 |

    30 | <%= form.label :resale_number %>
    31 | <%= form.text_field :resale_number %> 32 |

    33 |

    34 | <%= form.label :taxid %>
    35 | <%= form.text_field :taxid, :class => 'required' %>* 36 |

    37 |

    38 | <%= form.label :web_address %>
    39 | <%= form.text_field :web_address %> 40 |

    41 |

    42 | <%= form.label :terms %>
    43 | <%= form.select :terms, Spree::Wholesaler.term_options, :class => 'required' %>* 44 |

    45 |

    46 | <%= form.label :notes %>
    47 | <%= form.text_area :notes %> 48 |

    49 |
    50 | 51 |
    52 | User Details: 53 | <% if @wholesaler.new_record? %> 54 | <%= form.fields_for :user do |uf| %> 55 | <%= render 'spree/shared/user_form', :f => uf %> 56 | <% end %> 57 | <% else %> 58 |

    59 | User Id: 60 | <%= link_to @wholesaler.user.id, admin_user_path(@wholesaler.user) %> 61 |
    62 | <%= link_to "view user", admin_user_path(@wholesaler.user) %> / 63 | <%= link_to "edit user", edit_admin_user_path(@wholesaler.user) %> / 64 | <%= render "user_options" %> 65 |

    66 | <% end %> 67 |
    68 |
    69 | 70 |
    71 | <%= render "spree/shared/address_form", :form => form %> 72 |
    73 | -------------------------------------------------------------------------------- /app/views/spree/admin/wholesalers/index.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | 7 |
    8 |
    9 | 10 | 11 |

    <%= t("listing_wholesalers") %>

    12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | <% @wholesalers.each do |wholesaler|%> 28 | 29 | <%- locals = {:wholesaler => wholesaler} %> 30 | 31 | 32 | 33 | 34 | 35 | 40 | 41 | <% end %> 42 | 43 |
    <%= sort_link @search, :company, t('wholesaler') %><%= sort_link @search, :buyer_contact, t('buyer_contact') %><%= sort_link @search, :user_email, t('email') %><%= sort_link @search, :phone, t('phone') %><%= t('active') %> 23 |
    <%=link_to wholesaler.company, object_url(wholesaler) %><%= wholesaler.buyer_contact %><%= mail_to wholesaler.email %><%= wholesaler.phone %><%= wholesaler.roles.include?("wholesale") ? 'yes' : 'no' %> 36 | <%= link_to_edit wholesaler %>   37 | <%= link_to_delete wholesaler %>   38 | <%= render 'user_options', :wholesaler => wholesaler %> 39 |
    44 | 45 | <%= paginate @wholesalers %> 46 | 47 | 48 | 49 | <% content_for :sidebar do %> 50 |
    51 |

    <%= t('search') %>

    52 | 53 | <% @wholesaler = Spree::Wholesaler.search %> 54 | 55 | <%= search_form_for [:admin, @wholesaler] do |f| %> 56 | <%- locals = {:f => f} %> 57 |

    58 | <%= t(".company") %>
    59 | <%= f.text_field :company_cont, :size=>18 %> 60 |

    61 |

    62 | <%= t(".buyer_contact") %>
    63 | <%= f.text_field :buyer_contact_cont, :size=>18 %> 64 |

    65 |

    66 | <%= t(".manager_contact") %>
    67 | <%= f.text_field :manager_contact_cont, :size=>18 %> 68 |

    69 |

    70 | <%= t("phone") %>
    71 | <%= f.text_field :phone_cont, :size=>18 %> 72 |

    73 |

    74 | <%= t(".notes") %>
    75 | <%= f.text_field :notes_cont, :size=>18 %> 76 |

    77 | 78 |

    <%= button t("search") %>

    79 | <% end %> 80 |
    81 | <% end %> 82 | -------------------------------------------------------------------------------- /app/controllers/spree/admin/wholesalers_controller.rb: -------------------------------------------------------------------------------- 1 | class Spree::Admin::WholesalersController < Spree::Admin::ResourceController 2 | respond_to :html, :xml 3 | before_filter :approval_setup, :only => [ :approve, :reject ] 4 | 5 | def index 6 | end 7 | 8 | def show 9 | @wholesaler = Spree::Wholesaler.find(params[:id]) 10 | respond_with(@wholesaler) 11 | end 12 | 13 | def new 14 | @wholesaler = Spree::Wholesaler.new 15 | @wholesaler.build_user 16 | @wholesaler.bill_address = Spree::Address.default 17 | @wholesaler.ship_address = Spree::Address.default 18 | respond_with(@wholesaler) 19 | end 20 | 21 | def create 22 | @wholesaler = Spree::Wholesaler.new(params[:wholesaler]) 23 | if @wholesaler.save 24 | flash[:notice] = I18n.t('spree.admin.wholesaler.success') 25 | redirect_to spree.admin_wholesalers_path 26 | else 27 | flash[:error] = I18n.t('spree.admin.wholesaler.failed') 28 | render :action => "new" 29 | end 30 | end 31 | 32 | def edit 33 | @wholesaler = Spree::Wholesaler.find(params[:id]) 34 | respond_with(@wholesaler) 35 | end 36 | 37 | def update 38 | @wholesaler = Spree::Wholesaler.find(params[:id]) 39 | 40 | if @wholesaler.update_attributes(params[:wholesaler]) 41 | flash[:notice] = I18n.t('spree.admin.wholesaler.update_success') 42 | else 43 | flash[:error] = I18n.t('spree.admin.wholesaler.update_failed') 44 | end 45 | respond_with(@wholesaler) 46 | end 47 | 48 | def destroy 49 | @wholesaler = Spree::Wholesaler.find(params[:id]) 50 | @wholesaler.destroy 51 | flash[:notice] = I18n.t('spree.admin.wholesaler.destroy_success') 52 | respond_with(@wholesaler) 53 | end 54 | 55 | def approve 56 | return redirect_to request.referer, :flash => { :error => "Wholesaler is already active." } if @wholesaler.active? 57 | @wholesaler.activate! 58 | redirect_to request.referer, :flash => { :notice => "Wholesaler was successfully approved." } 59 | end 60 | 61 | def reject 62 | return redirect_to request.referer, :flash => { :error => "Wholesaler is already rejected." } unless @wholesaler.active? 63 | @wholesaler.deactivate! 64 | redirect_to request.referer, :flash => { :notice => "Wholesaler was successfully rejected." } 65 | end 66 | 67 | private 68 | 69 | def approval_setup 70 | @wholesaler = Spree::Wholesaler.find(params[:id]) 71 | @role = Spree::Role.find_or_create_by_name("wholesaler") 72 | end 73 | 74 | def collection 75 | return @collection if @collection.present? 76 | 77 | params[:search] ||= {} 78 | params[:search][:meta_sort] ||= "company.asc" 79 | @search = Spree::Wholesaler.ransack(params[:q]) 80 | @collection = @search.result.page(params[:page]).per(Spree::Config[:admin_products_per_page]) 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | billing_firstname: "Billing First Name" 3 | billing_lastname: "Billing Last Name" 4 | shipping_firstname: "Shipping First Name" 5 | shipping_lastname: "Shipping Last Name" 6 | 7 | buyer_contact: "Buyer Contact" 8 | 9 | wholesale: "Wholesale" 10 | wholesaler: "Wholesaler" 11 | wholesalers: "Wholesalers" 12 | new_wholesaler: "New Wholesaler" 13 | editing_wholesaler: "Edit Wholesaler" 14 | wholesaler_account: "Wholesaler Account" 15 | listing_wholesalers: "Listing Wholesaler" 16 | wholesale_login: "Wholesale Login" 17 | wholesale_apply: "Apply for a wholesale account" 18 | wholesale_signup: "Wholesale Signup" 19 | wholesale_update: "Wholesale Update" 20 | wholesale_price: "Wholesale Price" 21 | 22 | actions: 23 | cancel: "Cancel" 24 | 25 | shared: 26 | address_form: 27 | use_for_shipping: "Use billing address for shipping" 28 | 29 | wholesale_customer: "Wholesale Customer ID: #%{id}" 30 | 31 | spree: 32 | 33 | wholesaler: 34 | signup_success: Thank you for your interest in becoming a wholesaler! We'll be in touch shortly. 35 | signup_failed: Wholesale account could not be created 36 | parts_error_message: There are errors with the user or addresses 37 | success: Wholesale account was successfully created 38 | update_success: Wholesale account was successfully updated 39 | failed: Wholesale account could not be created 40 | update_failed: Wholesale account could not be updated 41 | destroy_success: Wholesale account was successfully deleted 42 | 43 | admin: 44 | 45 | hooks: 46 | product_form_right: 47 | master_wholesale_price: "Master Wholesale Price" 48 | admin_orders_index_search: 49 | show_wholesale_orders: "Show only wholesale orders" 50 | 51 | variants: 52 | form: 53 | wholesale_price: "Wholesale Price" 54 | 55 | 56 | wholesaler: 57 | success: Wholesale account was successfully created 58 | update_success: Wholesale account was successfully updated 59 | failed: Wholesale account could not be created 60 | update_failed: Wholesale account could not be updated 61 | destroy_success: Wholesale account was successfully deleted 62 | 63 | wholesalers: 64 | index: 65 | company: "Company" 66 | buyer_contact: "Buyer contact" 67 | manager_contact: "Manager contact" 68 | notes: "Notes" 69 | form: 70 | company: "Company" 71 | buyer_contact: "Buyer contact" 72 | manager_contact: "Manager contact" 73 | phone: "Phone" 74 | fax: "Fax" 75 | resale_number: "Resale number" 76 | taxid: "Taxid" 77 | web_address: "Web address" 78 | terms: "Terms" 79 | notes: "Notes" 80 | -------------------------------------------------------------------------------- /test/integration/spree/wholesalers_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Spree::WholesalersTest < ActiveSupport::IntegrationCase 4 | 5 | def setup 6 | Factory.create(:state) 7 | 8 | @user = Factory.create(:user) 9 | login_as @user 10 | 11 | @labels = %(Company, Buyer contact, Manager contact, Phone, Fax, Resale number, Taxid, Web address, Notes).split(', ') 12 | @values = %(Some Company, Buyer contact, Manager contact, 555-555-5555, 555-555-5555 ext 1, 12345, 123-45-6789, example.com, Yippee!).split(', ') 13 | @address_labels = %(First Name, Last Name, Street Address, City, Zip, Phone).split(', ') 14 | @address_values = %(Billy, Billerton, 111 State St, Santa Barbara, 93101, 555-555-5555).split(', ') 15 | end 16 | 17 | should "have a link to new wholesaler" do 18 | visit spree.wholesalers_path 19 | within ".login" do 20 | assert page.has_field?("Email") 21 | assert page.has_field?("Password") 22 | end 23 | assert page.has_link?("Apply Now!") 24 | click_link "Apply Now!" 25 | assert_equal spree.new_wholesaler_path, current_path 26 | end 27 | 28 | should "get new wholesaler" do 29 | visit spree.new_wholesaler_path 30 | assert page.has_content?(I18n.t('wholesale_signup')) 31 | within "#new_wholesaler" do 32 | @labels.each do |f| 33 | assert page.has_field?(f) 34 | end 35 | assert page.has_field?('Terms') 36 | end 37 | assert has_button?(I18n.t('wholesale_apply')) 38 | end 39 | 40 | should "validate wholesaler and parts" do 41 | visit spree.new_wholesaler_path 42 | click_button I18n.t('wholesale_apply') 43 | 44 | assert page.has_content?(I18n.t('spree.wholesaler.signup_failed')) 45 | assert page.has_content?('21 errors prohibited this record from being saved:') 46 | end 47 | 48 | should "be a valid wholesaler but invalid parts" do 49 | visit spree.new_wholesaler_path 50 | @labels.each_with_index do |label, index| 51 | fill_in label, :with => @values[index] 52 | end 53 | select 'Credit Card', :from => 'Terms' 54 | click_button I18n.t('wholesale_apply') 55 | assert page.has_content?(I18n.t('spree.wholesaler.signup_failed')) 56 | end 57 | 58 | should "create wholesaler and parts" do 59 | visit spree.new_wholesaler_path 60 | within ".wholesaler-details" do 61 | @labels.each_with_index do |label, index| 62 | fill_in label, :with => @values[index] 63 | end 64 | select 'Credit Card', :from => 'Terms' 65 | end 66 | within ".user-details" do 67 | fill_in "Email", :with => random_email 68 | fill_in "Password", :with => "password" 69 | fill_in "Password Confirmation", :with => "password" 70 | end 71 | within "#billing" do 72 | @address_labels.each_with_index do |label, index| 73 | fill_in label, :with => @address_values[index] 74 | end 75 | select 'California', :from => 'State' 76 | select 'United States', :from => 'Country' 77 | end 78 | 79 | within "#shipping" do 80 | check "Use Billing Address" 81 | end 82 | 83 | click_button I18n.t('wholesale_apply') 84 | assert page.has_content?(I18n.t('spree.wholesaler.signup_success')) 85 | end 86 | 87 | end 88 | -------------------------------------------------------------------------------- /test/fixtures/spree/variants.yml: -------------------------------------------------------------------------------- 1 | small-red-baseball: 2 | product: wholesale 3 | option_values: s, red 4 | sku: ROR-00001 5 | price: 19.99 6 | wholesale_price: 18.00 7 | cost_price: 17.00 8 | count_on_hand: 10 9 | small-blue-baseball: 10 | product: wholesale 11 | option_values: s, blue 12 | sku: ROR-00002 13 | price: 19.99 14 | wholesale_price: 18.00 15 | cost_price: 17.00 16 | count_on_hand: 10 17 | small-green-baseball: 18 | product: wholesale 19 | option_values: s, green 20 | sku: ROR-00003 21 | price: 19.99 22 | wholesale_price: 18.00 23 | cost_price: 17.00 24 | count_on_hand: 10 25 | med-red-baseball: 26 | product: wholesale 27 | option_values: m, red 28 | sku: ROR-00004 29 | price: 19.99 30 | wholesale_price: 18.00 31 | cost_price: 17.00 32 | count_on_hand: 3 33 | med-blue-baseball: 34 | product: wholesale 35 | option_values: m, blue 36 | sku: ROR-00005 37 | price: 19.99 38 | wholesale_price: 18.00 39 | cost_price: 17.00 40 | count_on_hand: 10 41 | med-green-baseball: 42 | product: wholesale 43 | option_values: m, green 44 | sku: ROR-00006 45 | price: 19.99 46 | wholesale_price: 18.00 47 | cost_price: 17.00 48 | count_on_hand: 14 49 | large-red-baseball: 50 | product: wholesale 51 | option_values: l, red 52 | sku: ROR-00007 53 | price: 19.99 54 | wholesale_price: 18.00 55 | cost_price: 17.00 56 | count_on_hand: 1 57 | large-blue-baseball: 58 | product: wholesale 59 | option_values: l, blue 60 | sku: ROR-00008 61 | price: 19.99 62 | wholesale_price: 18.00 63 | cost_price: 17.00 64 | count_on_hand: 10 65 | large-green-baseball: 66 | product: wholesale 67 | option_values: l, green 68 | sku: ROR-00009 69 | price: 19.99 70 | wholesale_price: 18.00 71 | cost_price: 17.00 72 | count_on_hand: 10 73 | xlarge-green-baseball: 74 | product: wholesale 75 | option_values: xl, red 76 | sku: ROR-00010 77 | price: 21.99 78 | cost_price: 20.00 79 | count_on_hand: 10 80 | wholesale: 81 | product: wholesale 82 | sku: ROR-001 83 | price: 19.99 84 | wholesale_price: 18.00 85 | cost_price: 17.00 86 | is_master: true 87 | count_on_hand: 10 88 | ror_tote_v: 89 | product: ror_tote 90 | sku: ROR-00011 91 | price: 15.99 92 | cost_price: 13.00 93 | is_master: true 94 | count_on_hand: 10 95 | ror_bag_v: 96 | product: ror_bag 97 | sku: ROR-00012 98 | price: 22.99 99 | cost_price: 21.00 100 | is_master: true 101 | count_on_hand: 10 102 | ror_jr_spaghetti_v: 103 | product: ror_jr_spaghetti 104 | sku: ROR-00013 105 | price: 19.99 106 | cost_price: 17.00 107 | is_master: true 108 | count_on_hand: 10 109 | ror_mug_v: 110 | product: ror_mug 111 | sku: ROR-00014 112 | price: 13.99 113 | cost_price: 11.00 114 | is_master: true 115 | count_on_hand: 10 116 | ror_ringer_v: 117 | product: ror_ringer 118 | sku: ROR-00015 119 | price: 17.99 120 | cost_price: 17.00 121 | is_master: true 122 | count_on_hand: 10 123 | ror_stein_v: 124 | product: ror_stein 125 | sku: ROR-00016 126 | price: 16.99 127 | cost_price: 15.00 128 | is_master: true 129 | count_on_hand: 10 130 | apache_baseball_v: 131 | product: apache_baseball_jersey 132 | sku: APC-00001 133 | price: 19.99 134 | cost_price: 17.00 135 | is_master: true 136 | count_on_hand: 10 137 | ruby_baseball_v: 138 | product: ruby_baseball_jersey 139 | sku: RUB-00001 140 | price: 19.99 141 | cost_price: 17.00 142 | is_master: true 143 | count_on_hand: 10 144 | -------------------------------------------------------------------------------- /test/support/integration_case.rb: -------------------------------------------------------------------------------- 1 | class ActiveSupport::IntegrationCase < ActiveSupport::TestCase 2 | 3 | include Capybara::DSL 4 | include Rails.application.routes.url_helpers 5 | include Warden::Test::Helpers 6 | 7 | # Extreme hax! wtf is this for anyways.. and why is it erroring? 8 | def testmail_admin_mail_method_url(*args) 9 | "#wtf" 10 | end 11 | alias :testmail_admin_mail_method_path :testmail_admin_mail_method_url 12 | 13 | 14 | self.use_transactional_fixtures = false 15 | 16 | # Checks for missing translations after each test 17 | teardown do 18 | unless source.blank? 19 | matches = source.match(/translation[\s-]+missing[^"<]*/) || [] 20 | assert_equal 0, matches.length, "** #{matches[0]}" 21 | end 22 | end 23 | 24 | # By defining this we don't need to depend on spree, just spree_core since the 25 | # included url helper lives in the spree root 26 | def spree 27 | Spree::Core::Engine.routes.url_helpers 28 | end 29 | 30 | # An assertion for ensuring content has made it to the page. 31 | # 32 | # assert_seen "Site Title" 33 | # assert_seen "Peanut Butter Jelly Time", :within => ".post-title h1" 34 | # 35 | def assert_seen(text, opts={}) 36 | if opts[:within] 37 | within(opts[:within]) do 38 | assert has_content?(text) 39 | end 40 | else 41 | assert has_content?(text) 42 | end 43 | end 44 | 45 | # Asserts the proper flash message 46 | # 47 | # assert_flash :notice, "Post was successfully saved!" 48 | # assert_flash :error, "Oh No, bad things happened!" 49 | # 50 | def assert_flash(key, text) 51 | within(".flash.#{key}") do 52 | assert_seen(text) 53 | end 54 | end 55 | 56 | # Asserts the proper browser title 57 | # 58 | # assert_title "My Site - Is super cool" 59 | # 60 | def assert_title(title) 61 | assert_seen title, :within => "head title" 62 | end 63 | 64 | # Asserts meta tags have proper content 65 | # 66 | # assert_meta :description, "So let me tell you about this one time..." 67 | # assert_meta :keywords, "seo, is, fun, jk." 68 | # 69 | def assert_meta(tag, text) 70 | tag = find(:xpath, "//head/meta[@name='#{tag.to_s}']") 71 | assert_equal text, tag.native.attribute("content") 72 | end 73 | 74 | end 75 | 76 | 77 | # # Define a bare test case to use with Capybara 78 | # 79 | # class ActiveSupport::IntegrationCase < ActiveSupport::TestCase 80 | # 81 | # include Capybara::DSL 82 | # include Rails.application.routes.url_helpers 83 | # include Warden::Test::Helpers 84 | # 85 | # self.use_transactional_fixtures = false 86 | # self.fixture_path = File.expand_path('../../fixtures', __FILE__) 87 | # 88 | # # Extreme hax! wtf is this for anyways.. and why is it erroring? 89 | # def testmail_admin_mail_method_url(*args) 90 | # "#wtf" 91 | # end 92 | # alias :testmail_admin_mail_method_path :testmail_admin_mail_method_url 93 | # 94 | # teardown do 95 | # Capybara.reset_sessions! # Forget the (simulated) browser state 96 | # Capybara.use_default_driver # Revert Capybara.current_driver to Capybara.default_driver 97 | # end 98 | # 99 | # def assert_seen(text, opts={}) 100 | # if opts[:within] 101 | # within(opts[:within]) do 102 | # assert page.has_content?(text) 103 | # end 104 | # else 105 | # assert page.has_content?(text) 106 | # end 107 | # end 108 | # 109 | # def assert_flash(key, text) 110 | # within(".errorExplanation") do 111 | # assert_seen(text) 112 | # end 113 | # end 114 | # 115 | # end 116 | -------------------------------------------------------------------------------- /test/integration/spree/admin/wholesalers_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Spree::Admin::WholesalersTest < ActiveSupport::IntegrationCase 4 | 5 | def setup 6 | Factory.create(:state) 7 | 8 | @user = Factory.create(:admin_user) 9 | login_as @user 10 | 11 | @labels = %(Company, Buyer contact, Manager contact, Phone, Fax, Resale number, Taxid, Web address, Notes).split(', ') 12 | @values = %(Some Company, Buyer contact, Manager contact, 555-555-5555, 555-555-5555 ext 1, 12345, 123-45-6789, example.com, Yippee!).split(', ') 13 | @address_labels = %(First Name, Last Name, Street Address, City, Zip, Phone).split(', ') 14 | @address_values = %(Billy, Billerton, 111 State St, Santa Barbara, 93101, 555-555-5555).split(', ') 15 | end 16 | 17 | should "have a link to new wholesaler" do 18 | visit spree.admin_wholesalers_path 19 | btn = find(".actions a.button").native 20 | assert_match(/#{spree.new_admin_wholesaler_path}$/, btn.attribute('href')) 21 | assert_equal "New Wholesaler", btn.text 22 | end 23 | 24 | should "get new wholesaler" do 25 | visit spree.new_admin_wholesaler_path 26 | # assert has_content?("New Wholesaler") 27 | assert page.has_content?('New Wholesaler') 28 | within "#new_wholesaler" do 29 | @labels.each do |f| 30 | assert has_field?(f) 31 | end 32 | assert has_field?('Terms') 33 | end 34 | end 35 | 36 | should "validate wholesaler and parts" do 37 | visit spree.new_admin_wholesaler_path 38 | click_button "Create" 39 | assert page.has_content?(I18n.t('spree.admin.wholesaler.failed')) 40 | end 41 | 42 | should "be a valid wholesaler but invalid parts" do 43 | visit spree.new_admin_wholesaler_path 44 | @labels.each_with_index do |label, index| 45 | fill_in label, :with => @values[index] 46 | end 47 | select 'Credit Card', :from => 'Terms' 48 | click_button "Create" 49 | assert page.has_content?(I18n.t('spree.admin.wholesaler.failed')) 50 | end 51 | 52 | should "create wholesaler and parts" do 53 | visit spree.new_admin_wholesaler_path 54 | 55 | within ".wholesaler-details" do 56 | @labels.each_with_index do |label, index| 57 | fill_in label, :with => @values[index] 58 | end 59 | select 'Credit Card', :from => 'Terms' 60 | end 61 | 62 | within ".user-details" do 63 | fill_in "Email", :with => random_email 64 | fill_in "Password", :with => "password" 65 | fill_in "Password Confirmation", :with => "password" 66 | end 67 | 68 | within "#billing" do 69 | @address_labels.each_with_index do |label, index| 70 | fill_in label, :with => @address_values[index] 71 | end 72 | select 'California', :from => 'State' 73 | select 'United States', :from => 'Country' 74 | end 75 | 76 | within "#shipping" do 77 | check "Use Billing Address" 78 | end 79 | 80 | click_button "Create" 81 | assert page.has_content?(I18n.t('spree.admin.wholesaler.success')) 82 | end 83 | 84 | 85 | context "an existing wholesaler" do 86 | 87 | setup do 88 | @wholesaler = Factory.create(:wholesaler) 89 | end 90 | 91 | should "update wholesaler, user and addresses" do 92 | visit spree.edit_admin_wholesaler_path(@wholesaler) 93 | within ".wholesaler-details" do 94 | @labels.each_with_index do |label, index| 95 | fill_in label, :with => @values[index].reverse 96 | end 97 | select 'Credit Card', :from => 'Terms' 98 | end 99 | within "#billing" do 100 | @address_labels.each_with_index do |label, index| 101 | fill_in label, :with => @address_values[index].reverse 102 | end 103 | select 'California', :from => 'State' 104 | select 'United States', :from => 'Country' 105 | end 106 | within "#shipping" do 107 | uncheck "Use Billing Address" 108 | @address_labels.each_with_index do |label, index| 109 | fill_in label.sub('Billing', 'Shipping'), :with => @address_values[index] 110 | end 111 | select 'California', :from => 'State' 112 | select 'United States', :from => 'Country' 113 | end 114 | click_button "Update" 115 | assert page.has_content?(I18n.t('spree.admin.wholesaler.update_success')) 116 | assert page.has_content?('ylliB notrelliB') 117 | end 118 | 119 | end 120 | 121 | end 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spree Wholesale [![Build Status](https://secure.travis-ci.org/citrus/spree_wholesale.png)](http://travis-ci.org/citrus/spree_wholesale) 2 | 3 | Spree wholesale is a simple wholesale solution for Spree Commerce. Spree wholesale adds a wholesaler login and signup page as well as an admin to approve and deny applicants. 4 | 5 | 6 | ------------------------------------------------------------------------------ 7 | Installation 8 | ------------------------------------------------------------------------------ 9 | 10 | If you're on Spree 0.50.0 and above, you'll have to install Spree Wholesale from the source since I haven't released it yet. Just add this to your Gemfile: 11 | 12 | ```ruby 13 | # spree 1.2.x 14 | gem 'spree_wholesale', :git => 'git://github.com/citrus/spree_wholesale', :branch => 'master' 15 | 16 | # spree 1.1.x 17 | gem 'spree_wholesale', :git => 'git://github.com/citrus/spree_wholesale', :branch => '1.1.x' 18 | 19 | # spree 1.0.x 20 | gem 'spree_wholesale', :git => 'git://github.com/citrus/spree_wholesale', :branch => '1.0.x' 21 | 22 | # spree 0.70.x 23 | gem 'spree_wholesale', :git => 'git://github.com/citrus/spree_wholesale', :branch => '0.70.x' 24 | 25 | # spree 0.60.x 26 | gem 'spree_wholesale', :git => 'git://github.com/citrus/spree_wholesale', :branch => '0.60.x' 27 | 28 | # spree 0.50.x 29 | gem 'spree_wholesale', :git => 'git://github.com/citrus/spree_wholesale', :branch => '0.50.x' 30 | ``` 31 | 32 | 33 | Otherwise just use the last stable release: 34 | 35 | ```ruby 36 | gem 'spree_wholesale', '0.40.2.2' 37 | ``` 38 | 39 | 40 | Then install the necessary migrations, db:migrate, and create the wholesale role: 41 | 42 | ```bash 43 | # spree 0.50.x and above 44 | rails g spree_wholesale:install 45 | rake db:migrate spree_wholesale:create_role 46 | 47 | # legacy spree 48 | rake spree_wholesale:install 49 | rake db:migrate spree_wholesale:create_role 50 | ``` 51 | 52 | 53 | If you'd like to generate sample wholesale prices based on a 66% discount: 54 | 55 | ```bash 56 | rake spree_wholesale:assume_wholesale_prices 57 | ``` 58 | 59 | 60 | ------------------------------------------------------------------------------ 61 | Testing 62 | ------------------------------------------------------------------------------ 63 | 64 | If you'd like to run tests: 65 | 66 | ```bash 67 | git clone git://github.com/citrus/spree_wholesale.git 68 | cd spree_wholesale 69 | bundle install 70 | bundle exec dummier 71 | bundle exec rake 72 | ``` 73 | 74 | 75 | ------------------------------------------------------------------------------ 76 | Demo 77 | ------------------------------------------------------------------------------ 78 | 79 | If you'd like a demo of spree_wholesale: 80 | 81 | ```bash 82 | git clone git://github.com/citrus/spree_wholesale.git 83 | cd spree_wholesale 84 | cp test/dummy_hooks/after_migrate.rb.sample test/dummy_hooks/after_migrate.rb 85 | bundle install 86 | bundle exec dummier 87 | cd test/dummy 88 | rails s 89 | ``` 90 | 91 | 92 | ------------------------------------------------------------------------------ 93 | To Do 94 | ------------------------------------------------------------------------------ 95 | 96 | * Write more/better tests 97 | * Finish i18n implementation 98 | 99 | 100 | ------------------------------------------------------------------------------ 101 | Known Issues 102 | ------------------------------------------------------------------------------ 103 | 104 | * A user created in the 'user' tab, or an already existing user with an added 'wholesaler' flag will not be able to purchase at wholesale price. They will see the retail and wholesale price, but when added to cart will purchase at retail price. Only accounts created using wholesale interface work properly. 105 | * Deface override for admin_tabs (Adds wholesalers tab to admin interface) isn't targeting hook correctly, and has been set to insert to bottom of the div#store-menu ul instead. 106 | 107 | 108 | ------------------------------------------------------------------------------ 109 | Contributors 110 | ------------------------------------------------------------------------------ 111 | 112 | * Spencer Steffen ([@citrus](https://github.com/citrus)) 113 | * John Hwang ([@tavon](https://github.com/tavon)) 114 | * Cameron Carroll ([@sanarothe](https://github.com/sanarothe)) 115 | * Les Cochrane ([@oldtinroof](https://github.com/oldtinroof)) 116 | 117 | 118 | ------------------------------------------------------------------------------ 119 | License 120 | ------------------------------------------------------------------------------ 121 | 122 | Copyright (c) 2011 - 2012 Spencer Steffen and Citrus, released under the New BSD License All rights reserved. 123 | -------------------------------------------------------------------------------- /test/fixtures/spree/states.yml: -------------------------------------------------------------------------------- 1 | --- 2 | states_043: 3 | name: Michigan 4 | country_id: "214" 5 | id: "931624400" 6 | abbr: MI 7 | states_032: 8 | name: South Dakota 9 | country_id: "214" 10 | id: "615306087" 11 | abbr: SD 12 | states_021: 13 | name: Washington 14 | country_id: "214" 15 | id: "414569975" 16 | abbr: WA 17 | states_010: 18 | name: Wisconsin 19 | country_id: "214" 20 | id: "103680699" 21 | abbr: WI 22 | states_044: 23 | name: Arizona 24 | country_id: "214" 25 | id: "948208802" 26 | abbr: AZ 27 | states_033: 28 | name: Illinois 29 | country_id: "214" 30 | id: "625629523" 31 | abbr: IL 32 | states_022: 33 | name: New Hampshire 34 | country_id: "214" 35 | id: "426832442" 36 | abbr: NH 37 | states_011: 38 | name: North Carolina 39 | country_id: "214" 40 | id: "177087202" 41 | abbr: NC 42 | states_045: 43 | name: Kansas 44 | country_id: "214" 45 | id: "969722173" 46 | abbr: KS 47 | states_034: 48 | name: Missouri 49 | country_id: "214" 50 | id: "653576146" 51 | abbr: MO 52 | states_023: 53 | name: Arkansas 54 | country_id: "214" 55 | id: "471470972" 56 | abbr: AR 57 | states_012: 58 | name: Nevada 59 | country_id: "214" 60 | id: "179539703" 61 | abbr: NV 62 | states_001: 63 | name: District of Columbia 64 | country_id: "214" 65 | id: "6764998" 66 | abbr: DC 67 | states_046: 68 | name: Idaho 69 | country_id: "214" 70 | id: "982433740" 71 | abbr: ID 72 | states_035: 73 | name: Nebraska 74 | country_id: "214" 75 | id: "673350891" 76 | abbr: NE 77 | states_024: 78 | name: Pennsylvania 79 | country_id: "214" 80 | id: "471711976" 81 | abbr: PA 82 | states_013: 83 | name: Hawaii 84 | country_id: "214" 85 | id: "199950338" 86 | abbr: HI 87 | states_002: 88 | name: Utah 89 | country_id: "214" 90 | id: "17199670" 91 | abbr: UT 92 | states_047: 93 | name: Vermont 94 | country_id: "214" 95 | id: "989115415" 96 | abbr: VT 97 | states_036: 98 | name: Delaware 99 | country_id: "214" 100 | id: "721598219" 101 | abbr: DE 102 | states_025: 103 | name: Rhode Island 104 | country_id: "214" 105 | id: "474001862" 106 | abbr: RI 107 | states_014: 108 | name: Oklahoma 109 | country_id: "214" 110 | id: "248548169" 111 | abbr: OK 112 | states_003: 113 | name: Louisiana 114 | country_id: "214" 115 | id: "37199952" 116 | abbr: LA 117 | states_048: 118 | name: Montana 119 | country_id: "214" 120 | id: "999156632" 121 | abbr: MT 122 | states_037: 123 | name: Tennessee 124 | country_id: "214" 125 | id: "726305632" 126 | abbr: TN 127 | states_026: 128 | name: Maryland 129 | country_id: "214" 130 | id: "480368357" 131 | abbr: MD 132 | states_015: 133 | name: Florida 134 | country_id: "214" 135 | id: "267271847" 136 | abbr: FL 137 | states_004: 138 | name: Virginia 139 | country_id: "214" 140 | id: "41111624" 141 | abbr: VA 142 | states_049: 143 | name: Minnesota 144 | country_id: "214" 145 | id: "1032288924" 146 | abbr: MN 147 | states_038: 148 | name: New Jersey 149 | country_id: "214" 150 | id: "750950030" 151 | abbr: NJ 152 | states_027: 153 | name: Ohio 154 | country_id: "214" 155 | id: "485193526" 156 | abbr: OH 157 | states_016: 158 | name: California 159 | country_id: "214" 160 | id: "276110813" 161 | abbr: CA 162 | states_005: 163 | name: North Dakota 164 | country_id: "214" 165 | id: "51943165" 166 | abbr: ND 167 | states_050: 168 | name: Maine 169 | country_id: "214" 170 | id: "1055056709" 171 | abbr: ME 172 | states_039: 173 | name: Indiana 174 | country_id: "214" 175 | id: "769938586" 176 | abbr: IN 177 | states_028: 178 | name: Texas 179 | country_id: "214" 180 | id: "525212995" 181 | abbr: TX 182 | states_017: 183 | name: Oregon 184 | country_id: "214" 185 | id: "298914262" 186 | abbr: OR 187 | states_006: 188 | name: Wyoming 189 | country_id: "214" 190 | id: "66390489" 191 | abbr: WY 192 | states_051: 193 | name: Alabama 194 | country_id: "214" 195 | id: "1061493585" 196 | abbr: AL 197 | states_040: 198 | name: Iowa 199 | country_id: "214" 200 | id: "825306985" 201 | abbr: IA 202 | states_029: 203 | name: Mississippi 204 | country_id: "214" 205 | id: "532363768" 206 | abbr: MS 207 | states_018: 208 | name: Kentucky 209 | country_id: "214" 210 | id: "308473843" 211 | abbr: KY 212 | states_007: 213 | name: New Mexico 214 | country_id: "214" 215 | id: "69729944" 216 | abbr: NM 217 | states_041: 218 | name: Georgia 219 | country_id: "214" 220 | id: "876916760" 221 | abbr: GA 222 | states_030: 223 | name: Colorado 224 | country_id: "214" 225 | id: "536031023" 226 | abbr: CO 227 | states_019: 228 | name: Massachusetts 229 | country_id: "214" 230 | id: "385551075" 231 | abbr: MA 232 | states_008: 233 | name: Connecticut 234 | country_id: "214" 235 | id: "69870734" 236 | abbr: CT 237 | states_042: 238 | name: New York 239 | country_id: "214" 240 | id: "889445952" 241 | abbr: NY 242 | states_031: 243 | name: South Carolina 244 | country_id: "214" 245 | id: "597434151" 246 | abbr: SC 247 | states_020: 248 | name: Alaska 249 | country_id: "214" 250 | id: "403740659" 251 | abbr: AK 252 | states_009: 253 | name: West Virginia 254 | country_id: "214" 255 | id: "91367981" 256 | abbr: WV 257 | -------------------------------------------------------------------------------- /app/views/spree/shared/_address_form.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <%= form.fields_for :bill_address do |bill_form| %> 3 | <%= t(:billing_address)%> 4 |
    5 |

    6 | <%= bill_form.label :firstname, t(:first_name) %>*
    7 | <%= bill_form.text_field :firstname, :class => 'required' %> 8 |

    9 |

    10 | <%= bill_form.label :lastname, t(:last_name) %>*
    11 | <%= bill_form.text_field :lastname, :class => 'required' %> 12 |

    13 |

    14 | <%= bill_form.label :address1, t(:street_address) %>*
    15 | <%= bill_form.text_field :address1, :class => 'required' %> 16 |

    17 |

    18 | <%= bill_form.label :address2, t(:street_address_2) %>
    19 | <%= bill_form.text_field :address2 %> 20 |

    21 |

    22 | <%= bill_form.label :city, t(:city) %>*
    23 | <%= bill_form.text_field :city, :class => 'required' %> 24 |

    25 | 26 | <% if Spree::Config[:address_requires_state] %> 27 |

    28 | <% have_states = !@wholesaler.bill_address.country.states.empty? %> 29 | <%= bill_form.label :state_id, t(:state) %>*
    30 | 33 | <% state_elements = [ 34 | bill_form.collection_select(:state_id, @wholesaler.bill_address.country.states, 35 | :id, :name, 36 | {:include_blank => true}, 37 | {:class => have_states ? 'required' : 'hidden', 38 | :disabled => !have_states}) + 39 | bill_form.text_field(:state_name, 40 | :class => !have_states ? 'required' : 'hidden', 41 | :disabled => have_states) 42 | ].join.gsub('"', "'").gsub("\n", "") 43 | %> 44 | <%= javascript_tag do -%> 45 | document.write("<%== state_elements %>"); 46 | <% end -%> 47 |

    48 | <% end %> 49 | 50 |

    51 | <%= bill_form.label :zipcode, t(:zip) %>*
    52 | <%= bill_form.text_field :zipcode, :class => 'required' %> 53 |

    54 |

    55 | <%= bill_form.label :country_id, t(:country) %>*
    56 | 57 | <%= bill_form.collection_select :country_id, available_countries, :id, :name, {}, {:class => 'required'} %> 58 | 59 |

    60 |

    61 | <%= bill_form.label :phone, t(:phone) %>*
    62 | <%= bill_form.text_field :phone, :class => 'required' %> 63 |

    64 | <% if Spree::Config[:alternative_billing_phone] %> 65 |

    66 | <%= bill_form.label :alternative_phone, t(:alternative_phone) %>
    67 | <%= bill_form.text_field :alternative_phone %> 68 |

    69 | <% end %> 70 |
    71 | <% end %> 72 |
    73 | 74 |
    75 | <%= form.fields_for :ship_address do |ship_form| %> 76 | <%= t(:shipping_address)%> 77 |

    78 | <%= check_box_tag 'wholesaler[use_billing]', '1', (!(@wholesaler.bill_address.empty? && @wholesaler.ship_address.empty?) && @wholesaler.bill_address.eql?(@wholesaler.ship_address)) %> 79 | <%= label_tag :wholesaler_use_billing, t(:use_billing_address) %> 80 |

    81 |
    82 |

    83 | <%= ship_form.label :firstname, t(:first_name) %>*
    84 | <%= ship_form.text_field :firstname, :class => 'required' %> 85 |

    86 |

    87 | <%= ship_form.label :lastname, t(:last_name) %>*
    88 | <%= ship_form.text_field :lastname, :class => 'required' %> 89 |

    90 |

    91 | <%= ship_form.label :address1, t(:street_address) %>*
    92 | <%= ship_form.text_field :address1, :class => 'required' %> 93 |

    94 |

    95 | <%= ship_form.label :address2, t(:street_address_2) %>
    96 | <%= ship_form.text_field :address2 %> 97 |

    98 |

    99 | <%= ship_form.label :city, t(:city) %>*
    100 | <%= ship_form.text_field :city, :class => 'required' %> 101 |

    102 | 103 | <% if Spree::Config[:address_requires_state] %> 104 |

    105 | <% have_states = !@wholesaler.ship_address.country.states.empty? %> 106 | <%= ship_form.label :state_id, t(:state) %>*
    107 | 110 | <% state_elements = [ 111 | ship_form.collection_select(:state_id, @wholesaler.ship_address.country.states, 112 | :id, :name, 113 | {:include_blank => true}, 114 | {:class => have_states ? 'required' : 'hidden', 115 | :disabled => !have_states}) + 116 | ship_form.text_field(:state_name, 117 | :class => !have_states ? 'required' : 'hidden', 118 | :disabled => have_states) 119 | ].join.gsub('"', "'").gsub("\n", "") 120 | %> 121 | <%= javascript_tag do -%> 122 | document.write("<%== state_elements %>"); 123 | <% end %> 124 |

    125 | <% end %> 126 | 127 |

    128 | <%= ship_form.label :zipcode, t(:zip) %>*
    129 | <%= ship_form.text_field :zipcode, :class => 'required' %> 130 |

    131 |

    132 | <%= ship_form.label :country_id, t(:country) %>*
    133 | 134 | <%= ship_form.collection_select :country_id, available_countries, :id, :name, {}, {:class => 'required'} %> 135 | 136 |

    137 |

    138 | <%= ship_form.label :phone, t(:phone) %>*
    139 | <%= ship_form.text_field :phone, :class => 'required' %> 140 |

    141 | <% if Spree::Config[:alternative_shipping_phone] %> 142 |

    143 | <%= ship_form.label :alternative_phone, t(:alternative_phone) %>
    144 | <%= ship_form.text_field :alternative_phone %> 145 |

    146 | <% end %> 147 |
    148 | <% end %> 149 |
    150 | -------------------------------------------------------------------------------- /test/fixtures/spree/products.yml: -------------------------------------------------------------------------------- 1 | ror_tote: 2 | name: Ruby on Rails Tote 3 | description: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nulla nonummy aliquet mi. Proin lacus. Ut placerat. Proin consequat, justo sit amet tempus consequat, elit est adipiscing odio, ut egestas pede eros in diam. Proin varius, lacus vitae suscipit varius, ipsum eros convallis nisi, sit amet sodales lectus pede non est. Duis augue. Suspendisse hendrerit pharetra metus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur nec pede. Quisque volutpat, neque ac porttitor sodales, sem lacus rutrum nulla, ullamcorper placerat ante tortor ac odio. Suspendisse vel libero. Nullam volutpat magna vel ligula. Suspendisse sit amet metus. Nunc quis massa. Nulla facilisi. In enim. In venenatis nisi id eros. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc sit amet felis sed lectus tincidunt egestas. Mauris nibh. 4 | available_on: <%= Time.zone.now.to_s(:db) %> 5 | permalink: ruby-on-rails-tote 6 | count_on_hand: 45 7 | 8 | ror_bag: 9 | name: Ruby on Rails Bag 10 | description: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nulla nonummy aliquet mi. Proin lacus. Ut placerat. Proin consequat, justo sit amet tempus consequat, elit est adipiscing odio, ut egestas pede eros in diam. Proin varius, lacus vitae suscipit varius, ipsum eros convallis nisi, sit amet sodales lectus pede non est. Duis augue. Suspendisse hendrerit pharetra metus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur nec pede. Quisque volutpat, neque ac porttitor sodales, sem lacus rutrum nulla, ullamcorper placerat ante tortor ac odio. Suspendisse vel libero. Nullam volutpat magna vel ligula. Suspendisse sit amet metus. Nunc quis massa. Nulla facilisi. In enim. In venenatis nisi id eros. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc sit amet felis sed lectus tincidunt egestas. Mauris nibh. 11 | available_on: <%= Time.zone.now.to_s(:db) %> 12 | permalink: ruby-on-rails-bag 13 | count_on_hand: 45 14 | 15 | 16 | wholesale: 17 | name: Ruby on Rails Baseball Jersey 18 | description: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nulla nonummy aliquet mi. Proin lacus. Ut placerat. Proin consequat, justo sit amet tempus consequat, elit est adipiscing odio, ut egestas pede eros in diam. Proin varius, lacus vitae suscipit varius, ipsum eros convallis nisi, sit amet sodales lectus pede non est. Duis augue. Suspendisse hendrerit pharetra metus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur nec pede. Quisque volutpat, neque ac porttitor sodales, sem lacus rutrum nulla, ullamcorper placerat ante tortor ac odio. Suspendisse vel libero. Nullam volutpat magna vel ligula. Suspendisse sit amet metus. Nunc quis massa. Nulla facilisi. In enim. In venenatis nisi id eros. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc sit amet felis sed lectus tincidunt egestas. Mauris nibh. 19 | available_on: <%= Time.zone.now.to_s(:db) %> 20 | permalink: ruby-on-rails-baseball-jersey 21 | 22 | 23 | ror_jr_spaghetti: 24 | name: Ruby on Rails Jr. Spaghetti 25 | description: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nulla nonummy aliquet mi. Proin lacus. Ut placerat. Proin consequat, justo sit amet tempus consequat, elit est adipiscing odio, ut egestas pede eros in diam. Proin varius, lacus vitae suscipit varius, ipsum eros convallis nisi, sit amet sodales lectus pede non est. Duis augue. Suspendisse hendrerit pharetra metus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur nec pede. Quisque volutpat, neque ac porttitor sodales, sem lacus rutrum nulla, ullamcorper placerat ante tortor ac odio. Suspendisse vel libero. Nullam volutpat magna vel ligula. Suspendisse sit amet metus. Nunc quis massa. Nulla facilisi. In enim. In venenatis nisi id eros. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc sit amet felis sed lectus tincidunt egestas. Mauris nibh. 26 | available_on: <%= Time.zone.now.to_s(:db) %> 27 | permalink: ruby-on-rails-jr-spaghetti 28 | count_on_hand: 75 29 | 30 | 31 | ror_mug: 32 | name: Ruby on Rails Mug 33 | description: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nulla nonummy aliquet mi. Proin lacus. Ut placerat. Proin consequat, justo sit amet tempus consequat, elit est adipiscing odio, ut egestas pede eros in diam. Proin varius, lacus vitae suscipit varius, ipsum eros convallis nisi, sit amet sodales lectus pede non est. Duis augue. Suspendisse hendrerit pharetra metus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur nec pede. Quisque volutpat, neque ac porttitor sodales, sem lacus rutrum nulla, ullamcorper placerat ante tortor ac odio. Suspendisse vel libero. Nullam volutpat magna vel ligula. Suspendisse sit amet metus. Nunc quis massa. Nulla facilisi. In enim. In venenatis nisi id eros. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc sit amet felis sed lectus tincidunt egestas. Mauris nibh. 34 | available_on: <%= Time.zone.now.to_s(:db) %> 35 | permalink: ruby-on-rails-mug 36 | count_on_hand: 75 37 | 38 | 39 | ror_ringer: 40 | name: Ruby on Rails Ringer T-Shirt 41 | description: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nulla nonummy aliquet mi. Proin lacus. Ut placerat. Proin consequat, justo sit amet tempus consequat, elit est adipiscing odio, ut egestas pede eros in diam. Proin varius, lacus vitae suscipit varius, ipsum eros convallis nisi, sit amet sodales lectus pede non est. Duis augue. Suspendisse hendrerit pharetra metus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur nec pede. Quisque volutpat, neque ac porttitor sodales, sem lacus rutrum nulla, ullamcorper placerat ante tortor ac odio. Suspendisse vel libero. Nullam volutpat magna vel ligula. Suspendisse sit amet metus. Nunc quis massa. Nulla facilisi. In enim. In venenatis nisi id eros. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc sit amet felis sed lectus tincidunt egestas. Mauris nibh. 42 | available_on: <%= Time.zone.now.to_s(:db) %> 43 | permalink: ruby-on-rails-ringer-t-shirt 44 | count_on_hand: 75 45 | 46 | 47 | ror_stein: 48 | name: Ruby on Rails Stein 49 | description: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nulla nonummy aliquet mi. Proin lacus. Ut placerat. Proin consequat, justo sit amet tempus consequat, elit est adipiscing odio, ut egestas pede eros in diam. Proin varius, lacus vitae suscipit varius, ipsum eros convallis nisi, sit amet sodales lectus pede non est. Duis augue. Suspendisse hendrerit pharetra metus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur nec pede. Quisque volutpat, neque ac porttitor sodales, sem lacus rutrum nulla, ullamcorper placerat ante tortor ac odio. Suspendisse vel libero. Nullam volutpat magna vel ligula. Suspendisse sit amet metus. Nunc quis massa. Nulla facilisi. In enim. In venenatis nisi id eros. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc sit amet felis sed lectus tincidunt egestas. Mauris nibh. 50 | available_on: <%= Time.zone.now.to_s(:db) %> 51 | permalink: ruby-on-rails-stein 52 | count_on_hand: 75 53 | 54 | 55 | ruby_baseball_jersey: 56 | name: Ruby Baseball Jersey 57 | description: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nulla nonummy aliquet mi. Proin lacus. Ut placerat. Proin consequat, justo sit amet tempus consequat, elit est adipiscing odio, ut egestas pede eros in diam. Proin varius, lacus vitae suscipit varius, ipsum eros convallis nisi, sit amet sodales lectus pede non est. Duis augue. Suspendisse hendrerit pharetra metus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur nec pede. Quisque volutpat, neque ac porttitor sodales, sem lacus rutrum nulla, ullamcorper placerat ante tortor ac odio. Suspendisse vel libero. Nullam volutpat magna vel ligula. Suspendisse sit amet metus. Nunc quis massa. Nulla facilisi. In enim. In venenatis nisi id eros. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc sit amet felis sed lectus tincidunt egestas. Mauris nibh. 58 | available_on: <%= Time.zone.now.to_s(:db) %> 59 | permalink: ruby-baseball-jersey 60 | count_on_hand: 75 61 | 62 | 63 | apache_baseball_jersey: 64 | name: Apache Baseball Jersey 65 | description: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nulla nonummy aliquet mi. Proin lacus. Ut placerat. Proin consequat, justo sit amet tempus consequat, elit est adipiscing odio, ut egestas pede eros in diam. Proin varius, lacus vitae suscipit varius, ipsum eros convallis nisi, sit amet sodales lectus pede non est. Duis augue. Suspendisse hendrerit pharetra metus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur nec pede. Quisque volutpat, neque ac porttitor sodales, sem lacus rutrum nulla, ullamcorper placerat ante tortor ac odio. Suspendisse vel libero. Nullam volutpat magna vel ligula. Suspendisse sit amet metus. Nunc quis massa. Nulla facilisi. In enim. In venenatis nisi id eros. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nunc sit amet felis sed lectus tincidunt egestas. Mauris nibh. 66 | available_on: <%= Time.zone.now.to_s(:db) %> 67 | permalink: apache-baseball-jersey 68 | count_on_hand: 0 69 | 70 | 71 | -------------------------------------------------------------------------------- /test/dummy_hooks/templates/store/screen.css: -------------------------------------------------------------------------------- 1 | @import url(http://fonts.googleapis.com/css?family=Ubuntu:400,700,400italic,700italic|&subset=latin,cyrillic,greek,greek-ext,latin-ext,cyrillic-ext); 2 | /*--------------------------------------*/ 3 | /* Colors 4 | /*--------------------------------------*/ 5 | /* Dark gray */ 6 | /* Mid light gray */ 7 | /* Light blue */ 8 | /* Lighter blue */ 9 | /* Light gray */ 10 | /* Spree green */ 11 | /* Error red */ 12 | /*--------------------------------------*/ 13 | /* Fonts 14 | /*--------------------------------------*/ 15 | /*--------------------------------------*/ 16 | /* Basic styles 17 | /*--------------------------------------*/ 18 | body { 19 | font-family: 'Ubuntu', sans-serif; 20 | color: #404042; 21 | line-height: 18px; 22 | font-size: 12px; } 23 | 24 | /* Line style */ 25 | hr { 26 | border-color: #dedede; } 27 | 28 | /* Custom text-selection colors (remove any text shadows: twitter.com/miketaylr/status/12228805301) */ 29 | ::-moz-selection { 30 | background: #00adee; 31 | color: white; 32 | text-shadow: none; } 33 | 34 | ::selection { 35 | background: #00adee; 36 | color: white; 37 | text-shadow: none; } 38 | 39 | /* j.mp/webkit-tap-highlight-color */ 40 | a:link { 41 | -webkit-tap-highlight-color: #00adee; } 42 | 43 | ins { 44 | background-color: #00adee; 45 | color: white; 46 | text-decoration: none; } 47 | 48 | mark { 49 | background-color: #00adee; 50 | color: white; 51 | font-style: italic; 52 | font-weight: bold; } 53 | 54 | /*--------------------------------------*/ 55 | /* Links 56 | /*--------------------------------------*/ 57 | a { 58 | text-decoration: none; 59 | color: #404042; } 60 | a:hover { 61 | color: #00adee !important; } 62 | 63 | /*--------------------------------------*/ 64 | /* Lists 65 | /*--------------------------------------*/ 66 | ul.inline li, ol.inline li { 67 | display: inline-block; } 68 | 69 | dl dt, dl dd { 70 | display: inline-block; 71 | width: 50%; 72 | padding: 5px; } 73 | dl dt.odd, dl dd.odd { 74 | background-color: #dedede; } 75 | dl dt { 76 | font-weight: bold; 77 | text-transform: uppercase; } 78 | dl dd { 79 | margin-left: -23px; } 80 | 81 | /*--------------------------------------*/ 82 | /* Headers 83 | /*--------------------------------------*/ 84 | h1, h2, h3, h4, h5, h6 { 85 | font-weight: bold; } 86 | 87 | h1 { 88 | font-size: 24px; 89 | line-height: 34px; } 90 | 91 | h2 { 92 | font-size: 23px; 93 | line-height: 32px; } 94 | 95 | h3 { 96 | font-size: 20px; 97 | line-height: 30px; } 98 | 99 | h4 { 100 | font-size: 18px; 101 | line-height: 28px; } 102 | 103 | h5 { 104 | font-size: 16px; 105 | line-height: 26px; } 106 | 107 | h6 { 108 | font-size: 14px; 109 | line-height: 24px; } 110 | 111 | /*--------------------------------------*/ 112 | /* Forms 113 | /*--------------------------------------*/ 114 | textarea, input[type="date"], 115 | input[type="datetime"], input[type="datetime-local"], 116 | input[type="email"], input[type="month"], input[type="number"], 117 | input[type="password"], input[type="search"], input[type="tel"], 118 | input[type="text"], input[type="time"], input[type="url"], 119 | input[type="week"] { 120 | border: 1px solid #dedede; 121 | padding: 2px 5px; 122 | font-family: "Ubuntu", sans-serif; } 123 | textarea:active, textarea:focus, input[type="date"]:active, input[type="date"]:focus, 124 | input[type="datetime"]:active, 125 | input[type="datetime"]:focus, input[type="datetime-local"]:active, input[type="datetime-local"]:focus, 126 | input[type="email"]:active, 127 | input[type="email"]:focus, input[type="month"]:active, input[type="month"]:focus, input[type="number"]:active, input[type="number"]:focus, 128 | input[type="password"]:active, 129 | input[type="password"]:focus, input[type="search"]:active, input[type="search"]:focus, input[type="tel"]:active, input[type="tel"]:focus, 130 | input[type="text"]:active, 131 | input[type="text"]:focus, 132 | select:active, 133 | select:focus, input[type="time"]:active, input[type="time"]:focus, input[type="url"]:active, input[type="url"]:focus, 134 | input[type="week"]:active, 135 | input[type="week"]:focus { 136 | border-color: #00adee; 137 | outline: none; 138 | -webkit-box-shadow: none; 139 | -moz-box-shadow: none; 140 | -o-box-shadow: none; 141 | box-shadow: none; } 142 | textarea.error, input[type="date"].error, 143 | input[type="datetime"].error, input[type="datetime-local"].error, 144 | input[type="email"].error, input[type="month"].error, input[type="number"].error, 145 | input[type="password"].error, input[type="search"].error, input[type="tel"].error, 146 | input[type="text"].error, input[type="time"].error, input[type="url"].error, 147 | input[type="week"].error { 148 | border-color: #e45353; } 149 | 150 | select { 151 | border: 1px solid #dedede; 152 | font-family: "Ubuntu", sans-serif; 153 | background-image: url("select_arrow.gif"); 154 | background-repeat: no-repeat; 155 | background-position: right center; } 156 | 157 | label.error { 158 | display: block; 159 | font-size: 11px; 160 | color: #e45353; 161 | margin-top: 3px; } 162 | 163 | input[type="submit"], input[type="button"], 164 | input[type="reset"], button, a.button { 165 | background-color: #00adee; 166 | background-image: none; 167 | text-shadow: none; 168 | color: white; 169 | font-weight: bold; 170 | font-family: "Ubuntu", sans-serif; 171 | border: 1px solid rgba(0, 138, 189, 0.75); 172 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4); 173 | -khtml-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4); 174 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4); 175 | -o-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4); 176 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4); 177 | -webkit-border-radius: 0px; 178 | -khtml-border-radius: 0px; 179 | -moz-border-radius: 0px; 180 | -ms-border-radius: 0px; 181 | -o-border-radius: 0px; 182 | border-radius: 0px; 183 | vertical-align: text-top; } 184 | input[type="submit"].large, input[type="button"].large, 185 | input[type="reset"].large, button.large, a.button.large { 186 | padding: 7px 10px; 187 | font-size: 14px; } 188 | input[type="submit"]:hover, input[type="button"]:hover, 189 | input[type="reset"]:hover, button:hover, a.button:hover { 190 | background-image: none; 191 | background-color: #404042; 192 | border-color: #404042; 193 | color: white !important; } 194 | 195 | .ie8 a.button { 196 | line-height: 16px; } 197 | 198 | input[type="checkbox"], input[type="button"], 199 | input[type="submit"], input[type="reset"], 200 | button, label { 201 | vertical-align: middle; } 202 | 203 | a.button { 204 | display: inline-block; 205 | line-height: 15px; 206 | margin-top: -2px; 207 | vertical-align: bottom; } 208 | 209 | /*--------------------------------------*/ 210 | /* Footer 211 | /*--------------------------------------*/ 212 | footer#footer { 213 | padding: 10px 0; 214 | border-top: 1px solid #dedede; } 215 | 216 | /*--------------------------------------*/ 217 | /* Paragraphs 218 | /*--------------------------------------*/ 219 | p { 220 | padding: 10px 0; } 221 | 222 | /*--------------------------------------*/ 223 | /* Tables 224 | /*--------------------------------------*/ 225 | table thead { 226 | background-color: #dedede; 227 | text-transform: uppercase; } 228 | table thead tr th { 229 | padding: 5px 10px; } 230 | table tbody tr, table tfoot tr { 231 | border-bottom: 1px solid #dedede; } 232 | table tbody tr td, table tfoot tr td { 233 | vertical-align: middle; 234 | padding: 5px 10px; } 235 | table tbody tr.alt, table tbody tr.odd, table tfoot tr.alt, table tfoot tr.odd { 236 | background-color: #f0f8ff; } 237 | 238 | /*--------------------------------------*/ 239 | /* Navigation 240 | /*--------------------------------------*/ 241 | nav#top-nav-bar { 242 | text-align: right; 243 | margin-top: 20px; } 244 | nav#top-nav-bar ul li { 245 | margin-bottom: 5px; 246 | padding-left: 10px; } 247 | nav#top-nav-bar ul li a { 248 | font-weight: bold; 249 | font-size: 14px; 250 | text-transform: uppercase; } 251 | 252 | nav #main-nav-bar { 253 | text-transform: uppercase; 254 | font-weight: bold; 255 | margin-top: 20px; 256 | border-bottom: 1px solid #dedede; 257 | padding-bottom: 6px; } 258 | nav #main-nav-bar li a { 259 | font-size: 16px; 260 | padding: 5px; } 261 | nav #main-nav-bar li#link-to-cart { 262 | float: right; 263 | padding-left: 24px; 264 | background: url("cart.png") no-repeat left center; } 265 | nav #main-nav-bar li#link-to-cart:hover { 266 | border-color: #00adee; } 267 | nav #main-nav-bar li#link-to-cart:hover .amount { 268 | border-color: #00adee; } 269 | nav #main-nav-bar li#link-to-cart a { 270 | font-weight: normal; 271 | font-size: 16px; 272 | color: #00adee; } 273 | nav #main-nav-bar li#link-to-cart a .amount { 274 | font-size: 18px; 275 | font-weight: bold; 276 | border-left: 1px solid #dedede; 277 | padding-left: 5px; 278 | padding-bottom: 5px; } 279 | 280 | nav#taxonomies .taxonomy-root { 281 | text-transform: uppercase; 282 | border-bottom: 1px solid #dedede; 283 | margin-bottom: 5px; 284 | color: #00adee; } 285 | nav#taxonomies .taxons-list { 286 | padding-left: 20px; 287 | margin-bottom: 20px; 288 | list-style: disc outside; } 289 | 290 | #breadcrumbs { 291 | border-bottom: 1px solid #dedede; 292 | padding: 3px 0; 293 | margin-bottom: 15px; } 294 | #breadcrumbs li a { 295 | color: #00adee; } 296 | #breadcrumbs li span { 297 | text-transform: uppercase; 298 | font-weight: bold; } 299 | 300 | /*--------------------------------------*/ 301 | /* Flash notices & errors 302 | /*--------------------------------------*/ 303 | .flash, .errorExplanation { 304 | padding: 10px; 305 | color: white; 306 | font-weight: bold; 307 | margin-bottom: 10px; } 308 | .flash.notice, .notice.errorExplanation { 309 | background-color: #00adee; } 310 | .flash.success, .success.errorExplanation { 311 | background-color: #8dba53; } 312 | .flash.error, .errorExplanation, .error.errorExplanation { 313 | background-color: #e45353; } 314 | 315 | .errorExplanation p { 316 | font-weight: normal; } 317 | .errorExplanation ul { 318 | list-style: disc outside; 319 | margin-left: 30px; } 320 | .errorExplanation ul li { 321 | font-weight: normal; } 322 | 323 | /*--------------------------------------*/ 324 | /* Main search bar 325 | /*--------------------------------------*/ 326 | #search-bar { 327 | display: block; } 328 | 329 | /*--------------------------------------*/ 330 | /* Products 331 | /*--------------------------------------*/ 332 | .product-section-title { 333 | text-transform: uppercase; 334 | color: #00adee; 335 | margin-top: 15px; } 336 | 337 | .add-to-cart { 338 | margin-top: 15px; } 339 | .add-to-cart input[type="number"] { 340 | width: 60px; 341 | vertical-align: middle; 342 | padding: 5px; 343 | height: 35px; } 344 | 345 | span.price, #checkout-summary table tr[data-hook="item_total"] td:last-child strong, #checkout-summary table #summary-order-total, #order_details td.price span, #order_details td.total span, #order_summary td.price span, #order_summary td.total span, table#cart-detail tbody#line_items tr td[data-hook="cart_item_price"], table#cart-detail tbody#line_items tr td[data-hook="cart_item_total"], div[data-hook="inside_cart_form"] #subtotal span.order-total { 346 | font-weight: bold; 347 | color: #00adee; } 348 | span.price.selling, #checkout-summary table tr[data-hook="item_total"] td:last-child strong.selling, #checkout-summary table .selling#summary-order-total, #order_details td.price span.selling, #order_details td.total span.selling, #order_summary td.price span.selling, #order_summary td.total span.selling, table#cart-detail tbody#line_items tr td.selling[data-hook="cart_item_price"], table#cart-detail tbody#line_items tr td.selling[data-hook="cart_item_total"], table#cart-detail tbody#line_items tr td[data-hook="cart_item_price"], table#cart-detail tbody#line_items tr td[data-hook="cart_item_total"], div[data-hook="inside_cart_form"] #subtotal span.selling.order-total { 349 | font-size: 20px; } 350 | span.price.diff, #checkout-summary table tr[data-hook="item_total"] td:last-child strong.diff, #checkout-summary table .diff#summary-order-total, #order_details td.price span.diff, #order_details td.total span.diff, #order_summary td.price span.diff, #order_summary td.total span.diff, table#cart-detail tbody#line_items tr td.diff[data-hook="cart_item_price"], table#cart-detail tbody#line_items tr td.diff[data-hook="cart_item_total"], div[data-hook="inside_cart_form"] #subtotal span.diff.order-total { 351 | font-weight: bold; } 352 | 353 | ul#products { 354 | margin-left: -10px; 355 | margin-right: -10px; } 356 | ul#products li { 357 | text-align: center; 358 | font-weight: bold; 359 | margin-bottom: 20px; } 360 | ul#products li a { 361 | display: block; } 362 | ul#products li a.info { 363 | height: 35px; 364 | margin-top: 5px; 365 | color: #bbbbbb; 366 | border-bottom: 1px solid #dedede; } 367 | ul#products li .product-image { 368 | border: 1px solid #dedede; 369 | padding: 5px; 370 | min-height: 110px; } 371 | ul#products li .product-image:hover { 372 | border-color: #00adee; } 373 | ul#products li .price { 374 | color: #00adee; 375 | font-size: 16px; 376 | padding-top: 5px; 377 | display: block; } 378 | 379 | .subtaxon-title { 380 | text-transform: uppercase; } 381 | .subtaxon-title a { 382 | color: #00adee; } 383 | 384 | .search-results-title { 385 | text-transform: uppercase; 386 | border-bottom: 1px solid #dedede; 387 | margin-bottom: 10px; } 388 | 389 | #sidebar_products_search .navigation { 390 | margin-bottom: 15px; } 391 | #sidebar_products_search span.category { 392 | display: block; 393 | font-weight: bold; 394 | text-transform: uppercase; 395 | border-bottom: 1px solid #ededed; 396 | margin-bottom: 5px; 397 | color: #00adee; 398 | font-size: 14px; 399 | line-height: 24px; } 400 | 401 | .taxon { 402 | overflow: hidden; } 403 | 404 | #product-images #main-image { 405 | text-align: center; 406 | border: 1px solid #dedede; } 407 | 408 | #product-description .product-title { 409 | border-bottom: 1px solid #dedede; 410 | margin-bottom: 15px; } 411 | 412 | #product-thumbnails { 413 | margin-top: 10px; } 414 | #product-thumbnails li { 415 | margin-right: 6px; 416 | border: 1px solid #dedede; } 417 | #product-thumbnails li img { 418 | padding: 5px; } 419 | #product-thumbnails li:hover, #product-thumbnails li.selected { 420 | border-color: #00adee; } 421 | 422 | #product-properties { 423 | border: 1px solid #dedede; 424 | padding: 10px; } 425 | 426 | #product-variants ul li { 427 | padding: 5px; } 428 | 429 | /*--------------------------------------*/ 430 | /* Checkout 431 | /*--------------------------------------*/ 432 | .progress-steps { 433 | list-style: decimal inside; 434 | overflow: auto; } 435 | .progress-steps li { 436 | float: left; 437 | margin-right: 20px; 438 | font-weight: bold; 439 | text-transform: uppercase; 440 | padding: 5px 20px; 441 | color: #bbbbbb; } 442 | .progress-steps li.current-first, .progress-steps li.current { 443 | background-color: #00adee; 444 | color: white; } 445 | .progress-steps li.completed-first, .progress-steps li.completed { 446 | background-color: #dedede; 447 | color: white; } 448 | .progress-steps li.completed-first a, .progress-steps li.completed a { 449 | color: white; } 450 | .progress-steps li.completed-first:hover, .progress-steps li.completed:hover { 451 | background-color: #00adee; 452 | color: white; } 453 | .progress-steps li.completed-first:hover a, .progress-steps li.completed:hover a { 454 | color: white; } 455 | .progress-steps li.completed-first:hover a:hover, .progress-steps li.completed:hover a:hover { 456 | color: white !important; } 457 | 458 | #checkout-summary { 459 | text-align: center; 460 | border: 1px solid #dedede; 461 | margin-top: 23px; 462 | margin-left: 0; } 463 | #checkout-summary h3 { 464 | text-transform: uppercase; 465 | font-size: 14px; 466 | color: #00adee; 467 | border-bottom: 1px solid #dedede; } 468 | #checkout-summary table { 469 | width: 100%; } 470 | #checkout-summary table tr[data-hook="order_total"] { 471 | border-bottom: none; } 472 | #checkout-summary table #summary-order-total { 473 | font-size: 14px; } 474 | 475 | #billing, #shipping, #shipping_method, 476 | #payment, #order_details, #order_summary { 477 | margin-top: 10px; 478 | border: 1px solid #dedede; 479 | padding: 10px; } 480 | #billing legend, #shipping legend, #shipping_method legend, 481 | #payment legend, #order_details legend, #order_summary legend { 482 | text-transform: uppercase; 483 | font-weight: bold; 484 | font-size: 14px; 485 | color: #00adee; 486 | padding: 5px; 487 | margin-left: 15px; } 488 | 489 | #order_details, #order_summary { 490 | padding: 0; } 491 | #order_details div:last-child, #order_summary div:last-child { 492 | margin-left: -1px; } 493 | #order_details .payment-info .cc-type img, #order_summary .payment-info .cc-type img { 494 | vertical-align: middle; } 495 | #order_details table tfoot, #order_summary table tfoot { 496 | text-align: right; 497 | color: #bbbbbb; } 498 | #order_details table tfoot tr, #order_summary table tfoot tr { 499 | border: none; } 500 | #order_details table tfoot#order-total, #order_summary table tfoot#order-total { 501 | text-transform: uppercase; 502 | font-size: 16px; 503 | color: #404042; } 504 | #order_details table tfoot#order-total tr, #order_summary table tfoot#order-total tr { 505 | border-top: 1px solid #dedede; } 506 | #order_details table tfoot#order-total tr td, #order_summary table tfoot#order-total tr td { 507 | padding: 10px; } 508 | #order_details .steps-data, #order_summary .steps-data { 509 | padding: 10px; } 510 | #order_details .steps-data h6, #order_summary .steps-data h6 { 511 | border-bottom: 1px solid #dedede; 512 | margin-bottom: 5px; } 513 | 514 | #shipping_method p label { 515 | float: left; 516 | font-weight: bold; 517 | font-size: 14px; 518 | margin-right: 40px; 519 | padding: 5px; } 520 | 521 | p[data-hook="use_billing"] { 522 | float: right; 523 | margin-top: -38px; 524 | background-color: white; 525 | padding: 5px; } 526 | 527 | /*--------------------------------------*/ 528 | /* Cart 529 | /*--------------------------------------*/ 530 | table#cart-detail { 531 | width: 100%; } 532 | table#cart-detail tbody#line_items tr td[data-hook="cart_item_quantity"] .line_item_quantity { 533 | width: 40px; } 534 | table#cart-detail tbody#line_items tr td[data-hook="cart_item_delete"] .delete { 535 | display: block; 536 | width: 20px; } 537 | 538 | div[data-hook="inside_cart_form"] .links { 539 | margin-top: 15px; } 540 | div[data-hook="inside_cart_form"] #subtotal { 541 | text-align: right; 542 | text-transform: uppercase; 543 | margin-top: 15px; } 544 | 545 | #empty-cart { 546 | margin-top: 15px; 547 | float: right; } 548 | 549 | /*--------------------------------------*/ 550 | /* Account 551 | /*--------------------------------------*/ 552 | #existing-customer h6, #new-customer h6, #forgot-password h6 { 553 | text-transform: uppercase; 554 | color: #00adee; } 555 | 556 | #registration h6 { 557 | text-transform: uppercase; 558 | color: #00adee; } 559 | #registration #existing-customer { 560 | width: auto; 561 | text-align: left; } 562 | 563 | #user-info { 564 | margin-bottom: 15px; 565 | border: 1px solid #dedede; 566 | padding: 10px; } 567 | 568 | /*--------------------------------------*/ 569 | /* Order 570 | /*--------------------------------------*/ 571 | #order_summary { 572 | margin-top: 0; } 573 | 574 | #order p[data-hook="links"] { 575 | margin-left: 10px; 576 | overflow: auto; } 577 | 578 | table.order-summary tbody tr td { 579 | width: 10%; 580 | text-align: center; } 581 | table.order-summary tbody tr td:first-child a { 582 | text-transform: uppercase; 583 | font-weight: bold; 584 | color: #00adee; } 585 | 586 | /* #Media Queries 587 | ================================================== */ 588 | /* Smaller than standard 960 (devices and browsers) */ 589 | /* Tablet Portrait size to standard 960 (devices and browsers) */ 590 | @media only screen and (min-width: 768px) and (max-width: 959px) { 591 | .container { 592 | padding-left: 10px; 593 | width: 758px; } 594 | 595 | footer#footer { 596 | width: 748px; } 597 | 598 | p[data-hook="use_billing"] { 599 | margin-top: -15px; } } 600 | /* All Mobile Sizes (devices and browser) */ 601 | @media only screen and (max-width: 767px) { 602 | html { 603 | -webkit-text-size-adjust: none; } 604 | 605 | nav#taxonomies { 606 | text-align: center; } 607 | nav#taxonomies ul { 608 | padding-left: 0 !important; 609 | list-style: none !important; } 610 | 611 | ul#nav-bar { 612 | text-align: center; } 613 | 614 | .steps-data div.columns { 615 | margin-bottom: 15px; 616 | text-align: center; } 617 | 618 | #order_details table[data-hook="order_details"], #order table[data-hook="order_details"] { 619 | width: 100%; } 620 | 621 | #update-cart #subtotal, #update-cart .links { 622 | width: 50%; 623 | float: left; 624 | text-align: left; } 625 | #update-cart #subtotal { 626 | text-align: right; } } 627 | /* Mobile Landscape Size to Tablet Portrait (devices and browsers) */ 628 | @media only screen and (min-width: 480px) and (max-width: 767px) { 629 | footer#footer { 630 | width: auto !important; } 631 | 632 | input, select { 633 | vertical-align: baseline !important; } 634 | 635 | figure#logo { 636 | text-align: center; } 637 | 638 | #link-to-login { 639 | display: block; 640 | text-align: center; } 641 | 642 | #search-bar { 643 | display: block; 644 | text-align: center; } 645 | #search-bar select { 646 | margin-bottom: 10px; } 647 | 648 | ul#products { 649 | margin-left: 0; 650 | margin-right: -20px; } 651 | ul#products li { 652 | width: 133px; 653 | margin-right: 10px; } 654 | 655 | table#cart-detail tbody tr td[data-hook="cart_item_description"], table#cart-detail tbody tr td[data-hook="order_item_description"], table[data-hook="order_details"] tbody tr td[data-hook="cart_item_description"], table[data-hook="order_details"] tbody tr td[data-hook="order_item_description"] { 656 | font-size: 11px; 657 | line-height: 15px; 658 | width: 100px; } 659 | table#cart-detail tbody tr td[data-hook="cart_item_description"] h4, table#cart-detail tbody tr td[data-hook="order_item_description"] h4, table[data-hook="order_details"] tbody tr td[data-hook="cart_item_description"] h4, table[data-hook="order_details"] tbody tr td[data-hook="order_item_description"] h4 { 660 | font-size: 14px; 661 | line-height: 17px; 662 | margin-bottom: 10px; } 663 | table#cart-detail tbody tr td[data-hook="cart_item_price"], table#cart-detail tbody tr td[data-hook="cart_item_total"], 664 | table#cart-detail tbody tr td[data-hook="order_item_price"], table#cart-detail tbody tr td[data-hook="order_item_total"], table[data-hook="order_details"] tbody tr td[data-hook="cart_item_price"], table[data-hook="order_details"] tbody tr td[data-hook="cart_item_total"], table[data-hook="order_details"] tbody tr td[data-hook="order_item_price"], table[data-hook="order_details"] tbody tr td[data-hook="order_item_total"] { 665 | font-size: 12px !important; } 666 | table#cart-detail tbody tr td[data-hook="cart_item_image"] img, table#cart-detail tbody tr td[data-hook="order_item_image"] img, table[data-hook="order_details"] tbody tr td[data-hook="cart_item_image"] img, table[data-hook="order_details"] tbody tr td[data-hook="order_item_image"] img { 667 | width: 70px; } } 668 | /* Mobile Portrait Size to Mobile Landscape Size (devices and browsers) */ 669 | @media only screen and (max-width: 479px) { 670 | .progress-steps li { 671 | padding: 0; 672 | margin: 0; 673 | width: 50%; } 674 | .progress-steps li span { 675 | display: block; 676 | padding: 10px 20px; } 677 | 678 | #shipping_method p label { 679 | float: none; 680 | display: block; 681 | text-align: center; 682 | margin-right: 0; } 683 | 684 | p[data-hook="use_billing"] { 685 | float: none; 686 | margin-top: 0; } 687 | 688 | table#cart-detail tbody tr td[data-hook="cart_item_description"], table#cart-detail tbody tr td[data-hook="order_item_description"], table[data-hook="order_details"] tbody tr td[data-hook="cart_item_description"], table[data-hook="order_details"] tbody tr td[data-hook="order_item_description"] { 689 | padding: 0 !important; 690 | text-indent: -9999px; } 691 | table#cart-detail tbody tr td[data-hook="cart_item_description"] h4, table#cart-detail tbody tr td[data-hook="order_item_description"] h4, table[data-hook="order_details"] tbody tr td[data-hook="cart_item_description"] h4, table[data-hook="order_details"] tbody tr td[data-hook="order_item_description"] h4 { 692 | display: none; } 693 | table#cart-detail tbody tr td[data-hook="cart_item_image"] img, table#cart-detail tbody tr td[data-hook="order_item_image"] img, table[data-hook="order_details"] tbody tr td[data-hook="cart_item_image"] img, table[data-hook="order_details"] tbody tr td[data-hook="order_item_image"] img { 694 | width: 70px; } 695 | table#cart-detail tbody tr td[data-hook="cart_item_price"], table#cart-detail tbody tr td[data-hook="cart_item_total"], table[data-hook="order_details"] tbody tr td[data-hook="cart_item_price"], table[data-hook="order_details"] tbody tr td[data-hook="cart_item_total"] { 696 | font-size: 14px !important; } 697 | 698 | table.order-summary { 699 | display: block; 700 | position: relative; 701 | width: 100%; } 702 | table.order-summary thead { 703 | display: block; 704 | float: left; } 705 | table.order-summary tbody { 706 | display: block; 707 | width: auto; 708 | position: relative; 709 | overflow-x: auto; 710 | white-space: nowrap; } 711 | table.order-summary thead tr { 712 | display: block; } 713 | table.order-summary th { 714 | display: block; } 715 | table.order-summary tbody tr { 716 | display: inline-block; 717 | vertical-align: top; } 718 | table.order-summary td { 719 | display: block; 720 | min-height: 1.25em; } 721 | 722 | figure#logo { 723 | text-align: center; } 724 | 725 | #link-to-login { 726 | display: block; 727 | text-align: center; } 728 | 729 | #search-bar { 730 | display: block; 731 | text-align: center; } 732 | #search-bar select { 733 | margin-bottom: 10px; } 734 | 735 | aside#sidebar { 736 | text-align: center; } 737 | aside#sidebar ul { 738 | padding-left: 0 !important; } 739 | aside#sidebar ul li { 740 | list-style-type: none; } 741 | 742 | ul#products { 743 | margin-left: 0; } 744 | ul#products li { 745 | width: 140px; 746 | margin-right: 15px; } 747 | 748 | #content { 749 | text-align: center; } } 750 | -------------------------------------------------------------------------------- /test/fixtures/spree/countries.yml: -------------------------------------------------------------------------------- 1 | --- 2 | countries_039: 3 | name: Chad 4 | iso3: TCD 5 | iso: TD 6 | iso_name: CHAD 7 | id: "39" 8 | numcode: "148" 9 | countries_065: 10 | name: Faroe Islands 11 | iso3: FRO 12 | iso: FO 13 | iso_name: FAROE ISLANDS 14 | id: "65" 15 | numcode: "234" 16 | countries_092: 17 | name: India 18 | iso3: IND 19 | iso: IN 20 | iso_name: INDIA 21 | id: "92" 22 | numcode: "356" 23 | countries_146: 24 | name: Nicaragua 25 | iso3: NIC 26 | iso: NI 27 | iso_name: NICARAGUA 28 | id: "146" 29 | numcode: "558" 30 | countries_172: 31 | name: Saint Lucia 32 | iso3: LCA 33 | iso: LC 34 | iso_name: SAINT LUCIA 35 | id: "172" 36 | numcode: "662" 37 | countries_066: 38 | name: Fiji 39 | iso3: FJI 40 | iso: FJ 41 | iso_name: FIJI 42 | id: "66" 43 | numcode: "242" 44 | countries_093: 45 | name: Indonesia 46 | iso3: IDN 47 | iso: ID 48 | iso_name: INDONESIA 49 | id: "93" 50 | numcode: "360" 51 | countries_147: 52 | name: Niger 53 | iso3: NER 54 | iso: NE 55 | iso_name: NIGER 56 | id: "147" 57 | numcode: "562" 58 | countries_173: 59 | name: Saint Pierre and Miquelon 60 | iso3: SPM 61 | iso: PM 62 | iso_name: SAINT PIERRE AND MIQUELON 63 | id: "173" 64 | numcode: "666" 65 | countries_067: 66 | name: Finland 67 | iso3: FIN 68 | iso: FI 69 | iso_name: FINLAND 70 | id: "67" 71 | numcode: "246" 72 | countries_148: 73 | name: Nigeria 74 | iso3: NGA 75 | iso: NG 76 | iso_name: NIGERIA 77 | id: "148" 78 | numcode: "566" 79 | countries_174: 80 | name: Saint Vincent and the Grenadines 81 | iso3: VCT 82 | iso: VC 83 | iso_name: SAINT VINCENT AND THE GRENADINES 84 | id: "174" 85 | numcode: "670" 86 | countries_068: 87 | name: France 88 | iso3: FRA 89 | iso: FR 90 | iso_name: FRANCE 91 | id: "68" 92 | numcode: "250" 93 | countries_094: 94 | name: Iran, Islamic Republic of 95 | iso3: IRN 96 | iso: IR 97 | iso_name: IRAN, ISLAMIC REPUBLIC OF 98 | id: "94" 99 | numcode: "364" 100 | countries_149: 101 | name: Niue 102 | iso3: NIU 103 | iso: NU 104 | iso_name: NIUE 105 | id: "149" 106 | numcode: "570" 107 | countries_175: 108 | name: Samoa 109 | iso3: WSM 110 | iso: WS 111 | iso_name: SAMOA 112 | id: "175" 113 | numcode: "882" 114 | countries_069: 115 | name: French Guiana 116 | iso3: GUF 117 | iso: GF 118 | iso_name: FRENCH GUIANA 119 | id: "69" 120 | numcode: "254" 121 | countries_095: 122 | name: Iraq 123 | iso3: IRQ 124 | iso: IQ 125 | iso_name: IRAQ 126 | id: "95" 127 | numcode: "368" 128 | countries_176: 129 | name: San Marino 130 | iso3: SMR 131 | iso: SM 132 | iso_name: SAN MARINO 133 | id: "176" 134 | numcode: "674" 135 | countries_096: 136 | name: Ireland 137 | iso3: IRL 138 | iso: IE 139 | iso_name: IRELAND 140 | id: "96" 141 | numcode: "372" 142 | countries_177: 143 | name: Sao Tome and Principe 144 | iso3: STP 145 | iso: ST 146 | iso_name: SAO TOME AND PRINCIPE 147 | id: "177" 148 | numcode: "678" 149 | countries_097: 150 | name: Israel 151 | iso3: ISR 152 | iso: IL 153 | iso_name: ISRAEL 154 | id: "97" 155 | numcode: "376" 156 | countries_178: 157 | name: Saudi Arabia 158 | iso3: SAU 159 | iso: SA 160 | iso_name: SAUDI ARABIA 161 | id: "178" 162 | numcode: "682" 163 | countries_098: 164 | name: Italy 165 | iso3: ITA 166 | iso: IT 167 | iso_name: ITALY 168 | id: "98" 169 | numcode: "380" 170 | countries_179: 171 | name: Senegal 172 | iso3: SEN 173 | iso: SN 174 | iso_name: SENEGAL 175 | id: "179" 176 | numcode: "686" 177 | countries_099: 178 | name: Jamaica 179 | iso3: JAM 180 | iso: JM 181 | iso_name: JAMAICA 182 | id: "99" 183 | numcode: "388" 184 | countries_100: 185 | name: Japan 186 | iso3: JPN 187 | iso: JP 188 | iso_name: JAPAN 189 | id: "100" 190 | numcode: "392" 191 | countries_101: 192 | name: Jordan 193 | iso3: JOR 194 | iso: JO 195 | iso_name: JORDAN 196 | id: "101" 197 | numcode: "400" 198 | countries_020: 199 | name: Belgium 200 | iso3: BEL 201 | iso: BE 202 | iso_name: BELGIUM 203 | id: "20" 204 | numcode: "56" 205 | countries_021: 206 | name: Belize 207 | iso3: BLZ 208 | iso: BZ 209 | iso_name: BELIZE 210 | id: "21" 211 | numcode: "84" 212 | countries_102: 213 | name: Kazakhstan 214 | iso3: KAZ 215 | iso: KZ 216 | iso_name: KAZAKHSTAN 217 | id: "102" 218 | numcode: "398" 219 | countries_210: 220 | name: Uganda 221 | iso3: UGA 222 | iso: UG 223 | iso_name: UGANDA 224 | id: "210" 225 | numcode: "800" 226 | countries_022: 227 | name: Benin 228 | iso3: BEN 229 | iso: BJ 230 | iso_name: BENIN 231 | id: "22" 232 | numcode: "204" 233 | countries_103: 234 | name: Kenya 235 | iso3: KEN 236 | iso: KE 237 | iso_name: KENYA 238 | id: "103" 239 | numcode: "404" 240 | countries_211: 241 | name: Ukraine 242 | iso3: UKR 243 | iso: UA 244 | iso_name: UKRAINE 245 | id: "211" 246 | numcode: "804" 247 | countries_023: 248 | name: Bermuda 249 | iso3: BMU 250 | iso: BM 251 | iso_name: BERMUDA 252 | id: "23" 253 | numcode: "60" 254 | countries_104: 255 | name: Kiribati 256 | iso3: KIR 257 | iso: KI 258 | iso_name: KIRIBATI 259 | id: "104" 260 | numcode: "296" 261 | countries_130: 262 | name: Mexico 263 | iso3: MEX 264 | iso: MX 265 | iso_name: MEXICO 266 | id: "130" 267 | numcode: "484" 268 | countries_212: 269 | name: United Arab Emirates 270 | iso3: ARE 271 | iso: AE 272 | iso_name: UNITED ARAB EMIRATES 273 | id: "212" 274 | numcode: "784" 275 | countries_024: 276 | name: Bhutan 277 | iso3: BTN 278 | iso: BT 279 | iso_name: BHUTAN 280 | id: "24" 281 | numcode: "64" 282 | countries_050: 283 | name: Cuba 284 | iso3: CUB 285 | iso: CU 286 | iso_name: CUBA 287 | id: "50" 288 | numcode: "192" 289 | countries_105: 290 | name: North Korea 291 | iso3: PRK 292 | iso: KP 293 | iso_name: KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF 294 | id: "105" 295 | numcode: "408" 296 | countries_131: 297 | name: Micronesia, Federated States of 298 | iso3: FSM 299 | iso: FM 300 | iso_name: MICRONESIA, FEDERATED STATES OF 301 | id: "131" 302 | numcode: "583" 303 | countries_213: 304 | name: United Kingdom 305 | iso3: GBR 306 | iso: GB 307 | iso_name: UNITED KINGDOM 308 | id: "213" 309 | numcode: "826" 310 | countries_025: 311 | name: Bolivia 312 | iso3: BOL 313 | iso: BO 314 | iso_name: BOLIVIA 315 | id: "25" 316 | numcode: "68" 317 | countries_051: 318 | name: Cyprus 319 | iso3: CYP 320 | iso: CY 321 | iso_name: CYPRUS 322 | id: "51" 323 | numcode: "196" 324 | countries_106: 325 | name: South Korea 326 | iso3: KOR 327 | iso: KR 328 | iso_name: KOREA, REPUBLIC OF 329 | id: "106" 330 | numcode: "410" 331 | countries_132: 332 | name: Moldova, Republic of 333 | iso3: MDA 334 | iso: MD 335 | iso_name: MOLDOVA, REPUBLIC OF 336 | id: "132" 337 | numcode: "498" 338 | countries_214: 339 | name: United States 340 | iso3: USA 341 | iso: US 342 | iso_name: UNITED STATES 343 | id: "214" 344 | numcode: "840" 345 | countries_026: 346 | name: Bosnia and Herzegovina 347 | iso3: BIH 348 | iso: BA 349 | iso_name: BOSNIA AND HERZEGOVINA 350 | id: "26" 351 | numcode: "70" 352 | countries_052: 353 | name: Czech Republic 354 | iso3: CZE 355 | iso: CZ 356 | iso_name: CZECH REPUBLIC 357 | id: "52" 358 | numcode: "203" 359 | countries_107: 360 | name: Kuwait 361 | iso3: KWT 362 | iso: KW 363 | iso_name: KUWAIT 364 | id: "107" 365 | numcode: "414" 366 | countries_133: 367 | name: Monaco 368 | iso3: MCO 369 | iso: MC 370 | iso_name: MONACO 371 | id: "133" 372 | numcode: "492" 373 | countries_215: 374 | name: Uruguay 375 | iso3: URY 376 | iso: UY 377 | iso_name: URUGUAY 378 | id: "215" 379 | numcode: "858" 380 | countries_027: 381 | name: Botswana 382 | iso3: BWA 383 | iso: BW 384 | iso_name: BOTSWANA 385 | id: "27" 386 | numcode: "72" 387 | countries_053: 388 | name: Denmark 389 | iso3: DNK 390 | iso: DK 391 | iso_name: DENMARK 392 | id: "53" 393 | numcode: "208" 394 | countries_080: 395 | name: Guadeloupe 396 | iso3: GLP 397 | iso: GP 398 | iso_name: GUADELOUPE 399 | id: "80" 400 | numcode: "312" 401 | countries_108: 402 | name: Kyrgyzstan 403 | iso3: KGZ 404 | iso: KG 405 | iso_name: KYRGYZSTAN 406 | id: "108" 407 | numcode: "417" 408 | countries_134: 409 | name: Mongolia 410 | iso3: MNG 411 | iso: MN 412 | iso_name: MONGOLIA 413 | id: "134" 414 | numcode: "496" 415 | countries_160: 416 | name: Philippines 417 | iso3: PHL 418 | iso: PH 419 | iso_name: PHILIPPINES 420 | id: "160" 421 | numcode: "608" 422 | countries_028: 423 | name: Brazil 424 | iso3: BRA 425 | iso: BR 426 | iso_name: BRAZIL 427 | id: "28" 428 | numcode: "76" 429 | countries_054: 430 | name: Djibouti 431 | iso3: DJI 432 | iso: DJ 433 | iso_name: DJIBOUTI 434 | id: "54" 435 | numcode: "262" 436 | countries_081: 437 | name: Guam 438 | iso3: GUM 439 | iso: GU 440 | iso_name: GUAM 441 | id: "81" 442 | numcode: "316" 443 | countries_109: 444 | name: Lao People's Democratic Republic 445 | iso3: LAO 446 | iso: LA 447 | iso_name: LAO PEOPLE'S DEMOCRATIC REPUBLIC 448 | id: "109" 449 | numcode: "418" 450 | countries_135: 451 | name: Montserrat 452 | iso3: MSR 453 | iso: MS 454 | iso_name: MONTSERRAT 455 | id: "135" 456 | numcode: "500" 457 | countries_161: 458 | name: Pitcairn 459 | iso3: PCN 460 | iso: PN 461 | iso_name: PITCAIRN 462 | id: "161" 463 | numcode: "612" 464 | countries_216: 465 | name: Uzbekistan 466 | iso3: UZB 467 | iso: UZ 468 | iso_name: UZBEKISTAN 469 | id: "216" 470 | numcode: "860" 471 | countries_029: 472 | name: Brunei Darussalam 473 | iso3: BRN 474 | iso: BN 475 | iso_name: BRUNEI DARUSSALAM 476 | id: "29" 477 | numcode: "96" 478 | countries_055: 479 | name: Dominica 480 | iso3: DMA 481 | iso: DM 482 | iso_name: DOMINICA 483 | id: "55" 484 | numcode: "212" 485 | countries_082: 486 | name: Guatemala 487 | iso3: GTM 488 | iso: GT 489 | iso_name: GUATEMALA 490 | id: "82" 491 | numcode: "320" 492 | countries_136: 493 | name: Morocco 494 | iso3: MAR 495 | iso: MA 496 | iso_name: MOROCCO 497 | id: "136" 498 | numcode: "504" 499 | countries_162: 500 | name: Poland 501 | iso3: POL 502 | iso: PL 503 | iso_name: POLAND 504 | id: "162" 505 | numcode: "616" 506 | countries_217: 507 | name: Vanuatu 508 | iso3: VUT 509 | iso: VU 510 | iso_name: VANUATU 511 | id: "217" 512 | numcode: "548" 513 | countries_056: 514 | name: Dominican Republic 515 | iso3: DOM 516 | iso: DO 517 | iso_name: DOMINICAN REPUBLIC 518 | id: "56" 519 | numcode: "214" 520 | countries_137: 521 | name: Mozambique 522 | iso3: MOZ 523 | iso: MZ 524 | iso_name: MOZAMBIQUE 525 | id: "137" 526 | numcode: "508" 527 | countries_163: 528 | name: Portugal 529 | iso3: PRT 530 | iso: PT 531 | iso_name: PORTUGAL 532 | id: "163" 533 | numcode: "620" 534 | countries_190: 535 | name: Sudan 536 | iso3: SDN 537 | iso: SD 538 | iso_name: SUDAN 539 | id: "190" 540 | numcode: "736" 541 | countries_218: 542 | name: Venezuela 543 | iso3: VEN 544 | iso: VE 545 | iso_name: VENEZUELA 546 | id: "218" 547 | numcode: "862" 548 | countries_057: 549 | name: Ecuador 550 | iso3: ECU 551 | iso: EC 552 | iso_name: ECUADOR 553 | id: "57" 554 | numcode: "218" 555 | countries_083: 556 | name: Guinea 557 | iso3: GIN 558 | iso: GN 559 | iso_name: GUINEA 560 | id: "83" 561 | numcode: "324" 562 | countries_138: 563 | name: Myanmar 564 | iso3: MMR 565 | iso: MM 566 | iso_name: MYANMAR 567 | id: "138" 568 | numcode: "104" 569 | countries_164: 570 | name: Puerto Rico 571 | iso3: PRI 572 | iso: PR 573 | iso_name: PUERTO RICO 574 | id: "164" 575 | numcode: "630" 576 | countries_191: 577 | name: Suriname 578 | iso3: SUR 579 | iso: SR 580 | iso_name: SURINAME 581 | id: "191" 582 | numcode: "740" 583 | countries_219: 584 | name: Viet Nam 585 | iso3: VNM 586 | iso: VN 587 | iso_name: VIET NAM 588 | id: "219" 589 | numcode: "704" 590 | countries_058: 591 | name: Egypt 592 | iso3: EGY 593 | iso: EG 594 | iso_name: EGYPT 595 | id: "58" 596 | numcode: "818" 597 | countries_084: 598 | name: Guinea-Bissau 599 | iso3: GNB 600 | iso: GW 601 | iso_name: GUINEA-BISSAU 602 | id: "84" 603 | numcode: "624" 604 | countries_139: 605 | name: Namibia 606 | iso3: NAM 607 | iso: NA 608 | iso_name: NAMIBIA 609 | id: "139" 610 | numcode: "516" 611 | countries_165: 612 | name: Qatar 613 | iso3: QAT 614 | iso: QA 615 | iso_name: QATAR 616 | id: "165" 617 | numcode: "634" 618 | countries_192: 619 | name: Svalbard and Jan Mayen 620 | iso3: SJM 621 | iso: SJ 622 | iso_name: SVALBARD AND JAN MAYEN 623 | id: "192" 624 | numcode: "744" 625 | countries_059: 626 | name: El Salvador 627 | iso3: SLV 628 | iso: SV 629 | iso_name: EL SALVADOR 630 | id: "59" 631 | numcode: "222" 632 | countries_085: 633 | name: Guyana 634 | iso3: GUY 635 | iso: GY 636 | iso_name: GUYANA 637 | id: "85" 638 | numcode: "328" 639 | countries_166: 640 | name: Reunion 641 | iso3: REU 642 | iso: RE 643 | iso_name: REUNION 644 | id: "166" 645 | numcode: "638" 646 | countries_086: 647 | name: Haiti 648 | iso3: HTI 649 | iso: HT 650 | iso_name: HAITI 651 | id: "86" 652 | numcode: "332" 653 | countries_167: 654 | name: Romania 655 | iso3: ROM 656 | iso: RO 657 | iso_name: ROMANIA 658 | id: "167" 659 | numcode: "642" 660 | countries_193: 661 | name: Swaziland 662 | iso3: SWZ 663 | iso: SZ 664 | iso_name: SWAZILAND 665 | id: "193" 666 | numcode: "748" 667 | countries_087: 668 | name: Holy See (Vatican City State) 669 | iso3: VAT 670 | iso: VA 671 | iso_name: HOLY SEE (VATICAN CITY STATE) 672 | id: "87" 673 | numcode: "336" 674 | countries_168: 675 | name: Russian Federation 676 | iso3: RUS 677 | iso: RU 678 | iso_name: RUSSIAN FEDERATION 679 | id: "168" 680 | numcode: "643" 681 | countries_194: 682 | name: Sweden 683 | iso3: SWE 684 | iso: SE 685 | iso_name: SWEDEN 686 | id: "194" 687 | numcode: "752" 688 | countries_088: 689 | name: Honduras 690 | iso3: HND 691 | iso: HN 692 | iso_name: HONDURAS 693 | id: "88" 694 | numcode: "340" 695 | countries_169: 696 | name: Rwanda 697 | iso3: RWA 698 | iso: RW 699 | iso_name: RWANDA 700 | id: "169" 701 | numcode: "646" 702 | countries_195: 703 | name: Switzerland 704 | iso3: CHE 705 | iso: CH 706 | iso_name: SWITZERLAND 707 | id: "195" 708 | numcode: "756" 709 | countries_089: 710 | name: Hong Kong 711 | iso3: HKG 712 | iso: HK 713 | iso_name: HONG KONG 714 | id: "89" 715 | numcode: "344" 716 | countries_196: 717 | name: Syrian Arab Republic 718 | iso3: SYR 719 | iso: SY 720 | iso_name: SYRIAN ARAB REPUBLIC 721 | id: "196" 722 | numcode: "760" 723 | countries_197: 724 | name: Taiwan 725 | iso3: TWN 726 | iso: TW 727 | iso_name: TAIWAN, PROVINCE OF CHINA 728 | id: "197" 729 | numcode: "158" 730 | countries_198: 731 | name: Tajikistan 732 | iso3: TJK 733 | iso: TJ 734 | iso_name: TAJIKISTAN 735 | id: "198" 736 | numcode: "762" 737 | countries_199: 738 | name: Tanzania, United Republic of 739 | iso3: TZA 740 | iso: TZ 741 | iso_name: TANZANIA, UNITED REPUBLIC OF 742 | id: "199" 743 | numcode: "834" 744 | countries_010: 745 | name: Armenia 746 | iso3: ARM 747 | iso: AM 748 | iso_name: ARMENIA 749 | id: "10" 750 | numcode: "51" 751 | countries_011: 752 | name: Aruba 753 | iso3: ABW 754 | iso: AW 755 | iso_name: ARUBA 756 | id: "11" 757 | numcode: "533" 758 | countries_012: 759 | name: Australia 760 | iso3: AUS 761 | iso: AU 762 | iso_name: AUSTRALIA 763 | id: "12" 764 | numcode: "36" 765 | countries_200: 766 | name: Thailand 767 | iso3: THA 768 | iso: TH 769 | iso_name: THAILAND 770 | id: "200" 771 | numcode: "764" 772 | countries_013: 773 | name: Austria 774 | iso3: AUT 775 | iso: AT 776 | iso_name: AUSTRIA 777 | id: "13" 778 | numcode: "40" 779 | countries_120: 780 | name: Madagascar 781 | iso3: MDG 782 | iso: MG 783 | iso_name: MADAGASCAR 784 | id: "120" 785 | numcode: "450" 786 | countries_201: 787 | name: Togo 788 | iso3: TGO 789 | iso: TG 790 | iso_name: TOGO 791 | id: "201" 792 | numcode: "768" 793 | countries_014: 794 | name: Azerbaijan 795 | iso3: AZE 796 | iso: AZ 797 | iso_name: AZERBAIJAN 798 | id: "14" 799 | numcode: "31" 800 | countries_040: 801 | name: Chile 802 | iso3: CHL 803 | iso: CL 804 | iso_name: CHILE 805 | id: "40" 806 | numcode: "152" 807 | countries_121: 808 | name: Malawi 809 | iso3: MWI 810 | iso: MW 811 | iso_name: MALAWI 812 | id: "121" 813 | numcode: "454" 814 | countries_202: 815 | name: Tokelau 816 | iso3: TKL 817 | iso: TK 818 | iso_name: TOKELAU 819 | id: "202" 820 | numcode: "772" 821 | countries_015: 822 | name: Bahamas 823 | iso3: BHS 824 | iso: BS 825 | iso_name: BAHAMAS 826 | id: "15" 827 | numcode: "44" 828 | countries_041: 829 | name: China 830 | iso3: CHN 831 | iso: CN 832 | iso_name: CHINA 833 | id: "41" 834 | numcode: "156" 835 | countries_122: 836 | name: Malaysia 837 | iso3: MYS 838 | iso: MY 839 | iso_name: MALAYSIA 840 | id: "122" 841 | numcode: "458" 842 | countries_203: 843 | name: Tonga 844 | iso3: TON 845 | iso: TO 846 | iso_name: TONGA 847 | id: "203" 848 | numcode: "776" 849 | countries_016: 850 | name: Bahrain 851 | iso3: BHR 852 | iso: BH 853 | iso_name: BAHRAIN 854 | id: "16" 855 | numcode: "48" 856 | countries_042: 857 | name: Colombia 858 | iso3: COL 859 | iso: CO 860 | iso_name: COLOMBIA 861 | id: "42" 862 | numcode: "170" 863 | countries_123: 864 | name: Maldives 865 | iso3: MDV 866 | iso: MV 867 | iso_name: MALDIVES 868 | id: "123" 869 | numcode: "462" 870 | countries_204: 871 | name: Trinidad and Tobago 872 | iso3: TTO 873 | iso: TT 874 | iso_name: TRINIDAD AND TOBAGO 875 | id: "204" 876 | numcode: "780" 877 | countries_017: 878 | name: Bangladesh 879 | iso3: BGD 880 | iso: BD 881 | iso_name: BANGLADESH 882 | id: "17" 883 | numcode: "50" 884 | countries_043: 885 | name: Comoros 886 | iso3: COM 887 | iso: KM 888 | iso_name: COMOROS 889 | id: "43" 890 | numcode: "174" 891 | countries_070: 892 | name: French Polynesia 893 | iso3: PYF 894 | iso: PF 895 | iso_name: FRENCH POLYNESIA 896 | id: "70" 897 | numcode: "258" 898 | countries_124: 899 | name: Mali 900 | iso3: MLI 901 | iso: ML 902 | iso_name: MALI 903 | id: "124" 904 | numcode: "466" 905 | countries_150: 906 | name: Norfolk Island 907 | iso3: NFK 908 | iso: NF 909 | iso_name: NORFOLK ISLAND 910 | id: "150" 911 | numcode: "574" 912 | countries_205: 913 | name: Tunisia 914 | iso3: TUN 915 | iso: TN 916 | iso_name: TUNISIA 917 | id: "205" 918 | numcode: "788" 919 | countries_018: 920 | name: Barbados 921 | iso3: BRB 922 | iso: BB 923 | iso_name: BARBADOS 924 | id: "18" 925 | numcode: "52" 926 | countries_044: 927 | name: Congo 928 | iso3: COG 929 | iso: CG 930 | iso_name: CONGO 931 | id: "44" 932 | numcode: "178" 933 | countries_071: 934 | name: Gabon 935 | iso3: GAB 936 | iso: GA 937 | iso_name: GABON 938 | id: "71" 939 | numcode: "266" 940 | countries_125: 941 | name: Malta 942 | iso3: MLT 943 | iso: MT 944 | iso_name: MALTA 945 | id: "125" 946 | numcode: "470" 947 | countries_151: 948 | name: Northern Mariana Islands 949 | iso3: MNP 950 | iso: MP 951 | iso_name: NORTHERN MARIANA ISLANDS 952 | id: "151" 953 | numcode: "580" 954 | countries_206: 955 | name: Turkey 956 | iso3: TUR 957 | iso: TR 958 | iso_name: TURKEY 959 | id: "206" 960 | numcode: "792" 961 | countries_045: 962 | name: Congo, the Democratic Republic of the 963 | iso3: COD 964 | iso: CD 965 | iso_name: CONGO, THE DEMOCRATIC REPUBLIC OF THE 966 | id: "45" 967 | numcode: "180" 968 | countries_126: 969 | name: Marshall Islands 970 | iso3: MHL 971 | iso: MH 972 | iso_name: MARSHALL ISLANDS 973 | id: "126" 974 | numcode: "584" 975 | countries_152: 976 | name: Norway 977 | iso3: NOR 978 | iso: "NO" 979 | iso_name: NORWAY 980 | id: "152" 981 | numcode: "578" 982 | countries_207: 983 | name: Turkmenistan 984 | iso3: TKM 985 | iso: TM 986 | iso_name: TURKMENISTAN 987 | id: "207" 988 | numcode: "795" 989 | countries_019: 990 | name: Belarus 991 | iso3: BLR 992 | iso: BY 993 | iso_name: BELARUS 994 | id: "19" 995 | numcode: "112" 996 | countries_046: 997 | name: Cook Islands 998 | iso3: COK 999 | iso: CK 1000 | iso_name: COOK ISLANDS 1001 | id: "46" 1002 | numcode: "184" 1003 | countries_072: 1004 | name: Gambia 1005 | iso3: GMB 1006 | iso: GM 1007 | iso_name: GAMBIA 1008 | id: "72" 1009 | numcode: "270" 1010 | countries_127: 1011 | name: Martinique 1012 | iso3: MTQ 1013 | iso: MQ 1014 | iso_name: MARTINIQUE 1015 | id: "127" 1016 | numcode: "474" 1017 | countries_153: 1018 | name: Oman 1019 | iso3: OMN 1020 | iso: OM 1021 | iso_name: OMAN 1022 | id: "153" 1023 | numcode: "512" 1024 | countries_180: 1025 | name: Seychelles 1026 | iso3: SYC 1027 | iso: SC 1028 | iso_name: SEYCHELLES 1029 | id: "180" 1030 | numcode: "690" 1031 | countries_208: 1032 | name: Turks and Caicos Islands 1033 | iso3: TCA 1034 | iso: TC 1035 | iso_name: TURKS AND CAICOS ISLANDS 1036 | id: "208" 1037 | numcode: "796" 1038 | countries_073: 1039 | name: Georgia 1040 | iso3: GEO 1041 | iso: GE 1042 | iso_name: GEORGIA 1043 | id: "73" 1044 | numcode: "268" 1045 | countries_128: 1046 | name: Mauritania 1047 | iso3: MRT 1048 | iso: MR 1049 | iso_name: MAURITANIA 1050 | id: "128" 1051 | numcode: "478" 1052 | countries_154: 1053 | name: Pakistan 1054 | iso3: PAK 1055 | iso: PK 1056 | iso_name: PAKISTAN 1057 | id: "154" 1058 | numcode: "586" 1059 | countries_181: 1060 | name: Sierra Leone 1061 | iso3: SLE 1062 | iso: SL 1063 | iso_name: SIERRA LEONE 1064 | id: "181" 1065 | numcode: "694" 1066 | countries_209: 1067 | name: Tuvalu 1068 | iso3: TUV 1069 | iso: TV 1070 | iso_name: TUVALU 1071 | id: "209" 1072 | numcode: "798" 1073 | countries_047: 1074 | name: Costa Rica 1075 | iso3: CRI 1076 | iso: CR 1077 | iso_name: COSTA RICA 1078 | id: "47" 1079 | numcode: "188" 1080 | countries_074: 1081 | name: Germany 1082 | iso3: DEU 1083 | iso: DE 1084 | iso_name: GERMANY 1085 | id: "74" 1086 | numcode: "276" 1087 | countries_129: 1088 | name: Mauritius 1089 | iso3: MUS 1090 | iso: MU 1091 | iso_name: MAURITIUS 1092 | id: "129" 1093 | numcode: "480" 1094 | countries_155: 1095 | name: Palau 1096 | iso3: PLW 1097 | iso: PW 1098 | iso_name: PALAU 1099 | id: "155" 1100 | numcode: "585" 1101 | countries_048: 1102 | name: Cote D'Ivoire 1103 | iso3: CIV 1104 | iso: CI 1105 | iso_name: COTE D'IVOIRE 1106 | id: "48" 1107 | numcode: "384" 1108 | countries_156: 1109 | name: Panama 1110 | iso3: PAN 1111 | iso: PA 1112 | iso_name: PANAMA 1113 | id: "156" 1114 | numcode: "591" 1115 | countries_182: 1116 | name: Singapore 1117 | iso3: SGP 1118 | iso: SG 1119 | iso_name: SINGAPORE 1120 | id: "182" 1121 | numcode: "702" 1122 | countries_049: 1123 | name: Croatia 1124 | iso3: HRV 1125 | iso: HR 1126 | iso_name: CROATIA 1127 | id: "49" 1128 | numcode: "191" 1129 | countries_075: 1130 | name: Ghana 1131 | iso3: GHA 1132 | iso: GH 1133 | iso_name: GHANA 1134 | id: "75" 1135 | numcode: "288" 1136 | countries_157: 1137 | name: Papua New Guinea 1138 | iso3: PNG 1139 | iso: PG 1140 | iso_name: PAPUA NEW GUINEA 1141 | id: "157" 1142 | numcode: "598" 1143 | countries_183: 1144 | name: Slovakia 1145 | iso3: SVK 1146 | iso: SK 1147 | iso_name: SLOVAKIA 1148 | id: "183" 1149 | numcode: "703" 1150 | countries_076: 1151 | name: Gibraltar 1152 | iso3: GIB 1153 | iso: GI 1154 | iso_name: GIBRALTAR 1155 | id: "76" 1156 | numcode: "292" 1157 | countries_158: 1158 | name: Paraguay 1159 | iso3: PRY 1160 | iso: PY 1161 | iso_name: PARAGUAY 1162 | id: "158" 1163 | numcode: "600" 1164 | countries_184: 1165 | name: Slovenia 1166 | iso3: SVN 1167 | iso: SI 1168 | iso_name: SLOVENIA 1169 | id: "184" 1170 | numcode: "705" 1171 | countries_077: 1172 | name: Greece 1173 | iso3: GRC 1174 | iso: GR 1175 | iso_name: GREECE 1176 | id: "77" 1177 | numcode: "300" 1178 | countries_159: 1179 | name: Peru 1180 | iso3: PER 1181 | iso: PE 1182 | iso_name: PERU 1183 | id: "159" 1184 | numcode: "604" 1185 | countries_185: 1186 | name: Solomon Islands 1187 | iso3: SLB 1188 | iso: SB 1189 | iso_name: SOLOMON ISLANDS 1190 | id: "185" 1191 | numcode: "90" 1192 | countries_078: 1193 | name: Greenland 1194 | iso3: GRL 1195 | iso: GL 1196 | iso_name: GREENLAND 1197 | id: "78" 1198 | numcode: "304" 1199 | countries_186: 1200 | name: Somalia 1201 | iso3: SOM 1202 | iso: SO 1203 | iso_name: SOMALIA 1204 | id: "186" 1205 | numcode: "706" 1206 | countries_079: 1207 | name: Grenada 1208 | iso3: GRD 1209 | iso: GD 1210 | iso_name: GRENADA 1211 | id: "79" 1212 | numcode: "308" 1213 | countries_187: 1214 | name: South Africa 1215 | iso3: ZAF 1216 | iso: ZA 1217 | iso_name: SOUTH AFRICA 1218 | id: "187" 1219 | numcode: "710" 1220 | countries_188: 1221 | name: Spain 1222 | iso3: ESP 1223 | iso: ES 1224 | iso_name: SPAIN 1225 | id: "188" 1226 | numcode: "724" 1227 | countries_189: 1228 | name: Sri Lanka 1229 | iso3: LKA 1230 | iso: LK 1231 | iso_name: SRI LANKA 1232 | id: "189" 1233 | numcode: "144" 1234 | countries_001: 1235 | name: Afghanistan 1236 | iso3: AFG 1237 | iso: AF 1238 | iso_name: AFGHANISTAN 1239 | id: "1" 1240 | numcode: "4" 1241 | countries_002: 1242 | name: Albania 1243 | iso3: ALB 1244 | iso: AL 1245 | iso_name: ALBANIA 1246 | id: "2" 1247 | numcode: "8" 1248 | countries_003: 1249 | name: Algeria 1250 | iso3: DZA 1251 | iso: DZ 1252 | iso_name: ALGERIA 1253 | id: "3" 1254 | numcode: "12" 1255 | countries_110: 1256 | name: Latvia 1257 | iso3: LVA 1258 | iso: LV 1259 | iso_name: LATVIA 1260 | id: "110" 1261 | numcode: "428" 1262 | countries_004: 1263 | name: American Samoa 1264 | iso3: ASM 1265 | iso: AS 1266 | iso_name: AMERICAN SAMOA 1267 | id: "4" 1268 | numcode: "16" 1269 | countries_030: 1270 | name: Bulgaria 1271 | iso3: BGR 1272 | iso: BG 1273 | iso_name: BULGARIA 1274 | id: "30" 1275 | numcode: "100" 1276 | countries_111: 1277 | name: Lebanon 1278 | iso3: LBN 1279 | iso: LB 1280 | iso_name: LEBANON 1281 | id: "111" 1282 | numcode: "422" 1283 | countries_005: 1284 | name: Andorra 1285 | iso3: AND 1286 | iso: AD 1287 | iso_name: ANDORRA 1288 | id: "5" 1289 | numcode: "20" 1290 | countries_031: 1291 | name: Burkina Faso 1292 | iso3: BFA 1293 | iso: BF 1294 | iso_name: BURKINA FASO 1295 | id: "31" 1296 | numcode: "854" 1297 | countries_112: 1298 | name: Lesotho 1299 | iso3: LSO 1300 | iso: LS 1301 | iso_name: LESOTHO 1302 | id: "112" 1303 | numcode: "426" 1304 | countries_006: 1305 | name: Angola 1306 | iso3: AGO 1307 | iso: AO 1308 | iso_name: ANGOLA 1309 | id: "6" 1310 | numcode: "24" 1311 | countries_032: 1312 | name: Burundi 1313 | iso3: BDI 1314 | iso: BI 1315 | iso_name: BURUNDI 1316 | id: "32" 1317 | numcode: "108" 1318 | countries_113: 1319 | name: Liberia 1320 | iso3: LBR 1321 | iso: LR 1322 | iso_name: LIBERIA 1323 | id: "113" 1324 | numcode: "430" 1325 | countries_220: 1326 | name: Virgin Islands, British 1327 | iso3: VGB 1328 | iso: VG 1329 | iso_name: VIRGIN ISLANDS, BRITISH 1330 | id: "220" 1331 | numcode: "92" 1332 | countries_007: 1333 | name: Anguilla 1334 | iso3: AIA 1335 | iso: AI 1336 | iso_name: ANGUILLA 1337 | id: "7" 1338 | numcode: "660" 1339 | countries_033: 1340 | name: Cambodia 1341 | iso3: KHM 1342 | iso: KH 1343 | iso_name: CAMBODIA 1344 | id: "33" 1345 | numcode: "116" 1346 | countries_060: 1347 | name: Equatorial Guinea 1348 | iso3: GNQ 1349 | iso: GQ 1350 | iso_name: EQUATORIAL GUINEA 1351 | id: "60" 1352 | numcode: "226" 1353 | countries_114: 1354 | name: Libyan Arab Jamahiriya 1355 | iso3: LBY 1356 | iso: LY 1357 | iso_name: LIBYAN ARAB JAMAHIRIYA 1358 | id: "114" 1359 | numcode: "434" 1360 | countries_140: 1361 | name: Nauru 1362 | iso3: NRU 1363 | iso: NR 1364 | iso_name: NAURU 1365 | id: "140" 1366 | numcode: "520" 1367 | countries_221: 1368 | name: Virgin Islands, U.S. 1369 | iso3: VIR 1370 | iso: VI 1371 | iso_name: VIRGIN ISLANDS, U.S. 1372 | id: "221" 1373 | numcode: "850" 1374 | countries_008: 1375 | name: Antigua and Barbuda 1376 | iso3: ATG 1377 | iso: AG 1378 | iso_name: ANTIGUA AND BARBUDA 1379 | id: "8" 1380 | numcode: "28" 1381 | countries_034: 1382 | name: Cameroon 1383 | iso3: CMR 1384 | iso: CM 1385 | iso_name: CAMEROON 1386 | id: "34" 1387 | numcode: "120" 1388 | countries_115: 1389 | name: Liechtenstein 1390 | iso3: LIE 1391 | iso: LI 1392 | iso_name: LIECHTENSTEIN 1393 | id: "115" 1394 | numcode: "438" 1395 | countries_141: 1396 | name: Nepal 1397 | iso3: NPL 1398 | iso: NP 1399 | iso_name: NEPAL 1400 | id: "141" 1401 | numcode: "524" 1402 | countries_222: 1403 | name: Wallis and Futuna 1404 | iso3: WLF 1405 | iso: WF 1406 | iso_name: WALLIS AND FUTUNA 1407 | id: "222" 1408 | numcode: "876" 1409 | countries_223: 1410 | name: Western Sahara 1411 | iso3: ESH 1412 | iso: EH 1413 | iso_name: WESTERN SAHARA 1414 | id: "223" 1415 | numcode: "732" 1416 | countries_009: 1417 | name: Argentina 1418 | iso3: ARG 1419 | iso: AR 1420 | iso_name: ARGENTINA 1421 | id: "9" 1422 | numcode: "32" 1423 | countries_035: 1424 | name: Canada 1425 | iso3: CAN 1426 | iso: CA 1427 | iso_name: CANADA 1428 | id: "35" 1429 | numcode: "124" 1430 | countries_061: 1431 | name: Eritrea 1432 | iso3: ERI 1433 | iso: ER 1434 | iso_name: ERITREA 1435 | id: "61" 1436 | numcode: "232" 1437 | countries_116: 1438 | name: Lithuania 1439 | iso3: LTU 1440 | iso: LT 1441 | iso_name: LITHUANIA 1442 | id: "116" 1443 | numcode: "440" 1444 | countries_142: 1445 | name: Netherlands 1446 | iso3: NLD 1447 | iso: NL 1448 | iso_name: NETHERLANDS 1449 | id: "142" 1450 | numcode: "528" 1451 | countries_224: 1452 | name: Yemen 1453 | iso3: YEM 1454 | iso: YE 1455 | iso_name: YEMEN 1456 | id: "224" 1457 | numcode: "887" 1458 | countries_036: 1459 | name: Cape Verde 1460 | iso3: CPV 1461 | iso: CV 1462 | iso_name: CAPE VERDE 1463 | id: "36" 1464 | numcode: "132" 1465 | countries_062: 1466 | name: Estonia 1467 | iso3: EST 1468 | iso: EE 1469 | iso_name: ESTONIA 1470 | id: "62" 1471 | numcode: "233" 1472 | countries_117: 1473 | name: Luxembourg 1474 | iso3: LUX 1475 | iso: LU 1476 | iso_name: LUXEMBOURG 1477 | id: "117" 1478 | numcode: "442" 1479 | countries_143: 1480 | name: Netherlands Antilles 1481 | iso3: ANT 1482 | iso: AN 1483 | iso_name: NETHERLANDS ANTILLES 1484 | id: "143" 1485 | numcode: "530" 1486 | countries_170: 1487 | name: Saint Helena 1488 | iso3: SHN 1489 | iso: SH 1490 | iso_name: SAINT HELENA 1491 | id: "170" 1492 | numcode: "654" 1493 | countries_225: 1494 | name: Zambia 1495 | iso3: ZMB 1496 | iso: ZM 1497 | iso_name: ZAMBIA 1498 | id: "225" 1499 | numcode: "894" 1500 | countries_037: 1501 | name: Cayman Islands 1502 | iso3: CYM 1503 | iso: KY 1504 | iso_name: CAYMAN ISLANDS 1505 | id: "37" 1506 | numcode: "136" 1507 | countries_063: 1508 | name: Ethiopia 1509 | iso3: ETH 1510 | iso: ET 1511 | iso_name: ETHIOPIA 1512 | id: "63" 1513 | numcode: "231" 1514 | countries_090: 1515 | name: Hungary 1516 | iso3: HUN 1517 | iso: HU 1518 | iso_name: HUNGARY 1519 | id: "90" 1520 | numcode: "348" 1521 | countries_118: 1522 | name: Macao 1523 | iso3: MAC 1524 | iso: MO 1525 | iso_name: MACAO 1526 | id: "118" 1527 | numcode: "446" 1528 | countries_144: 1529 | name: New Caledonia 1530 | iso3: NCL 1531 | iso: NC 1532 | iso_name: NEW CALEDONIA 1533 | id: "144" 1534 | numcode: "540" 1535 | countries_226: 1536 | name: Zimbabwe 1537 | iso3: ZWE 1538 | iso: ZW 1539 | iso_name: ZIMBABWE 1540 | id: "226" 1541 | numcode: "716" 1542 | countries_038: 1543 | name: Central African Republic 1544 | iso3: CAF 1545 | iso: CF 1546 | iso_name: CENTRAL AFRICAN REPUBLIC 1547 | id: "38" 1548 | numcode: "140" 1549 | countries_064: 1550 | name: Falkland Islands (Malvinas) 1551 | iso3: FLK 1552 | iso: FK 1553 | iso_name: FALKLAND ISLANDS (MALVINAS) 1554 | id: "64" 1555 | numcode: "238" 1556 | countries_091: 1557 | name: Iceland 1558 | iso3: ISL 1559 | iso: IS 1560 | iso_name: ICELAND 1561 | id: "91" 1562 | numcode: "352" 1563 | countries_119: 1564 | name: Macedonia 1565 | iso3: MKD 1566 | iso: MK 1567 | iso_name: MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF 1568 | id: "119" 1569 | numcode: "807" 1570 | countries_145: 1571 | name: New Zealand 1572 | iso3: NZL 1573 | iso: NZ 1574 | iso_name: NEW ZEALAND 1575 | id: "145" 1576 | numcode: "554" 1577 | countries_171: 1578 | name: Saint Kitts and Nevis 1579 | iso3: KNA 1580 | iso: KN 1581 | iso_name: SAINT KITTS AND NEVIS 1582 | id: "171" 1583 | numcode: "659" 1584 | --------------------------------------------------------------------------------