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.
- checksums.yaml +7 -0
- data/.gitignore +73 -0
- data/.rubocop.yml +6 -0
- data/.travis.yml +9 -0
- data/Gemfile +63 -0
- data/Gemfile.lock +367 -0
- data/Guardfile +28 -0
- data/README.md +44 -0
- data/Rakefile +6 -0
- data/app/assets/images/admin/baskets-2.jpg +0 -0
- data/app/assets/images/admin/baskets.jpg +0 -0
- data/app/assets/images/admin/categories-2.jpg +0 -0
- data/app/assets/images/admin/categories.jpg +0 -0
- data/app/assets/images/admin/clerks-2.jpg +0 -0
- data/app/assets/images/admin/clerks.jpg +0 -0
- data/app/assets/images/admin/orders-2.jpg +0 -0
- data/app/assets/images/admin/orders.jpg +0 -0
- data/app/assets/images/admin/products-2.jpg +0 -0
- data/app/assets/images/admin/products.jpg +0 -0
- data/app/assets/images/admin/purchases-2.jpg +0 -0
- data/app/assets/images/admin/purchases.jpg +0 -0
- data/app/assets/images/admin/suppliers-2.jpg +0 -0
- data/app/assets/images/admin/suppliers.jpg +0 -0
- data/app/assets/images/alpha.png +0 -0
- data/app/assets/images/down-icon.png +0 -0
- data/app/assets/images/header.gif +0 -0
- data/app/assets/images/hihih.jpg +0 -0
- data/app/assets/images/hue.png +0 -0
- data/app/assets/images/koe.jpg +0 -0
- data/app/assets/images/missing.png +0 -0
- data/app/assets/images/saturation.png +0 -0
- data/app/assets/images/shop/basket.jpg +0 -0
- data/app/assets/images/shop/farfalla-logo.jpg +0 -0
- data/app/assets/images/shop/header.gif +0 -0
- data/app/assets/images/shop/header.jpg +0 -0
- data/app/assets/images/shop/header.png +0 -0
- data/app/assets/images/shop/kansi.jpg +0 -0
- data/app/assets/images/shop/kansi.png +0 -0
- data/app/assets/images/shop/logo.png +0 -0
- data/app/assets/images/shop/menu-gourmet.jpg +0 -0
- data/app/assets/images/shop/menu-hius.jpg +0 -0
- data/app/assets/images/shop/menu-huonetuoksu.jpg +0 -0
- data/app/assets/images/shop/menu-info.jpg +0 -0
- data/app/assets/images/shop/menu-kasvo.jpg +0 -0
- data/app/assets/images/shop/menu-laukut.jpg +0 -0
- data/app/assets/images/shop/menu-luomu.jpg +0 -0
- data/app/assets/images/shop/menu-men.jpg +0 -0
- data/app/assets/images/shop/menu-oljy.jpg +0 -0
- data/app/assets/images/shop/menu-tuoksu1.jpg +0 -0
- data/app/assets/images/shop/menu-vaatteet.jpg +0 -0
- data/app/assets/images/shop/menu-valaisin.jpg +0 -0
- data/app/assets/images/shop/menu-vartalo.jpg +0 -0
- data/app/assets/images/shop/spacer.gif +0 -0
- data/app/assets/images/shop/tausta.jpg +0 -0
- data/app/assets/images/shop/tervetuloa.gif +0 -0
- data/app/assets/images/shop/valk.png +0 -0
- data/app/assets/images/shop/valk3.png +0 -0
- data/app/assets/images/shop/violetti-lev.jpg +0 -0
- data/app/assets/images/shop/violetti.jpg +0 -0
- data/app/assets/images/ui-anim_basic_16x16.gif +0 -0
- data/app/assets/images/up-icon.png +0 -0
- data/app/assets/javascripts/admin.js +29 -0
- data/app/assets/javascripts/shop.js +30 -0
- data/app/assets/stylesheets/admin.css.scss +129 -0
- data/app/assets/stylesheets/shop-receipt.css.scss +80 -0
- data/app/assets/stylesheets/shop.css.scss +403 -0
- data/app/controllers/admin_controller.rb +23 -0
- data/app/controllers/application_controller.rb +41 -0
- data/app/controllers/baskets_controller.rb +176 -0
- data/app/controllers/categories_controller.rb +69 -0
- data/app/controllers/clerks_controller.rb +64 -0
- data/app/controllers/orders_controller.rb +57 -0
- data/app/controllers/products_controller.rb +126 -0
- data/app/controllers/purchases_controller.rb +72 -0
- data/app/controllers/sessions_controller.rb +46 -0
- data/app/controllers/shop_controller.rb +90 -0
- data/app/controllers/suppliers_controller.rb +63 -0
- data/app/helpers/admin_helper.rb +28 -0
- data/app/helpers/application_helper.rb +25 -0
- data/app/helpers/baskets_helper.rb +4 -0
- data/app/helpers/categories_helper.rb +4 -0
- data/app/helpers/orders_helper.rb +4 -0
- data/app/helpers/products_helper.rb +4 -0
- data/app/helpers/purchases_helper.rb +4 -0
- data/app/helpers/shop_helper.rb +17 -0
- data/app/helpers/suppliers_helper.rb +4 -0
- data/app/mailers/.keep +0 -0
- data/app/models/basket.rb +114 -0
- data/app/models/category.rb +27 -0
- data/app/models/clerk.rb +46 -0
- data/app/models/item.rb +26 -0
- data/app/models/order.rb +72 -0
- data/app/models/product.rb +100 -0
- data/app/models/purchase.rb +26 -0
- data/app/models/supplier.rb +15 -0
- data/app/views/addresses/_form_fields.html.haml +4 -0
- data/app/views/addresses/show.html.haml +23 -0
- data/app/views/baskets/_small.html.haml +25 -0
- data/app/views/baskets/edit.html.haml +74 -0
- data/app/views/baskets/index.html.haml +55 -0
- data/app/views/baskets/show.html.haml +18 -0
- data/app/views/categories/_treeview.html.haml +8 -0
- data/app/views/categories/_triple.html.haml +12 -0
- data/app/views/categories/edit.html.haml +36 -0
- data/app/views/categories/index.html.haml +49 -0
- data/app/views/categories/show.html.haml +30 -0
- data/app/views/clerks/edit.html.haml +20 -0
- data/app/views/clerks/index.html.haml +37 -0
- data/app/views/clerks/show.html.haml +19 -0
- data/app/views/layouts/_admin_menu.html.haml +14 -0
- data/app/views/layouts/_google.haml +8 -0
- data/app/views/layouts/_messages.html.haml +5 -0
- data/app/views/layouts/admin.html.haml +42 -0
- data/app/views/layouts/shop.html.haml +96 -0
- data/app/views/orders/edit.html.haml +25 -0
- data/app/views/orders/index.html.haml +93 -0
- data/app/views/orders/show.html.haml +37 -0
- data/app/views/products/_line.html.haml +25 -0
- data/app/views/products/_name.html.haml +4 -0
- data/app/views/products/_online.html.haml +2 -0
- data/app/views/products/_triple.html.haml +12 -0
- data/app/views/products/edit.html.haml +42 -0
- data/app/views/products/index.html.haml +86 -0
- data/app/views/products/show.html.haml +94 -0
- data/app/views/purchases/edit.html.haml +9 -0
- data/app/views/purchases/index.html.haml +52 -0
- data/app/views/purchases/show.html.haml +32 -0
- data/app/views/sessions/new.html.haml +10 -0
- data/app/views/sessions/new_clerk.html.haml +22 -0
- data/app/views/shop/_groups.html.haml +8 -0
- data/app/views/shop/_menu.html.haml +5 -0
- data/app/views/shop/_sub.html.haml +0 -0
- data/app/views/shop/checkout.haml +142 -0
- data/app/views/shop/group.html.haml +9 -0
- data/app/views/shop/main_group.html.haml +9 -0
- data/app/views/shop/order.haml +104 -0
- data/app/views/shop/product.html.haml +67 -0
- data/app/views/shop/product_list.html.haml +50 -0
- data/app/views/shop/saved_search.html +6 -0
- data/app/views/shop/sub_group.html.haml +19 -0
- data/app/views/shop/tuotteista.html.haml +40 -0
- data/app/views/shop/welcome.html.haml +103 -0
- data/app/views/suppliers/edit.html.haml +10 -0
- data/app/views/suppliers/index.html.haml +40 -0
- data/app/views/suppliers/show.html.haml +27 -0
- data/bin/bundle +3 -0
- data/bin/rails +8 -0
- data/bin/rake +8 -0
- data/bin/rspec +7 -0
- data/bin/spring +18 -0
- data/config.ru +7 -0
- data/config/application.example.yml +13 -0
- data/config/application.rb +59 -0
- data/config/application.yml +13 -0
- data/config/boot.rb +7 -0
- data/config/database.yml +25 -0
- data/config/environment.rb +6 -0
- data/config/environments/development.rb +31 -0
- data/config/environments/production.rb +80 -0
- data/config/environments/test.rb +36 -0
- data/config/i18n-tasks.yml +11 -0
- data/config/initializers/attack.rb +32 -0
- data/config/initializers/backtrace_silencers.rb +7 -0
- data/config/initializers/filter_parameter_logging.rb +4 -0
- data/config/initializers/inflections.rb +16 -0
- data/config/initializers/migrate.rb +6 -0
- data/config/initializers/mime_types.rb +6 -0
- data/config/initializers/secret_token.rb +12 -0
- data/config/initializers/session_store.rb +3 -0
- data/config/initializers/simple_form.rb +149 -0
- data/config/initializers/simple_form_bootstrap.rb +48 -0
- data/config/initializers/wrap_parameters.rb +14 -0
- data/config/locales/config.yml +23 -0
- data/config/locales/en.yml +183 -0
- data/config/locales/fi.yml +211 -0
- data/config/routes.rb +108 -0
- data/db/migrate/20131224171442_clerks.rb +15 -0
- data/db/migrate/20131226110406_suppliers.rb +9 -0
- data/db/migrate/20131226143612_categories.rb +18 -0
- data/db/migrate/20131226143747_items.rb +14 -0
- data/db/migrate/20131226144134_orders.rb +21 -0
- data/db/migrate/20131226144316_baskets.rb +12 -0
- data/db/migrate/20131226151151_purchases.rb +11 -0
- data/db/migrate/20131226151332_products.rb +27 -0
- data/db/schema.rb +138 -0
- data/db/seeds.rb +4 -0
- data/lib/assets/.keep +0 -0
- data/lib/office_clerk.rb +20 -0
- data/lib/office_clerk/shipping_method.rb +35 -0
- data/lib/tasks/.keep +0 -0
- data/lib/tasks/import.rake +33 -0
- data/lib/templates/haml/scaffold/_form.html.haml +8 -0
- data/office_clerk.gemspec +46 -0
- data/public/404.html +58 -0
- data/public/422.html +58 -0
- data/public/500.html +57 -0
- data/public/favicon.ico +0 -0
- data/public/humans.txt +20 -0
- data/public/robots.txt +5 -0
- data/spec/controllers/baskets_controller_spec.rb +112 -0
- data/spec/controllers/categories_controller_spec.rb +133 -0
- data/spec/controllers/clerks_controller_spec.rb +70 -0
- data/spec/controllers/orders_controller_spec.rb +105 -0
- data/spec/controllers/products_controller_spec.rb +138 -0
- data/spec/controllers/purchases_controller_spec.rb +123 -0
- data/spec/controllers/sessions_controller_spec.rb +18 -0
- data/spec/controllers/suppliers_controller_spec.rb +137 -0
- data/spec/factories/addresses.rb +11 -0
- data/spec/factories/baskets.rb +15 -0
- data/spec/factories/categories.rb +8 -0
- data/spec/factories/clerks.rb +15 -0
- data/spec/factories/items.rb +19 -0
- data/spec/factories/orders.rb +9 -0
- data/spec/factories/products.rb +10 -0
- data/spec/factories/purchases.rb +10 -0
- data/spec/factories/suppliers.rb +7 -0
- data/spec/features/baskets/buttons_spec.rb +35 -0
- data/spec/features/baskets/edit_spec.rb +50 -0
- data/spec/features/baskets/index_spec.rb +12 -0
- data/spec/features/baskets/search_spec.rb +33 -0
- data/spec/features/baskets/show_spec.rb +21 -0
- data/spec/features/categories_spec.rb +21 -0
- data/spec/features/clerks_spec.rb +21 -0
- data/spec/features/orders_spec.rb +14 -0
- data/spec/features/products/index_spec.rb +30 -0
- data/spec/features/products/new_spec.rb +20 -0
- data/spec/features/purchases_spec.rb +14 -0
- data/spec/features/shops_spec.rb +18 -0
- data/spec/features/suppliers_spec.rb +18 -0
- data/spec/models/baskets/inventory_spec.rb +34 -0
- data/spec/models/baskets/items_spec.rb +37 -0
- data/spec/models/baskets/products_spec.rb +31 -0
- data/spec/models/category_spec.rb +18 -0
- data/spec/models/clerk/email_spec.rb +69 -0
- data/spec/models/clerk/password_spec.rb +83 -0
- data/spec/models/item_spec.rb +33 -0
- data/spec/models/locale_spec.rb +17 -0
- data/spec/models/order_spec.rb +19 -0
- data/spec/models/product_spec.rb +29 -0
- data/spec/models/purchase_spec.rb +8 -0
- data/spec/models/supplier_spec.rb +8 -0
- data/spec/routing/baskets_routing_spec.rb +35 -0
- data/spec/routing/categories_routing_spec.rb +35 -0
- data/spec/routing/orders_routing_spec.rb +35 -0
- data/spec/routing/products_routing_spec.rb +35 -0
- data/spec/routing/purchases_routing_spec.rb +35 -0
- data/spec/routing/suppliers_routing_spec.rb +35 -0
- data/spec/spec_helper.rb +74 -0
- data/spec/support/request_helper.rb +31 -0
- data/test/fixtures/baskets.yml +41993 -0
- data/test/fixtures/categories.yml +638 -0
- data/test/fixtures/clerks.yml +694 -0
- data/test/fixtures/items.yml +83651 -0
- data/test/fixtures/orders.yml +58918 -0
- data/test/fixtures/products.yml +110904 -0
- data/test/fixtures/purchases.yml +1755 -0
- data/test/fixtures/suppliers.yml +341 -0
- data/todo +8 -0
- 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
|
data/app/models/clerk.rb
ADDED
|
@@ -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
|
data/app/models/item.rb
ADDED
|
@@ -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
|
data/app/models/order.rb
ADDED
|
@@ -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,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
|