comable-core 0.6.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/Rakefile +97 -0
- data/app/assets/javascripts/comable/application.js +13 -0
- data/app/assets/stylesheets/comable/application.css +13 -0
- data/app/controllers/concerns/comable/permitted_attributes.rb +15 -0
- data/app/helpers/comable/application_helper.rb +84 -0
- data/app/helpers/comable/products_helper.rb +82 -0
- data/app/mailers/comable/order_mailer.rb +18 -0
- data/app/models/comable/ability.rb +18 -0
- data/app/models/comable/address.rb +39 -0
- data/app/models/comable/category.rb +72 -0
- data/app/models/comable/image.rb +15 -0
- data/app/models/comable/order.rb +121 -0
- data/app/models/comable/order/associations.rb +22 -0
- data/app/models/comable/order/callbacks.rb +43 -0
- data/app/models/comable/order/morrisable.rb +20 -0
- data/app/models/comable/order/scopes.rb +17 -0
- data/app/models/comable/order/validations.rb +39 -0
- data/app/models/comable/order_item.rb +98 -0
- data/app/models/comable/order_item/csvable.rb +32 -0
- data/app/models/comable/page.rb +30 -0
- data/app/models/comable/payment.rb +87 -0
- data/app/models/comable/payment_method.rb +27 -0
- data/app/models/comable/product.rb +55 -0
- data/app/models/comable/product/csvable.rb +20 -0
- data/app/models/comable/shipment.rb +85 -0
- data/app/models/comable/shipment_method.rb +9 -0
- data/app/models/comable/stock.rb +71 -0
- data/app/models/comable/stock/csvable.rb +26 -0
- data/app/models/comable/store.rb +30 -0
- data/app/models/comable/theme.rb +25 -0
- data/app/models/comable/tracker.rb +17 -0
- data/app/models/comable/user.rb +130 -0
- data/app/models/concerns/comable/cart_owner.rb +94 -0
- data/app/models/concerns/comable/checkout.rb +85 -0
- data/app/models/concerns/comable/importable.rb +67 -0
- data/app/models/concerns/comable/liquidable.rb +12 -0
- data/app/models/concerns/comable/product/search.rb +41 -0
- data/app/models/concerns/comable/ransackable.rb +38 -0
- data/app/models/concerns/comable/role_owner.rb +15 -0
- data/app/models/concerns/comable/sku_choice.rb +19 -0
- data/app/models/concerns/comable/sku_item.rb +17 -0
- data/app/uploaders/image_uploader.rb +7 -0
- data/app/views/comable/order_mailer/complete.text.erb +42 -0
- data/config/initializers/comma.rb +8 -0
- data/config/locales/en.yml +424 -0
- data/config/locales/ja.yml +425 -0
- data/db/migrate/20131214194807_create_comable_products.rb +15 -0
- data/db/migrate/20140120032559_create_comable_users.rb +46 -0
- data/db/migrate/20140502060116_create_comable_stocks.rb +14 -0
- data/db/migrate/20140723175431_create_comable_orders.rb +20 -0
- data/db/migrate/20140723175810_create_comable_order_items.rb +19 -0
- data/db/migrate/20140817194104_create_comable_payment_methods.rb +13 -0
- data/db/migrate/20140921191416_create_comable_shipment_methods.rb +11 -0
- data/db/migrate/20140926063541_create_comable_stores.rb +11 -0
- data/db/migrate/20141024025526_create_comable_addresses.rb +17 -0
- data/db/migrate/20150111031228_create_comable_categories.rb +10 -0
- data/db/migrate/20150111031229_create_comable_products_categories.rb +8 -0
- data/db/migrate/20150112173706_create_comable_images.rb +9 -0
- data/db/migrate/20150423095210_create_comable_shipments.rb +13 -0
- data/db/migrate/20150511171940_create_comable_payments.rb +12 -0
- data/db/migrate/20150513185230_create_comable_trackers.rb +12 -0
- data/db/migrate/20150519080729_create_comable_pages.rb +17 -0
- data/db/migrate/20150612143226_create_comable_themes.rb +15 -0
- data/db/migrate/20150612143445_add_theme_id_to_comable_stores.rb +7 -0
- data/db/seeds.rb +5 -0
- data/db/seeds/comable/users.rb +51 -0
- data/lib/comable/core.rb +48 -0
- data/lib/comable/core/configuration.rb +22 -0
- data/lib/comable/core/engine.rb +50 -0
- data/lib/comable/deprecator.rb +26 -0
- data/lib/comable/payment_provider.rb +14 -0
- data/lib/comable/payment_provider/base.rb +42 -0
- data/lib/comable/payment_provider/general.rb +15 -0
- data/lib/comable/state_machine_patch.rb +32 -0
- data/lib/comma_extractor_extentions.rb +31 -0
- data/lib/generators/comable/install/install_generator.rb +133 -0
- data/lib/generators/comable/install/templates/config/initializers/comable.rb +31 -0
- data/lib/tasks/comable_tasks.rake +4 -0
- metadata +346 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
module Comable
|
2
|
+
class Tracker < ActiveRecord::Base
|
3
|
+
extend Enumerize
|
4
|
+
|
5
|
+
validates :name, presence: true, length: { maximum: 255 }
|
6
|
+
validates :tracker_id, length: { maximum: 255 }
|
7
|
+
validates :code, presence: true
|
8
|
+
validates :place, presence: true, length: { maximum: 255 }
|
9
|
+
|
10
|
+
scope :activated, -> { where(activated_flag: true) }
|
11
|
+
|
12
|
+
enumerize :place, in: %i(
|
13
|
+
everywhere
|
14
|
+
checkout
|
15
|
+
), scope: true
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Comable
|
2
|
+
class User < ActiveRecord::Base
|
3
|
+
include Comable::CartOwner
|
4
|
+
include Comable::RoleOwner
|
5
|
+
include Comable::Ransackable
|
6
|
+
include Comable::Liquidable
|
7
|
+
|
8
|
+
has_many :orders, class_name: Comable::Order.name
|
9
|
+
has_many :addresses, class_name: Comable::Address.name, dependent: :destroy
|
10
|
+
belongs_to :bill_address, class_name: Comable::Address.name, dependent: :destroy
|
11
|
+
belongs_to :ship_address, class_name: Comable::Address.name, dependent: :destroy
|
12
|
+
|
13
|
+
accepts_nested_attributes_for :addresses
|
14
|
+
accepts_nested_attributes_for :bill_address
|
15
|
+
accepts_nested_attributes_for :ship_address
|
16
|
+
|
17
|
+
scope :this_month, -> { where(created_at: Time.now.beginning_of_month..Time.now.end_of_month) }
|
18
|
+
scope :this_week, -> { where(created_at: Time.now.beginning_of_week..Time.now.end_of_week) }
|
19
|
+
scope :last_week, -> { where(created_at: 1.week.ago.beginning_of_week..1.week.ago.end_of_week) }
|
20
|
+
|
21
|
+
validates :email, presence: true, length: { maximum: 255 }
|
22
|
+
|
23
|
+
devise(*Comable::Config.devise_strategies[:user])
|
24
|
+
|
25
|
+
ransack_options ransackable_attributes: { except: [:encrypted_password, :reset_password_token, :reset_password_sent_at, :remember_created_at, :bill_address_id, :ship_address_id] }
|
26
|
+
|
27
|
+
liquid_methods :email, :bill_full_name, :ship_full_name, :cart
|
28
|
+
|
29
|
+
delegate :full_name, to: :bill_address, allow_nil: true, prefix: :bill
|
30
|
+
delegate :full_name, to: :ship_address, allow_nil: true, prefix: :ship
|
31
|
+
|
32
|
+
def with_cookies(cookies)
|
33
|
+
@cookies = cookies
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
# Add conditions for the orders association.
|
38
|
+
# Override method of the orders association to support Rails 3.x.
|
39
|
+
def orders
|
40
|
+
super.complete.order('completed_at DESC, id DESC')
|
41
|
+
end
|
42
|
+
|
43
|
+
def other_addresses
|
44
|
+
addresses - [bill_address] - [ship_address]
|
45
|
+
end
|
46
|
+
|
47
|
+
def update_bill_address_by(bill_address)
|
48
|
+
update_attributes(bill_address: addresses.find_or_clone(bill_address))
|
49
|
+
end
|
50
|
+
|
51
|
+
def update_ship_address_by(ship_address)
|
52
|
+
update_attributes(ship_address: addresses.find_or_clone(ship_address))
|
53
|
+
end
|
54
|
+
|
55
|
+
def signed_in?
|
56
|
+
!new_record?
|
57
|
+
end
|
58
|
+
|
59
|
+
def not_signed_in?
|
60
|
+
!signed_in?
|
61
|
+
end
|
62
|
+
|
63
|
+
# TODO: Add a test case
|
64
|
+
def reload(*_)
|
65
|
+
super.tap do
|
66
|
+
@cart_items = nil
|
67
|
+
@incomplete_order = nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def cart_items
|
72
|
+
incomplete_order.order_items
|
73
|
+
end
|
74
|
+
|
75
|
+
def incomplete_order
|
76
|
+
@incomplete_order = nil if @incomplete_order.try(:completed?)
|
77
|
+
@incomplete_order ||= find_incomplete_order || initialize_incomplete_order
|
78
|
+
end
|
79
|
+
|
80
|
+
def after_set_user
|
81
|
+
return unless current_guest_token
|
82
|
+
|
83
|
+
guest_order = Comable::Order.incomplete.preload(:order_items).where(guest_token: current_guest_token).first
|
84
|
+
return unless guest_order
|
85
|
+
|
86
|
+
incomplete_order.inherit!(guest_order)
|
87
|
+
inherit_cart_items(guest_order)
|
88
|
+
end
|
89
|
+
|
90
|
+
def human_id
|
91
|
+
"##{id}"
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def current_guest_token
|
97
|
+
@cookies.signed[:guest_token] if @cookies
|
98
|
+
end
|
99
|
+
|
100
|
+
def initialize_incomplete_order
|
101
|
+
order = Comable::Order.create(incomplete_order_attributes)
|
102
|
+
@cookies.permanent.signed[:guest_token] = order.guest_token if @cookies
|
103
|
+
# enable preload
|
104
|
+
find_incomplete_order
|
105
|
+
end
|
106
|
+
|
107
|
+
def incomplete_order_attributes
|
108
|
+
{
|
109
|
+
user_id: id,
|
110
|
+
email: email
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
def find_incomplete_order
|
115
|
+
guest_token ||= current_guest_token unless signed_in?
|
116
|
+
Comable::Order
|
117
|
+
.incomplete
|
118
|
+
.preload(:order_items)
|
119
|
+
.where(guest_token: guest_token)
|
120
|
+
.by_user(self)
|
121
|
+
.first
|
122
|
+
end
|
123
|
+
|
124
|
+
def inherit_cart_items(guest_order)
|
125
|
+
guest_order.order_items.each do |order_item|
|
126
|
+
move_cart_item(order_item)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Comable
|
2
|
+
module CartOwner
|
3
|
+
def add_cart_item(obj, quantity: 1)
|
4
|
+
process_cart_item(obj) do |stock|
|
5
|
+
add_stock_to_cart(stock, quantity)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def remove_cart_item(obj, quantity: -1)
|
10
|
+
add_cart_item(obj, quantity: quantity)
|
11
|
+
end
|
12
|
+
|
13
|
+
def reset_cart_item(obj, quantity: 0)
|
14
|
+
process_cart_item(obj) do |stock|
|
15
|
+
reset_stock_from_cart(stock, quantity)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def move_cart_item(cart_item)
|
20
|
+
add_cart_item(cart_item.stock, quantity: cart_item.quantity) && cart_item.destroy
|
21
|
+
end
|
22
|
+
|
23
|
+
def cart_items
|
24
|
+
fail 'You should implement cart_items method.'
|
25
|
+
end
|
26
|
+
|
27
|
+
def cart
|
28
|
+
Cart.new(cart_items)
|
29
|
+
end
|
30
|
+
|
31
|
+
class Cart < Array
|
32
|
+
def price
|
33
|
+
sum(&:current_subtotal_price)
|
34
|
+
end
|
35
|
+
|
36
|
+
def count
|
37
|
+
sum(&:quantity)
|
38
|
+
end
|
39
|
+
|
40
|
+
alias_method :size, :count
|
41
|
+
|
42
|
+
# TODO: Refactoring
|
43
|
+
def errors
|
44
|
+
ActiveModel::Errors.new(self).tap do |obj|
|
45
|
+
map(&:errors).map(&:full_messages).flatten.each do |full_message|
|
46
|
+
obj[:base] << full_message
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def process_cart_item(obj)
|
55
|
+
case obj
|
56
|
+
when Comable::Product
|
57
|
+
yield obj.stocks.first
|
58
|
+
when Comable::Stock
|
59
|
+
yield obj
|
60
|
+
when Array
|
61
|
+
obj.map { |item| yield item }
|
62
|
+
else
|
63
|
+
fail
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_stock_to_cart(stock, quantity)
|
68
|
+
cart_item = find_cart_item_by(stock)
|
69
|
+
if cart_item
|
70
|
+
cart_item.quantity += quantity
|
71
|
+
(cart_item.quantity > 0) ? cart_item.save : cart_item.destroy
|
72
|
+
else
|
73
|
+
cart_items.build(stock_id: stock.id, quantity: quantity).save
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def reset_stock_from_cart(stock, quantity)
|
78
|
+
cart_item = find_cart_item_by(stock)
|
79
|
+
if quantity > 0
|
80
|
+
return add_stock_to_cart(stock, quantity) unless cart_item
|
81
|
+
cart_item.update_attributes(quantity: quantity)
|
82
|
+
else
|
83
|
+
return false unless cart_item
|
84
|
+
cart_items.destroy(cart_item)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def find_cart_item_by(stock)
|
89
|
+
# TODO: Refactoring
|
90
|
+
fail unless stock.is_a?(Comable::Stock)
|
91
|
+
cart_items.find { |cart_item| cart_item.stock.id == stock.id }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Comable
|
2
|
+
module Checkout
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
state_machine initial: :cart do
|
7
|
+
state :cart
|
8
|
+
state :orderer
|
9
|
+
state :delivery
|
10
|
+
state :shipment
|
11
|
+
state :payment
|
12
|
+
state :confirm
|
13
|
+
state :completed
|
14
|
+
state :canceled
|
15
|
+
state :returned
|
16
|
+
state :resumed
|
17
|
+
|
18
|
+
event :next_state do
|
19
|
+
transition :cart => :orderer, if: :orderer_required?
|
20
|
+
transition [:cart, :orderer] => :delivery, if: :delivery_required?
|
21
|
+
transition [:cart, :orderer, :delivery] => :shipment, if: :shipment_required?
|
22
|
+
transition [:cart, :orderer, :delivery, :shipment] => :payment, if: :payment_required?
|
23
|
+
transition all - [:confirm, :completed] => :confirm
|
24
|
+
transition :confirm => :completed
|
25
|
+
end
|
26
|
+
|
27
|
+
event :cancel do
|
28
|
+
transition to: :canceled, from: [:completed, :resumed], if: :allow_cancel?
|
29
|
+
end
|
30
|
+
|
31
|
+
event :return do
|
32
|
+
transition to: :returned, from: [:completed, :resumed], if: :allow_return?
|
33
|
+
end
|
34
|
+
|
35
|
+
event :resume do
|
36
|
+
transition to: :resumed, from: :canceled
|
37
|
+
end
|
38
|
+
|
39
|
+
before_transition to: :completed, do: :complete!
|
40
|
+
after_transition to: :canceled, do: :restock!
|
41
|
+
after_transition to: :resumed, do: :unstock!
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module ClassMethods
|
46
|
+
def state_names
|
47
|
+
state_machine.states.keys
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def stated?(target_state)
|
52
|
+
target_state_index = self.class.state_names.index(target_state.to_sym)
|
53
|
+
current_state_index = self.class.state_names.index(state_name)
|
54
|
+
target_state_index < current_state_index
|
55
|
+
end
|
56
|
+
|
57
|
+
def orderer_required?
|
58
|
+
bill_address.nil? || bill_address.new_record?
|
59
|
+
end
|
60
|
+
|
61
|
+
def delivery_required?
|
62
|
+
ship_address.nil? || ship_address.new_record?
|
63
|
+
end
|
64
|
+
|
65
|
+
def payment_required?
|
66
|
+
Comable::PaymentMethod.exists? && payment.nil?
|
67
|
+
end
|
68
|
+
|
69
|
+
def shipment_required?
|
70
|
+
Comable::ShipmentMethod.activated.exists? && shipment.nil?
|
71
|
+
end
|
72
|
+
|
73
|
+
def allow_cancel?
|
74
|
+
# TODO: Implement shipments
|
75
|
+
# !shipments.exists?
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
def allow_return?
|
80
|
+
# TODO: Implement shipments
|
81
|
+
# shipments.exists?
|
82
|
+
false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Comable
|
2
|
+
module Importable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
class Exception < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
class UnknownFileType < Comable::Importable::Exception
|
9
|
+
end
|
10
|
+
|
11
|
+
class RecordInvalid < Comable::Importable::Exception
|
12
|
+
end
|
13
|
+
|
14
|
+
# from http://railscasts.com/episodes/396-importing-csv-and-excel
|
15
|
+
module ClassMethods
|
16
|
+
def import_from(file, primary_key: :code)
|
17
|
+
spreadsheet = open_spreadsheet(file)
|
18
|
+
read_spreadsheet(spreadsheet) do |header, row|
|
19
|
+
attributes = attributes_from_header_and_row(header, row)
|
20
|
+
record = find_by(primary_key => attributes[primary_key]) || new
|
21
|
+
begin
|
22
|
+
record.update_attributes!(attributes)
|
23
|
+
rescue ActiveRecord::RecordInvalid => e
|
24
|
+
raise RecordInvalid, "#{record.class.human_attribute_name(primary_key)} \"#{record.send(primary_key)}\": #{e.message}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def open_spreadsheet(file)
|
32
|
+
case File.extname(file.original_filename)
|
33
|
+
when '.csv' then Roo::CSV.new(file.path)
|
34
|
+
when '.xls' then Roo::Excel.new(file.path, nil, :ignore)
|
35
|
+
when '.xlsx' then Roo::Excelx.new(file.path, nil, :ignore)
|
36
|
+
else fail UnknownFileType, Comable.t('admin.unknown_file_type', filename: file.original_filename)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def read_spreadsheet(spreadsheet)
|
41
|
+
header = spreadsheet.row(1)
|
42
|
+
(2..spreadsheet.last_row).each do |i|
|
43
|
+
yield header, spreadsheet.row(i)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def attributes_from_header_and_row(header, row)
|
48
|
+
human_attributes = Hash[[header, row].transpose].to_hash
|
49
|
+
human_attributes_to_attributes(human_attributes)
|
50
|
+
end
|
51
|
+
|
52
|
+
def human_attributes_to_attributes(human_attributes)
|
53
|
+
comma_column_names.each.with_object({}) do |(key, _value), result|
|
54
|
+
human_key = Comma::HeaderExtractor.value_humanizer.call(key.to_sym, self)
|
55
|
+
result[key.to_sym] = human_attributes[human_key] if human_attributes[human_key]
|
56
|
+
result
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def comma_column_names(style = :default)
|
61
|
+
header_extractor_class = Comma::HeaderExtractor.dup
|
62
|
+
header_extractor_class.value_humanizer = -> (value, _model_class) { value.to_s }
|
63
|
+
extract_with(header_extractor_class, style)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Comable
|
2
|
+
module Liquidable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
# from: http://www.codedisqus.com/0SHjkUjjgW/how-can-i-expose-all-available-liquid-methods-for-a-model.html
|
7
|
+
def available_liquid_methods
|
8
|
+
self::LiquidDropClass.public_instance_methods - Liquid::Drop.public_instance_methods
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Comable
|
2
|
+
class Product
|
3
|
+
module Search
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
SEARCH_COLUMNS = %i( code name caption )
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def search(query)
|
10
|
+
keywords = parse_to_keywords(query)
|
11
|
+
return (Rails::VERSION::MAJOR == 3) ? scoped : all if keywords.empty?
|
12
|
+
where(keywords_to_arel(keywords))
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def keywords_to_arel(keywords)
|
18
|
+
keywords.inject(nil) do |arel_chain, keyword|
|
19
|
+
arel = keyword_to_arel(keyword)
|
20
|
+
arel_chain ? arel_chain.and(arel) : arel
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def keyword_to_arel(keyword)
|
25
|
+
SEARCH_COLUMNS.inject(nil) do |arel_chain, column|
|
26
|
+
arel = arel_table[column].matches("%#{keyword}%")
|
27
|
+
arel_chain ? arel_chain.or(arel) : arel
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse_to_keywords(query)
|
32
|
+
return [] if query.blank?
|
33
|
+
query
|
34
|
+
.delete('%')
|
35
|
+
.tr(' ', ' ')
|
36
|
+
.split(' ')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|