comable-core 0.6.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.
- 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
|