cartify 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +69 -0
  4. data/Rakefile +32 -0
  5. data/app/assets/config/cartify_manifest.js +2 -0
  6. data/app/assets/javascripts/cartify/application.js +14 -0
  7. data/app/assets/javascripts/cartify/checkout.js +11 -0
  8. data/app/assets/javascripts/cartify/masks.js +10 -0
  9. data/app/assets/javascripts/cartify/orders.js +7 -0
  10. data/app/assets/stylesheets/cartify/application.css +15 -0
  11. data/app/concerns/cartify/authenticatable.rb +17 -0
  12. data/app/concerns/cartify/current_session.rb +52 -0
  13. data/app/concerns/cartify/showable.rb +41 -0
  14. data/app/concerns/cartify/updatable.rb +39 -0
  15. data/app/controllers/cartify/addresses_controller.rb +18 -0
  16. data/app/controllers/cartify/application_controller.rb +6 -0
  17. data/app/controllers/cartify/carts_controller.rb +20 -0
  18. data/app/controllers/cartify/checkout_controller.rb +29 -0
  19. data/app/controllers/cartify/order_items_controller.rb +35 -0
  20. data/app/controllers/cartify/orders_controller.rb +17 -0
  21. data/app/decorator/cartify/order_decorator.rb +39 -0
  22. data/app/forms/cartify/addresses_form.rb +71 -0
  23. data/app/helpers/cartify/application_helper.rb +18 -0
  24. data/app/helpers/cartify/carts_helper.rb +10 -0
  25. data/app/helpers/cartify/checkout_helper.rb +22 -0
  26. data/app/helpers/cartify/order_helper.rb +12 -0
  27. data/app/models/cartify/address.rb +23 -0
  28. data/app/models/cartify/application_record.rb +5 -0
  29. data/app/models/cartify/billing.rb +4 -0
  30. data/app/models/cartify/coupon.rb +10 -0
  31. data/app/models/cartify/credit_card.rb +25 -0
  32. data/app/models/cartify/delivery.rb +8 -0
  33. data/app/models/cartify/order.rb +64 -0
  34. data/app/models/cartify/order_item.rb +39 -0
  35. data/app/models/cartify/order_status.rb +18 -0
  36. data/app/models/cartify/shipping.rb +4 -0
  37. data/app/queries/cartify/orders_query.rb +17 -0
  38. data/app/views/cartify/addresses/index.haml +9 -0
  39. data/app/views/cartify/carts/_order_items.haml +17 -0
  40. data/app/views/cartify/carts/_order_summary.haml +15 -0
  41. data/app/views/cartify/carts/_quantity.haml +7 -0
  42. data/app/views/cartify/carts/_xs_order_items.haml +24 -0
  43. data/app/views/cartify/carts/show.html.haml +22 -0
  44. data/app/views/cartify/checkout/_order_summary.haml +4 -0
  45. data/app/views/cartify/checkout/_progress.haml +21 -0
  46. data/app/views/cartify/checkout/addresses.haml +35 -0
  47. data/app/views/cartify/checkout/complete.haml +19 -0
  48. data/app/views/cartify/checkout/confirm.haml +27 -0
  49. data/app/views/cartify/checkout/delivery.haml +11 -0
  50. data/app/views/cartify/checkout/login.haml +39 -0
  51. data/app/views/cartify/checkout/partials/_addresses_short.haml +12 -0
  52. data/app/views/cartify/checkout/partials/_delivery-lg.haml +22 -0
  53. data/app/views/cartify/checkout/partials/_delivery-xs.haml +22 -0
  54. data/app/views/cartify/checkout/partials/_list_of_items.haml +54 -0
  55. data/app/views/cartify/checkout/payment.haml +27 -0
  56. data/app/views/cartify/order_items/create.js.erb +5 -0
  57. data/app/views/cartify/order_items/destroy.js.erb +3 -0
  58. data/app/views/cartify/order_items/update.js.erb +3 -0
  59. data/app/views/cartify/orders/_filters.haml +16 -0
  60. data/app/views/cartify/orders/_orders-lg.haml +10 -0
  61. data/app/views/cartify/orders/_orders-xs.haml +23 -0
  62. data/app/views/cartify/orders/index.haml +21 -0
  63. data/app/views/cartify/orders/show.haml +28 -0
  64. data/app/views/cartify/shared/_address.haml +28 -0
  65. data/app/views/cartify/shared/_addresses_form.haml +31 -0
  66. data/app/views/cartify/shared/_checkout_summary_numbers.haml +15 -0
  67. data/app/views/cartify/shared/_order_summary_numbers.haml +15 -0
  68. data/config/locales/en.yml +135 -0
  69. data/config/routes.rb +9 -0
  70. data/db/migrate/20171005130857_create_cartify_coupons.rb +8 -0
  71. data/db/migrate/20171005131198_create_cartify_credit_cards.rb +12 -0
  72. data/db/migrate/20171005131199_create_cartify_deliveries.rb +9 -0
  73. data/db/migrate/20171005131200_create_cartify_order_statuses.rb +7 -0
  74. data/db/migrate/20171005131201_create_cartify_orders.rb +19 -0
  75. data/db/migrate/20171005131202_create_cartify_order_items.rb +14 -0
  76. data/db/migrate/20171006133037_create_cartify_addresses.rb +17 -0
  77. data/lib/cartify.rb +19 -0
  78. data/lib/cartify/engine.rb +30 -0
  79. data/lib/cartify/version.rb +5 -0
  80. data/lib/generators/initializer/USAGE +49 -0
  81. data/lib/generators/initializer/initializer_generator.rb +17 -0
  82. data/lib/generators/initializer/templates/initializer.rb +10 -0
  83. data/lib/tasks/cartify_tasks.rake +4 -0
  84. metadata +478 -0
@@ -0,0 +1,17 @@
1
+ require_dependency 'cartify/application_controller'
2
+
3
+ module Cartify
4
+ class OrdersController < ApplicationController
5
+ before_action :cartify_authenticate_user!
6
+
7
+ def index
8
+ all_in_one = cartify_current_user.orders.includes(:delivery, :order_status, :order_items)
9
+ @orders = Cartify::OrdersQuery.new(all_in_one).run(params[:filter]).decorate
10
+ end
11
+
12
+ def show
13
+ @order = cartify_current_user.orders.find(params[:id]).decorate
14
+ redirect_to checkout_path(:confirm) if @order.status == t('order.status.in_progress')
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,39 @@
1
+ module Cartify
2
+ class OrderDecorator < Draper::Decorator
3
+ delegate_all
4
+ ORDER_STATUSES = {
5
+ in_progress: I18n.t('order.status.in_progress'),
6
+ in_queue: I18n.t('order.status.in_queue'),
7
+ in_delivery: I18n.t('order.status.in_delivery'),
8
+ delivered: I18n.t('order.status.delivered'),
9
+ canceled: I18n.t('order.status.canceled')
10
+ }.freeze
11
+
12
+ def complited_at
13
+ object.updated_at.strftime('%F')
14
+ end
15
+
16
+ def number
17
+ id = object.id.to_s
18
+ template = 'R0000000'
19
+ template[0..-id.size] + id
20
+ end
21
+
22
+ def status
23
+ ORDER_STATUSES[object.order_status.name.to_sym]
24
+ end
25
+
26
+ def creation_date
27
+ data = object.updated_at
28
+ I18n.t('date.month_names')[data.month] + " #{data.strftime('%d, %Y')}"
29
+ end
30
+
31
+ def sharp_number
32
+ "#{I18n.t('complete.order')} ##{number}"
33
+ end
34
+
35
+ def secret_card_number
36
+ '** ** ** ' + object.credit_card.number.last(4)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,71 @@
1
+ module Cartify
2
+ class AddressesForm
3
+ include ActiveModel::Model
4
+ include Virtus.model
5
+
6
+ attr_reader :params, :target, :use_billing
7
+
8
+ def initialize(params = false)
9
+ @save = false
10
+ @params = params
11
+ @target = Cartify::Order.find_by(id: order_id) ||
12
+ Cartify.user_class.find_by(id: user_id) ||
13
+ Cartify.user_class.new
14
+ end
15
+
16
+ def save
17
+ @save = true
18
+ return false unless valid?
19
+ persist!
20
+ true
21
+ end
22
+
23
+ def errors
24
+ { billing: billing.errors, shipping: shipping.errors }
25
+ end
26
+
27
+ def billing
28
+ fresh_bill = target.addresses.find_or_initialize_by(type: 'Cartify::Billing')
29
+ fresh_bill.assign_attributes(params_for(:billing)) if save?
30
+ @billing ||= fresh_bill
31
+ end
32
+
33
+ def shipping
34
+ fresh_shipp = target.addresses.find_or_initialize_by(type: 'Cartify::Shipping')
35
+ fresh_shipp.assign_attributes(params_for(:shipping)) if save?
36
+ @shipping ||= fresh_shipp
37
+ end
38
+
39
+ private
40
+
41
+ def user_id
42
+ params.fetch(:user_id, false) || (params[:billing][:user_id] if nested?)
43
+ end
44
+
45
+ def order_id
46
+ params.fetch(:order_id, false) || (params[:billing][:order_id] if nested?)
47
+ end
48
+
49
+ def nested?
50
+ params.fetch(:billing, false)
51
+ end
52
+
53
+ def save?
54
+ @save
55
+ end
56
+
57
+ def persist!
58
+ billing.save
59
+ shipping.save
60
+ end
61
+
62
+ def valid?
63
+ billing.valid? && shipping.valid?
64
+ end
65
+
66
+ def params_for(type)
67
+ type = params[:use_billing] == '1' ? :billing : type
68
+ params.require(type).permit(:first_name, :last_name, :address, :city, :zip, :country, :phone, :user_id, :order_id)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,18 @@
1
+ module Cartify
2
+ module ApplicationHelper
3
+ def shop_icon_quantity
4
+ qty = current_order.order_items.reload.collect(&:quantity).compact.sum
5
+ "<span class='shop-quantity'>#{qty}</span>".html_safe unless qty.zero?
6
+ end
7
+
8
+ def active_class(link_path)
9
+ return '' if request.GET.empty?
10
+ link_path.include? request.GET.first.join('=') ? 'active' : ''
11
+ end
12
+
13
+ def country_name(object)
14
+ country = ISO3166::Country[object.country]
15
+ country.translations[I18n.locale.to_s] || country.name
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ module Cartify
2
+ module CartsHelper
3
+ def add_to_cart(product, qqty = 1, name = 'Add to cart')
4
+ link_to name,
5
+ cartify.order_items_path(order_item: { quantity: qqty, product_id: product&.id }),
6
+ method: :post,
7
+ remote: true
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,22 @@
1
+ module Cartify
2
+ module CheckoutHelper
3
+ def active_step?(current_step)
4
+ 'active' if current_step == step
5
+ end
6
+
7
+ def order_summary_text_position
8
+ return 'text-center general-text-right' if right?
9
+ 'general-text-align'
10
+ end
11
+
12
+ def order_summary_table_position
13
+ 'general-summary-table-right general-text-right' if right?
14
+ end
15
+
16
+ private
17
+
18
+ def right?
19
+ %i[confirm complete].include? step
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ module Cartify::OrderHelper
2
+ def order_active_filter
3
+ case request.GET[:filter]
4
+ when 'in_progress' then t('order.status.in_progress')
5
+ when 'in_queue' then t('order.status.in_queue')
6
+ when 'in_delivery' then t('order.status.in_delivery')
7
+ when 'delivered' then t('order.status.delivered')
8
+ when 'canceled' then t('order.status.canceled')
9
+ else t('order.status.all')
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ module Cartify
2
+ class Address < ApplicationRecord
3
+ belongs_to :user, class_name: Cartify.user_class.to_s, optional: true
4
+ belongs_to :order, optional: true
5
+
6
+ scope :shipping, -> { where(type: 'Cartify::Shipping') }
7
+ scope :billing, -> { where(type: 'Cartify::Billing') }
8
+
9
+ validates :first_name, :last_name, :address, :city, :zip, :country, :phone, presence: true
10
+ validates_length_of :first_name, :last_name, :address, :city, :country, maximum: 49
11
+ validates :first_name, :last_name, format: { with: /\A[A-Za-z]{0,49}\z/ }
12
+ validates :city, :country, format: { with: /\A[A-Za-z\s]{0,49}\z/ }
13
+ validates :address, format: { with: /\A[-A-Za-z\s\d,]{0,49}\z/ }
14
+
15
+ validates_length_of :phone, maximum: 15
16
+ validates_length_of :zip, maximum: 10
17
+ before_validation :clear_mask
18
+
19
+ def clear_mask
20
+ phone&.gsub!(/[-\s()]/, '')
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ module Cartify
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ module Cartify
2
+ class Billing < Address
3
+ end
4
+ end
@@ -0,0 +1,10 @@
1
+ module Cartify
2
+ class Coupon < ApplicationRecord
3
+ validates :name, presence: true, uniqueness: true
4
+ validates :value, presence: true
5
+ validates :value, numericality: { only_float: true }
6
+ validates_length_of :name, is: 15
7
+
8
+ has_many :orders
9
+ end
10
+ end
@@ -0,0 +1,25 @@
1
+ module Cartify
2
+ class CreditCard < ApplicationRecord
3
+ validates :number, :name, :mm_yy, :cvv, presence: true
4
+ validates_length_of :cvv, in: 3..4
5
+ validates :cvv, numericality: { only_integer: true }
6
+ validates_format_of :mm_yy, with: %r{\A(0[1-9]|10|11|12)\/\d\d\z}, message: I18n.t('validation.mm_yy')
7
+ validates_format_of :number, with: /\A\d{16}\z/, message: I18n.t('validation.cart_number')
8
+ validates_format_of :name, with: /\A[a-zA-Z\s]{0,49}\z/, message: I18n.t('validation.cart_name')
9
+ before_validation :clear_mask
10
+ has_one :order
11
+
12
+ after_save :connect_to_order
13
+
14
+ private
15
+
16
+ def connect_to_order
17
+ user = CurrentSession.user unless CurrentSession.user.nil?
18
+ user&.orders&.where_status(:in_progress)&.first&.update_attributes(credit_card_id: id)
19
+ end
20
+
21
+ def clear_mask
22
+ number&.gsub!(/\s/, '')
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,8 @@
1
+ module Cartify
2
+ class Delivery < ApplicationRecord
3
+ has_many :orders
4
+ validates :name, :duration, :price, presence: true
5
+ validates :price, numericality: { greater_than_or_equal_to: 0.01 }
6
+ validates_length_of :name, in: 3..100
7
+ end
8
+ end
@@ -0,0 +1,64 @@
1
+ module Cartify
2
+ class Order < ApplicationRecord
3
+ belongs_to :order_status
4
+ belongs_to :coupon, optional: true
5
+ belongs_to :user, class_name: Cartify.user_class.to_s, optional: true
6
+ belongs_to :delivery, optional: true
7
+ belongs_to :credit_card, optional: true
8
+ has_many :order_items, dependent: :destroy
9
+ has_many :addresses, dependent: :destroy
10
+ has_one :billing
11
+ has_one :shipping
12
+ has_many :books, through: :order_items
13
+ before_validation :set_order_status, on: :create
14
+ before_save :update_subtotal, :update_total, :connect_to_user
15
+
16
+ scope :where_status, ->(status_name) { joins(:order_status).where(cartify_order_statuses: { name: status_name }) }
17
+ scope :processing_list, -> { joins(:order_status).where(cartify_order_statuses: { name: %i[in_queue in_progress in_delivery] }) }
18
+ scope :processing_order, -> { where_status('in_queue').order('updated_at').last }
19
+ scope :in_progress, -> { where_status('in_progress').first.try(:id) }
20
+
21
+ def subtotal
22
+ order_items.sum(&:total_price)
23
+ end
24
+
25
+ def total
26
+ subtotal_item_total + shipping_price
27
+ end
28
+
29
+ def subtotal_item_total
30
+ subtotal - discount
31
+ end
32
+
33
+ def discount
34
+ coupon.try(:value) || 0.00
35
+ end
36
+
37
+ def shipping_price
38
+ delivery.try(:price) || 0.00
39
+ end
40
+
41
+ def finalize
42
+ set_order_status :in_queue
43
+ save!
44
+ end
45
+
46
+ private
47
+
48
+ def set_order_status(status = :in_progress)
49
+ self.order_status_id = Cartify::OrderStatus.find_or_create_by(name: status).id
50
+ end
51
+
52
+ def update_subtotal
53
+ self[:subtotal] = subtotal
54
+ end
55
+
56
+ def update_total
57
+ self[:total] = total
58
+ end
59
+
60
+ def connect_to_user
61
+ self[:user_id] = CurrentSession.user.id unless CurrentSession.user.nil?
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,39 @@
1
+ module Cartify
2
+ class OrderItem < ApplicationRecord
3
+ belongs_to :product, class_name: Cartify.product_class.to_s
4
+ belongs_to :order
5
+ has_one :order_status, through: :order
6
+ has_one :category, through: :product # was :book
7
+
8
+ validates :quantity, presence: true, numericality: { only_integer: true, greater_than: 0 }
9
+ validate :product_present
10
+ validate :order_present
11
+
12
+ before_save :finalize
13
+ default_scope { order(product_id: :asc) }
14
+
15
+ def unit_price
16
+ return self[:unit_price] if persisted?
17
+ product.price
18
+ end
19
+
20
+ def total_price
21
+ unit_price * quantity
22
+ end
23
+
24
+ private
25
+
26
+ def product_present
27
+ errors.add(:product, 'is not valid or is not active.') if product.nil?
28
+ end
29
+
30
+ def order_present
31
+ errors.add(:order, 'is not a valid order.') if order.nil?
32
+ end
33
+
34
+ def finalize
35
+ self[:unit_price] = unit_price
36
+ self[:total_price] = quantity * self[:unit_price]
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,18 @@
1
+ module Cartify
2
+ class OrderStatus < ApplicationRecord
3
+ has_many :orders
4
+
5
+ validates :name, presence: true, uniqueness: true
6
+ validates_length_of :name, maximum: 30
7
+
8
+ scope :prohibited_to_change, -> { where(name: %i[in_progress in_queue]) }
9
+
10
+ def valid_step?(target)
11
+ if Cartify::OrderStatus.find(target).name == 'delivered'
12
+ name == 'in_delivery'
13
+ else
14
+ Cartify::OrderStatus.prohibited_to_change.ids.exclude? target.to_i
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,4 @@
1
+ module Cartify
2
+ class Shipping < Address
3
+ end
4
+ end
@@ -0,0 +1,17 @@
1
+
2
+ # filter for orders
3
+ module Cartify
4
+ class OrdersQuery
5
+ attr_reader :relation
6
+
7
+ def initialize(relation = Cartify::Order.none)
8
+ @relation = relation
9
+ end
10
+
11
+ def run(filter_params = '')
12
+ status_id = Cartify::OrderStatus.find_by(name: filter_params)
13
+ return relation.where(order_status_id: status_id) if status_id
14
+ relation
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ %main.container
2
+ %h1.general-title-margin= t('settings.settings')
3
+ %ul.nav.clearfix.mb-20{role: "tablist"}
4
+ %li.tab-item{role: "presentation"}
5
+ = link_to t('settings.address'), settings_addresses_path, class: 'tab-link filter-link active'
6
+ %li.tab-item{role: "presentation"}
7
+ = link_to t('settings.privacy'), 'settings_privacy_path', class: 'tab-link'
8
+ .tab-content
9
+ = render 'cartify/shared/addresses_form'