shopping_cart 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +100 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/config/shopping_cart_manifest.js +2 -0
  6. data/app/assets/images/shopping_cart/back.png +0 -0
  7. data/app/assets/images/shopping_cart/delete.png +0 -0
  8. data/app/assets/images/shopping_cart/empty_cart.png +0 -0
  9. data/app/assets/images/shopping_cart/refresh.png +0 -0
  10. data/app/assets/javascripts/shopping_cart/application.js +4 -0
  11. data/app/assets/javascripts/shopping_cart/order_items.js.coffee +4 -0
  12. data/app/assets/stylesheets/shopping_cart/application.scss +3 -0
  13. data/app/assets/stylesheets/shopping_cart/order_items.scss +7 -0
  14. data/app/controllers/shopping_cart/application_controller.rb +23 -0
  15. data/app/controllers/shopping_cart/checkout_controller.rb +99 -0
  16. data/app/controllers/shopping_cart/order_items_controller.rb +51 -0
  17. data/app/helpers/shopping_cart/application_helper.rb +4 -0
  18. data/app/helpers/shopping_cart/order_items_helper.rb +7 -0
  19. data/app/helpers/shopping_cart/orders_helper.rb +7 -0
  20. data/app/jobs/shopping_cart/application_job.rb +4 -0
  21. data/app/mailers/shopping_cart/application_mailer.rb +6 -0
  22. data/app/models/ability.rb +8 -0
  23. data/app/models/shopping_cart/address.rb +7 -0
  24. data/app/models/shopping_cart/application_record.rb +5 -0
  25. data/app/models/shopping_cart/credit_card.rb +9 -0
  26. data/app/models/shopping_cart/discount.rb +5 -0
  27. data/app/models/shopping_cart/order.rb +47 -0
  28. data/app/models/shopping_cart/order_item.rb +9 -0
  29. data/app/models/shopping_cart/shipping.rb +4 -0
  30. data/app/views/layouts/shopping_cart/application.html.haml +12 -0
  31. data/app/views/layouts/shopping_cart/checkout.html.haml +30 -0
  32. data/app/views/shopping_cart/checkout/complete.html.haml +54 -0
  33. data/app/views/shopping_cart/checkout/index.html.haml +23 -0
  34. data/app/views/shopping_cart/checkout/payment.html.haml +26 -0
  35. data/app/views/shopping_cart/checkout/shipping.html.haml +6 -0
  36. data/app/views/shopping_cart/order_items/index.html.haml +68 -0
  37. data/config/locales/shopping_cart.en.yml +35 -0
  38. data/config/routes.rb +16 -0
  39. data/db/migrate/20160810062129_create_shopping_cart_order_items.rb +11 -0
  40. data/db/migrate/20160810063613_create_shopping_cart_orders.rb +14 -0
  41. data/db/migrate/20160811075244_create_shopping_cart_discounts.rb +10 -0
  42. data/db/migrate/20160815065526_create_shopping_cart_addresses.rb +13 -0
  43. data/db/migrate/20160816093049_create_shopping_cart_shippings.rb +8 -0
  44. data/db/migrate/20160817082741_create_shopping_cart_credit_cards.rb +13 -0
  45. data/lib/generators/shopping_cart/config_generator.rb +24 -0
  46. data/lib/generators/shopping_cart/controller_generator.rb +47 -0
  47. data/lib/generators/shopping_cart/translate_generator.rb +43 -0
  48. data/lib/generators/shopping_cart/views_generator.rb +17 -0
  49. data/lib/shopping_cart.rb +7 -0
  50. data/lib/shopping_cart/engine.rb +15 -0
  51. data/lib/shopping_cart/version.rb +3 -0
  52. data/lib/tasks/shopping_cart_tasks.rake +4 -0
  53. metadata +193 -0
@@ -0,0 +1,5 @@
1
+ module ShoppingCart
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ module ShoppingCart
2
+ class CreditCard < ApplicationRecord
3
+ belongs_to :order
4
+
5
+ validates :first_name, :last_name, :number, :cvv, :expiration_month,
6
+ :expiration_year, presence: true
7
+ validates :cvv, numericality: { only_integer: true }
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ module ShoppingCart
2
+ class Discount < ApplicationRecord
3
+ belongs_to :order
4
+ end
5
+ end
@@ -0,0 +1,47 @@
1
+ module ShoppingCart
2
+ class Order < ApplicationRecord
3
+ include AASM
4
+
5
+ aasm column: :state do
6
+ state :in_progress, initial: true
7
+ state :placed
8
+ state :canceled
9
+
10
+ event :place_order do
11
+ transitions from: :in_progress, to: :placed
12
+ end
13
+
14
+ event :cancel_order do
15
+ transitions from: :in_progress, to: :canceled
16
+ end
17
+ end
18
+
19
+ belongs_to :user, class_name: ShoppingCart.user_class
20
+ has_many :order_items, dependent: :destroy
21
+ has_one :discount, dependent: :destroy
22
+ has_one :address, dependent: :destroy
23
+ has_one :credit_card, dependent: :destroy
24
+ belongs_to :shipping
25
+
26
+ delegate :amount, to: :discount, prefix: true, allow_nil: true
27
+ delegate :price, to: :shipping, prefix: true, allow_nil: true
28
+
29
+ def subtotal_price
30
+ order_items.inject(0) do |sum, item|
31
+ sum + item.quantity * item.price
32
+ end
33
+ end
34
+
35
+ def total_price
36
+ subtotal = subtotal_price
37
+ discount = discount_amount ? subtotal * (discount_amount.to_f / 100) : 0
38
+ shipping_price ||= 0
39
+ subtotal - shipping_price - discount
40
+ end
41
+
42
+ def self.current_order(user)
43
+ Order.find_by(state: 'in_progress', user: user)
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,9 @@
1
+ module ShoppingCart
2
+ class OrderItem < ApplicationRecord
3
+ belongs_to :productable, polymorphic: true
4
+ belongs_to :order, counter_cache: true
5
+
6
+ validates :quantity, presence: true
7
+ validates :quantity, numericality: { only_integer: true }
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ module ShoppingCart
2
+ class Shipping < ApplicationRecord
3
+ end
4
+ end
@@ -0,0 +1,12 @@
1
+ !!!
2
+ %html
3
+ %head
4
+ %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
5
+ %title Shopping cart
6
+ = csrf_meta_tags
7
+ = stylesheet_link_tag "shopping_cart/application", media: "all"
8
+ = javascript_include_tag "shopping_cart/application"
9
+ %body.container
10
+ %p.notice= notice
11
+ %p.alert= alert
12
+ = content_for?(:checkout) ? yield(:checkout) : yield
@@ -0,0 +1,30 @@
1
+ -content_for :checkout do
2
+ %ul.nav.nav-tabs
3
+ %li{class: ('active' if current_page?('/shopping_cart/checkout'))}
4
+ = link_to(t('shopping_cart.address'), '/shopping_cart/checkout')
5
+ - ShoppingCart.order_steps.try(:each) do |step|
6
+ - step = step.to_s
7
+ %li{class: ('active' if current_page?('/shopping_cart/checkout/' + step))}
8
+ = link_to(t(step), '/shopping_cart/checkout/' + step)
9
+ %li{class: ('active' if current_page?('/shopping_cart/checkout/complete'))}
10
+ = link_to(t('shopping_cart.complete'), '/shopping_cart/checkout/complete')
11
+ .row{style: 'padding: 15px'}
12
+ - if current_page?('/shopping_cart/checkout/complete')
13
+ .col-lg-12
14
+ =yield
15
+ - else
16
+ .col-lg-7
17
+ =yield
18
+ .col-lg-4.col-lg-offset-1
19
+ %h3 #{t('shopping_cart.summary')}
20
+ - subtotal = @current_order.subtotal_price
21
+ %strong #{t('shopping_cart.subtotal')}: #{subtotal} #{t('shopping_cart.currency')}
22
+ - if @current_order.discount
23
+ %br/
24
+ %strong #{t('shopping_cart.discount')}: #{calculate_discount(@current_order)} #{t('shopping_cart.currency')}
25
+ - if @current_order.shipping
26
+ %br/
27
+ %strong #{t('shopping_cart.delivery')}: #{@current_order.shipping_price} #{t('shopping_cart.currency')}
28
+ %h4 #{t('shopping_cart.total')}: #{@current_order.total_price} #{t('shopping_cart.currency')}
29
+
30
+ =render template: 'layouts/shopping_cart/application'
@@ -0,0 +1,54 @@
1
+ .row
2
+ .col-lg-3
3
+ %h4 #{t('shopping_cart.address')}
4
+ %div #{t('shopping_cart.first_name')}: #{@current_order.address.first_name}
5
+ %div #{t('shopping_cart.last_name')}: #{@current_order.address.last_name}
6
+ %div #{t('shopping_cart.city')}: #{@current_order.address.city}
7
+ %div #{t('shopping_cart.phone')}: #{@current_order.address.phone}
8
+ %div #{t('shopping_cart.details')}: #{@current_order.address.details}
9
+
10
+ - if ShoppingCart::order_steps && ShoppingCart::order_steps.include?(:shipping)
11
+ .col-lg-3
12
+ %h4 #{t('shopping_cart.delivery')}
13
+ %div #{t('shopping_cart.delivery')}: #{@current_order.shipping.title}
14
+ - if ShoppingCart::order_steps && ShoppingCart::order_steps.include?(:payment)
15
+ .col-lg-3
16
+ %h4 #{t('shopping_cart.payment')}
17
+ %div #{t('shopping_cart.first_name')}: #{@current_order.credit_card.first_name}
18
+ %div #{t('shopping_cart.last_name')}: #{@current_order.credit_card.last_name}
19
+ %div #{t('shopping_cart.card_number')}: #{@current_order.credit_card.number}
20
+
21
+ %table.table
22
+ %thead
23
+ %tr
24
+ %th #{t('shopping_cart.product')}
25
+ %th #{t('shopping_cart.price')}
26
+ %th #{t('shopping_cart.qty')}
27
+ %th #{t('shopping_cart.total')}
28
+ %tbody
29
+ - @current_order.order_items.each do |item|
30
+ %tr
31
+ %td #{item.productable.title}
32
+ %td #{item.price} #{t('shopping_cart.currency')}
33
+ %td #{item.quantity}
34
+ %td #{total_for_item(item)} #{t('shopping_cart.currency')}
35
+ %tr
36
+ %td{colspan: 2}
37
+ %td
38
+ %strong #{t('shopping_cart.subtotal')} :
39
+ %td #{@current_order.subtotal_price} #{t('shopping_cart.currency')}
40
+ %tr
41
+ %td{colspan: 2}
42
+ %td
43
+ %strong #{t('shopping_cart.discount')} :
44
+ %td #{calculate_discount(@current_order)} #{t('shopping_cart.currency')}
45
+ %tr
46
+ %td{colspan: 2}
47
+ %td
48
+ %strong #{t('shopping_cart.total')} :
49
+ %td #{@current_order.total_price} #{t('shopping_cart.currency')}
50
+ .col-lg-12.col-md-12.col-sm-12
51
+ .pull-right.col-lg-offset-1.col-md-offset-1.col-sm-offset-1
52
+ =link_to(t('shopping_cart.place_order'),checkout_index_path , method: :post, class: 'btn btn-success')
53
+ .pull-right
54
+ =link_to(t('shopping_cart.go_shopping'),'/',class: 'btn btn-primary')
@@ -0,0 +1,23 @@
1
+ .row
2
+ = form_for @address, url: add_address_path, method: :post do |f|
3
+ .form-group
4
+ = f.text_field :first_name, class: 'form-control', placeholder: t('shopping_cart.first_name'), required: true
5
+ .form-group
6
+ = f.text_field :last_name, class: 'form-control', placeholder: t('shopping_cart.last_name'), required: true
7
+ .form-group
8
+ = f.text_field :city, class: 'form-control', placeholder: t('shopping_cart.city')
9
+ .form-group
10
+ = f.text_field :phone, class: 'form-control phone', placeholder: t('shopping_cart.phone'), required: true
11
+ .form-group
12
+ = f.text_area :details, class: 'form-control phone', placeholder: t('shopping_cart.details')
13
+ = f.submit nil, class: 'btn btn-primary'
14
+ .row
15
+ .col-lg-12.col-md-12.col-sm-12
16
+ -if @address.errors.any?
17
+ %ul.alert.alert-danger{role: 'alert'}
18
+ -@address.errors.full_messages.each do |msg|
19
+ %li
20
+ %span.glyphicon.glyphicon-exclamation-sign{aria:{hidden: true}}
21
+ =msg
22
+
23
+ .col-lg-4.col-md-4.col-sm-4
@@ -0,0 +1,26 @@
1
+ = form_for @credit_card, url: add_payment_path, method: :post do |f|
2
+ .form-group
3
+ = f.text_field :first_name, class: 'form-control', placeholder: t('shopping_cart.first_name'), required: true
4
+ .form-group
5
+ = f.text_field :last_name, class: 'form-control', placeholder: t('shopping_cart.last_name'), required: true
6
+ .form-group
7
+ =f.text_field :number, class: 'form-control', placeholder: t('shopping_cart.card_number'), required: true
8
+ %div #{t('expire')}:
9
+ .col-lg-3.col-md-3.col-sm-3
10
+ .form-group
11
+ =select_month(Date.today,{}, class: 'form-control', name: 'credit_card[expiration_month]')
12
+ .col-lg-3.col-md-3.col-sm-3
13
+ .form-group
14
+ =select_year(Date.today,{},class: 'form-control', name: 'credit_card[expiration_year]')
15
+ .col-lg-3.col-md-3.col-sm-3
16
+ =f.text_field :cvv, class: 'form-control', placeholder: 'CVV', type: 'password', required: true
17
+ .col-lg-12.col-md-12.col-sm-12
18
+ .form-group
19
+ = f.submit nil, class: 'btn btn-primary'
20
+ .col-lg-12.col-md-12.col-sm-12
21
+ -if @credit_card.errors.any?
22
+ %ul.alert.alert-danger{role: 'alert'}
23
+ - @credit_card.errors.full_messages.each do |msg|
24
+ %li
25
+ %span.glyphicon.glyphicon-exclamation-sign{aria:{hidden: true}}
26
+ =msg
@@ -0,0 +1,6 @@
1
+ = form_tag add_shipping_path, method: :post do
2
+ - @shippings.each do |delivery|
3
+ .form-group
4
+ = radio_button_tag :id, delivery.id, delivery.id == @current_order.shipping.try(:id)
5
+ = label_tag("id_#{delivery.id}", "#{delivery.title} - #{delivery.price} #{t('shopping_cart.currency')}")
6
+ = submit_tag nil, class: 'btn btn-primary'
@@ -0,0 +1,68 @@
1
+ .row
2
+ .col-lg-12
3
+ = image_tag('shopping_cart/back.png')
4
+ =link_to(t('shopping_cart.back'),'/')
5
+ -if !@current_order || @current_order.order_items_count == 0
6
+ .col-lg-6.col-md-6.col-sm-6.col-lg-offset-4.col-md-offset-4.col-sm-offset-4
7
+ %h3 #{t('shopping_cart.back')}
8
+ = image_tag('shopping_cart/empty_cart.png')
9
+ -else
10
+ %table.table
11
+ %thead
12
+ %tr
13
+ %th #{t('shopping_cart.product')}
14
+ %th #{t('shopping_cart.price')}
15
+ %th #{t('shopping_cart.qty')}
16
+ %th #{t('shopping_cart.total')}
17
+ %th
18
+ %tbody
19
+ - @current_order.order_items.each do |item|
20
+ %tr
21
+ %td #{item.productable.title}
22
+ %td #{item.price} #{t('shopping_cart.currency')}
23
+ %td
24
+ = form_tag order_item_path(item), method: 'patch' do
25
+ = text_field_tag :quantity, item.quantity, type: 'number', required: 'true'
26
+ = button_tag image_tag('shopping_cart/refresh.png')
27
+ %td #{total_for_item(item)} #{t('shopping_cart.currency')}
28
+ %td #{link_to(image_tag('shopping_cart/delete.png'), item, method: :delete)}
29
+ %tr
30
+ %td{colspan: 3}
31
+ %td
32
+ %strong #{t('shopping_cart.subtotal')} :
33
+ %td #{@current_order.subtotal_price} #{t('shopping_cart.currency')}
34
+ - if @current_order.discount
35
+ %tr
36
+ %td{colspan: 3}
37
+ %td
38
+ %strong #{t('shopping_cart.discount')} :
39
+ %td #{calculate_discount(@current_order)} #{t('shopping_cart.currency')}
40
+ %tr
41
+ %td{colspan: 3}
42
+ %td
43
+ %strong #{t('shopping_cart.total')} :
44
+ %td #{@current_order.total_price} #{t('shopping_cart.currency')}
45
+ .col-lg-12.col-md-12.col-sm-12
46
+ .col-lg-3.col-md-3.col-sm-3
47
+ =link_to(t('shopping_cart.clear_cart'),clear_cart_path,class: 'btn btn-primary', method: :post)
48
+ .col-lg-3.col-md-3.col-sm-3
49
+ =link_to(t('shopping_cart.go_shopping'),'/',class: 'btn btn-primary')
50
+ .col-lg-3.col-md-3.col-sm-3
51
+ =link_to(t('shopping_cart.coupon'),'',class: 'btn btn-primary', id: 'coupon')
52
+ .col-lg-3.col-md-3.col-sm-3
53
+ =link_to(t('shopping_cart.checkout'),checkout_index_path ,class: 'btn btn-success')
54
+
55
+ = form_tag(discount_path, method: :post, id: 'coupon_form')
56
+ #add_couponModal.modal.fade{role: 'dialog', tabindex: '-1'}
57
+ .modal-dialog
58
+ .modal-content
59
+ .modal-header
60
+ %button.close{"aria-label" => "Close", "data-dismiss" => "modal", type: "button"}
61
+ %span{"aria-hidden" => "true"} &times;
62
+ %h4.modal-title #{t('shopping_cart.code')}
63
+ .modal-body
64
+ =text_field_tag 'code', '', required: true
65
+ %i.error_message
66
+ .modal-footer
67
+ %button.btn.btn-default{"data-dismiss" => "modal", type: "button"} #{t('shopping_cart.close')}
68
+ %button.btn.btn-primary #{t('shopping_cart.send')}
@@ -0,0 +1,35 @@
1
+ en:
2
+ shopping_cart:
3
+ currency: $
4
+ back: Back
5
+ empty_cart: Your cart is empty
6
+ product: Product
7
+ price: Price
8
+ qty: Quantity
9
+ total: Total
10
+ subtotal: Subtotal
11
+ discount: Discount
12
+ clear_cart: Clear your cart
13
+ go_shopping: Proceed shopping
14
+ coupon: Discount coupon
15
+ checkout: Checkout
16
+ address: Address
17
+ complete: Complete
18
+ code: Coupon code
19
+ close: Close
20
+ send: Send
21
+ delivery: Delivery
22
+ summary: Order summary
23
+ place_order: Place order
24
+ first_name: First name
25
+ last_name: Last name
26
+ city: City
27
+ phone: Phone
28
+ details: Details
29
+ payment: Payment
30
+ card_number: Card number
31
+ item_added: You added item to cart
32
+ choose_shipping: You need to choose shipping method
33
+ cart_updated: You updated a cart
34
+ valid_coupon: Your discount is %{amount}% from cart total price
35
+ order_placed: Congratulations! Yout order placed successfully. Will get in touch asap.
data/config/routes.rb ADDED
@@ -0,0 +1,16 @@
1
+ ShoppingCart::Engine.routes.draw do
2
+ root 'order_items#index'
3
+ resources :order_items
4
+ resources :checkout, only: [:index, :create]
5
+ scope :checkout do
6
+ ShoppingCart::order_steps.try(:each) do |step|
7
+ next unless [:payment, :shipping].include?(step)
8
+ get "/#{step}", to: "checkout##{step}"
9
+ post "/add_#{step}", to: "checkout#add_#{step}"
10
+ end
11
+ get '/complete', to: 'checkout#complete', as: 'complete'
12
+ post '/add_address', to: 'checkout#add_address', as: 'add_address'
13
+ end
14
+ post 'clear', to: 'order_items#destroy_items', as: 'clear_cart'
15
+ post 'discount', to: 'order_items#discount', as: 'discount'
16
+ end
@@ -0,0 +1,11 @@
1
+ class CreateShoppingCartOrderItems < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :shopping_cart_order_items do |t|
4
+ t.integer :quantity
5
+ t.decimal :price, precision: 5, scale: 2
6
+ t.references :productable, polymorphic: true, index: {name: 'productable'}
7
+ t.belongs_to :order, index: true
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ class CreateShoppingCartOrders < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :shopping_cart_orders do |t|
4
+ t.decimal :total_price, precision: 7, scale: 2
5
+ t.string :state, default: 'in_progress'
6
+ t.integer :step
7
+ t.belongs_to :user, index: true
8
+ t.belongs_to :shipping, index: true
9
+ t.integer :order_items_count
10
+ t.timestamp :completed_at
11
+ t.timestamps
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ class CreateShoppingCartDiscounts < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :shopping_cart_discounts do |t|
4
+ t.string :code, index: true, null: false
5
+ t.integer :amount, default: 0
6
+ t.belongs_to :order, index: true
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ class CreateShoppingCartAddresses < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :shopping_cart_addresses do |t|
4
+ t.string :first_name
5
+ t.string :last_name
6
+ t.string :city
7
+ t.string :phone
8
+ t.belongs_to :order, index: true
9
+ t.text :details
10
+ t.timestamps
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ class CreateShoppingCartShippings < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :shopping_cart_shippings do |t|
4
+ t.string :title
5
+ t.decimal :price, precision: 5, scale: 2
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ class CreateShoppingCartCreditCards < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :shopping_cart_credit_cards do |t|
4
+ t.string :number
5
+ t.integer :cvv
6
+ t.integer :expiration_month
7
+ t.integer :expiration_year
8
+ t.string :first_name
9
+ t.string :last_name
10
+ t.belongs_to :order, index: true
11
+ end
12
+ end
13
+ end