stall 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +7 -0
  4. data/Rakefile +22 -0
  5. data/app/assets/javascripts/stall.coffee +12 -0
  6. data/app/assets/javascripts/stall/add-to-cart-form.coffee +22 -0
  7. data/app/assets/javascripts/stall/application.js +13 -0
  8. data/app/assets/javascripts/stall/carts.js +2 -0
  9. data/app/assets/stylesheets/stall/application.css +15 -0
  10. data/app/assets/stylesheets/stall/carts.css +4 -0
  11. data/app/controllers/stall/application_controller.rb +18 -0
  12. data/app/controllers/stall/carts_controller.rb +28 -0
  13. data/app/controllers/stall/checkout/steps_controller.rb +32 -0
  14. data/app/controllers/stall/checkouts_controller.rb +9 -0
  15. data/app/controllers/stall/line_items_controller.rb +26 -0
  16. data/app/helpers/stall/add_to_cart_helper.rb +10 -0
  17. data/app/helpers/stall/application_helper.rb +4 -0
  18. data/app/helpers/stall/cart_helper.rb +28 -0
  19. data/app/helpers/stall/checkout_helper.rb +7 -0
  20. data/app/models/stall.rb +5 -0
  21. data/app/models/stall/address.rb +5 -0
  22. data/app/models/stall/address_ownership.rb +15 -0
  23. data/app/models/stall/cart.rb +14 -0
  24. data/app/models/stall/customer.rb +5 -0
  25. data/app/models/stall/line_item.rb +45 -0
  26. data/app/models/stall/product_list.rb +56 -0
  27. data/app/models/stall/shipment.rb +15 -0
  28. data/app/models/stall/shipping_method.rb +5 -0
  29. data/app/services/stall/add_to_cart_service.rb +52 -0
  30. data/app/services/stall/base_service.rb +4 -0
  31. data/app/services/stall/shipping_fee_calculator_service.rb +21 -0
  32. data/app/views/checkout/steps/_informations.html.haml +41 -0
  33. data/app/views/checkout/steps/_payment_method.html.haml +1 -0
  34. data/app/views/checkout/steps/_shipping_method.html.haml +10 -0
  35. data/app/views/layouts/stall/application.html.erb +15 -0
  36. data/app/views/stall/carts/show.html.haml +23 -0
  37. data/app/views/stall/checkout/steps/show.html.haml +1 -0
  38. data/app/views/stall/line_items/_add_error.html.haml +17 -0
  39. data/app/views/stall/line_items/_added.html.haml +23 -0
  40. data/app/views/stall/line_items/_form.html.haml +12 -0
  41. data/config/locales/stall.en.yml +60 -0
  42. data/config/locales/stall.fr.yml +60 -0
  43. data/db/migrate/20151103154313_create_stall_line_items.rb +19 -0
  44. data/db/migrate/20160118121116_create_stall_product_lists.rb +15 -0
  45. data/db/migrate/20160118124016_create_customers.rb +11 -0
  46. data/db/migrate/20160122143746_create_stall_addresses.rb +17 -0
  47. data/db/migrate/20160122143748_create_stall_address_ownerships.rb +17 -0
  48. data/db/migrate/20160124014144_create_stall_shipping_methods.rb +10 -0
  49. data/db/migrate/20160124020313_create_stall_shipments.rb +17 -0
  50. data/lib/generators/stall/checkout/step/step_generator.rb +20 -0
  51. data/lib/generators/stall/checkout/step/templates/step.rb.erb +2 -0
  52. data/lib/generators/stall/checkout/wizard/templates/wizard.rb.erb +3 -0
  53. data/lib/generators/stall/checkout/wizard/wizard_generator.rb +20 -0
  54. data/lib/generators/stall/install/install_generator.rb +17 -0
  55. data/lib/generators/stall/install/templates/initializer.rb +47 -0
  56. data/lib/stall.rb +38 -0
  57. data/lib/stall/addressable.rb +44 -0
  58. data/lib/stall/checkout.rb +15 -0
  59. data/lib/stall/checkout/informations_checkout_step.rb +41 -0
  60. data/lib/stall/checkout/payment_checkout_step.rb +6 -0
  61. data/lib/stall/checkout/payment_method_checkout_step.rb +6 -0
  62. data/lib/stall/checkout/shipping_method_checkout_step.rb +20 -0
  63. data/lib/stall/checkout/step.rb +47 -0
  64. data/lib/stall/checkout/wizard.rb +85 -0
  65. data/lib/stall/config.rb +27 -0
  66. data/lib/stall/engine.rb +30 -0
  67. data/lib/stall/rails/currency_helper.rb +26 -0
  68. data/lib/stall/rails/routing_mapper.rb +13 -0
  69. data/lib/stall/routes.rb +27 -0
  70. data/lib/stall/sellable.rb +8 -0
  71. data/lib/stall/sellable/mixin.rb +17 -0
  72. data/lib/stall/sellable/model.rb +46 -0
  73. data/lib/stall/shipping.rb +16 -0
  74. data/lib/stall/shipping/calculator.rb +28 -0
  75. data/lib/stall/shipping/config.rb +17 -0
  76. data/lib/stall/shipping/country_weight_table_calculator.rb +69 -0
  77. data/lib/stall/shipping/free_shipping_calculator.rb +23 -0
  78. data/lib/stall/utils.rb +22 -0
  79. data/lib/stall/utils/config_dsl.rb +19 -0
  80. data/lib/stall/version.rb +3 -0
  81. data/lib/tasks/stall_tasks.rake +4 -0
  82. metadata +362 -0
@@ -0,0 +1,15 @@
1
+ module Stall
2
+ class Shipment < ActiveRecord::Base
3
+ belongs_to :cart
4
+ belongs_to :shipping_method
5
+
6
+ monetize :eot_price_cents, :price_cents,
7
+ with_model_currency: :currency, allow_nil: true
8
+
9
+ validates :cart, presence: true
10
+
11
+ def currency
12
+ cart.try(:currency) || Money.default_currency
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ module Stall
2
+ class ShippingMethod < ActiveRecord::Base
3
+ has_many :shipments, dependent: :nullify
4
+ end
5
+ end
@@ -0,0 +1,52 @@
1
+ module Stall
2
+ class AddToCartService < Stall::BaseService
3
+ attr_reader :cart, :params
4
+
5
+ def initialize(cart, params)
6
+ @cart = cart
7
+ @params = params
8
+ end
9
+
10
+ def call
11
+ return false unless line_item.valid?
12
+
13
+ unless assemble_identical_line_items
14
+ cart.line_items << line_item
15
+ end
16
+
17
+ cart.save
18
+ end
19
+
20
+ def line_item
21
+ @line_item ||= sellable.to_line_item.tap do |line_item|
22
+ line_item.quantity = params[:quantity]
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def assemble_identical_line_items
29
+ # Find an existing line item which is like our new one
30
+ existing_line_item = cart.line_items.find do |li|
31
+ line_item.like?(li)
32
+ end
33
+
34
+ # If we found one, we assemble both line items into the old one, to avoid
35
+ # duplicating the same sellables in the product list
36
+ if existing_line_item
37
+ existing_line_item.quantity += line_item.quantity
38
+ @line_item = existing_line_item
39
+ else
40
+ false
41
+ end
42
+ end
43
+
44
+ def sellable
45
+ @sellable ||= sellable_class.find(params[:sellable_id])
46
+ end
47
+
48
+ def sellable_class
49
+ @sellable_class ||= params[:sellable_type].camelize.constantize
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,4 @@
1
+ module Stall
2
+ class BaseService
3
+ end
4
+ end
@@ -0,0 +1,21 @@
1
+ module Stall
2
+ class ShippingFeeCalculatorService < Stall::BaseService
3
+ attr_reader :cart
4
+
5
+ def initialize(cart)
6
+ @cart = cart
7
+ end
8
+
9
+ def call
10
+ return unless cart.shipment && cart.shipment.shipping_method
11
+
12
+ calculator_identifier = cart.shipment.shipping_method.identifier
13
+ calculator_class = Stall::Shipping.calculators[calculator_identifier]
14
+
15
+ if calculator_class
16
+ calculator = calculator_class.new(cart, cart.shipment.shipping_method)
17
+ cart.shipment.update_attributes(price: calculator.price)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,41 @@
1
+ %h1= t('stall.checkout.informations.title')
2
+
3
+ = simple_form_for cart, as: :cart, url: step_path(cart) do |form|
4
+ = form.fields_for :customer do |customer_form|
5
+ = customer_form.input :email
6
+
7
+ = form.fields_for :address_ownerships, [cart.address_ownership_for(:shipping)] do |ownership_form|
8
+ %fieldset{ data: { :'address-form' => :shipping } }
9
+ = ownership_form.hidden_field :shipping
10
+
11
+ = ownership_form.fields_for :address do |address_form|
12
+ = address_form.input :civility
13
+ = address_form.input :first_name
14
+ = address_form.input :last_name
15
+ = address_form.input :address
16
+ = address_form.input :address_details
17
+ = address_form.input :zip
18
+ = address_form.input :city
19
+ = address_form.input :country
20
+ = address_form.input :phone
21
+
22
+ = form.input :use_another_address_for_billing do
23
+ = check_box_tag :use_another_address_for_billing, '1', params[:use_another_address_for_billing]
24
+
25
+ = form.fields_for :address_ownerships, [cart.address_ownership_for(:billing)] do |ownership_form|
26
+ %fieldset{ data: { :'address-form' => :billing } }
27
+ = ownership_form.hidden_field :billing
28
+
29
+ = ownership_form.fields_for :address do |address_form|
30
+ = address_form.input :civility
31
+ = address_form.input :first_name
32
+ = address_form.input :last_name
33
+ = address_form.input :address
34
+ = address_form.input :address_details
35
+ = address_form.input :zip
36
+ = address_form.input :city
37
+ = address_form.input :country
38
+ = address_form.input :phone
39
+
40
+ %button.btn.btn-primary{ type: 'submit' }
41
+ = t('stall.checkout.informations.validate')
@@ -0,0 +1 @@
1
+ %h1= t('stall.checkout.payment_method.title')
@@ -0,0 +1,10 @@
1
+ %h1= t('stall.checkout.shipping_method.title')
2
+
3
+ = simple_form_for cart, as: :cart, url: step_path(cart) do |form|
4
+ = form.fields_for :shipment do |shipment_fields|
5
+ = shipment_fields.association :shipping_method, as: :radio_buttons
6
+
7
+ .form-actions
8
+ %button.btn.btn-primary{ type: 'submit' }
9
+ = t('stall.checkout.shipping_method.validate')
10
+
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Stall</title>
5
+ <meta charset="utf-8">
6
+ <%= stylesheet_link_tag "stall/application", media: "all" %>
7
+ <%= javascript_include_tag "stall/application" %>
8
+ <%= csrf_meta_tags %>
9
+ </head>
10
+ <body>
11
+
12
+ <%= yield %>
13
+
14
+ </body>
15
+ </html>
@@ -0,0 +1,23 @@
1
+ .cart-recap{ data: { :'cart-recap' => true } }
2
+ = simple_form_for @cart, url: cart_path(@cart), as: :cart do |form|
3
+ %table.table.table-striped
4
+ %thead
5
+ %tr
6
+ %th= Stall::LineItem.human_attribute_name(:name)
7
+ %th= Stall::LineItem.human_attribute_name(:unit_price)
8
+ %th= Stall::LineItem.human_attribute_name(:quantity)
9
+ %th= Stall::LineItem.human_attribute_name(:price)
10
+ %tbody
11
+ = form.fields_for :line_items do |line_item_fields|
12
+ %tr{ data: { :'line-item-id' => line_item_fields.object.id } }
13
+ %td= line_item_fields.object.name
14
+ %td= line_item_fields.object.unit_price
15
+ %td= line_item_fields.input_field :quantity, class: 'form-control', data: { :'quantity-field' => true }
16
+ %td= line_item_fields.object.price
17
+
18
+ .form-actions
19
+ %button.btn.btn-default{ type: 'submit' }
20
+ = t('stall.carts.recap.update')
21
+
22
+ = link_to checkout_path(@cart), class: 'btn btn-primary' do
23
+ = t('stall.carts.recap.validate')
@@ -0,0 +1 @@
1
+ = render partial: "checkout/steps/#{ @wizard.current_step_name }", locals: { wizard: @wizard, cart: @cart }
@@ -0,0 +1,17 @@
1
+ .modal.danger.fade{ role: 'dialog', tabindex: '-1', data: { :'line-item' => @line_item.as_json(only: [:name, :quantity, :eot_price, :price, :vat_rate]) } }
2
+ .modal-dialog
3
+ .modal-content
4
+ .modal-header
5
+ %button.close{ type: 'button', :'aria-label' => 'Close', data: { dismiss: 'modal' } }
6
+ %span{ :'aria-hidden' => 'true' }
7
+ ×
8
+ %h4.modal-title
9
+ = t('stall.line_items.add_error.title')
10
+
11
+ .modal-body
12
+ %p.text-danger
13
+ = t('stall.line_items.add_error.description')
14
+
15
+ .modal-footer
16
+ %button.btn.btn-default{ type: 'button', data: { dismiss: 'modal' } }
17
+ = t('stall.shared.close')
@@ -0,0 +1,23 @@
1
+ .modal.fade{ role: 'dialog', tabindex: '-1', data: { :'cart' => { :'total-quantity' => current_cart.total_quantity } } }
2
+ .modal-dialog
3
+ .modal-content
4
+ .modal-header
5
+ %button.close{ type: 'button', :'aria-label' => 'Close', data: { dismiss: 'modal' } }
6
+ %span{ :'aria-hidden' => 'true' }
7
+ ×
8
+ %h4.modal-title
9
+ = t('stall.line_items.added.title')
10
+
11
+ .modal-body
12
+ = @line_item.name
13
+ x
14
+ = @quantity
15
+ \=
16
+ = number_to_currency(@line_item.unit_price * @quantity)
17
+
18
+ .modal-footer
19
+ %button.btn.btn-default{ type: 'button', data: { dismiss: 'modal' } }
20
+ = t('stall.line_items.added.continue_shopping')
21
+
22
+ = link_to cart_path(@cart), class: 'btn btn-primary' do
23
+ = t('stall.line_items.added.view_cart')
@@ -0,0 +1,12 @@
1
+ = simple_form_for line_item, as: :line_item, url: cart_line_items_path(cart), remote: true, data: { :'add-to-cart-form' => true } do |form|
2
+ = form.hidden_field :sellable_type
3
+ = form.hidden_field :sellable_id
4
+
5
+ .input-group
6
+ = form.input_field :quantity, spinner: false, value: 1, class: 'form-group'
7
+
8
+ %span.input-group-btn
9
+ %button.btn.btn-primary{ type: 'submit', style: 'font-size: 12px' }
10
+ %i.fa.fa-shopping-cart
11
+ = t('stall.line_items.form.add_to_cart')
12
+
@@ -0,0 +1,60 @@
1
+ en:
2
+ stall:
3
+ shared:
4
+ close: "Close"
5
+
6
+ carts:
7
+ flashes:
8
+ update:
9
+ success: "Your cart has successfully been updated"
10
+ error: "Your cart could not be updated, please check that all the fields are correctly filled"
11
+ recap:
12
+ update: "Update the cart"
13
+ validate: "Proceed to order"
14
+
15
+ line_items:
16
+ form:
17
+ add_to_cart: "Add to cart"
18
+ added:
19
+ title: "Votre produit a bien été ajouté à votre panier"
20
+ continue_shopping: "Continuer mes achats"
21
+ view_cart: "Voir mon panier"
22
+ add_error:
23
+ title: "Votre produit n'a pu être ajouté"
24
+ description: "Merci de vérifier que vous avez bien rempli la quantité avant d'ajouter le produit au panier."
25
+
26
+ checkout:
27
+ informations:
28
+ title: "Vos informations"
29
+ validate: "Valider mes adresses"
30
+ error: "Your informations could not be saved, please check that all the fields are correctly filled"
31
+ shipping_method:
32
+ title: "Mode de livraison"
33
+ validate: "Valider mon mode de livraison"
34
+ payment_method:
35
+ title: "Mode de paiement"
36
+ validate: "Valider mon mode de paiement"
37
+ payment:
38
+ title: "Paiement"
39
+
40
+ activerecord:
41
+ attributes:
42
+ stall/customer:
43
+ email: "E-mail address"
44
+ stall/line_item:
45
+ name: "Produit"
46
+ unit_price: "Prix unitaire"
47
+ unit_eot_price: "Prix unitaire HT"
48
+ price: "Prix"
49
+ eot_price: "Prix HT"
50
+ quantity: "Quantité"
51
+ stall/address:
52
+ civility: "Civility"
53
+ first_name: "First name"
54
+ last_name: "Last name"
55
+ address: "Adress"
56
+ address_details: "Address 2"
57
+ zip: "Zip code"
58
+ city: "City"
59
+ country: "Country"
60
+ phone: "Phone"
@@ -0,0 +1,60 @@
1
+ fr:
2
+ stall:
3
+ shared:
4
+ close: "Fermer"
5
+
6
+ carts:
7
+ flashes:
8
+ update:
9
+ success: "Votre panier a bien été mis à jour."
10
+ error: "Votre panier n'a pu être mis à jour, merci de vérifier les champs."
11
+ recap:
12
+ update: "Mettre à jour le panier"
13
+ validate: "Passer la commande"
14
+
15
+ line_items:
16
+ form:
17
+ add_to_cart: "Ajouter au panier"
18
+ added:
19
+ title: "Votre produit a bien été ajouté à votre panier"
20
+ continue_shopping: "Continuer mes achats"
21
+ view_cart: "Voir mon panier"
22
+ add_error:
23
+ title: "Votre produit n'a pu être ajouté"
24
+ description: "Merci de vérifier que vous avez bien rempli la quantité avant d'ajouter le produit au panier."
25
+
26
+ checkout:
27
+ informations:
28
+ title: "Vos informations"
29
+ validate: "Valider mes adresses"
30
+ error: "Vos informations n'ont pu être enregistrées, merci de vérifier les champs"
31
+ shipping_method:
32
+ title: "Mode de livraison"
33
+ validate: "Valider mon mode de livraison"
34
+ payment_method:
35
+ title: "Mode de paiement"
36
+ validate: "Valider mon mode de paiement"
37
+ payment:
38
+ title: "Paiement"
39
+
40
+ activerecord:
41
+ attributes:
42
+ stall/line_item:
43
+ name: "Produit"
44
+ unit_price: "Prix unitaire"
45
+ unit_eot_price: "Prix unitaire HT"
46
+ price: "Prix"
47
+ eot_price: "Prix HT"
48
+ quantity: "Quantité"
49
+ stall/customer:
50
+ email: "Adresse e-mail"
51
+ stall/address:
52
+ civility: "Civilité"
53
+ first_name: "Prénom"
54
+ last_name: "Nom"
55
+ address: "Adresse"
56
+ address_details: "Complément d'adresse"
57
+ zip: "Code postal"
58
+ city: "Ville"
59
+ country: "Pays"
60
+ phone: "Téléphone"
@@ -0,0 +1,19 @@
1
+ class CreateStallLineItems < ActiveRecord::Migration
2
+ def change
3
+ create_table :stall_line_items do |t|
4
+ t.references :sellable, polymorphic: true, index: true
5
+ t.references :product_list, index: true
6
+
7
+ t.string :name, null: false
8
+ t.integer :quantity, null: false
9
+ t.monetize :unit_eot_price, null: false, currency: { present: false }
10
+ t.monetize :unit_price, null: false, currency: { present: false }
11
+ t.monetize :eot_price, null: false, currency: { present: false }
12
+ t.monetize :price, null: false, currency: { present: false }
13
+ t.decimal :vat_rate, null: false
14
+ t.json :data
15
+
16
+ t.timestamps null: false
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ class CreateStallProductLists < ActiveRecord::Migration
2
+ def change
3
+ create_table :stall_product_lists do |t|
4
+ t.string :state, null: false
5
+ t.string :type, null: false
6
+ t.string :currency, null: false
7
+ t.references :customer, index: true
8
+ t.string :token, null: false
9
+
10
+ t.timestamps null: false
11
+ end
12
+
13
+ add_foreign_key :stall_line_items, :stall_product_lists, column: :product_list_id
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ class CreateCustomers < ActiveRecord::Migration
2
+ def change
3
+ create_table :stall_customers do |t|
4
+ t.string :email
5
+
6
+ t.timestamps null: false
7
+ end
8
+
9
+ add_foreign_key :stall_product_lists, :stall_customers, column: :customer_id
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ class CreateStallAddresses < ActiveRecord::Migration
2
+ def change
3
+ create_table :stall_addresses do |t|
4
+ t.integer :civility
5
+ t.string :first_name
6
+ t.string :last_name
7
+ t.text :address
8
+ t.text :address_details
9
+ t.string :zip
10
+ t.string :city
11
+ t.string :country
12
+ t.string :phone
13
+
14
+ t.timestamps null: false
15
+ end
16
+ end
17
+ end