cartify 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 (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'