shoppe 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
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