shoppe 0.0.7 → 0.0.8

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 (75) hide show
  1. data/app/assets/images/shoppe/chosen-sprite.png +0 -0
  2. data/app/assets/images/shoppe/chosen-sprite@2x.png +0 -0
  3. data/app/assets/images/shoppe/icons/bag.svg +1 -0
  4. data/app/assets/images/shoppe/icons/balance.svg +1 -0
  5. data/app/assets/images/shoppe/icons/box.svg +1 -0
  6. data/app/assets/images/shoppe/icons/building.svg +1 -0
  7. data/app/assets/images/shoppe/icons/chart.svg +1 -0
  8. data/app/assets/images/shoppe/icons/chat.svg +1 -0
  9. data/app/assets/images/shoppe/icons/checkbox.svg +1 -0
  10. data/app/assets/images/shoppe/icons/checkbox2.svg +1 -0
  11. data/app/assets/images/shoppe/icons/cloud.svg +1 -0
  12. data/app/assets/images/shoppe/icons/cone.svg +1 -0
  13. data/app/assets/images/shoppe/icons/credit_card.svg +1 -0
  14. data/app/assets/images/shoppe/icons/currency.svg +1 -0
  15. data/app/assets/images/shoppe/icons/flowchart.svg +1 -0
  16. data/app/assets/images/shoppe/icons/gift.svg +1 -0
  17. data/app/assets/images/shoppe/icons/globe.svg +1 -0
  18. data/app/assets/images/shoppe/icons/id.svg +1 -0
  19. data/app/assets/images/shoppe/icons/id2.svg +1 -0
  20. data/app/assets/images/shoppe/icons/locked.svg +1 -0
  21. data/app/assets/images/shoppe/icons/report.svg +1 -0
  22. data/app/assets/images/shoppe/icons/search.svg +1 -0
  23. data/app/assets/images/shoppe/icons/support.svg +1 -0
  24. data/app/assets/images/shoppe/icons/tags.svg +1 -0
  25. data/app/assets/images/shoppe/icons/toolbox.svg +1 -0
  26. data/app/assets/images/shoppe/icons/unlocked.svg +1 -0
  27. data/app/assets/images/shoppe/icons/wallet.svg +1 -0
  28. data/app/assets/images/shoppe/move.svg +1 -0
  29. data/app/assets/javascripts/shoppe/application.coffee +32 -1
  30. data/app/assets/javascripts/shoppe/chosen.jquery.js +1166 -0
  31. data/app/assets/javascripts/shoppe/jquery_ui.js +6 -0
  32. data/app/assets/stylesheets/shoppe/application.scss +95 -20
  33. data/app/assets/stylesheets/shoppe/chosen.css +430 -0
  34. data/app/assets/stylesheets/shoppe/elements.scss +6 -7
  35. data/app/controllers/shoppe/delivery_service_prices_controller.rb +1 -1
  36. data/app/controllers/shoppe/products_controller.rb +1 -1
  37. data/app/helpers/shoppe/shoppe_helper.rb +2 -0
  38. data/app/models/shoppe/delivery_service_price.rb +1 -0
  39. data/app/models/shoppe/order.rb +18 -1
  40. data/app/models/shoppe/order_item.rb +11 -5
  41. data/app/models/shoppe/product.rb +19 -4
  42. data/app/models/shoppe/product/product_attributes.rb +15 -0
  43. data/app/models/shoppe/product_attribute.rb +51 -0
  44. data/app/views/shoppe/delivery_service_prices/_form.html.haml +23 -19
  45. data/app/views/shoppe/delivery_service_prices/edit.html.haml +1 -1
  46. data/app/views/shoppe/delivery_service_prices/index.html.haml +1 -1
  47. data/app/views/shoppe/delivery_service_prices/new.html.haml +1 -1
  48. data/app/views/shoppe/delivery_services/_form.html.haml +27 -25
  49. data/app/views/shoppe/delivery_services/edit.html.haml +1 -1
  50. data/app/views/shoppe/delivery_services/index.html.haml +12 -8
  51. data/app/views/shoppe/delivery_services/new.html.haml +1 -1
  52. data/app/views/shoppe/orders/index.html.haml +16 -12
  53. data/app/views/shoppe/orders/show.html.haml +28 -25
  54. data/app/views/shoppe/product_categories/_form.html.haml +7 -6
  55. data/app/views/shoppe/product_categories/edit.html.haml +1 -1
  56. data/app/views/shoppe/product_categories/index.html.haml +8 -4
  57. data/app/views/shoppe/product_categories/new.html.haml +1 -1
  58. data/app/views/shoppe/products/_form.html.haml +88 -45
  59. data/app/views/shoppe/products/edit.html.haml +1 -1
  60. data/app/views/shoppe/products/index.html.haml +14 -13
  61. data/app/views/shoppe/products/new.html.haml +1 -1
  62. data/app/views/shoppe/users/_form.html.haml +17 -15
  63. data/app/views/shoppe/users/edit.html.haml +1 -1
  64. data/app/views/shoppe/users/index.html.haml +1 -1
  65. data/app/views/shoppe/users/new.html.haml +1 -1
  66. data/db/migrate/20131012123829_create_shoppe_product_attributes.rb +11 -0
  67. data/db/migrate/20131012163301_add_public_boolean_to_product_attributes.rb +5 -0
  68. data/db/migrate/20131013123937_add_cost_prices_to_various_objects.rb +8 -0
  69. data/db/migrate/20131013131658_add_stock_control_boolean_to_products.rb +5 -0
  70. data/db/seeds.rb +78 -30
  71. data/lib/shoppe/version.rb +1 -1
  72. data/test/dummy/db/development.sqlite3 +0 -0
  73. data/test/dummy/db/schema.rb +22 -6
  74. data/test/dummy/log/development.log +29 -0
  75. metadata +42 -5
@@ -9,17 +9,16 @@
9
9
  background:none;
10
10
  text-decoration:none;
11
11
  font-size:12px;
12
- font-weight:600;
12
+ font-weight:500 !important;
13
13
  text-transform:uppercase;
14
14
  font-family:$font;
15
15
  color:#fff !important;
16
- letter-spacing:-0.5px;
17
- padding:7px 20px;
18
- border-radius:4px;
19
- background-color:#888;
16
+ padding:8px 20px;
17
+ border-radius:30px;
18
+ background-color:#40454D;
20
19
  display:inline-block;
21
20
  &:active {
22
- background:#999;
21
+ background:#40454D;
23
22
  }
24
23
 
25
24
  &.green {
@@ -63,7 +62,7 @@
63
62
 
64
63
  &.button-mini {
65
64
  font-size:11px;
66
- padding:3px 5px;
65
+ padding:4px 10px;
67
66
  box-shadow:none;
68
67
  font-weight:normal;
69
68
  text-shadow:none;
@@ -36,7 +36,7 @@ class Shoppe::DeliveryServicePricesController < Shoppe::ApplicationController
36
36
  private
37
37
 
38
38
  def safe_params
39
- params[:delivery_service_price].permit(:price, :tax_rate, :min_weight, :max_weight, :code)
39
+ params[:delivery_service_price].permit(:price, :cost_price, :tax_rate, :min_weight, :max_weight, :code)
40
40
  end
41
41
 
42
42
  end
@@ -39,7 +39,7 @@ class Shoppe::ProductsController < Shoppe::ApplicationController
39
39
  private
40
40
 
41
41
  def safe_params
42
- params[:product].permit(:product_category_id, :title, :sku, :permalink, :description, :short_description, :weight, :price, :tax_rate, :stock, :default_image_file, :data_sheet_file, :active, :featured, :in_the_box)
42
+ params[:product].permit(:product_category_id, :title, :sku, :permalink, :description, :short_description, :weight, :price, :cost_price, :tax_rate, :stock_control, :stock, :default_image_file, :data_sheet_file, :active, :featured, :in_the_box, :product_attributes_array => [:key, :value, :searchable, :public])
43
43
  end
44
44
 
45
45
  end
@@ -22,6 +22,8 @@ module Shoppe::ShoppeHelper
22
22
  s << "</div>"
23
23
  s << "</div>"
24
24
  end.html_safe
25
+ else
26
+ "<div class='attachmentPreview'><div class='imgContainer'><div class='img none'></div></div><div class='desc none'>No attachment</div></div>".html_safe
25
27
  end
26
28
  end
27
29
 
@@ -8,6 +8,7 @@ class Shoppe::DeliveryServicePrice < ActiveRecord::Base
8
8
 
9
9
  # Validations
10
10
  validates :price, :numericality => true
11
+ validates :cost_price, :numericality => true, :allow_blank => true
11
12
  validates :tax_rate, :numericality => true
12
13
  validates :min_weight, :numericality => true
13
14
  validates :max_weight, :numericality => true
@@ -109,6 +109,17 @@ class Shoppe::Order < ActiveRecord::Base
109
109
  @total_items ||= order_items.inject(0) { |t,i| t + i.quantity }
110
110
  end
111
111
 
112
+ # The total cost of the order
113
+ def total_cost
114
+ self.delivery_cost_price +
115
+ order_items.inject(BigDecimal(0)) { |t, i| t + i.total_cost }
116
+ end
117
+
118
+ # Return the price for the order
119
+ def profit
120
+ total_before_tax - total_cost
121
+ end
122
+
112
123
  # The total price of the order before tax
113
124
  def total_before_tax
114
125
  self.delivery_price +
@@ -163,11 +174,16 @@ class Shoppe::Order < ActiveRecord::Base
163
174
  @delivery_service_price ||= self.delivery_service && self.delivery_service.delivery_service_prices.for_weight(self.total_weight).first
164
175
  end
165
176
 
166
- # The cost of delivering this order in its current state
177
+ # The price for delivering this order in its current state
167
178
  def delivery_price
168
179
  @delivery_price ||= read_attribute(:delivery_price) || delivery_service_price.try(:price) || 0.0
169
180
  end
170
181
 
182
+ # The cost of delivering this order in its current state
183
+ def delivery_cost_price
184
+ @delivery_cost_price ||= read_attribute(:delivery_cost_price) || delivery_service_price.try(:cost_price) || 0.0
185
+ end
186
+
171
187
  # The tax amount due for the delivery of this order in its current state
172
188
  def delivery_tax_amount
173
189
  @delivery_tax_amount ||= begin
@@ -245,6 +261,7 @@ class Shoppe::Order < ActiveRecord::Base
245
261
  if self.delivery_service
246
262
  write_attribute :delivery_service_id, self.delivery_service.id
247
263
  write_attribute :delivery_price, self.delivery_price
264
+ write_attribute :delivery_cost_price, self.delivery_cost_price
248
265
  write_attribute :delivery_tax_amount, self.delivery_tax_amount
249
266
  write_attribute :delivery_tax_rate, self.delivery_tax_rate
250
267
  end
@@ -14,6 +14,7 @@ class Shoppe::OrderItem < ActiveRecord::Base
14
14
  before_validation do
15
15
  if self.product
16
16
  self.unit_price = self.product.price if self.unit_price.blank?
17
+ self.unit_cost_price = self.product.cost_price if self.unit_cost_price.blank?
17
18
  self.tax_rate = self.product.tax_rate if self.tax_rate.blank?
18
19
  if unit_price_changed? || quantity_changed?
19
20
  self.tax_amount = (self.sub_total / BigDecimal(100)) * self.tax_rate
@@ -55,7 +56,7 @@ class Shoppe::OrderItem < ActiveRecord::Base
55
56
  def increase!(amount = 1)
56
57
  transaction do
57
58
  self.quantity += amount
58
- if self.product.stock < self.quantity
59
+ if self.product.stock_control? && self.product.stock < self.quantity
59
60
  raise Shoppe::Errors::NotEnoughStock, :product => self.product, :requested_stock => self.quantity
60
61
  end
61
62
  self.save!
@@ -72,6 +73,11 @@ class Shoppe::OrderItem < ActiveRecord::Base
72
73
  end
73
74
  end
74
75
 
76
+ # Return the total cost for the product
77
+ def total_cost
78
+ quantity * unit_cost_price
79
+ end
80
+
75
81
  # Return the sub total for the product
76
82
  def sub_total
77
83
  quantity * unit_price
@@ -90,7 +96,7 @@ class Shoppe::OrderItem < ActiveRecord::Base
90
96
 
91
97
  # Do we have the stock needed to fulfil this order?
92
98
  def in_stock?
93
- if self.product.respond_to?(:stock)
99
+ if self.product.stock_control?
94
100
  self.product.stock >= self.quantity
95
101
  else
96
102
  true
@@ -101,12 +107,12 @@ class Shoppe::OrderItem < ActiveRecord::Base
101
107
  # before an order is completed. If we have run out of this product, we will update the quantity to an
102
108
  # appropriate level (or remove the order item) and return the object.
103
109
  def validate_stock_levels
104
- unless in_stock?
110
+ if in_stock?
111
+ false
112
+ else
105
113
  self.quantity = self.product.stock
106
114
  self.quantity == 0 ? self.destroy : self.save!
107
115
  self
108
- else
109
- false
110
116
  end
111
117
  end
112
118
 
@@ -3,6 +3,9 @@ class Shoppe::Product < ActiveRecord::Base
3
3
  # Set the table name
4
4
  self.table_name = 'shoppe_products'
5
5
 
6
+ # Require some concerns
7
+ require_dependency 'shoppe/product/product_attributes'
8
+
6
9
  # Attachments
7
10
  attachment :default_image
8
11
  attachment :data_sheet
@@ -21,6 +24,7 @@ class Shoppe::Product < ActiveRecord::Base
21
24
  validates :short_description, :presence => true
22
25
  validates :weight, :numericality => true
23
26
  validates :price, :numericality => true
27
+ validates :cost_price, :numericality => true, :allow_blank => true
24
28
  validates :tax_rate, :numericality => true
25
29
  validates :stock, :numericality => {:only_integer => true}
26
30
 
@@ -30,16 +34,18 @@ class Shoppe::Product < ActiveRecord::Base
30
34
  # Scopes
31
35
  scope :active, -> { where(:active => true) }
32
36
  scope :featured, -> {where(:featured => true)}
33
-
37
+
34
38
  # Is this product currently in stock?
35
39
  def in_stock?
36
- stock > 0
40
+ !stock_control? || stock > 0
37
41
  end
38
42
 
39
43
  # Remove the provided number of units from the current stock level of this product
40
44
  def update_stock_level(purchased = 1)
41
- self.stock -= purchased
42
- self.save!
45
+ if self.stock_control?
46
+ self.stock -= purchased
47
+ self.save!
48
+ end
43
49
  end
44
50
 
45
51
  # Specify which attributes can be searched
@@ -52,5 +58,14 @@ class Shoppe::Product < ActiveRecord::Base
52
58
  []
53
59
  end
54
60
 
61
+ # Search for products which include the guven attributes and return an active record
62
+ # scope of these products. Chainable with other scopes and with_attributes methods.
63
+ # For example:
64
+ #
65
+ # Shoppe::Product.active.with_attribute('Manufacturer', 'Apple').with_attribute('Model', ['Macbook', 'iPhone'])
66
+ def self.with_attributes(key, values)
67
+ product_ids = Shoppe::ProductAttribute.searchable.where(:key => key, :value => values).pluck(:product_id).uniq
68
+ where(:id => product_ids)
69
+ end
55
70
 
56
71
  end
@@ -0,0 +1,15 @@
1
+ class Shoppe::Product
2
+
3
+ # Relationships
4
+ has_many :product_attributes, -> { order(:position) }, :class_name => 'Shoppe::ProductAttribute'
5
+
6
+ # Attribute for providing the hash
7
+ attr_accessor :product_attributes_array
8
+
9
+ # Save the attributes after saving the record
10
+ after_save do
11
+ return unless product_attributes_array.is_a?(Array)
12
+ self.product_attributes.update_from_array(product_attributes_array)
13
+ end
14
+
15
+ end
@@ -0,0 +1,51 @@
1
+ class Shoppe::ProductAttribute < ActiveRecord::Base
2
+
3
+ # Set the table name
4
+ self.table_name = 'shoppe_product_attributes'
5
+
6
+ # Validations
7
+ validates :key, :presence => true
8
+
9
+ # Relationships
10
+ belongs_to :product, :class_name => 'Shoppe::Product'
11
+
12
+ # Scopes
13
+ scope :searchable, -> { where(:searchable => true) }
14
+ scope :public, -> { where(:public => true) }
15
+
16
+ # Return the the available options as a hash
17
+ def self.grouped_hash
18
+ all.group_by(&:key).inject(Hash.new) do |h, (key, attributes)|
19
+ h[key] = attributes.map(&:value).uniq
20
+ h
21
+ end
22
+ end
23
+
24
+ # Create/update attributes for a product based on the provided hash of
25
+ # keys & values
26
+ def self.update_from_array(array)
27
+ existing_keys = self.pluck(:key)
28
+ index = 0
29
+ array.each do |hash|
30
+ next if hash['key'].blank?
31
+ index += 1
32
+ params = hash.merge({
33
+ :searchable => hash['searchable'].to_s == '1',
34
+ :public => hash['public'].to_s == '1',
35
+ :position => index
36
+ })
37
+ if existing_attr = self.where(:key => hash['key']).first
38
+ if hash['value'].blank?
39
+ existing_attr.destroy
40
+ index -= 1
41
+ else
42
+ existing_attr.update_attributes(params)
43
+ end
44
+ else
45
+ attribute = self.create(params)
46
+ end
47
+ end
48
+ self.where(:key => existing_keys - array.map { |h| h['key']}).delete_all
49
+ end
50
+
51
+ end
@@ -1,28 +1,32 @@
1
1
  = form_for [@delivery_service, @delivery_service_price] do |f|
2
2
  = f.error_messages
3
- = field_set_tag "Weight allowance" do
4
- %dl
5
- %dt= f.label :min_weight
6
- %dd= f.text_field :min_weight
7
- %dl
8
- %dt= f.label :max_weight
9
- %dd= f.text_field :max_weight
3
+ = field_set_tag "Identification & Weight" do
4
+ .splitContainer
5
+ %dl.third
6
+ %dt= f.label :code
7
+ %dd= f.text_field :code
8
+
9
+ %dl.third
10
+ %dt= f.label :min_weight
11
+ %dd= f.text_field :min_weight
12
+ %dl.third
13
+ %dt= f.label :max_weight
14
+ %dd= f.text_field :max_weight
10
15
 
11
16
  = field_set_tag "Pricing" do
12
- %dl
13
- %dt= f.label :price
14
- %dd= f.text_field :price
15
- %dl
16
- %dt= f.label :tax_rate
17
- %dd= f.text_field :tax_rate
18
-
19
- = field_set_tag "Details" do
20
- %dl
21
- %dt= f.label :code
22
- %dd= f.text_field :code
17
+ .splitContainer
18
+ %dl.third
19
+ %dt= f.label :price
20
+ %dd= f.text_field :price
21
+ %dl.third
22
+ %dt= f.label :cost_price
23
+ %dd= f.text_field :cost_price
24
+ %dl.third
25
+ %dt= f.label :tax_rate
26
+ %dd= f.text_field :tax_rate
23
27
 
24
28
  %p.submit
25
29
  - unless @delivery_service_price.new_record?
26
30
  %span.right= link_to "Delete", [@delivery_service, @delivery_service_price], :class => 'button purple', :method => :delete, :data => {:confirm => "Are you sure you wish to remove this price?"}
27
31
  = f.submit :class => 'button green'
28
- = link_to "Cancel", :delivery_services, :class => 'button'
32
+ = link_to "Cancel", [@delivery_service, :delivery_service_prices], :class => 'button'
@@ -1,5 +1,5 @@
1
1
  - @page_title = "Delivery Services"
2
2
  = content_for :header do
3
3
  %p.buttons= link_to "Back to prices", [@delivery_service, :delivery_service_prices], :class => 'button'
4
- %h2 Edit Delivery Service Price
4
+ %h2.delivery_services Delivery Services
5
5
  = render 'form'
@@ -4,7 +4,7 @@
4
4
  %p.buttons
5
5
  = link_to "New price", [:new, @delivery_service, :delivery_service_price], :class => 'button green'
6
6
  = link_to "Back to delivery services", :delivery_services, :class => 'button'
7
- %h2 Delivery Pricing for #{@delivery_service.name}
7
+ %h2.delivery_services Delivery Pricing for #{@delivery_service.name}
8
8
 
9
9
  .table
10
10
  %table.data
@@ -1,5 +1,5 @@
1
1
  - @page_title = "Delivery Services"
2
2
  = content_for :header do
3
3
  %p.buttons= link_to "Back to prices", [@delivery_service, :delivery_service_prices], :class => 'button'
4
- %h2 New Delivery Service Price
4
+ %h2.delivery_services Delivery Services
5
5
  = render 'form'
@@ -1,33 +1,35 @@
1
1
  = form_for @delivery_service do |f|
2
2
  = f.error_messages
3
3
  = field_set_tag "Details" do
4
- %dl
5
- %dt= f.label :name
6
- %dd= f.text_field :name
7
- %dl
8
- %dt= f.label :code
9
- %dd= f.text_field :code
10
- %dl
11
- %dt= f.label :active
12
- %dd= f.check_box :active
13
- %dl
14
- %dt= f.label :default
15
- %dd= f.check_box :default
4
+ .splitContainer
5
+ %dl.half
6
+ %dt= f.label :name
7
+ %dd= f.text_field :name
8
+ %dl.half
9
+ %dt= f.label :code
10
+ %dd= f.text_field :code
11
+ .splitContainer
12
+ %dl.half
13
+ %dt= f.label :active
14
+ %dd.checkbox
15
+ = f.check_box :active
16
+ = f.label :active, 'Service will be available for use'
17
+ %dl.half
18
+ %dt= f.label :default
19
+ %dd.checkbox
20
+ = f.check_box :default
21
+ = f.label :default, 'Service will be used by default (if possible)'
16
22
  = field_set_tag "Courier" do
17
- %dl
18
- %dt= f.label :courier, "Courier Name"
19
- %dd= f.text_field :courier
20
- %dl
21
- %dt= f.label :tracking_url, "Tracking URL"
22
- %dd
23
- = f.text_field :tracking_url
24
- %p.help Use <code>{{consignment_number}}</code> to insert the consignment number.
23
+ .splitContainer
24
+ %dl.half
25
+ %dt= f.label :courier, "Courier Name"
26
+ %dd= f.text_field :courier
27
+ %dl.half
28
+ %dt= f.label :tracking_url, "Tracking URL"
29
+ %dd
30
+ = f.text_field :tracking_url
31
+ %p.help Use <code>{{consignment_number}}</code> to insert the consignment number.
25
32
 
26
- - unless @delivery_service.new_record?
27
- = field_set_tag "Pricing" do
28
- %dl
29
- %dt Pricing
30
- %dd= link_to "Set pricing for delivery service", [@delivery_service, :delivery_service_prices], :class => 'button button-mini'
31
33
  %p.submit
32
34
  - unless @delivery_service.new_record?
33
35
  %span.right= link_to "Delete", [@delivery_service], :class => 'button purple', :method => :delete, :data => {:confirm => "Are you sure you wish to remove this delivery service?"}
@@ -1,5 +1,5 @@
1
1
  - @page_title = "Delivery Services"
2
2
  = content_for :header do
3
3
  %p.buttons= link_to "Back to delivery services", :delivery_services, :class => 'button'
4
- %h2 Edit Delivery Service #{@delivery_service.id}
4
+ %h2.delivery_services Delivery Services
5
5
  = render 'form'
@@ -2,7 +2,7 @@
2
2
 
3
3
  = content_for :header do
4
4
  %p.buttons= link_to "New delivery service", :new_delivery_service, :class => 'button green'
5
- %h2 Delivery Services
5
+ %h2.delivery_services Delivery Services
6
6
 
7
7
  .table
8
8
  %table.data
@@ -14,11 +14,15 @@
14
14
  %th Active?
15
15
  %th Default?
16
16
  %tbody
17
- - for delivery_service in @delivery_services
18
- %tr
19
- %td= link_to delivery_service.name, [:edit, delivery_service]
20
- %td= link_to "Set Prices", [delivery_service, :delivery_service_prices]
21
- %td= delivery_service.courier
22
- %td= boolean_tag :active
23
- %td= boolean_tag :default
17
+ - if @delivery_services.empty?
18
+ %tr.empty
19
+ %td{:colspan => 5} No delivery services to display.
20
+ - else
21
+ - for delivery_service in @delivery_services
22
+ %tr
23
+ %td= link_to delivery_service.name, [:edit, delivery_service]
24
+ %td= link_to "Set Prices", [delivery_service, :delivery_service_prices]
25
+ %td= delivery_service.courier
26
+ %td= boolean_tag :active
27
+ %td= boolean_tag :default
24
28