diff --git a/Gemfile b/Gemfile index b1e78b35..b75e4073 100644 --- a/Gemfile +++ b/Gemfile @@ -33,6 +33,8 @@ gem 'couch_potato' , :git => 'git://github.com/bterkuile/couch_potato.git' gem 'simply_stored' , :git => 'git://github.com/bterkuile/simply_stored.git' gem 'devise', '2.0.4' gem 'devise_simply_stored' +gem 'simple_form' +gem 'draper' gem 'rqrcode' gem 'mini_magick' @@ -48,12 +50,16 @@ group :development do gem 'pry' gem 'pry-remote' gem 'rspec-rails' + gem 'rb-fsevent', :require => false if RUBY_PLATFORM =~ /darwin/i + gem 'guard-rspec' gem 'thin' end group :test do - gem 'steak' gem 'pry' + gem 'steak' + gem 'rb-fsevent', :require => false if RUBY_PLATFORM =~ /darwin/i + gem 'guard-rspec' gem 'factory_girl_rails' end diff --git a/Gemfile.lock b/Gemfile.lock index f48f5728..44dff7fe 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -9,7 +9,7 @@ GIT GIT remote: git://github.com/bterkuile/simply_stored.git - revision: 955a484c4046b3343f07fd0d783342d01bf26f66 + revision: 04f58b15bb308420f78c66d27ce4d4d3b58183ff specs: simply_stored (1.0.0) activesupport @@ -88,6 +88,9 @@ GEM devise_simply_stored (0.0.3) devise diff-lcs (1.1.3) + draper (0.18.0) + actionpack (~> 3.2) + activesupport (~> 3.2) erubis (2.7.0) eventmachine (1.0.0) execjs (1.4.0) @@ -99,6 +102,14 @@ GEM railties (>= 3.0.0) ffi (1.2.0) fssm (0.2.9) + guard (1.5.4) + listen (>= 0.4.2) + lumberjack (>= 1.0.2) + pry (>= 0.9.10) + thor (>= 0.14.6) + guard-rspec (2.3.0) + guard (>= 1.1) + rspec (~> 2.11) haml (3.1.7) haml-rails (0.3.5) actionpack (>= 3.1, < 4.1) @@ -127,6 +138,8 @@ GEM libwebsocket (0.1.7.1) addressable websocket + listen (0.6.0) + lumberjack (1.0.2) mail (2.4.4) i18n (>= 0.4.0) mime-types (~> 1.16) @@ -135,7 +148,7 @@ GEM mime-types (1.19) mini_magick (3.4) subexec (~> 0.2.1) - multi_json (1.3.7) + multi_json (1.4.0) mustache (0.99.4) nokogiri (1.5.5) orm_adapter (0.0.7) @@ -172,12 +185,17 @@ GEM rdoc (~> 3.4) thor (>= 0.14.6, < 2.0) rake (10.0.2) + rb-fsevent (0.9.2) rdoc (3.12) json (~> 1.4) rest-client (1.6.7) mime-types (>= 1.16) rqrcode (0.4.2) - rspec-core (2.12.0) + rspec (2.12.0) + rspec-core (~> 2.12.0) + rspec-expectations (~> 2.12.0) + rspec-mocks (~> 2.12.0) + rspec-core (2.12.1) rspec-expectations (2.12.0) diff-lcs (~> 1.1.3) rspec-mocks (2.12.0) @@ -199,6 +217,9 @@ GEM libwebsocket (~> 0.1.3) multi_json (~> 1.0) rubyzip + simple_form (2.0.4) + actionpack (~> 3.0) + activemodel (~> 3.0) slim (1.3.4) temple (~> 0.5.5) tilt (~> 1.3.3) @@ -208,7 +229,7 @@ GEM railties (~> 3.0) slim (~> 1.0) slop (3.3.3) - sprockets (2.2.1) + sprockets (2.2.2) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) @@ -229,7 +250,7 @@ GEM treetop (1.4.12) polyglot polyglot (>= 0.3.1) - twitter-bootstrap-rails (2.1.6) + twitter-bootstrap-rails (2.1.7) actionpack (>= 3.1) execjs railties (>= 3.1) @@ -252,7 +273,9 @@ DEPENDENCIES couch_potato! devise (= 2.0.4) devise_simply_stored + draper factory_girl_rails + guard-rspec haml-rails jquery-rails kaminari-bootstrap @@ -263,9 +286,11 @@ DEPENDENCIES pry-remote rack-cors rails (= 3.2.9) + rb-fsevent rqrcode rspec-rails sass-rails (~> 3.2.3) + simple_form simply_stored! slim-rails steak diff --git a/Guardfile b/Guardfile new file mode 100644 index 00000000..f5478504 --- /dev/null +++ b/Guardfile @@ -0,0 +1,24 @@ +# A sample Guardfile +# More info at https://github.com/guard/guard#readme + +guard 'rspec' do + watch(%r{^spec/.+_spec\.rb$}) + watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { "spec" } + + # Rails example + watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } + watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } + watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] } + watch(%r{^spec/support/(.+)\.rb$}) { "spec" } + watch('config/routes.rb') { "spec/routing" } + watch('app/controllers/application_controller.rb') { "spec/controllers" } + + # Capybara features specs + watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/features/#{m[1]}_spec.rb" } + + # Turnip features and steps + watch(%r{^spec/acceptance/(.+)\.feature$}) + watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' } +end + diff --git a/app/assets/javascripts/supplier/application.js b/app/assets/javascripts/supplier/application.js index 7a8d92b7..f7c7d0fc 100644 --- a/app/assets/javascripts/supplier/application.js +++ b/app/assets/javascripts/supplier/application.js @@ -9,13 +9,13 @@ // // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD // GO AFTER THE REQUIRES BELOW. -// //= require jquery //= require jquery_ujs //= require jquery-ui //= require twitter/bootstrap //= require mustache //= require faye +//= require supplier/base //= require_directory . //= require_self var path_mapping = { @@ -33,8 +33,5 @@ function redirect_to(mapping, variables){ window.location = path_mapping[mapping] + '?' + vars.join('&') } function currency(num) { - if (isNaN(num) || num === '' || num === null) { - num = 0.0; - } - return '€ ' + parseFloat(num).toFixed(2); + return Qwaiter.currency(num); } diff --git a/app/assets/javascripts/supplier/base.js.coffee b/app/assets/javascripts/supplier/base.js.coffee new file mode 100644 index 00000000..ae74d1de --- /dev/null +++ b/app/assets/javascripts/supplier/base.js.coffee @@ -0,0 +1,6 @@ +jQuery -> + $('#product-category-list').sortable + axis: 'y' + handle: '.handle' + update: -> + $.post($(this).data('update-url'), $(this).sortable('serialize')) diff --git a/app/assets/javascripts/user/quser.js.coffee b/app/assets/javascripts/user/quser.js.coffee index 27c66238..7e3f88fe 100644 --- a/app/assets/javascripts/user/quser.js.coffee +++ b/app/assets/javascripts/user/quser.js.coffee @@ -283,29 +283,23 @@ class Quser @populate_products_table('/user/list_products_for_table.json?'+authentication_string+'&table_id='+table_id) populate_products_table: (src)-> $.getJSON(data_host + src, (res) => - if res.has_occupied_info - include_order_buttons = !res.is_occupied - delete(res['has_occupied_info']) - delete(res['is_occupied']) - else - include_order_buttons = true - body = $('#products-table tbody') - if res.table_number - $('.table-number').text(res.table_number) - delete(res['table_number']) - if res.supplier_name - $('.supplier-name').text(res.supplier_name) - delete(res['supplier_name']) + include_order_buttons = res.my_list + + $('.table-number').text(res.table_number) if res.table_number + $('.supplier-name').text(res.supplier_name) if res.supplier_name + window.products = {} + body = $('#products-table tbody') body.find('tr').remove() script_id = if include_order_buttons then '#products-category-for-order-template' else '#products-category-template' - for category, products of res + #for category, products of res + for category in res.categories body.append @mustache(script_id, - category: category, - products: products, + category: category.name, + products: category.products, include_order_buttons: include_order_buttons ) - for product in products + for product in category.products window.products[product._id] = product ) increment_products_counter: (product_id)-> diff --git a/app/assets/stylesheets/supplier/product_categories.css.sass b/app/assets/stylesheets/supplier/product_categories.css.sass new file mode 100644 index 00000000..6fa1fc03 --- /dev/null +++ b/app/assets/stylesheets/supplier/product_categories.css.sass @@ -0,0 +1,7 @@ +#product-category-list + list-style: none + li + clear: both + margin-bottom: 8px + .name + padding: 5px 5px diff --git a/app/assets/stylesheets/supplier/structure.css.sass b/app/assets/stylesheets/supplier/structure.css.sass index 23e078b7..2335edec 100644 --- a/app/assets/stylesheets/supplier/structure.css.sass +++ b/app/assets/stylesheets/supplier/structure.css.sass @@ -26,3 +26,7 @@ body border: 1px solid black padding: 2px display: inline-block +.handle + cursor: move + font-size: 0.8em + color: #777 diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 37a3a0ba..423a26f7 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -19,7 +19,7 @@ class DashboardController < ApplicationController # Testing action def select_qrcode - @tables = Table.all.sample(2) + @tables = Table.all.sample(2) | List.active.map(&:table) render layout: 'phone' end diff --git a/app/controllers/suppliers/product_categories_controller.rb b/app/controllers/suppliers/product_categories_controller.rb index b536c2f4..9e8c10b8 100644 --- a/app/controllers/suppliers/product_categories_controller.rb +++ b/app/controllers/suppliers/product_categories_controller.rb @@ -4,7 +4,7 @@ module Suppliers # GET /product_categories # GET /product_categories.json def index - @product_categories = current_supplier.product_categories + @product_categories = current_supplier.product_categories.sort_by(&:position) respond_to do |format| format.html # index.html.erb @@ -83,5 +83,14 @@ module Suppliers format.json { head :no_content } end end + + # POST /supplier/product_categories/sort + # params ~= product_category: ['abc', 'def', 'another id', ...] + def sort + @product_categories = ProductCategory.find(params[:product_category]) + 1.upto(@product_categories.size){|i| @product_categories[i-1].position = i} + CouchPotato.database.couchrest_database.bulk_save(@product_categories) + render nothing: true + end end end diff --git a/app/controllers/suppliers/products_controller.rb b/app/controllers/suppliers/products_controller.rb index da8bf91c..a78e77c0 100644 --- a/app/controllers/suppliers/products_controller.rb +++ b/app/controllers/suppliers/products_controller.rb @@ -4,7 +4,7 @@ module Suppliers # GET /products # GET /products.json def index - @products = current_supplier.products + @products = ProductDecorator.decorate(current_supplier.products) respond_to do |format| format.html # index.html.erb @@ -15,7 +15,7 @@ module Suppliers # GET /products/1 # GET /products/1.json def show - @product = Product.find(params[:id]) + @product = ProductDecorator.find(params[:id]) respond_to do |format| format.html # show.html.erb @@ -27,7 +27,6 @@ module Suppliers # GET /products/new.json def new @product = Product.new - @product.product_category_id = params[:product_category_id].presence respond_to do |format| format.html # new.html.erb diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb index bbe935d7..b787fcd1 100644 --- a/app/controllers/user_controller.rb +++ b/app/controllers/user_controller.rb @@ -85,11 +85,17 @@ class UserController < ApplicationController handle_message_params end format.json do - products = list.supplier.products - products.include_relation(:product_categories) - products.sort_by!{|p| p.product_category.try(:position) || 90000} - h = products.inject({table_number: list.table_number, supplier_name: @supplier.name}){|h, p| n = p.product_category.try(:name) || 'other'; h[n] ||= []; h[n] << p; h} + h = ProductCategory.for_user(current_user, table: list.table, list: list) # list is performance parameter render json: h + #products = list.supplier.products + #product_categories = list.supplier.product_categories + #other = product_categories.find(&:other?) || (product_categories << ProductCategory.other).last # Container for non categorized products + + #product_categories.sort_by!{|p| p.product_category.try(:position) || 90000} + #h = {table_number: list.table_number, supplier_name: @supplier.name} + #h[:categories] = product_categories.map{|pc| {pc.name => pc.product_ids.map{|p| p.as_json}}} + #){|h, p| n = p.product_category.try(:name) || 'other'; h[n] ||= []; h[n] << p; h} + #render json: h end end end @@ -100,15 +106,7 @@ class UserController < ApplicationController format.html do end format.json do - products = @table.supplier.products - products.include_relation(:product_categories) - products.sort_by!{|p| p.product_category.try(:position) || 90000} - h = products.inject({ - table_number: @table.number, - supplier_name: @table.supplier.name, - has_occupied_info: true, - is_occupied: @table.occupied? - }){|h, p| n = p.product_category.try(:name) || 'other'; h[n] ||= []; h[n] << p; h} + h = ProductCategory.for_user(current_user, table: @table) render json: h end end diff --git a/app/decorators/product_decorator.rb b/app/decorators/product_decorator.rb new file mode 100644 index 00000000..97484ba9 --- /dev/null +++ b/app/decorators/product_decorator.rb @@ -0,0 +1,40 @@ +class ProductDecorator < Draper::Base + decorates :product + + def category_links(options = {}) + if namespace = options[:namespace] + product_categories.map{|pc| h.link_to pc.name, [namespace, pc]}.join(', ').html_safe + else + product_categories.map{|pc| h.link_to pc.name, pc}.join(', ').html_safe + end + end + + # Accessing Helpers + # You can access any helper via a proxy + # + # Normal Usage: helpers.number_to_currency(2) + # Abbreviated : h.number_to_currency(2) + # + # Or, optionally enable "lazy helpers" by including this module: + # include Draper::LazyHelpers + # Then use the helpers with no proxy: + # number_to_currency(2) + + # Defining an Interface + # Control access to the wrapped subject's methods using one of the following: + # + # To allow only the listed methods (whitelist): + # allows :method1, :method2 + # + # To allow everything except the listed methods (blacklist): + # denies :method1, :method2 + + # Presentation Methods + # Define your own instance methods, even overriding accessors + # generated by ActiveRecord: + # + # def created_at + # h.content_tag :span, attributes["created_at"].strftime("%a %m/%d/%y"), + # :class => 'timestamp' + # end +end diff --git a/app/models/list.rb b/app/models/list.rb index 332f1869..9ecb2421 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -8,6 +8,8 @@ class List property :closed_at, type: Time property :join_requests, type: Array, default: [] property :price, type: Float + property :is_payed, type: :boolean, default: false + property :payed_at, type: Time has_many :orders, dependent: :destroy belongs_to :table @@ -90,6 +92,11 @@ class List database.view(for_supplier_view({startkey: [supplier.id, range.last], endkey: [supplier.id, range.first], include_docs: true, reduce: false, descending: true}.merge(options))) end + def mark_as_payed + self.is_payed = true + self.payed_at = Time.now + end + def close! orders.include_relation(:product_orders) set_price @@ -155,7 +162,6 @@ class List add_user(user) user.save self.is_dirty - binding.pry if save broadcast_user user.id, 'join_request_approved' end diff --git a/app/models/product.rb b/app/models/product.rb index c243a0d8..144d4423 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -5,10 +5,26 @@ class Product property :code property :price, type: Float - belongs_to :product_category + #belongs_to :product_category + has_and_belongs_to_many :product_categories, storing_keys: false belongs_to :supplier # direct! category is an aid has_many :product_orders validates :supplier_id, presence: true + after_save :persist_product_category_ids + + def product_category_ids=(ids) + @product_category_ids = ids.select(&:present?) + end + +private + def persist_product_category_ids + return unless @product_category_ids.present? + database.load(@product_category_ids).each do |product_category| + product_category.product_ids ||= [] + product_category.product_ids |= [id] + product_category.save + end + end end diff --git a/app/models/product_category.rb b/app/models/product_category.rb index 3c692e1a..c7287095 100644 --- a/app/models/product_category.rb +++ b/app/models/product_category.rb @@ -5,10 +5,41 @@ class ProductCategory property :position, type: Fixnum, default: 0 belongs_to :supplier - has_many :products + has_and_belongs_to_many :products, storing_keys: true attr_protected :supplier_id validates :position, numericality: true validates :supplier_id, presence: true + + def self.for_user(user, options = {}) + table = options[:table] + raise "ProductCategory.for_user requires a table" unless table.present? + list = options[:list] || table.active_list + + # Get supplier objects + products = table.supplier.products + product_categories = table.supplier.product_categories || [] + + # sort categories + product_categories.sort_by!{|pc| (pc.position || 90000).to_i } + + # Append other category if not defined by supplier + other = product_categories.find(&:other?) || (product_categories << self.other).last # Container for non categorized products + + # Initialize base return object + h = {table_number: table.number, supplier_name: table.supplier.name, my_list: list.try(:user_ids).to_a.include?(user.id)} + + (products - product_categories.map(&:products).flatten).each{ |p| other.add_product(p) } + + h[:categories] = product_categories.map{|pc| {name: pc.name, products: pc.products.to_a.map{|p| p.as_json}}}.select{|pc| pc && pc[:products].present?} + h + end + + def other? + %w[other overig].include?(name.to_s.downcase) + end + def self.other(options = {}) + new(options.reverse_merge(name: I18n.t('user.product_category.other_name'))) + end end diff --git a/app/views/layouts/tablet.html.slim b/app/views/layouts/tablet.html.slim index 0e37438e..4b5dd41e 100644 --- a/app/views/layouts/tablet.html.slim +++ b/app/views/layouts/tablet.html.slim @@ -50,6 +50,7 @@ html lang="en" //li= link_to t('supplier.menu.active_orders', orders: Order.model_name.human_plural), supplier_active_orders_path //li= link_to t('supplier.menu.active_lists', lists: List.model_name.human_plural), supplier_active_lists_path li= link_to ProductCategory.model_name.human_plural, suppliers_product_categories_path + li= link_to ProductCategory.model_name.human_plural, suppliers_product_categories_path li= link_to Product.model_name.human_plural, suppliers_products_path li= link_to Section.model_name.human_plural, suppliers_sections_path li= link_to Table.model_name.human_plural, suppliers_tables_path diff --git a/app/views/products/_form.html.slim b/app/views/products/_form.html.slim index e6f1aa4c..c3191179 100644 --- a/app/views/products/_form.html.slim +++ b/app/views/products/_form.html.slim @@ -12,7 +12,7 @@ = f.label :price, class: 'control-label' .controls = f.text_field :price, class: 'text_field' - .control-group class=(@product.errors[:product_category_id].any? ? 'error' : nil) + /.control-group class=(@product.errors[:product_category_id].any? ? 'error' : nil) = f.label :product_category_id, ProductCategory.model_name.human, class: 'control-label' .controls = f.collection_select :product_category_id, @product_categories, :id, :name, include_blank: '' diff --git a/app/views/suppliers/product_categories/_form.html.slim b/app/views/suppliers/product_categories/_form.html.slim index d3b0e2ec..e506d92d 100644 --- a/app/views/suppliers/product_categories/_form.html.slim +++ b/app/views/suppliers/product_categories/_form.html.slim @@ -1,14 +1,15 @@ -= form_for [:suppliers, @product_category], html: {class: 'form-horizontal' } do |f| += simple_form_for [:suppliers, @product_category], html: {class: 'form-horizontal'} do |f| = render 'error_messages', target: @product_category - .control-group class=(@product_category.errors[:name].any? ? 'error' : nil) - = f.label :name, class: 'control-label' + = f.input :name + .control-group + = hidden_field_tag 'product_category[product_ids][]', '' + = label_tag nil, Product.model_name.human_plural, class: 'control-label' .controls - = f.text_field :name, class: 'text_field' - .control-group class=(@product_category.errors[:position].any? ? 'error' : nil) - = f.label :position, class: 'control-label' - .controls - = f.text_field :position, class: 'text_field' + - for product in current_supplier.products + = check_box_tag "product_category[product_ids][]", product.id, @product_category.product_ids.to_a.include?(product.id), id: "product-checker-#{product.id}" + = label_tag "product-checker-#{product.id}", product.name + br .form-actions - = f.submit nil, class: 'btn btn-primary' + = f.button :submit, class: 'btn-primary' ' = link_to t("helpers.links.cancel"), suppliers_product_categories_path, class: 'btn' diff --git a/app/views/suppliers/product_categories/index.html.slim b/app/views/suppliers/product_categories/index.html.slim index 6c825906..8a0b0144 100644 --- a/app/views/suppliers/product_categories/index.html.slim +++ b/app/views/suppliers/product_categories/index.html.slim @@ -2,25 +2,16 @@ .page-header= title :index, model_class .well - if @product_categories.any? - table.table - thead - tr - th= model_class.human_attribute_name(:name) - th= model_class.human_attribute_name(:position) - th.timestamp= model_class.human_attribute_name(:created_at) - th.actions=t 'helpers.actions' - tbody - - @product_categories.each do |product_category| - tr - td.link= link_to product_category.name, [:suppliers, product_category] - td= product_category.position - td.timestamp=l product_category.created_at, format: :short - td.actions - = link_to t('helpers.links.edit'), [:edit, :suppliers, product_category], class: 'btn btn-mini' - ' - = link_to t("helpers.links.destroy"), [:suppliers, product_category], method: :delete, data: {confirm: are_you_sure? }, class: 'btn btn-mini btn-danger' + ul#product-category-list data-update-url=sort_suppliers_product_categories_path + - for product_category in @product_categories + = content_tag_for :li, product_category do + span.handle [drag] + span.name= link_to product_category.name, [:suppliers, product_category] + .pull-right.actions + = link_to t('helpers.links.edit'), [:edit, :suppliers, product_category], class: 'btn btn-mini' + ' + = link_to t("helpers.links.destroy"), [:suppliers, product_category], method: :delete, data: {confirm: are_you_sure? }, class: 'btn btn-mini btn-danger' - else = no_content_given model_class .form-actions = link_to t("helpers.links.new"), new_suppliers_product_category_path, class: 'btn btn-primary' - diff --git a/app/views/suppliers/product_categories/show.html.slim b/app/views/suppliers/product_categories/show.html.slim index f358e6b3..cc7b1201 100644 --- a/app/views/suppliers/product_categories/show.html.slim +++ b/app/views/suppliers/product_categories/show.html.slim @@ -14,5 +14,5 @@ dl.dl-horizontal.show-list ' = link_to t("helpers.links.destroy"), [:suppliers, @product_category], method: :delete, data: {confirm: are_you_sure? }, class: 'btn btn-danger' - content_for :row do - - @products = @product_category.products + - @products = ProductDecorator.decorate(@product_category.products) = render template: 'suppliers/products/index' diff --git a/app/views/suppliers/products/_form.html.slim b/app/views/suppliers/products/_form.html.slim index c288b0ad..1bb70909 100644 --- a/app/views/suppliers/products/_form.html.slim +++ b/app/views/suppliers/products/_form.html.slim @@ -1,22 +1,18 @@ -= form_for [:suppliers, @product], html: {class: 'form-horizontal' } do |f| += simple_form_for [:suppliers, @product], html: {class: 'form-horizontal'} do |f| = render 'error_messages', target: @product - .control-group class=(@product.errors[:name].any? ? 'error' : nil) - = f.label :name, class: 'control-label' + = f.input :name + = f.input :code + = f.input :price + .control-group + = hidden_field_tag 'product[product_category_ids][]', [] + = label_tag nil, ProductCategory.model_name.human_plural, class: 'control-label' .controls - = f.text_field :name, class: 'text_field' - .control-group class=(@product.errors[:code].any? ? 'error' : nil) - = f.label :code, class: 'control-label' - .controls - = f.text_field :code, class: 'text_field' - .control-group class=(@product.errors[:price].any? ? 'error' : nil) - = f.label :price, class: 'control-label' - .controls - = f.text_field :price, class: ['text_field', :currency] - .control-group class=(@product.errors[:product_category_id].any? ? 'error' : nil) - = f.label :product_category_id, ProductCategory.model_name.human, class: 'control-label' - .controls - = f.collection_select :product_category_id, current_supplier.product_categories, :id, :name, include_blank: '' + - for product_category in current_supplier.product_categories + = check_box_tag "product[product_category_ids][]", product_category.id, @product.product_categories.to_a.include?(product_category), id: "product-category-checker-#{product_category.id}" + = label_tag "product-category-checker-#{product_category.id}", product_category.name + br + .form-actions - = f.submit nil, class: 'btn btn-primary' + = f.button :submit, class: 'btn-primary' ' = link_to t("helpers.links.cancel"), suppliers_products_path, class: 'btn' diff --git a/app/views/suppliers/products/index.html.slim b/app/views/suppliers/products/index.html.slim index 9c890720..6e4f4d6d 100644 --- a/app/views/suppliers/products/index.html.slim +++ b/app/views/suppliers/products/index.html.slim @@ -8,7 +8,7 @@ th= model_class.human_attribute_name(:name) th= model_class.human_attribute_name(:code) th.currency= model_class.human_attribute_name(:price) - th= ProductCategory.model_name.human + th= ProductCategory.model_name.human_plural th.timestamp= model_class.human_attribute_name(:created_at) th.actions=t 'helpers.actions' tbody @@ -17,7 +17,7 @@ td.link= link_to product.name, [:suppliers, product] td= product.code td.currency=currency product.price - td.link= link_to_if product.product_category.present?, product.product_category.try(:name), [:suppliers, product.product_category] + td.link= product.category_links namespace: :suppliers td.timestamp=l product.created_at, format: :short td.actions = link_to t('helpers.links.edit'), [:edit, :suppliers, product], class: 'btn btn-mini' diff --git a/app/views/suppliers/products/show.html.slim b/app/views/suppliers/products/show.html.slim index 4eaf2e5b..2d158321 100644 --- a/app/views/suppliers/products/show.html.slim +++ b/app/views/suppliers/products/show.html.slim @@ -8,9 +8,8 @@ dl.dl-horizontal.show-list dd= @product.code dt= model_class.human_attribute_name(:price) dd=currency @product.price - - if @product.product_category.present? - dt= ProductCategory.model_name.human - dd= link_to @product.product_category.name, @product.product_category + dt= ProductCategory.model_name.human_plural + dd= @product.category_links(namespace: :suppliers) .form-actions = link_to t("helpers.links.back"), suppliers_products_path, class: 'btn' diff --git a/config/initializers/simple_form.rb b/config/initializers/simple_form.rb new file mode 100644 index 00000000..e3f8d096 --- /dev/null +++ b/config/initializers/simple_form.rb @@ -0,0 +1,142 @@ +# Use this setup block to configure all options available in SimpleForm. +SimpleForm.setup do |config| + # Wrappers are used by the form builder to generate a + # complete input. You can remove any component from the + # wrapper, change the order or even add your own to the + # stack. The options given below are used to wrap the + # whole input. + config.wrappers :default, :class => :input, + :hint_class => :field_with_hint, :error_class => :field_with_errors do |b| + ## Extensions enabled by default + # Any of these extensions can be disabled for a + # given input by passing: `f.input EXTENSION_NAME => false`. + # You can make any of these extensions optional by + # renaming `b.use` to `b.optional`. + + # Determines whether to use HTML5 (:email, :url, ...) + # and required attributes + b.use :html5 + + # Calculates placeholders automatically from I18n + # You can also pass a string as f.input :placeholder => "Placeholder" + b.use :placeholder + + ## Optional extensions + # They are disabled unless you pass `f.input EXTENSION_NAME => :lookup` + # to the input. If so, they will retrieve the values from the model + # if any exists. If you want to enable the lookup for any of those + # extensions by default, you can change `b.optional` to `b.use`. + + # Calculates maxlength from length validations for string inputs + b.optional :maxlength + + # Calculates pattern from format validations for string inputs + b.optional :pattern + + # Calculates min and max from length validations for numeric inputs + b.optional :min_max + + # Calculates readonly automatically from readonly attributes + b.optional :readonly + + ## Inputs + b.use :label_input + b.use :hint, :wrap_with => { :tag => :span, :class => :hint } + b.use :error, :wrap_with => { :tag => :span, :class => :error } + end + + # The default wrapper to be used by the FormBuilder. + config.default_wrapper = :default + + # Define the way to render check boxes / radio buttons with labels. + # Defaults to :nested for bootstrap config. + # :inline => input + label + # :nested => label > input + config.boolean_style = :nested + + # Default class for buttons + config.button_class = 'btn' + + # Method used to tidy up errors. Specify any Rails Array method. + # :first lists the first message for each field. + # Use :to_sentence to list all errors for each field. + # config.error_method = :first + + # Default tag used for error notification helper. + config.error_notification_tag = :div + + # CSS class to add for error notification helper. + config.error_notification_class = 'alert alert-error' + + # ID to add for error notification helper. + # config.error_notification_id = nil + + # Series of attempts to detect a default label method for collection. + # config.collection_label_methods = [ :to_label, :name, :title, :to_s ] + + # Series of attempts to detect a default value method for collection. + # config.collection_value_methods = [ :id, :to_s ] + + # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none. + # config.collection_wrapper_tag = nil + + # You can define the class to use on all collection wrappers. Defaulting to none. + # config.collection_wrapper_class = nil + + # You can wrap each item in a collection of radio/check boxes with a tag, + # defaulting to :span. Please note that when using :boolean_style = :nested, + # SimpleForm will force this option to be a label. + # config.item_wrapper_tag = :span + + # You can define a class to use in all item wrappers. Defaulting to none. + # config.item_wrapper_class = nil + + # How the label text should be generated altogether with the required text. + # config.label_text = lambda { |label, required| "#{required} #{label}" } + + # You can define the class to use on all labels. Default is nil. + config.label_class = 'control-label' + + # You can define the class to use on all forms. Default is simple_form. + # config.form_class = :simple_form + + # You can define which elements should obtain additional classes + # config.generate_additional_classes_for = [:wrapper, :label, :input] + + # Whether attributes are required by default (or not). Default is true. + # config.required_by_default = true + + # Tell browsers whether to use default HTML5 validations (novalidate option). + # Default is enabled. + config.browser_validations = false + + # Collection of methods to detect if a file type was given. + # config.file_methods = [ :mounted_as, :file?, :public_filename ] + + # Custom mappings for input types. This should be a hash containing a regexp + # to match as key, and the input type that will be used when the field name + # matches the regexp as value. + # config.input_mappings = { /count/ => :integer } + + # Custom wrappers for input types. This should be a hash containing an input + # type as key and the wrapper that will be used for all inputs with specified type. + # config.wrapper_mappings = { :string => :prepend } + + # Default priority for time_zone inputs. + # config.time_zone_priority = nil + + # Default priority for country inputs. + # config.country_priority = nil + + # Default size for text inputs. + # config.default_input_size = 50 + + # When false, do not use translations for labels. + # config.translate_labels = true + + # Automatically discover new inputs in Rails' autoload path. + # config.inputs_discovery = true + + # Cache SimpleForm inputs discovery + # config.cache_discovery = !Rails.env.development? +end diff --git a/config/initializers/simple_form_bootstrap.rb b/config/initializers/simple_form_bootstrap.rb new file mode 100644 index 00000000..1a229676 --- /dev/null +++ b/config/initializers/simple_form_bootstrap.rb @@ -0,0 +1,45 @@ +# Use this setup block to configure all options available in SimpleForm. +SimpleForm.setup do |config| + config.wrappers :bootstrap, :tag => 'div', :class => 'control-group', :error_class => 'error' do |b| + b.use :html5 + b.use :placeholder + b.use :label + b.wrapper :tag => 'div', :class => 'controls' do |ba| + ba.use :input + ba.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' } + ba.use :hint, :wrap_with => { :tag => 'p', :class => 'help-block' } + end + end + + config.wrappers :prepend, :tag => 'div', :class => "control-group", :error_class => 'error' do |b| + b.use :html5 + b.use :placeholder + b.use :label + b.wrapper :tag => 'div', :class => 'controls' do |input| + input.wrapper :tag => 'div', :class => 'input-prepend' do |prepend| + prepend.use :input + end + input.use :hint, :wrap_with => { :tag => 'span', :class => 'help-block' } + input.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' } + end + end + + config.wrappers :append, :tag => 'div', :class => "control-group", :error_class => 'error' do |b| + b.use :html5 + b.use :placeholder + b.use :label + b.wrapper :tag => 'div', :class => 'controls' do |input| + input.wrapper :tag => 'div', :class => 'input-append' do |append| + append.use :input + end + input.use :hint, :wrap_with => { :tag => 'span', :class => 'help-block' } + input.use :error, :wrap_with => { :tag => 'span', :class => 'help-inline' } + end + end + + # Wrappers for forms and inputs using the Twitter Bootstrap toolkit. + # Check the Bootstrap docs (http://twitter.github.com/bootstrap) + # to learn about the different styles for forms and inputs, + # buttons and other elements. + config.default_wrapper = :bootstrap +end diff --git a/config/locales/en.yml b/config/locales/en.yml index a8dd6be4..293f8b7f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -14,9 +14,21 @@ en: are_you_sure: 'Are you sure?' place_order: Place order show_active_list: Show %{list} + edit: Edit + show: Show + new: New + destroy: Delete + back: Back + cancel: Cancel forms: errors: title: There are problems found during saving (%{count}) + submit: + create: 'Add %{model}' + update: 'Update %{model}' + submit: 'Save %{model}' + list: + no_records: There are no items present messages: cannot_order_on_non_active_list: You cannot place an order on a closed list order_is_placed: Your order has been received in good order @@ -130,6 +142,8 @@ en: show_active_list_products: Go to the menu basket: total: Total + product_category: + other_name: Overig section: first_section_title: Room manage_tables: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 6a38ff29..bdff529b 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -13,9 +13,21 @@ nl: are_you_sure: 'Weet je dit zeker?' place_order: Bestellen show_active_list: Toon %{list} + edit: Bewerk + show: Toon + new: Nieuw + destroy: Verwijder + back: Terug + cancel: Terug forms: errors: title: Er zijn een problemen opgetreden (%{count}) + submit: + create: '%{model} toevoegen' + update: '%{model} bewaren' + submit: '%{model} opslaan' + list: + no_records: Er zijn geen items aanwezig messages: cannot_order_on_non_active_list: Je kan niet bestellen op een gesloten lijst order_is_placed: Je bestelling is in goede orde aangekomen @@ -143,6 +155,8 @@ nl: show_active_list_products: Ga naar het menu basket: total: Totaal + product_category: + other_name: Overig section: first_section_title: Ruimte manage_tables: diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml new file mode 100644 index 00000000..0df11fe0 --- /dev/null +++ b/config/locales/simple_form.en.yml @@ -0,0 +1,26 @@ +en: + simple_form: + "yes": 'Yes' + "no": 'No' + required: + text: 'required' + mark: '*' + # You can uncomment the line below if you need to overwrite the whole required html. + # When using html, text and mark won't be used. + # html: '*' + error_notification: + default_message: "Please review the problems below:" + # Labels and hints examples + # labels: + # defaults: + # password: 'Password' + # user: + # new: + # email: 'E-mail to sign in.' + # edit: + # email: 'E-mail.' + # hints: + # defaults: + # username: 'User name to sign in.' + # password: 'No special characters, please.' + diff --git a/config/routes.rb b/config/routes.rb index 55e373d2..4b5f7dee 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -90,7 +90,11 @@ Qwaiter::Application.routes.draw do end resources :products resources :lists - resources :product_categories + resources :product_categories do + collection do + post :sort + end + end root to: 'sections#index' end diff --git a/lib/templates/slim/scaffold/_form.html.slim b/lib/templates/slim/scaffold/_form.html.slim new file mode 100644 index 00000000..a2ff775a --- /dev/null +++ b/lib/templates/slim/scaffold/_form.html.slim @@ -0,0 +1,10 @@ += simple_form_for(@<%= singular_table_name %>) do |f| + = f.error_notification + + .form-inputs +<%- attributes.each do |attribute| -%> + = f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> +<%- end -%> + + .form-actions + = f.button :submit diff --git a/spec/acceptance/supplier_main_board_spec.rb_spec.rb b/spec/acceptance/supplier_main_board_spec.rb_spec.rb index 89e43e61..f5f4b4c4 100644 --- a/spec/acceptance/supplier_main_board_spec.rb_spec.rb +++ b/spec/acceptance/supplier_main_board_spec.rb_spec.rb @@ -9,8 +9,12 @@ feature 'Supplier main board spec.rb', %q{ create_supplier 'supplier@qwaiter.com' create_user 'user@qwaiter.com' end + def create_active_list(options = {}) + + end scenario 'loaded orders should have a list_id present for handling actions' do + create_active_list login_supplier_as 'supplier@qwaiter.com' end diff --git a/spec/decorators/product_decorator_spec.rb b/spec/decorators/product_decorator_spec.rb new file mode 100644 index 00000000..7e5221be --- /dev/null +++ b/spec/decorators/product_decorator_spec.rb @@ -0,0 +1,4 @@ +require 'spec_helper' + +describe ProductDecorator do +end diff --git a/spec/factories/list_factory.rb b/spec/factories/list_factory.rb index 81dfb3fb..2eca981d 100644 --- a/spec/factories/list_factory.rb +++ b/spec/factories/list_factory.rb @@ -1,5 +1,6 @@ FactoryGirl.define do factory :list do association :table + association :supplier end end diff --git a/spec/factories/user_factory.rb b/spec/factories/user_factory.rb new file mode 100644 index 00000000..9cb79a2a --- /dev/null +++ b/spec/factories/user_factory.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :user do + email "test@example.com" + password "secret" + end +end diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb index a7968129..884479c4 100644 --- a/spec/models/list_spec.rb +++ b/spec/models/list_spec.rb @@ -14,4 +14,17 @@ describe List do end end + describe :mark_as_payed do + it "should set payed_at to a time" do + @list.payed_at.should be_nil + @list.mark_as_payed + @list.payed_at.should be_kind_of Time + end + it "should set is_payed to true" do + @list.is_payed.should be_false + @list.mark_as_payed + @list.is_payed.should be_true + end + end + end diff --git a/spec/models/product_spec.rb b/spec/models/product_spec.rb new file mode 100644 index 00000000..42c46d72 --- /dev/null +++ b/spec/models/product_spec.rb @@ -0,0 +1,6 @@ +require 'spec_helper' + +describe Product do + + +end