office_clerk 0.0.1 → 0.1

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +14 -9
  3. data/README.md +1 -0
  4. data/app/assets/images/shop/ikkuna.jpg +0 -0
  5. data/app/assets/images/shop/violetti-lev.jpg +0 -0
  6. data/app/assets/javascripts/admin.js +2 -2
  7. data/app/assets/javascripts/shop.js +2 -1
  8. data/app/assets/stylesheets/shop-receipt.css.scss +1 -1
  9. data/app/assets/stylesheets/shop.css.scss +52 -31
  10. data/app/controllers/application_controller.rb +11 -5
  11. data/app/controllers/baskets_controller.rb +3 -27
  12. data/app/controllers/categories_controller.rb +2 -1
  13. data/app/controllers/clerks_controller.rb +3 -8
  14. data/app/controllers/manage_controller.rb +7 -0
  15. data/app/controllers/orders_controller.rb +16 -2
  16. data/app/controllers/products_controller.rb +20 -40
  17. data/app/controllers/purchases_controller.rb +2 -1
  18. data/app/controllers/sessions_controller.rb +15 -22
  19. data/app/helpers/admin_helper.rb +6 -8
  20. data/app/helpers/orders_helper.rb +6 -0
  21. data/app/models/basket.rb +1 -2
  22. data/app/models/category.rb +2 -0
  23. data/app/models/product.rb +43 -17
  24. data/app/models/purchase.rb +7 -6
  25. data/app/views/baskets/edit.html.haml +6 -8
  26. data/app/views/baskets/show.html.haml +1 -1
  27. data/app/views/clerks/edit.html.haml +1 -1
  28. data/app/views/layouts/_admin_menu.html.haml +2 -0
  29. data/app/views/layouts/admin.html.haml +9 -4
  30. data/app/views/layouts/shop.html.haml +8 -5
  31. data/app/views/manage/all.haml +3 -0
  32. data/app/views/orders/ship.haml +91 -0
  33. data/app/views/orders/show.html.haml +12 -4
  34. data/app/views/products/_head.haml +12 -0
  35. data/app/views/products/_line.html.haml +4 -8
  36. data/app/views/products/_online.html.haml +1 -1
  37. data/app/views/products/edit.html.haml +1 -7
  38. data/app/views/products/index.html.haml +4 -1
  39. data/app/views/products/show.html.haml +4 -18
  40. data/app/views/purchases/show.html.haml +3 -3
  41. data/app/views/sessions/{new.html.haml → sign_in.haml} +1 -1
  42. data/app/views/sessions/{new_clerk.html.haml → sign_up.haml} +2 -2
  43. data/app/views/shop/checkout.haml +3 -3
  44. data/app/views/shop/liikkeemme.html.haml +59 -0
  45. data/app/views/shop/order.haml +20 -15
  46. data/app/views/shop/product_list.html.haml +16 -13
  47. data/app/views/shop/tilaushistoria.html.haml +221 -0
  48. data/app/views/shop/toimitusehdot.html.haml +99 -0
  49. data/config/locales/config.yml +2 -1
  50. data/config/locales/en.yml +4 -5
  51. data/config/locales/fi.yml +13 -6
  52. data/config/routes.rb +11 -12
  53. data/lib/office_clerk/shipping_method.rb +1 -1
  54. data/office_clerk.gemspec +1 -1
  55. data/spec/controllers/products_controller_spec.rb +3 -3
  56. data/spec/controllers/sessions_controller_spec.rb +11 -3
  57. data/spec/factories/orders.rb +7 -1
  58. data/spec/factories/products.rb +16 -0
  59. data/spec/factories/purchases.rb +8 -4
  60. data/spec/features/baskets/buttons_spec.rb +1 -2
  61. data/spec/features/baskets/index_spec.rb +10 -4
  62. data/spec/features/clerks_spec.rb +22 -6
  63. data/spec/features/orders_spec.rb +18 -0
  64. data/spec/features/products/edit_spec.rb +32 -0
  65. data/spec/features/products/header_spec.rb +48 -0
  66. data/spec/features/products/index_spec.rb +4 -18
  67. data/spec/features/purchases_spec.rb +19 -0
  68. data/spec/features/sessions_spec.rb +58 -0
  69. data/spec/features/shop_spec.rb +63 -0
  70. data/spec/features/suppliers_spec.rb +2 -0
  71. data/spec/models/product_spec.rb +54 -8
  72. data/spec/models/purchase_spec.rb +20 -1
  73. data/spec/models/shipping_spec.rb +25 -0
  74. metadata +22 -9
  75. data/app/views/products/_name.html.haml +0 -4
  76. data/spec/features/products/new_spec.rb +0 -20
  77. data/spec/features/shops_spec.rb +0 -18
@@ -27,7 +27,7 @@ class ProductsController < AdminController
27
27
  def new
28
28
  if params[:parent_id]
29
29
  parent = Product.find params[:parent_id]
30
- @product = parent.new_line_item
30
+ @product = parent.new_product_item
31
31
  else
32
32
  @product = Product.new :tax => OfficeClerk.config("defaults.tax")
33
33
  end
@@ -38,47 +38,19 @@ class ProductsController < AdminController
38
38
  end
39
39
 
40
40
  def create
41
- flash.notice = ""
42
41
  @product = Product.create(params_for_model)
43
- #TODO maybe there is a better way, but this "validation" happens "after the fact", ie by adding
44
- # an item to a parent the parent can become "invalid" even it is not what is being edited. hmmm
45
- if @product.line_item? and not @product.product.ean.blank?
46
- flash.notice += t(:product_line_has_ean)
47
- flash.notice += "<br/>"
48
- end
49
- if @product.line_item? and not @product.product.scode.blank?
50
- flash.notice += t(:product_line_has_scode)
51
- flash.notice += "<br/>"
52
- end
53
42
  if @product.save
54
- flash.notice += t(:create_success, :model => "product")
55
- redirect_to product_path(@product)
43
+ flash.notice = t(:create_success, :model => "product")
44
+ show = @product.product_item? ? @product.product : @product
45
+ redirect_to product_path(show)
56
46
  else
57
47
  render :action => :edit
58
48
  end
59
49
  end
60
50
 
61
51
  def update
62
- flash.notice = ""
63
- if ok = @product.update_attributes(params_for_model)
64
- flash.notice += t(:update_success, :model => "product")
65
- flash.notice += "<br/>"
66
- end
67
- if (@product.line_item? and not @product.link.blank?)
68
- flash.notice += t(:product_item_has_link)
69
- flash.notice += "<br/>"
70
- ok = false
71
- end
72
- if (@product.line_item? and @product.product.ean) or (@product.line? and not @product.ean.blank?)
73
- flash.notice += t(:product_line_has_ean)
74
- flash.notice += "<br/>"
75
- ok = false
76
- end
77
- if (@product.line_item? and @product.product.scode) or (@product.line? and not @product.scode.blank?)
78
- flash.notice += t(:product_line_has_scode)
79
- ok = false
80
- end
81
- if ok
52
+ if @product.update_attributes(params_for_model)
53
+ flash.notice = t(:update_success, :model => "product")
82
54
  redirect_to product_path(@product)
83
55
  else
84
56
  render :action => :edit
@@ -90,29 +62,37 @@ class ProductsController < AdminController
90
62
  if @product.save
91
63
  redirect_to products_url , :notice => t("deleted")
92
64
  else
93
- redirect_to products_url , :notice => t("error")
65
+ redirect_to product_url , :notice => "#{t(:error)} : #{t(:inventory)}"
94
66
  end
95
67
  end
96
68
 
97
69
  # loads of ways to create barcodes nowadays, this is a bit older.
98
70
  # Used to be html but moved to pdf for better layout control
99
71
  def barcode
100
- pdf = Prawn::Document.new( :page_size => [ 54.mm , 25.mm ] , :margin => 2.mm )
101
- pdf.text( @product.full_name , :align => :left )
102
- pdf.text( "#{@product.price} € " , :align => :right , :padding => 5.mm)
103
- code = @product.ean.blank? || ""
72
+ code = @product.ean
73
+ code = @product.name if code.blank?
104
74
  if code.length == 12
105
75
  aBarcode = ::Barby::EAN13.new( code )
106
76
  else
77
+ puts "product #{code}"
78
+ puts code.class
107
79
  aBarcode = ::Barby::Code128B.new( code )
108
80
  end
81
+ pdf = create_pdf
109
82
  pdf.image( StringIO.new( aBarcode.to_png(:xdim => 5)) , :width => 50.mm ,
110
83
  :height => 10.mm , :at => [ 0 , 10.mm])
111
84
  send_data pdf.render , :type => "application/pdf" , :filename => "#{@product.full_name}.pdf"
112
85
  end
113
86
 
114
87
  private
115
-
88
+
89
+ def create_pdf
90
+ pdf = Prawn::Document.new( :page_size => [ 54.mm , 25.mm ] , :margin => 2.mm )
91
+ pdf.text( @product.full_name , :align => :left )
92
+ pdf.text( "#{@product.price} € " , :align => :right , :padding => 5.mm)
93
+ pdf
94
+ end
95
+
116
96
  def load_product
117
97
  @product = Product.find(params[:id])
118
98
  end
@@ -30,7 +30,8 @@ class PurchasesController < AdminController
30
30
  # receive the stuff (ie add to inventory)
31
31
  def inventory
32
32
  items = @purchase.inventory!
33
- redirect_to purchase_path(@purchase), :notice => [t(:inventorized) ,items ,t(:items)].join(" ")
33
+ flash.notice = [t(:inventorized) ,items ,t(:items)].join(" ")
34
+ redirect_to purchase_path(@purchase)
34
35
  end
35
36
 
36
37
  def new
@@ -1,7 +1,7 @@
1
1
  class SessionsController < ApplicationController
2
2
  layout "shop"
3
3
 
4
- def new
4
+ def sign_in
5
5
  end
6
6
 
7
7
  def create
@@ -11,36 +11,29 @@ class SessionsController < ApplicationController
11
11
  url = clerk.admin ? baskets_url : root_url
12
12
  redirect_to url , :notice => I18n.t(:signed_in)
13
13
  else
14
- render "new" , :notice => I18n.t(:sign_in_invalid)
14
+ redirect_to :sign_in , :notice => I18n.t(:sign_in_invalid)
15
15
  end
16
16
  end
17
17
 
18
- def destroy
18
+ def sign_out
19
19
  session[:clerk_email] = nil
20
20
  redirect_to root_url, :notice => I18n.t(:signed_out)
21
21
  end
22
22
 
23
- def new_clerk
24
- @clerk = Clerk.new
25
- end
26
-
27
- def update_clerk
28
- @clerk = current_clerk
29
- if @clerk.update_attributes(params[:clerk])
30
- redirect_to root_url, :notice => I18n.t(:updated)
23
+ def sign_up
24
+ if request.get?
25
+ @clerk = Clerk.new
31
26
  else
32
- render :action => 'edit'
27
+ @clerk = Clerk.new(params_for_clerk)
28
+ if @clerk.save
29
+ session[:clerk_email] = @clerk.email
30
+ redirect_to root_url, :notice => "Signed up!"
31
+ return
32
+ end
33
33
  end
34
34
  end
35
-
36
- def create_clerk
37
- @clerk = Clerk.new(params[:clerk])
38
- if @clerk.save
39
- session[:clerk_email] = @clerk.email
40
- redirect_to root_url, :notice => "Signed up!"
41
- else
42
- render "new"
43
- end
35
+ def params_for_clerk
36
+ params.require(:clerk).permit(:email,:password,:password_confirmation)
44
37
  end
45
-
38
+
46
39
  end
@@ -4,23 +4,21 @@ module AdminHelper
4
4
  def basket_edit_link basket , options = {}
5
5
  return "---" unless basket
6
6
  return "" unless request.url.include?("basket")
7
- text = t(:edit) + " "
7
+ text = t(:edit) + " "
8
8
  link = edit_basket_path(basket)
9
- case basket.kori_type
10
- when "Order"
9
+ case basket.kori
10
+ when Order
11
11
  text += I18n.t(:order)
12
12
  link = order_path(basket.kori) rescue ""
13
- when "Purchase"
13
+ when Purchase
14
14
  text += I18n.t(:purchase)
15
15
  link = purchase_path(basket.kori) rescue ""
16
+ else
17
+ text += t(:basket)
16
18
  end
17
19
  return link_to text , link , options
18
20
  end
19
21
 
20
- def sorting_header(model_name, attribute_name, namespace)
21
- attribute_name
22
- end
23
-
24
22
  def sort_date key
25
23
  return "" unless params[:q]
26
24
  params[:q][key] || ""
@@ -1,4 +1,10 @@
1
1
  # encoding : utf-8
2
2
  require "admin_helper"
3
3
  module OrdersHelper
4
+ def print_styles
5
+ OfficeClerk.config(:print_styles).split
6
+ end
7
+ def print_path style
8
+ eval("#{style}_order_path(@order)")
9
+ end
4
10
  end
data/app/models/basket.rb CHANGED
@@ -80,8 +80,7 @@ class Basket < ActiveRecord::Base
80
80
  end
81
81
 
82
82
  def isa typ
83
- return typ == :cart if self.kori_type == nil
84
- self.kori_type.downcase == typ.to_s.downcase
83
+ self.kori_type.to_s.downcase == typ.to_s.downcase && self.kori_id != nil
85
84
  end
86
85
 
87
86
  def suppliers
@@ -7,6 +7,8 @@ class Category < ActiveRecord::Base
7
7
  belongs_to :category
8
8
  has_attached_file :main_picture
9
9
  has_attached_file :extra_picture
10
+ validates_attachment_content_type :main_picture, :content_type => /\Aimage\/.*\Z/
11
+ validates_attachment_content_type :extra_picture, :content_type => /\Aimage\/.*\Z/
10
12
 
11
13
  validates :name, :presence => true
12
14
  validates :link, presence: true, :if => :generate_url_if_needed
@@ -21,6 +21,8 @@ class Product < ActiveRecord::Base
21
21
  belongs_to :supplier
22
22
  has_attached_file :main_picture
23
23
  has_attached_file :extra_picture
24
+ validates_attachment_content_type :main_picture, :content_type => /\Aimage\/.*\Z/
25
+ validates_attachment_content_type :extra_picture, :content_type => /\Aimage\/.*\Z/
24
26
 
25
27
  # default product scope only lists non-deleted products
26
28
  default_scope {where(:deleted_on => nil).order('created_at DESC') }
@@ -32,10 +34,12 @@ class Product < ActiveRecord::Base
32
34
  validates :price, :numericality => true
33
35
  validates :cost, :numericality => true
34
36
  validates :name, :presence => true
37
+ validates :deleted_on , :absence => true , :if => "inventory > 0"
35
38
 
36
- before_save :generate_url
39
+ before_save :check_attributes
37
40
  before_save :adjust_cost
38
41
  after_save :update_line_inventory , :if => :product_id
42
+ after_save :check_parent_ean , :if => :product_id
39
43
 
40
44
 
41
45
  def update_line_inventory
@@ -46,13 +50,24 @@ class Product < ActiveRecord::Base
46
50
  parent.save! if inv != parent.inventory
47
51
  end
48
52
 
53
+ def check_parent_ean
54
+ parent = self.product
55
+ return unless parent
56
+ parent.check_attributes
57
+ parent.save! if parent.changed?
58
+ end
59
+
49
60
  # if no url is set we generate one based on the name
50
- # but line_items don't have urls, so not for them
51
- def generate_url
52
- if line_item? or deleted?
61
+ # but product_items don't have urls, so not for them
62
+ def check_attributes
63
+ if product_item?
53
64
  self.link = ""
54
65
  else
55
- self.link = name.gsub(" " , "_").downcase if link.blank? && name != nil
66
+ self.link = self.name.gsub(" " , "_").downcase if self.link.blank? && self.name != nil
67
+ end
68
+ if line?
69
+ self.ean = "" unless self.ean.blank?
70
+ self.scode = "" unless self.scode.blank?
56
71
  end
57
72
  end
58
73
 
@@ -61,6 +76,7 @@ class Product < ActiveRecord::Base
61
76
  self.cost = self.price / 2
62
77
  end
63
78
  end
79
+
64
80
  def deleted?
65
81
  not deleted_on.blank?
66
82
  end
@@ -72,28 +88,38 @@ class Product < ActiveRecord::Base
72
88
  self
73
89
  end
74
90
 
75
- #this product represents a product line (ie is not sellable in itself)
91
+ # the type is one of:
92
+ # - product
93
+ # -product_line
94
+ # -product_item
95
+ # mostly used for translation. Function below let you test for each of the possibilities
96
+ def type
97
+ return :product_item if product_item?
98
+ products.empty? ? :product : :product_line
99
+ end
100
+
101
+ # this product represents a product line (ie is not sellable in itself)
76
102
  def line?
77
- !line_item? and !products.empty?
103
+ !product_item? and !products.empty?
104
+ end
105
+ # this product is an item of a product line (so is sellable)
106
+ def product_item?
107
+ self.product_id != nil
108
+ end
109
+ # only products and product items are sellable. in other words if it's not a line
110
+ def sellable?
111
+ !line?
78
112
  end
79
113
 
80
114
  def full_name
81
- if line_item?
115
+ if product_item?
82
116
  product.name + " : " + self.name
83
117
  else
84
118
  self.name
85
119
  end
86
120
  end
87
- #this product is an item of a product line (so is sellable)
88
- def line_item?
89
- self.product_id != nil
90
- end
91
-
92
- def sellable?
93
- !line?
94
- end
95
121
 
96
- def new_line_item
122
+ def new_product_item
97
123
  Product.new :tax => self.tax , :weight => self.weight , :cost => self.cost , :product_id => self.id ,
98
124
  :supplier_id => self.supplier_id , :category_id => self.category_id , :price => self.price
99
125
  end
@@ -9,18 +9,19 @@ class Purchase < ActiveRecord::Base
9
9
 
10
10
  def receive!
11
11
  items = basket.receive!
12
- self.ordered_on = Date.today unless ordered_on
13
- self.received_on = Date.today
14
- self.save!
15
- items
12
+ return stamp_today items
16
13
  end
17
14
 
18
15
  def inventory!
19
- items = basket.receive!
16
+ items = basket.inventory!
17
+ return stamp_today items
18
+ end
19
+
20
+ private
21
+ def stamp_today items
20
22
  self.ordered_on = Date.today unless ordered_on
21
23
  self.received_on = Date.today
22
24
  self.save!
23
25
  items
24
26
  end
25
-
26
27
  end
@@ -51,13 +51,10 @@
51
51
  .row.form-actions
52
52
  .col-md-2
53
53
  = f.submit :class => "btn btn-success commit"
54
- .col-md-2
55
- = link_to t(:back), baskets_path, :class => "btn btn-warning"
56
- .col-md-2
57
- = link_to t(:new) + ' ' + t(:basket) , new_basket_path , :class => "btn btn-primary"
58
- - if @basket.isa(:cart) and not @basket.empty?
54
+ - if @basket.kori_type.blank? and not @basket.empty?
55
+ .col-md-1
56
+ = link_to t(:make_order), order_basket_path(@basket), :class => "btn btn-primary make_order"
59
57
  .col-md-2
60
- - link_to t(:make_order), order_basket_path(@basket), :class => "btn btn-primary make_order"
61
58
  = link_to t(:make_purchase), purchase_basket_path(@basket), :class => "btn btn-primary make_purchase"
62
59
  .col-md-2{"data-no-turbolink" => true}
63
60
  = link_to t(:checkout), checkout_basket_path(@basket) , :target => "_blank" , :class => "btn btn-primary print_order"
@@ -69,6 +66,7 @@
69
66
  - elsif @basket.isa(:purchase)
70
67
  .col-md-2
71
68
  = link_to t(:to_purchase), purchase_path(@basket.kori), :class => "btn btn-primary to_purchase"
72
-
73
69
  .col-md-2
74
- = link_to t(:destroy) , basket_path(@basket), :data => { :confirm => t(:are_you_sure )}, :method => :delete, :title => t(:destroy) , :class => "btn btn-danger" unless @basket.kori_type
70
+ = link_to t(:destroy) , basket_path(@basket), :data => { :confirm => t(:are_you_sure )}, :method => :delete, :title => t(:destroy) , :class => "btn btn-danger" unless @basket.kori
71
+ .col-md-1
72
+ = link_to t(:new) + ' ' + t(:basket) , new_basket_path , :class => "btn btn-primary"
@@ -15,4 +15,4 @@
15
15
  .col-md-12{"data-no-turbolink" => true}
16
16
  = link_to t(:back), orders_path, :class => "btn btn-warning"
17
17
  = link_to t(:destroy) , basket_path(@basket), :data => { :confirm => t(:are_you_sure) }, :method => :delete, :title => t(:destroy) , :class => "btn btn-danger" unless @basket.kori_type
18
- = link_to t(:print_order), invoice_order_path(@basket.kori) , :target => "_blank" , :class => "btn btn-primary print_order" if @basket.isa(:order)
18
+ = link_to t(:print_order), receipt_order_path(@basket.kori) , :target => "_blank" , :class => "btn btn-primary print_order" if @basket.isa(:order)
@@ -16,5 +16,5 @@
16
16
  = f.input :password
17
17
  = f.input :password_confirmation
18
18
  .form-actions
19
- = f.submit :class => "btn btn-success"
19
+ = f.submit :class => "btn btn-success" , :id => "submit"
20
20
  = link_to t(:back), clerks_path, :class => "btn btn-warning"
@@ -12,3 +12,5 @@
12
12
  = link_to t(:suppliers), suppliers_path
13
13
  %li{:class => params[:controller] == "clerks" && "active" }
14
14
  = link_to t(:clerks), clerks_path
15
+ %li{:class => params[:controller] == "manage" && "active" }
16
+ = link_to t(:manage), manage_all_path
@@ -20,23 +20,28 @@
20
20
  %li= link_to current_clerk.email , edit_clerk_path(current_clerk)
21
21
  %li= link_to t(:sign_out), sign_out_path
22
22
 
23
- .row.content
24
- - if not flash[:notice].blank? then
23
+ - if not flash[:notice].blank? then
24
+ .row.content
25
25
  .col-md-12.alert
26
26
  .alert-notice.row
27
27
  .col-md-3
28
28
  .alert-heading= t(:info) + " :"
29
29
  .col-md-9
30
30
  .alert-message!= flash[:notice]
31
- - if not flash[:error].blank? then
31
+ - if not flash[:alert].blank? then
32
+ .row.content
32
33
  .col-md-12.alert
33
34
  .alert-error.row
34
35
  .col-md-3
35
36
  .alert-heading= t(:info) + " :"
36
37
  .col-md-9
37
- .alert-message!= flash[:notice]
38
+ .alert-message= flash[:alert]
38
39
  .large-12.column
39
40
  .row.space
40
41
  =image_tag "shop/spacer.gif"
41
42
 
42
43
  = yield
44
+
45
+ .row
46
+ .col-md-12
47
+ %br