office_clerk 0.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 (259) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +73 -0
  3. data/.rubocop.yml +6 -0
  4. data/.travis.yml +9 -0
  5. data/Gemfile +63 -0
  6. data/Gemfile.lock +367 -0
  7. data/Guardfile +28 -0
  8. data/README.md +44 -0
  9. data/Rakefile +6 -0
  10. data/app/assets/images/admin/baskets-2.jpg +0 -0
  11. data/app/assets/images/admin/baskets.jpg +0 -0
  12. data/app/assets/images/admin/categories-2.jpg +0 -0
  13. data/app/assets/images/admin/categories.jpg +0 -0
  14. data/app/assets/images/admin/clerks-2.jpg +0 -0
  15. data/app/assets/images/admin/clerks.jpg +0 -0
  16. data/app/assets/images/admin/orders-2.jpg +0 -0
  17. data/app/assets/images/admin/orders.jpg +0 -0
  18. data/app/assets/images/admin/products-2.jpg +0 -0
  19. data/app/assets/images/admin/products.jpg +0 -0
  20. data/app/assets/images/admin/purchases-2.jpg +0 -0
  21. data/app/assets/images/admin/purchases.jpg +0 -0
  22. data/app/assets/images/admin/suppliers-2.jpg +0 -0
  23. data/app/assets/images/admin/suppliers.jpg +0 -0
  24. data/app/assets/images/alpha.png +0 -0
  25. data/app/assets/images/down-icon.png +0 -0
  26. data/app/assets/images/header.gif +0 -0
  27. data/app/assets/images/hihih.jpg +0 -0
  28. data/app/assets/images/hue.png +0 -0
  29. data/app/assets/images/koe.jpg +0 -0
  30. data/app/assets/images/missing.png +0 -0
  31. data/app/assets/images/saturation.png +0 -0
  32. data/app/assets/images/shop/basket.jpg +0 -0
  33. data/app/assets/images/shop/farfalla-logo.jpg +0 -0
  34. data/app/assets/images/shop/header.gif +0 -0
  35. data/app/assets/images/shop/header.jpg +0 -0
  36. data/app/assets/images/shop/header.png +0 -0
  37. data/app/assets/images/shop/kansi.jpg +0 -0
  38. data/app/assets/images/shop/kansi.png +0 -0
  39. data/app/assets/images/shop/logo.png +0 -0
  40. data/app/assets/images/shop/menu-gourmet.jpg +0 -0
  41. data/app/assets/images/shop/menu-hius.jpg +0 -0
  42. data/app/assets/images/shop/menu-huonetuoksu.jpg +0 -0
  43. data/app/assets/images/shop/menu-info.jpg +0 -0
  44. data/app/assets/images/shop/menu-kasvo.jpg +0 -0
  45. data/app/assets/images/shop/menu-laukut.jpg +0 -0
  46. data/app/assets/images/shop/menu-luomu.jpg +0 -0
  47. data/app/assets/images/shop/menu-men.jpg +0 -0
  48. data/app/assets/images/shop/menu-oljy.jpg +0 -0
  49. data/app/assets/images/shop/menu-tuoksu1.jpg +0 -0
  50. data/app/assets/images/shop/menu-vaatteet.jpg +0 -0
  51. data/app/assets/images/shop/menu-valaisin.jpg +0 -0
  52. data/app/assets/images/shop/menu-vartalo.jpg +0 -0
  53. data/app/assets/images/shop/spacer.gif +0 -0
  54. data/app/assets/images/shop/tausta.jpg +0 -0
  55. data/app/assets/images/shop/tervetuloa.gif +0 -0
  56. data/app/assets/images/shop/valk.png +0 -0
  57. data/app/assets/images/shop/valk3.png +0 -0
  58. data/app/assets/images/shop/violetti-lev.jpg +0 -0
  59. data/app/assets/images/shop/violetti.jpg +0 -0
  60. data/app/assets/images/ui-anim_basic_16x16.gif +0 -0
  61. data/app/assets/images/up-icon.png +0 -0
  62. data/app/assets/javascripts/admin.js +29 -0
  63. data/app/assets/javascripts/shop.js +30 -0
  64. data/app/assets/stylesheets/admin.css.scss +129 -0
  65. data/app/assets/stylesheets/shop-receipt.css.scss +80 -0
  66. data/app/assets/stylesheets/shop.css.scss +403 -0
  67. data/app/controllers/admin_controller.rb +23 -0
  68. data/app/controllers/application_controller.rb +41 -0
  69. data/app/controllers/baskets_controller.rb +176 -0
  70. data/app/controllers/categories_controller.rb +69 -0
  71. data/app/controllers/clerks_controller.rb +64 -0
  72. data/app/controllers/orders_controller.rb +57 -0
  73. data/app/controllers/products_controller.rb +126 -0
  74. data/app/controllers/purchases_controller.rb +72 -0
  75. data/app/controllers/sessions_controller.rb +46 -0
  76. data/app/controllers/shop_controller.rb +90 -0
  77. data/app/controllers/suppliers_controller.rb +63 -0
  78. data/app/helpers/admin_helper.rb +28 -0
  79. data/app/helpers/application_helper.rb +25 -0
  80. data/app/helpers/baskets_helper.rb +4 -0
  81. data/app/helpers/categories_helper.rb +4 -0
  82. data/app/helpers/orders_helper.rb +4 -0
  83. data/app/helpers/products_helper.rb +4 -0
  84. data/app/helpers/purchases_helper.rb +4 -0
  85. data/app/helpers/shop_helper.rb +17 -0
  86. data/app/helpers/suppliers_helper.rb +4 -0
  87. data/app/mailers/.keep +0 -0
  88. data/app/models/basket.rb +114 -0
  89. data/app/models/category.rb +27 -0
  90. data/app/models/clerk.rb +46 -0
  91. data/app/models/item.rb +26 -0
  92. data/app/models/order.rb +72 -0
  93. data/app/models/product.rb +100 -0
  94. data/app/models/purchase.rb +26 -0
  95. data/app/models/supplier.rb +15 -0
  96. data/app/views/addresses/_form_fields.html.haml +4 -0
  97. data/app/views/addresses/show.html.haml +23 -0
  98. data/app/views/baskets/_small.html.haml +25 -0
  99. data/app/views/baskets/edit.html.haml +74 -0
  100. data/app/views/baskets/index.html.haml +55 -0
  101. data/app/views/baskets/show.html.haml +18 -0
  102. data/app/views/categories/_treeview.html.haml +8 -0
  103. data/app/views/categories/_triple.html.haml +12 -0
  104. data/app/views/categories/edit.html.haml +36 -0
  105. data/app/views/categories/index.html.haml +49 -0
  106. data/app/views/categories/show.html.haml +30 -0
  107. data/app/views/clerks/edit.html.haml +20 -0
  108. data/app/views/clerks/index.html.haml +37 -0
  109. data/app/views/clerks/show.html.haml +19 -0
  110. data/app/views/layouts/_admin_menu.html.haml +14 -0
  111. data/app/views/layouts/_google.haml +8 -0
  112. data/app/views/layouts/_messages.html.haml +5 -0
  113. data/app/views/layouts/admin.html.haml +42 -0
  114. data/app/views/layouts/shop.html.haml +96 -0
  115. data/app/views/orders/edit.html.haml +25 -0
  116. data/app/views/orders/index.html.haml +93 -0
  117. data/app/views/orders/show.html.haml +37 -0
  118. data/app/views/products/_line.html.haml +25 -0
  119. data/app/views/products/_name.html.haml +4 -0
  120. data/app/views/products/_online.html.haml +2 -0
  121. data/app/views/products/_triple.html.haml +12 -0
  122. data/app/views/products/edit.html.haml +42 -0
  123. data/app/views/products/index.html.haml +86 -0
  124. data/app/views/products/show.html.haml +94 -0
  125. data/app/views/purchases/edit.html.haml +9 -0
  126. data/app/views/purchases/index.html.haml +52 -0
  127. data/app/views/purchases/show.html.haml +32 -0
  128. data/app/views/sessions/new.html.haml +10 -0
  129. data/app/views/sessions/new_clerk.html.haml +22 -0
  130. data/app/views/shop/_groups.html.haml +8 -0
  131. data/app/views/shop/_menu.html.haml +5 -0
  132. data/app/views/shop/_sub.html.haml +0 -0
  133. data/app/views/shop/checkout.haml +142 -0
  134. data/app/views/shop/group.html.haml +9 -0
  135. data/app/views/shop/main_group.html.haml +9 -0
  136. data/app/views/shop/order.haml +104 -0
  137. data/app/views/shop/product.html.haml +67 -0
  138. data/app/views/shop/product_list.html.haml +50 -0
  139. data/app/views/shop/saved_search.html +6 -0
  140. data/app/views/shop/sub_group.html.haml +19 -0
  141. data/app/views/shop/tuotteista.html.haml +40 -0
  142. data/app/views/shop/welcome.html.haml +103 -0
  143. data/app/views/suppliers/edit.html.haml +10 -0
  144. data/app/views/suppliers/index.html.haml +40 -0
  145. data/app/views/suppliers/show.html.haml +27 -0
  146. data/bin/bundle +3 -0
  147. data/bin/rails +8 -0
  148. data/bin/rake +8 -0
  149. data/bin/rspec +7 -0
  150. data/bin/spring +18 -0
  151. data/config.ru +7 -0
  152. data/config/application.example.yml +13 -0
  153. data/config/application.rb +59 -0
  154. data/config/application.yml +13 -0
  155. data/config/boot.rb +7 -0
  156. data/config/database.yml +25 -0
  157. data/config/environment.rb +6 -0
  158. data/config/environments/development.rb +31 -0
  159. data/config/environments/production.rb +80 -0
  160. data/config/environments/test.rb +36 -0
  161. data/config/i18n-tasks.yml +11 -0
  162. data/config/initializers/attack.rb +32 -0
  163. data/config/initializers/backtrace_silencers.rb +7 -0
  164. data/config/initializers/filter_parameter_logging.rb +4 -0
  165. data/config/initializers/inflections.rb +16 -0
  166. data/config/initializers/migrate.rb +6 -0
  167. data/config/initializers/mime_types.rb +6 -0
  168. data/config/initializers/secret_token.rb +12 -0
  169. data/config/initializers/session_store.rb +3 -0
  170. data/config/initializers/simple_form.rb +149 -0
  171. data/config/initializers/simple_form_bootstrap.rb +48 -0
  172. data/config/initializers/wrap_parameters.rb +14 -0
  173. data/config/locales/config.yml +23 -0
  174. data/config/locales/en.yml +183 -0
  175. data/config/locales/fi.yml +211 -0
  176. data/config/routes.rb +108 -0
  177. data/db/migrate/20131224171442_clerks.rb +15 -0
  178. data/db/migrate/20131226110406_suppliers.rb +9 -0
  179. data/db/migrate/20131226143612_categories.rb +18 -0
  180. data/db/migrate/20131226143747_items.rb +14 -0
  181. data/db/migrate/20131226144134_orders.rb +21 -0
  182. data/db/migrate/20131226144316_baskets.rb +12 -0
  183. data/db/migrate/20131226151151_purchases.rb +11 -0
  184. data/db/migrate/20131226151332_products.rb +27 -0
  185. data/db/schema.rb +138 -0
  186. data/db/seeds.rb +4 -0
  187. data/lib/assets/.keep +0 -0
  188. data/lib/office_clerk.rb +20 -0
  189. data/lib/office_clerk/shipping_method.rb +35 -0
  190. data/lib/tasks/.keep +0 -0
  191. data/lib/tasks/import.rake +33 -0
  192. data/lib/templates/haml/scaffold/_form.html.haml +8 -0
  193. data/office_clerk.gemspec +46 -0
  194. data/public/404.html +58 -0
  195. data/public/422.html +58 -0
  196. data/public/500.html +57 -0
  197. data/public/favicon.ico +0 -0
  198. data/public/humans.txt +20 -0
  199. data/public/robots.txt +5 -0
  200. data/spec/controllers/baskets_controller_spec.rb +112 -0
  201. data/spec/controllers/categories_controller_spec.rb +133 -0
  202. data/spec/controllers/clerks_controller_spec.rb +70 -0
  203. data/spec/controllers/orders_controller_spec.rb +105 -0
  204. data/spec/controllers/products_controller_spec.rb +138 -0
  205. data/spec/controllers/purchases_controller_spec.rb +123 -0
  206. data/spec/controllers/sessions_controller_spec.rb +18 -0
  207. data/spec/controllers/suppliers_controller_spec.rb +137 -0
  208. data/spec/factories/addresses.rb +11 -0
  209. data/spec/factories/baskets.rb +15 -0
  210. data/spec/factories/categories.rb +8 -0
  211. data/spec/factories/clerks.rb +15 -0
  212. data/spec/factories/items.rb +19 -0
  213. data/spec/factories/orders.rb +9 -0
  214. data/spec/factories/products.rb +10 -0
  215. data/spec/factories/purchases.rb +10 -0
  216. data/spec/factories/suppliers.rb +7 -0
  217. data/spec/features/baskets/buttons_spec.rb +35 -0
  218. data/spec/features/baskets/edit_spec.rb +50 -0
  219. data/spec/features/baskets/index_spec.rb +12 -0
  220. data/spec/features/baskets/search_spec.rb +33 -0
  221. data/spec/features/baskets/show_spec.rb +21 -0
  222. data/spec/features/categories_spec.rb +21 -0
  223. data/spec/features/clerks_spec.rb +21 -0
  224. data/spec/features/orders_spec.rb +14 -0
  225. data/spec/features/products/index_spec.rb +30 -0
  226. data/spec/features/products/new_spec.rb +20 -0
  227. data/spec/features/purchases_spec.rb +14 -0
  228. data/spec/features/shops_spec.rb +18 -0
  229. data/spec/features/suppliers_spec.rb +18 -0
  230. data/spec/models/baskets/inventory_spec.rb +34 -0
  231. data/spec/models/baskets/items_spec.rb +37 -0
  232. data/spec/models/baskets/products_spec.rb +31 -0
  233. data/spec/models/category_spec.rb +18 -0
  234. data/spec/models/clerk/email_spec.rb +69 -0
  235. data/spec/models/clerk/password_spec.rb +83 -0
  236. data/spec/models/item_spec.rb +33 -0
  237. data/spec/models/locale_spec.rb +17 -0
  238. data/spec/models/order_spec.rb +19 -0
  239. data/spec/models/product_spec.rb +29 -0
  240. data/spec/models/purchase_spec.rb +8 -0
  241. data/spec/models/supplier_spec.rb +8 -0
  242. data/spec/routing/baskets_routing_spec.rb +35 -0
  243. data/spec/routing/categories_routing_spec.rb +35 -0
  244. data/spec/routing/orders_routing_spec.rb +35 -0
  245. data/spec/routing/products_routing_spec.rb +35 -0
  246. data/spec/routing/purchases_routing_spec.rb +35 -0
  247. data/spec/routing/suppliers_routing_spec.rb +35 -0
  248. data/spec/spec_helper.rb +74 -0
  249. data/spec/support/request_helper.rb +31 -0
  250. data/test/fixtures/baskets.yml +41993 -0
  251. data/test/fixtures/categories.yml +638 -0
  252. data/test/fixtures/clerks.yml +694 -0
  253. data/test/fixtures/items.yml +83651 -0
  254. data/test/fixtures/orders.yml +58918 -0
  255. data/test/fixtures/products.yml +110904 -0
  256. data/test/fixtures/purchases.yml +1755 -0
  257. data/test/fixtures/suppliers.yml +341 -0
  258. data/todo +8 -0
  259. metadata +607 -0
@@ -0,0 +1,27 @@
1
+ class Category < ActiveRecord::Base
2
+
3
+ default_scope { order('position') }
4
+
5
+ has_many :products, :dependent => :nullify
6
+ has_many :categories, :dependent => :nullify
7
+ belongs_to :category
8
+ has_attached_file :main_picture
9
+ has_attached_file :extra_picture
10
+
11
+ validates :name, :presence => true
12
+ validates :link, presence: true, :if => :generate_url_if_needed
13
+
14
+ scope :online, -> { where(:online => true) }
15
+
16
+ def generate_url_if_needed
17
+ if self.link.blank? && self.name != nil
18
+ self.link = self.name.gsub(" " , "_").downcase
19
+ end
20
+ true
21
+ end
22
+
23
+ # just a shorthand to apply Products.shop_products scope to the groups products
24
+ def shop_products
25
+ products.shop_products
26
+ end
27
+ end
@@ -0,0 +1,46 @@
1
+ require 'bcrypt'
2
+
3
+ class Clerk < ActiveRecord::Base
4
+ PEPPER = "0bfa9e2cb4a5efd0d976518a3d82e345060547913d2fd1dd2f32b0c8dbbbb5d3dc20b86d0fed31aca9513bccdf51643700ea277d9c64d9ce8ef886bf39293453"
5
+ has_many :basket
6
+
7
+ attr_accessor :password , :password_confirmation
8
+
9
+ before_validation :prepare_password
10
+ validates_presence_of :password, :on => :create
11
+ validates_confirmation_of :password
12
+ validates_presence_of :encrypted_password
13
+
14
+ validates_uniqueness_of :email
15
+ validates :email, :presence => true, :email => true # extra gem to check email validity
16
+
17
+ store :address, accessors: [ :name , :street , :city , :phone ] #, coder: JSON
18
+
19
+ def whole_address
20
+ [ name , street , city , phone ].join(" ")
21
+ end
22
+
23
+ def valid_password?(password)
24
+ return false if password.blank?
25
+ encrypted = encrypt_password(password)
26
+ return false if self.encrypted_password.blank? || encrypted.bytesize != self.encrypted_password.bytesize
27
+ left = encrypted.unpack "C#{encrypted.bytesize}"
28
+ res = 0
29
+ self.encrypted_password.each_byte { |byte| res |= byte ^ left.shift }
30
+ res == 0
31
+ end
32
+
33
+ private
34
+
35
+ def prepare_password
36
+ unless password.blank?
37
+ self.password_salt = BCrypt::Engine.generate_salt
38
+ self.encrypted_password = encrypt_password(password)
39
+ end
40
+ end
41
+ def encrypt_password(password)
42
+ encrypted = [password, self.password_salt].flatten.join('')
43
+ 20.times { encrypted = Digest::SHA512.hexdigest(encrypted) }
44
+ encrypted
45
+ end
46
+ end
@@ -0,0 +1,26 @@
1
+ class Item < ActiveRecord::Base
2
+ belongs_to :basket , :touch => true
3
+ belongs_to :product
4
+
5
+ validates :name, :presence => true
6
+
7
+ # total price, ie quantity times price
8
+ def total
9
+ self.price * self.quantity
10
+ end
11
+
12
+ def product
13
+ Product.unscoped.find self.product_id
14
+ end
15
+
16
+ # tax included in the total
17
+ def tax_amount
18
+ ((self.total * self.tax ) / 100.0).round(2)
19
+ end
20
+
21
+ #relation of price to product price down from 100 %
22
+ def discount
23
+ return 0 unless product.price and product.price != 0
24
+ (100 - (price/product.price)*100).round(0)
25
+ end
26
+ end
@@ -0,0 +1,72 @@
1
+ class Order < ActiveRecord::Base
2
+ has_one :basket , :as => :kori , :autosave => true
3
+
4
+ store :address, accessors: [ :name , :street , :city , :phone ] #, coder: JSON
5
+
6
+ after_validation :generate_order_number, :on => :create
7
+
8
+ validates :name, :presence => true , :if => :needs_address?
9
+ validates :street,:presence => true , :if => :needs_address?
10
+ validates :city, :presence => true , :if => :needs_address?
11
+ validates :phone, :presence => true , :if => :needs_address?
12
+ validates :email, :presence => true , :email => true # extra gem to check email validity
13
+
14
+ default_scope { order('created_at DESC')}
15
+
16
+ # many a european goverment requires buisnesses to have running order/transaction numbers.
17
+ # this is what we use, but it can easily be changed by redifining this method
18
+ # format RYYYYRUNIN R, 4 digit year and a running number
19
+ def generate_order_number
20
+ if last = Order.first # last, but default order is reversed
21
+ num = last.number[5..9].to_i + 1
22
+ else
23
+ num = 30000
24
+ end
25
+ self.number = "R#{Time.now.year}#{num}"
26
+ end
27
+
28
+ def total_price
29
+ basket.total_price + shipment_price
30
+ end
31
+
32
+ # total tax is for when the rates don't matter, usually to cutomers.
33
+ # only on bills or invoices do we need the detailed list you get from the taxes function
34
+ def total_tax
35
+ basket.total_tax + shipment_tax*shipment_price
36
+ end
37
+
38
+ # return a hash of rate => amount , because products may have different taxes,
39
+ # the items in an order may have a collection of tax rates.
40
+ def taxes
41
+ cart = basket.taxes
42
+ s_tax = self.shipment_price * shipment_tax
43
+ #relies on basket creating a default value of 0
44
+ cart[self.shipment_tax] += s_tax if self.shipment_tax and self.shipment_tax != 0
45
+ cart
46
+ end
47
+
48
+ #quick checkout, ie ship (hand over) and pay (externally)
49
+ def pos_checkout email
50
+ self.ordered_on = Date.today unless self.ordered_on
51
+ self.paid_on = Date.today unless self.paid_on
52
+ self.shipped_on = Date.today unless self.shipped_on
53
+ self.shipment_price = 0 unless self.shipment_price
54
+ self.shipment_tax = 0 unless self.shipment_tax
55
+ self.email = email
56
+ self.basket.deduct!
57
+ end
58
+
59
+ def shipment_type= typ
60
+ write_attribute(:shipment_type, typ)
61
+ calc = OfficeClerk::ShippingMethod.all[typ.to_sym]
62
+ cost = calc.price_for(self.basket)
63
+ self.shipment_price = cost
64
+ end
65
+
66
+ private
67
+ # the name says a lot ,but what for? for shipping. For pickup or store sales we don't need an address
68
+ def needs_address?
69
+ return false unless shipment_type
70
+ return shipment_type != "pickup"
71
+ end
72
+ end
@@ -0,0 +1,100 @@
1
+ # RubyClerk product may represent one of three things
2
+ # - the normal product (all attributes work as expected)
3
+ # - a product line, in which case it may not be sold and inventory is the sum of the items
4
+ # other attributes (like price/cost) act as a template and are copied into children
5
+ # where they are then used.
6
+ # - an (line) item of a product line, in which case the default view concatenates name and description and
7
+ # shows prices relative to it's parent
8
+ #
9
+ # This makes the model a three headed one and validation is a little more complicated
10
+ # - a product line _may not_ have an ean (as it can't be sold)
11
+ # - a product and product line _must_ have a link , but (those are generated if needed)
12
+ # - line item _may not_ have a link (this is enforced, not validated)
13
+
14
+ # attributes : see permitted_attributes + inventory + deleted_on
15
+
16
+ class Product < ActiveRecord::Base
17
+ has_many :products
18
+ store :properties, accessors: [ :color, :size , :model_number ] #, coder: JSON
19
+ belongs_to :product
20
+ belongs_to :category
21
+ belongs_to :supplier
22
+ has_attached_file :main_picture
23
+ has_attached_file :extra_picture
24
+
25
+ # default product scope only lists non-deleted products
26
+ default_scope {where(:deleted_on => nil).order('created_at DESC') }
27
+ scope :online, -> { where(:online => true) }
28
+ scope :no_items, -> { where(:product_id => nil) }
29
+ scope :with_inventory, -> { where("inventory > 0") }
30
+ scope :shop_products , -> { online.no_items.with_inventory }
31
+
32
+ validates :price, :numericality => true
33
+ validates :cost, :numericality => true
34
+ validates :name, :presence => true
35
+
36
+ before_save :generate_url
37
+ before_save :adjust_cost
38
+ after_save :update_line_inventory , :if => :product_id
39
+
40
+
41
+ def update_line_inventory
42
+ parent = self.product
43
+ return unless parent
44
+ inv = parent.inventory
45
+ parent.inventory = parent.products.sum(:inventory)
46
+ parent.save! if inv != parent.inventory
47
+ end
48
+
49
+ # 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?
53
+ self.link = ""
54
+ else
55
+ self.link = name.gsub(" " , "_").downcase if link.blank? && name != nil
56
+ end
57
+ end
58
+
59
+ def adjust_cost
60
+ if self.cost == 0.0
61
+ self.cost = self.price / 2
62
+ end
63
+ end
64
+ def deleted?
65
+ not deleted_on.blank?
66
+ end
67
+
68
+ def delete
69
+ self.deleted_on = Date.today
70
+ self.link = ""
71
+ products.each {|p| p.delete}
72
+ self
73
+ end
74
+
75
+ #this product represents a product line (ie is not sellable in itself)
76
+ def line?
77
+ !line_item? and !products.empty?
78
+ end
79
+
80
+ def full_name
81
+ if line_item?
82
+ product.name + " : " + self.name
83
+ else
84
+ self.name
85
+ end
86
+ 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
+
96
+ def new_line_item
97
+ Product.new :tax => self.tax , :weight => self.weight , :cost => self.cost , :product_id => self.id ,
98
+ :supplier_id => self.supplier_id , :category_id => self.category_id , :price => self.price
99
+ end
100
+ end
@@ -0,0 +1,26 @@
1
+ class Purchase < ActiveRecord::Base
2
+
3
+ has_one :basket , :as => :kori , :autosave => true
4
+
5
+ def order!
6
+ self.ordered_on = Date.today
7
+ save!
8
+ end
9
+
10
+ def receive!
11
+ items = basket.receive!
12
+ self.ordered_on = Date.today unless ordered_on
13
+ self.received_on = Date.today
14
+ self.save!
15
+ items
16
+ end
17
+
18
+ def inventory!
19
+ items = basket.receive!
20
+ self.ordered_on = Date.today unless ordered_on
21
+ self.received_on = Date.today
22
+ self.save!
23
+ items
24
+ end
25
+
26
+ end
@@ -0,0 +1,15 @@
1
+ class Supplier < ActiveRecord::Base
2
+
3
+ default_scope { order('supplier_name') }
4
+
5
+ has_many :products, :dependent => :nullify
6
+
7
+ store :address, accessors: [ :name , :street , :city , :country , :phone ] #, coder: JSON
8
+
9
+ validates :supplier_name, :presence => true
10
+
11
+ def whole_address
12
+ [ name , street , city , country , phone ].join(" ")
13
+ end
14
+
15
+ end
@@ -0,0 +1,4 @@
1
+ = f.input :name
2
+ = f.input :street
3
+ = f.input :city
4
+ = f.input :phone
@@ -0,0 +1,23 @@
1
+ %h2
2
+ = t(:show)
3
+ = t(:address)
4
+ %p
5
+ %b
6
+ = t(:first_name) + " : " + @address.first_name
7
+ %p
8
+ %b
9
+ = t(:last_name) + " : " + @address.last_name
10
+ %p
11
+ %b
12
+ = t(:street1) + " : " + @address.street1
13
+ %p
14
+ %b
15
+ = t(:street2) + " : " + @address.street2
16
+ %p
17
+ %b
18
+ = t(:city) + " : " + @address.city
19
+ %p
20
+ %b
21
+ = t(:country) + " : " + @address.country
22
+ = link_to t(:edit), edit_address_path(@address), :class => "btn btn-success"
23
+ = link_to t(:back), addresses_path, :class => "btn btn-warning"
@@ -0,0 +1,25 @@
1
+ .col-md-12
2
+ %table.table.table-striped.table-bordered.table-condensed
3
+ %thead
4
+ %tr
5
+ %th= t(:picture)
6
+ %th= t(:name)
7
+ %th= t(:quantity)
8
+ %th= t(:price)
9
+ %tbody
10
+ - basket.items.each do |item|
11
+ %tr{:class => "line-#{cycle("1","2")}"}
12
+ %td= image_tag item.product.main_picture.url(:thumb) if item.product
13
+ %td=link_to_if item.product, item.name , product_path(item.product)
14
+ %td= item.quantity
15
+ %td= euros item.price
16
+ %tr
17
+ %td{:colspan => 2}
18
+ %b
19
+ = basket_edit_link basket
20
+ %td
21
+ = t(:quantity)
22
+ = basket.quantity
23
+ %td
24
+ = t(:total)
25
+ = euros basket.total_price
@@ -0,0 +1,74 @@
1
+ .row
2
+ .col-md-12
3
+ -if @basket.kori_type
4
+ %h2
5
+ = t(@basket.kori_type.downcase)
6
+ .row
7
+ .col-md-8.ean_form
8
+ = label_tag :ean
9
+ = form_tag ean_basket_path(@basket) do |f|
10
+ = text_field_tag :ean , "" , :autofocus => true , :class => "ean_field" , :size => 45
11
+ = submit_tag t(:search) , :class => "btn btn-xs ean_submit"
12
+ .col-md-3
13
+ = t(:discount )
14
+ = form_tag edit_basket_path(@basket) , :method => :get do
15
+ = select_tag("discount" , options_for_select( ["",0,5,10,15,20,25,30,35,40,50,60,70] ), :onchange => "window.location.href='#{discount_basket_path(@basket)}?discount=' + this.value")
16
+ .row
17
+ = form_for(@basket) do |f|
18
+ %table.table.table-striped.table-bordered.table-condensed
19
+ %thead
20
+ %tr
21
+ %th= t(:picture)
22
+ %th= t(:name)
23
+ %th= t(:quantity)
24
+ %th= t(:price)
25
+ %th= t(:discount)
26
+ %th{:colspan => "3"}= t(:actions)
27
+ %tbody
28
+ - @basket.items.each do |item|
29
+ %tr{:class => "line-#{cycle("1","2")}"}
30
+ %td.picture= image_tag item.product.main_picture.url(:thumb) if item.product
31
+ %td.name= item.name
32
+ = f.simple_fields_for :items , item do |iform|
33
+ %td= iform.text_field :quantity , :label => false
34
+ %td= iform.text_field :price , :label => false
35
+ %td
36
+ = select_tag("discount" , options_for_select( [item.discount,0,5,10,15,20,25,30,35,40,50,60,70] ), :onchange => "window.location.href='#{discount_basket_path(@basket)}?item=#{item.id}&discount=' + this.value" )
37
+ %td
38
+ = link_to t(:delete) , edit_basket_path(:delete => item.product.id)
39
+ = link_to t(:add) , edit_basket_path(:add => item.product.id)
40
+
41
+ %tr
42
+ %td{:colspan => 4}
43
+ %td.count
44
+ = t(:count)
45
+ = @basket.items.length
46
+ %td.total
47
+ = t(:total)
48
+ = euros @basket.total_price
49
+
50
+
51
+ .row.form-actions
52
+ .col-md-2
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?
59
+ .col-md-2
60
+ - link_to t(:make_order), order_basket_path(@basket), :class => "btn btn-primary make_order"
61
+ = link_to t(:make_purchase), purchase_basket_path(@basket), :class => "btn btn-primary make_purchase"
62
+ .col-md-2{"data-no-turbolink" => true}
63
+ = link_to t(:checkout), checkout_basket_path(@basket) , :target => "_blank" , :class => "btn btn-primary print_order"
64
+ - elsif @basket.isa(:order)
65
+ .col-md-2
66
+ = link_to t(:to_order), order_path(@basket.kori), :class => "btn btn-primary to_order"
67
+ .col-md-2{"data-no-turbolink" => true}
68
+ = link_to t(:print_order), invoice_order_path(@basket.kori), :target => "_blank" , :class => "btn btn-primary print_order" , :target => "_blank"
69
+ - elsif @basket.isa(:purchase)
70
+ .col-md-2
71
+ = link_to t(:to_purchase), purchase_path(@basket.kori), :class => "btn btn-primary to_purchase"
72
+
73
+ .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