cartify 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +69 -0
- data/Rakefile +32 -0
- data/app/assets/config/cartify_manifest.js +2 -0
- data/app/assets/javascripts/cartify/application.js +14 -0
- data/app/assets/javascripts/cartify/checkout.js +11 -0
- data/app/assets/javascripts/cartify/masks.js +10 -0
- data/app/assets/javascripts/cartify/orders.js +7 -0
- data/app/assets/stylesheets/cartify/application.css +15 -0
- data/app/concerns/cartify/authenticatable.rb +17 -0
- data/app/concerns/cartify/current_session.rb +52 -0
- data/app/concerns/cartify/showable.rb +41 -0
- data/app/concerns/cartify/updatable.rb +39 -0
- data/app/controllers/cartify/addresses_controller.rb +18 -0
- data/app/controllers/cartify/application_controller.rb +6 -0
- data/app/controllers/cartify/carts_controller.rb +20 -0
- data/app/controllers/cartify/checkout_controller.rb +29 -0
- data/app/controllers/cartify/order_items_controller.rb +35 -0
- data/app/controllers/cartify/orders_controller.rb +17 -0
- data/app/decorator/cartify/order_decorator.rb +39 -0
- data/app/forms/cartify/addresses_form.rb +71 -0
- data/app/helpers/cartify/application_helper.rb +18 -0
- data/app/helpers/cartify/carts_helper.rb +10 -0
- data/app/helpers/cartify/checkout_helper.rb +22 -0
- data/app/helpers/cartify/order_helper.rb +12 -0
- data/app/models/cartify/address.rb +23 -0
- data/app/models/cartify/application_record.rb +5 -0
- data/app/models/cartify/billing.rb +4 -0
- data/app/models/cartify/coupon.rb +10 -0
- data/app/models/cartify/credit_card.rb +25 -0
- data/app/models/cartify/delivery.rb +8 -0
- data/app/models/cartify/order.rb +64 -0
- data/app/models/cartify/order_item.rb +39 -0
- data/app/models/cartify/order_status.rb +18 -0
- data/app/models/cartify/shipping.rb +4 -0
- data/app/queries/cartify/orders_query.rb +17 -0
- data/app/views/cartify/addresses/index.haml +9 -0
- data/app/views/cartify/carts/_order_items.haml +17 -0
- data/app/views/cartify/carts/_order_summary.haml +15 -0
- data/app/views/cartify/carts/_quantity.haml +7 -0
- data/app/views/cartify/carts/_xs_order_items.haml +24 -0
- data/app/views/cartify/carts/show.html.haml +22 -0
- data/app/views/cartify/checkout/_order_summary.haml +4 -0
- data/app/views/cartify/checkout/_progress.haml +21 -0
- data/app/views/cartify/checkout/addresses.haml +35 -0
- data/app/views/cartify/checkout/complete.haml +19 -0
- data/app/views/cartify/checkout/confirm.haml +27 -0
- data/app/views/cartify/checkout/delivery.haml +11 -0
- data/app/views/cartify/checkout/login.haml +39 -0
- data/app/views/cartify/checkout/partials/_addresses_short.haml +12 -0
- data/app/views/cartify/checkout/partials/_delivery-lg.haml +22 -0
- data/app/views/cartify/checkout/partials/_delivery-xs.haml +22 -0
- data/app/views/cartify/checkout/partials/_list_of_items.haml +54 -0
- data/app/views/cartify/checkout/payment.haml +27 -0
- data/app/views/cartify/order_items/create.js.erb +5 -0
- data/app/views/cartify/order_items/destroy.js.erb +3 -0
- data/app/views/cartify/order_items/update.js.erb +3 -0
- data/app/views/cartify/orders/_filters.haml +16 -0
- data/app/views/cartify/orders/_orders-lg.haml +10 -0
- data/app/views/cartify/orders/_orders-xs.haml +23 -0
- data/app/views/cartify/orders/index.haml +21 -0
- data/app/views/cartify/orders/show.haml +28 -0
- data/app/views/cartify/shared/_address.haml +28 -0
- data/app/views/cartify/shared/_addresses_form.haml +31 -0
- data/app/views/cartify/shared/_checkout_summary_numbers.haml +15 -0
- data/app/views/cartify/shared/_order_summary_numbers.haml +15 -0
- data/config/locales/en.yml +135 -0
- data/config/routes.rb +9 -0
- data/db/migrate/20171005130857_create_cartify_coupons.rb +8 -0
- data/db/migrate/20171005131198_create_cartify_credit_cards.rb +12 -0
- data/db/migrate/20171005131199_create_cartify_deliveries.rb +9 -0
- data/db/migrate/20171005131200_create_cartify_order_statuses.rb +7 -0
- data/db/migrate/20171005131201_create_cartify_orders.rb +19 -0
- data/db/migrate/20171005131202_create_cartify_order_items.rb +14 -0
- data/db/migrate/20171006133037_create_cartify_addresses.rb +17 -0
- data/lib/cartify.rb +19 -0
- data/lib/cartify/engine.rb +30 -0
- data/lib/cartify/version.rb +5 -0
- data/lib/generators/initializer/USAGE +49 -0
- data/lib/generators/initializer/initializer_generator.rb +17 -0
- data/lib/generators/initializer/templates/initializer.rb +10 -0
- data/lib/tasks/cartify_tasks.rake +4 -0
- 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,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,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,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,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'
|