diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index d05ea0f..61d367b 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -1,15 +1,24 @@ /* - * This is a manifest file that'll be compiled into application.css, which will include all the files - * listed below. - * - * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's - * vendor/assets/stylesheets directory can be referenced here using a relative path. - * - * You're free to add application-wide styles to this file and they'll appear at the bottom of the - * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS - * files in this directory. Styles in this file should be added after the last require_* statement. - * It is generally better to create a new file per style scope. - * - *= require_tree . - *= require_self - */ + = require scaffold +*/ + +.simple_form label:first-child { + width: 160px; + text-align: right; + vertical-align: top; + display: inline-block; + margin-right: 10px; + margin-bottom: 10px; + padding-top: 2.5px; +} +.simple_form input, .simple_form select { + margin-bottom: 10px; +} +.simple_form .field_with_errors label { + color: #c00; + font-weight: bold; +} +.simple_form .error { + margin-left: 10px; + color: #c00; +} diff --git a/app/assets/stylesheets/scaffold.css b/app/assets/stylesheets/scaffold.css new file mode 100644 index 0000000..8f239a3 --- /dev/null +++ b/app/assets/stylesheets/scaffold.css @@ -0,0 +1,74 @@ +body { background-color: #fff; color: #333; } + +body, p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; +} + +a { color: #000; } +a:visited { color: #666; } +a:hover { color: #fff; background-color:#000; } + +.fieldWithErrors { + padding: 2px; + background-color: red; + display: table; +} + +#errorExplanation { + width: 400px; + border: 2px solid red; + padding: 7px; + padding-bottom: 12px; + margin-bottom: 20px; + background-color: #f0f0f0; +} + +#errorExplanation h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px; + background-color: #c00; + color: #fff; +} + +#errorExplanation p { + color: #333; + margin-bottom: 0; + padding: 5px; +} + +#errorExplanation ul li { + font-size: 12px; + list-style: square; +} + +div.uploadStatus { + margin: 5px; +} + +div.progressBar { + margin: 5px; +} + +div.progressBar div.border { + background-color: #fff; + border: 1px solid grey; + width: 100%; +} + +div.progressBar div.background { + background-color: #333; + height: 18px; + width: 0%; +} + diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 09705d1..b74db76 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,2 +1,32 @@ +# Filters added to this controller apply to all controllers in the application. +# Likewise, all the methods added will be available for all controllers. + class ApplicationController < ActionController::Base + + protect_from_forgery + + before_action :login_required! + + helper_method :current_user + + private + + def current_user + @current_user ||= User.find(session[:user_id]) if session[:user_id] + end + + def login_required! + if current_user.nil? + flash[:error] = "Login required" + redirect_to log_in_url + end + end + + def authenticate_supplier_admin! + @supplier = Supplier.find((params[:supplier_id] || params[:id])) + unless current_user.has_access_to?(@supplier) + flash[:error] = "Not authorized!" + redirect_to root_url + end + end end diff --git a/app/controllers/articles_controller.rb b/app/controllers/articles_controller.rb new file mode 100644 index 0000000..875a89d --- /dev/null +++ b/app/controllers/articles_controller.rb @@ -0,0 +1,147 @@ +# encoding: utf-8 + +class ArticlesController < ApplicationController + + before_action :authenticate_supplier_admin! + + # GET /supplier/:id/articles + # GET /supplier/:id/articles.xml + def index + if params[:filter] + @filter = params[:filter] + @articles = @supplier.articles + @articles = @articles.where('name LIKE ?', "%#{@filter}%") unless @filter.nil? + @articles = @articles.page(params[:page]) + elsif params[:order] + case params[:order] + when 'updated_on' + @articles = @supplier.articles.paginate :all, :order => "updated_on DESC", :page => params[:page] + @updated_on = true + end + else + @articles = @supplier.articles.paginate :page => params[:page] + end + + respond_to do |format| + format.html # index.haml + format.xml { render :xml => @articles.to_xml } + end + end + + # GET /supplier/1/articles/1 + # GET /supplier/1/articles/1.xml + def show + @article = @supplier.articles.find(params[:id]) + + respond_to do |format| + format.html # show.haml + format.xml { render :xml => @article.to_xml } + end + end + + # GET /supplier/1/articles/new + def new + @article = @supplier.articles.build + end + + # GET /supplier/1/articles/1/edit + def edit + @article = @supplier.articles.find(params[:id]) + end + + # POST /supplier/1/articles + # POST /supplier/1/articles.xml + def create + @article = Article.new(params[:article]) + respond_to do |format| + if @article.save + flash[:notice] = 'Article was successfully created.' + format.html { redirect_to supplier_article_url(@article.supplier, @article) } + format.xml { head :created, :location => supplier_article_url(@article.supplier, @article) } + else + format.html { render :action => "new" } + format.xml { render :xml => @article.errors.to_xml } + end + end + end + + # PUT /supplier/1/articles/1 + # PUT /supplier/1/articles/1.xml + def update + @article = @supplier.articles.find(params[:id]) + respond_to do |format| + if @article.update_attributes(params[:article]) + flash[:notice] = 'Article was successfully updated.' + format.html { redirect_to supplier_article_url(@article.supplier, @article) } + format.xml { head :ok } + else + format.html { render :action => "edit" } + format.xml { render :xml => @article.errors.to_xml } + end + end + end + + # DELETE /supplier/1/articles/1 + # DELETE /supplier/1/articles/1.xml + def destroy + @article = @supplier.articles.find(params[:id]) + @article.destroy + + respond_to do |format| + format.html { redirect_to supplier_articles_url(@supplier) } + format.xml { head :ok } + end + end + + # Renders the upload form + def upload + end + + # parse the file to load articles + # checks if the article should be updated, create or destroyed + def parse + if params[:articles].blank? + flash[:error] = "Please select a file to import" + redirect_to upload_supplier_articles_url(@supplier) + return + end + if params[:type].blank? + flash[:error] = "Please select a file-format" + redirect_to upload_supplier_articles_url(@supplier) + return + end + + file = params[:articles]["file"].tempfile + filename = params[:articles]["file"].original_filename + type = params[:type].presence + encoding = params[:encoding].presence + + begin + Article.transaction do + Article.delete_all :supplier_id => @supplier.id unless params[:delete_existing].blank? + + @outlisted_counter, @new_counter, @updated_counter, @invalid_articles = + @supplier.update_articles_from_file(file, type: type, encoding: encoding, filename: filename) + + if @invalid_articles.empty? + flash[:notice] = "Hochladen erfolgreich: #{@new_counter} neue, #{@updated_counter} aktualisiert und #{@outlisted_counter} ausgelistet." + redirect_to supplier_articles_url(@supplier) + else + flash[:error] = "#{@invalid_articles.size} Artikel konnte(n) nicht gespeichert werden" + render :template => 'articles/parse_errors' + end + end + rescue => error + flash[:error] = "Fehler beim hochladen der Artikel: #{error.message}" + redirect_to upload_supplier_articles_url(@supplier) + end + end + + # deletes all articles of a supplier + def destroy_all + Article.where(supplier_id: @supplier.id).delete_all + flash[:notice] = "Alle Artikel wurden gelöscht" + redirect_to supplier_articles_url(@supplier) + end + +end diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000..c6e9156 --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,24 @@ +class SessionsController < ApplicationController + + skip_before_action :login_required! + + def new + end + + def create + user = User.authenticate(params[:email], params[:password]) + if user + session[:user_id] = user.id + flash[:notice] = "Logged in!" + redirect_to root_url + else + flash.now[:error] = "Invalid email or password" + render "new" + end + end + + def destroy + session[:user_id] = nil + redirect_to root_url, :notice => "Logged out!" + end +end diff --git a/app/controllers/suppliers_controller.rb b/app/controllers/suppliers_controller.rb new file mode 100644 index 0000000..e11c069 --- /dev/null +++ b/app/controllers/suppliers_controller.rb @@ -0,0 +1,89 @@ +class SuppliersController < ApplicationController + + before_action :authenticate_supplier_admin!, :except => [:index, :new, :create] + + # GET /suppliers + # GET /suppliers.xml + def index + @suppliers = Supplier.all + + respond_to do |format| + format.html # index.rhtml + format.xml { render :xml => @suppliers.to_xml } + end + end + + # GET /suppliers/1 + # GET /suppliers/1.xml + def show + @supplier = Supplier.find(params[:id]) + + respond_to do |format| + format.html # show.rhtml + format.xml { render :xml => @supplier.to_xml } + end + end + + # GET /suppliers/new + def new + @supplier = Supplier.new + end + + # GET /suppliers/1;edit + def edit + @supplier = Supplier.find(params[:id]) + end + + # POST /suppliers + # POST /suppliers.xml + def create + @supplier = Supplier.new(params[:supplier]) + + respond_to do |format| + if @supplier.save + flash[:notice] = 'Supplier was successfully created.' + format.html { redirect_to supplier_url(@supplier) } + format.xml { head :created, :location => supplier_url(@supplier) } + else + format.html { render :action => "new" } + format.xml { render :xml => @supplier.errors.to_xml } + end + end + end + + # PUT /suppliers/1 + # PUT /suppliers/1.xml + def update + @supplier = Supplier.find(params[:id]) + attrs = params[:supplier] + + respond_to do |format| + # @todo fix by generating proper hidden input in html + attrs[:ftp_sync] ||= false + attrs[:mail_sync] ||= false + # don't set password to blank on saving + attrs = attrs.reject {|k,v| k == 'ftp_password' } if attrs[:ftp_password].blank? + + if @supplier.update_attributes(attrs) + flash[:notice] = 'Supplier was successfully updated.' + format.html { redirect_to supplier_url(@supplier) } + format.xml { head :ok } + else + format.html { render :action => "edit" } + format.xml { render :xml => @supplier.errors.to_xml } + end + end + end + + # DELETE /suppliers/1 + # DELETE /suppliers/1.xml + def destroy + @supplier = Supplier.find(params[:id]) + @supplier.destroy + + respond_to do |format| + format.html { redirect_to suppliers_url } + format.xml { head :ok } + end + end +end diff --git a/app/helpers/articles_helper.rb b/app/helpers/articles_helper.rb new file mode 100644 index 0000000..2968277 --- /dev/null +++ b/app/helpers/articles_helper.rb @@ -0,0 +1,2 @@ +module ArticlesHelper +end diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb new file mode 100644 index 0000000..309f8b2 --- /dev/null +++ b/app/helpers/sessions_helper.rb @@ -0,0 +1,2 @@ +module SessionsHelper +end diff --git a/app/helpers/suppliers_helper.rb b/app/helpers/suppliers_helper.rb new file mode 100644 index 0000000..8994104 --- /dev/null +++ b/app/helpers/suppliers_helper.rb @@ -0,0 +1,2 @@ +module SuppliersHelper +end diff --git a/app/models/application_record.rb b/app/models/application_record.rb deleted file mode 100644 index 10a4cba..0000000 --- a/app/models/application_record.rb +++ /dev/null @@ -1,3 +0,0 @@ -class ApplicationRecord < ActiveRecord::Base - self.abstract_class = true -end diff --git a/app/models/article.rb b/app/models/article.rb new file mode 100644 index 0000000..a7afbf0 --- /dev/null +++ b/app/models/article.rb @@ -0,0 +1,62 @@ +# == Schema Information +# +# Table name: articles +# +# id :integer(4) not null, primary key +# name :string(255) not null +# supplier_id :integer(4) not null +# number :string(255) +# note :string(255) +# manufacturer :string(255) +# origin :string(255) +# unit :string(255) +# price :decimal(8, 2) default(0.0), not null +# tax :decimal(3, 1) default(7.0), not null +# deposit :decimal(8, 2) default(0.0), not null +# unit_quantity :decimal(4, 1) default(1.0), not null +# scale_quantity :decimal(4, 2) +# scale_price :decimal(8, 2) +# created_on :datetime +# updated_on :datetime +# list :string(255) +# category :string(255) +# + +class Article < ActiveRecord::Base + belongs_to :supplier + + validates_numericality_of :price, :tax, :deposit, :unit_quantity + validates_uniqueness_of :number, :scope => :supplier_id + validates_presence_of :name, :price + + + # Custom attribute setter that accepts decimal numbers using localized decimal separator. + def price=(price) + self[:price] = delocalizeDecimalString(price) + end + def unit_quantity=(unit_quantity) + self[:unit_quantity] = delocalizeDecimalString(unit_quantity) + end + def tax=(tax) + self[:tax] = delocalizeDecimalString(tax) + end + def deposit=(deposit) + self[:deposit] = delocalizeDecimalString(deposit) + end + def scale_quantity=(scale_quantity) + self[:scale_quantity] = delocalizeDecimalString(scale_quantity) + end + def scale_price=(scale_price) + self[:scale_price] = delocalizeDecimalString(scale_price) + end + + def delocalizeDecimalString(string) + if (string && string.is_a?(String) && !string.empty?) + separator = "," + if (separator != '.' && string.index(separator)) + string = string.sub(separator, '.') + end + end + return string + end +end diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/app/models/supplier.rb b/app/models/supplier.rb new file mode 100644 index 0000000..09ce52f --- /dev/null +++ b/app/models/supplier.rb @@ -0,0 +1,146 @@ +class Supplier < ActiveRecord::Base + has_many :articles, :dependent => :destroy + has_many :user_accesses, :dependent => :destroy + has_many :users, :through => :user_accesses + + # save lists in an array in database + serialize :lists + + FTP_TYPES = ['bnn', 'foodsoft'].freeze + EMAIL_RE = /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i.freeze + + validates :name, :address, :phone, presence: true + validates :ftp_host, :ftp_user, :ftp_password, :ftp_sync, presence: true, if: :ftp_sync? + validates :ftp_type, presence: true, inclusion: { in: FTP_TYPES }, if: :ftp_sync? + validates :mail_from, presence: true, format: { with: EMAIL_RE }, if: :mail_sync? + validates :mail_type, inclusion: { in: ArticleImport.file_formats.keys }, presence: true, if: :mail_sync? + + scope :ftp_sync, ->{ where(ftp_sync: true) } + scope :mail_sync, ->{ where(mail_sync: true) } + + before_create :create_salt + + def ftp_path + # path should read "ftp_files", but has "bnn_files" for historical reasons + Rails.root.join("supplier_assets", "bnn_files", id.to_s) + end + + def mail_path + Rails.root.join("supplier_assets", "mail_files", id.to_s) + end + + # mail hash checked on receiving articles update mail + def articles_mail_hash + digest = Digest::SHA1.new + digest.update self.id.to_s + digest.update ":" + digest.update salt + digest.update ":mail:articles" + Base32.encode(digest.digest[1..10]) + end + + def articles_mail_address + return unless salt.present? + "#{ENV["MAILER_PREFIX"]}#{id}.#{articles_mail_hash}@#{ENV["MAILER_DOMAIN"]}" + end + + def sync_ftp_files + new_files = FtpSync::sync(self) + + unless new_files.empty? + logger.info "New FTP-synced files for #{name}: #{new_files.inspect}" + + new_files.each do |file| + logger.debug "parse #{file}..." + outlisted_counter, new_counter, updated_counter, invalid_articles = + update_articles_from_file(File.join(ftp_path, file), type: ftp_type) + logger.info "#{file} successfully parsed: #{new_counter} new, #{updated_counter} updated, #{outlisted_counter} outlisted, #{invalid_articles.size} invalid" + end + + if $missing_bnn_codes + logger.info "missing bnn-codes: #{$missing_bnn_codes.uniq.sort.join(", ")}" + end + end + end + + # parses file and updates articles + # + # @param file [File] File to update articles from + # @returns [Array] counters for outlisted, new and updated articles, and invalid articles + # @note options are passed on to {ArticleImport.parse} + def update_articles_from_file(file, **opts) + + specials = invalid_articles = Array.new + outlisted_counter, new_counter, updated_counter = 0, 0, 0 + listed = Array.new + upload_lists = Hash.new(0) + + ArticleImport.parse(file, **opts) do |parsed_article, status| + parsed_article[:upload_list] = opts[:upload_list] if opts[:upload_list] + upload_lists[parsed_article[:upload_list]] += 1 if parsed_article[:upload_list] + + article = articles.find_by_number(parsed_article[:number]) + # create new article + if status.nil? && article.nil? + new_article = articles.build(parsed_article) + if new_article.valid? && new_article.save + new_counter += 1 + listed << new_article.id + else + invalid_articles << new_article + end + + # update existing article + elsif status.nil? && article + updated_counter += 1 if article.update_attributes(parsed_article) + listed << article.id + + # delete outlisted article + elsif status == :outlisted && article + article.destroy && outlisted_counter += 1 + + # remember special info for article; store data to allow article after its special + elsif status == :special && article + specials << article + + # mention parsing problems + elsif status.is_a?(String) + new_article = articles.build(parsed_article) + new_article.valid? + new_article.errors[''] = status + invalid_articles << new_article + + end + end + + # updates articles with special infos + specials.each do |special| + if article = articles.find_by_number(special[:number]) + if article.note + article.note += " | #{special[:note]}" + else + article.note = special[:note] + end + article.save + end + end + + # remove unlisted articles when requested + if opts[:outlist_unlisted] || ArticleImport.file_formats[opts[:type]]::OUTLIST + to_delete = articles.where('id NOT IN (?)', listed) # @rails4 `.where.not()` + outlisted_counter += to_delete.delete_all + end + + return [outlisted_counter, new_counter, updated_counter, invalid_articles] + end + + def articles_updated_at + articles.order('articles.updated_on DESC').first.try(:updated_on) + end + + private + + def create_salt + self.salt = [Array.new(6){rand(256).chr}.join].pack("m").chomp + end +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..85884fa --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,40 @@ +class User < ActiveRecord::Base + + has_many :user_accesses, :dependent => :destroy + has_many :suppliers, :through => :user_accesses + + attr_accessible :email, :password, :password_confirmation + + attr_accessor :password + before_save :encrypt_password + + validates_confirmation_of :password + validates_presence_of :password, :on => :create + validates_presence_of :email + validates_uniqueness_of :email + + def self.authenticate(email, password) + user = find_by_email(email) + if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt) + user + else + nil + end + end + + def encrypt_password + if password.present? + self.password_salt = BCrypt::Engine.generate_salt + self.password_hash = BCrypt::Engine.hash_secret(password, password_salt) + end + end + + def has_access_to?(supplier) + admin? or !UserAccess.first(:conditions => {:supplier_id => supplier.id, :user_id => id}).nil? + end + + def admin? + !!admin + end + +end diff --git a/app/models/user_access.rb b/app/models/user_access.rb new file mode 100644 index 0000000..b43e247 --- /dev/null +++ b/app/models/user_access.rb @@ -0,0 +1,4 @@ +class UserAccess < ActiveRecord::Base + belongs_to :user + belongs_to :supplier +end diff --git a/app/views/articles/_form.haml b/app/views/articles/_form.haml new file mode 100644 index 0000000..12873df --- /dev/null +++ b/app/views/articles/_form.haml @@ -0,0 +1,54 @@ +%p + Name: + %br/ + = f.text_field :name +%p + Nummer: + %br/ + = f.text_field :number +%p + Notiz: + %br/ + = f.text_field :note +%p + Herstellerin: + %br/ + = f.text_field :manufacturer +%p + Herkunft: + %br/ + = f.text_field :origin +%p + Einheit: + %br/ + = f.text_field :unit +%p + Preis: + %br/ + = f.text_field :price +%p + Steuer: + %br/ + = f.text_field :tax +%p + Pfand: + %br/ + = f.text_field :deposit +%p + Gebindegröße: + %br/ + = f.text_field :unit_quantity +%p + Kategorie: + %br/ + = f.text_field :category +%p + Rabattmenge: + %br/ + = f.text_field :scale_quantity +%p + Rabattpreis: + %br/ + = f.text_field :scale_price +%p= submit_tag "Speichern" += f.hidden_field :supplier_id diff --git a/app/views/articles/edit.haml b/app/views/articles/edit.haml new file mode 100644 index 0000000..21a5735 --- /dev/null +++ b/app/views/articles/edit.haml @@ -0,0 +1,7 @@ +%h1 Artikel bearbeiten += form_for(:article, :url => supplier_article_path(@article.supplier, @article), :html => { :method => :put }) do |f| + = render 'form', :f => f + += link_to 'Show', supplier_article_path(@article.supplier, @article) +| += link_to 'zurück', supplier_articles_path(@article.supplier) \ No newline at end of file diff --git a/app/views/articles/index.haml b/app/views/articles/index.haml new file mode 100644 index 0000000..fdefd7b --- /dev/null +++ b/app/views/articles/index.haml @@ -0,0 +1,57 @@ +%h1 + = @supplier.articles.size + Artikel von + = @supplier.name + +%p + Aktionen: + = link_to 'Alle löschen', destroy_all_supplier_articles_url(@supplier), :method => :delete, :confirm => "Willst Du wirklich alle löschen?" + | + = link_to "Artikel hochladen", upload_supplier_articles_url(@supplier) + | + = link_to 'Neuer Artikel', new_supplier_article_url(@supplier) + | + = link_to "zurück", suppliers_url + +#filter{:style => "float:left;padding-right:1em;"} + = form_tag supplier_articles_url(@supplier), :method => :get do + Suchen im Namen: + = text_field_tag :filter, @filter + = submit_tag 'Suchen' +#sort{:style => "float:left;padding-right:1em;"} + = form_tag supplier_articles_url(@supplier), :method => :get do + Sortieren nach: aktualisiert + = check_box_tag :order, "updated_on", @updated_on, {:onclick => "submit();"} +| += link_to 'Filter zurücksetzen', supplier_articles_url(@supplier) + +#list{:style => "clear:both;padding-top:1em"} + %p= will_paginate @articles + %table + %tr + %th Name + %th Nummer + %th Notiz + %th Herstellerin + %th Herkunft + %th Einheit + %th Preis + %th Steuer + %th Pfand + %th Gebindegröße + - for article in @articles + %tr + %td=h article.name + %td=h article.number + %td=h article.note + %td=h article.manufacturer + %td=h article.origin + %td=h article.unit + %td=h article.price + %td=h article.tax + %td=h article.deposit + %td=h article.unit_quantity + %th= link_to 'Anzeigen', supplier_article_url(@supplier, article) + %th= link_to 'Bearbeiten', edit_supplier_article_url(article.supplier, article) + %th= link_to 'Löschen', supplier_article_url(@supplier, article), :confirm => 'Bist du sicher?', :method => :delete + %p= will_paginate @articles \ No newline at end of file diff --git a/app/views/articles/new.haml b/app/views/articles/new.haml new file mode 100644 index 0000000..019b3b6 --- /dev/null +++ b/app/views/articles/new.haml @@ -0,0 +1,5 @@ +%h1 Neuer Artikel += form_for(:article, :url => supplier_articles_path) do |f| + = render 'form', :f => f + += link_to 'zurück', supplier_articles_path(@article.supplier) \ No newline at end of file diff --git a/app/views/articles/parse_errors.haml b/app/views/articles/parse_errors.haml new file mode 100644 index 0000000..900f0bd --- /dev/null +++ b/app/views/articles/parse_errors.haml @@ -0,0 +1,18 @@ +%h1 Fehler beim hochladen +%p + = @new_counter + wurden neu gespeichert, + = @updated_counter + aktualisiert und + = @outlisted_counter + wurden ausgelistet. +%p bei folgende Artikeln sind Fehler aufgetreten: +%ul + - for article in @invalid_articles + %li + = article.name + %ul + - article.errors.each do |attr, msg| + %li= "#{attr}: #{msg}" + +%p= link_to 'Artikel', supplier_articles_url(@supplier) diff --git a/app/views/articles/show.haml b/app/views/articles/show.haml new file mode 100644 index 0000000..14b7eeb --- /dev/null +++ b/app/views/articles/show.haml @@ -0,0 +1,57 @@ +%h1 Artikel anzeigen +%p + Name: + %br/ + %strong= @article.name +%p + Nummer: + %br/ + %strong= @article.number +%p + Notiz: + %br/ + %strong= @article.note +%p + Herstellerin: + %br/ + %strong= @article.manufacturer +%p + Herkunft: + %br/ + %strong= @article.origin +%p + Einheit: + %br/ + %strong= @article.unit +%p + Preis: + %br/ + %strong= @article.price +%p + Steuer: + %br/ + %strong= @article.tax +%p + Pfand: + %br/ + %strong= @article.deposit +%p + Gebindegröße: + %br/ + %strong= @article.unit_quantity +%p + Kategorie: + %br/ + %strong= @article.category +%p + Rabattmenge: + %br/ + %strong= @article.scale_quantity +%p + Rabattpreis: + %br/ + %strong= @article.scale_price + += link_to 'Bearbeiten', edit_supplier_article_path(@article.supplier, @article) +| += link_to 'Zurück', supplier_articles_path(@article.supplier) diff --git a/app/views/articles/upload.haml b/app/views/articles/upload.haml new file mode 100644 index 0000000..cd8a567 --- /dev/null +++ b/app/views/articles/upload.haml @@ -0,0 +1,26 @@ +%h1 + Artikel für + = @supplier.name + hochladen +%p + %i Bitte (BNN-)Datei zum upload auswählen +#uploadArticles.uploadForm + = form_for(:articles, :url => {:controller => 'articles', :action => 'parse'}, :html => { :multipart => true }) do |form| + %p + Upload: + = form.file_field("file") + %p + Datei-Format auswählen: + %select{:name => 'type'} + = options_for_select([["", ""]] + ArticleImport::file_formats.invert.map{|f,k| [f::NAME, k]}) + %p + Zeichenkodierung: + %select{:name => 'encoding'} + = options_for_select({"Default" => "", "UTF-8" => "UTF-8", "IBM850 (Terra)" => "IBM850", "ISO-8859-15 (Ökotopia)" => "ISO-8859-15"}) + %p + = check_box_tag :delete_existing + = label_tag :delete_existing, "Delete existing articles first" + + = submit_tag("Artikel hochladen") + = hidden_field_tag :supplier_id, @supplier.id +%p= link_to 'zurück', supplier_articles_url(@supplier) diff --git a/app/views/articles/verify_upload.haml b/app/views/articles/verify_upload.haml new file mode 100644 index 0000000..97a9709 --- /dev/null +++ b/app/views/articles/verify_upload.haml @@ -0,0 +1,45 @@ +%h1 hochgeladenen Artikel überprüfen +%p + %i + Bitte überprüfe die Daten aus der Datei und wähle unten den Lieferanten aus. + %br/ + Artikel dessen Namen schon in der Datenbank vorhanden sind, werden ignoriert. + += form_tag(:action => 'createArticlesFromFile') do + %table.list + %tr + %th Änderungsk. + %th Name + %th Nummer + %th Notiz + %th Hersteller + %th Herkunft + %th Einheit + %th Preis + %th MwSt. + %th Pfand + %th Geb.Gr. + %th RabattMeng. + %th RabattPreis + - for @article in @articles + %tr + %td= text_field 'article[]', 'changing', :size => 3 + %td= text_field 'article[]', 'name', :size => 0 + %td= text_field 'article[]', 'number', :size => 5 + %td= text_field 'article[]', 'note', :size => 5 + %td= text_field 'article[]', 'manufacturer', :size => 5 + %td= text_field 'article[]', 'origin', :size => 5 + %td= text_field 'article[]', 'unit', :size => 5 + %td= text_field 'article[]', 'price', :size => 5 + %td= text_field 'article[]', 'tax', :size => 5 + %td= text_field 'article[]', 'deposit', :size => 5 + %td= text_field 'article[]', 'unit_quantity', :size => 5 + %td= text_field 'article[]', 'scale_quantity', :size => 5 + %td= text_field 'article[]', 'scale_price', :size => 5 + %p + Lieferanten auswählen: + = select('supplier', 'id', Supplier.all.collect {|s| [ s.name, s.id ] }, :selected => nil) + = submit_tag 'Artikel speichern' + %p + = link_to 'zurück', :action => 'uploadArticles' + \ No newline at end of file diff --git a/app/views/layouts/application.haml b/app/views/layouts/application.haml new file mode 100644 index 0000000..8764f6e --- /dev/null +++ b/app/views/layouts/application.haml @@ -0,0 +1,23 @@ +!!! +%html + %head + %meta{"http-equiv" => "content-type", :content => "text/html;charset=UTF-8"} + %title=h "SharedLists: " + controller.controller_name + = stylesheet_link_tag 'application' + = csrf_meta_tag + = javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' + %body + #content + - if flash[:notice] + %h3{:style => "color: green"}= flash[:notice] + - if flash[:error] + %h3{:style => "color: red"}= flash[:error] + = yield + #footer + %hr/ + - if current_user.present? + Logged in as #{current_user.email}, #{link_to("log out", log_out_path)} + - else + = link_to "Log in", log_in_path + + = yield :javascript diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml deleted file mode 100644 index 3a83c8d..0000000 --- a/app/views/layouts/application.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -!!! -%html - %head - %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ - %title Sharedlists - %meta{:content => "width=device-width,initial-scale=1", :name => "viewport"}/ - = csrf_meta_tags - = csp_meta_tag - = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' - = javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' - %body - = yield diff --git a/app/views/layouts/mailer.html.haml b/app/views/layouts/mailer.html.haml deleted file mode 100644 index cbf6b8e..0000000 --- a/app/views/layouts/mailer.html.haml +++ /dev/null @@ -1,8 +0,0 @@ -!!! -%html - %head - %meta{:content => "text/html; charset=utf-8", "http-equiv" => "Content-Type"}/ - :css - /* Email styles need to be inline */ - %body - = yield diff --git a/app/views/layouts/mailer.text.haml b/app/views/layouts/mailer.text.haml deleted file mode 100644 index 0a90f09..0000000 --- a/app/views/layouts/mailer.text.haml +++ /dev/null @@ -1 +0,0 @@ -= yield diff --git a/app/views/sessions/new.html.haml b/app/views/sessions/new.html.haml new file mode 100644 index 0000000..8078d38 --- /dev/null +++ b/app/views/sessions/new.html.haml @@ -0,0 +1,13 @@ +%h1 Log in + += form_tag sessions_path do + %p + = label_tag :email + %br/ + = text_field_tag :email, params[:email] + %p + = label_tag :password + %br/ + = password_field_tag :password + %p.button + = submit_tag "Log in" \ No newline at end of file diff --git a/app/views/suppliers/_form.haml b/app/views/suppliers/_form.haml new file mode 100644 index 0000000..9d4e08b --- /dev/null +++ b/app/views/suppliers/_form.haml @@ -0,0 +1,49 @@ += simple_form_for @supplier do |f| + = f.input :name, required: true + = f.input :address, required: true + = f.input :phone, required: true + = f.input :phone2 + = f.input :fax + = f.input :email + = f.input :url + = f.input :delivery_days + /= f.input :order_howto, as: :text, input_html: {rows: 5} + = f.input :note, as: :text, input_html: {rows: 5, cols: 60} + /= f.input :min_order_quantity + /= f.input :article_info_url + + = f.input :ftp_sync + %div#ftp_details{style: ('display: none' unless @supplier.ftp_sync?)} + = f.input :ftp_host + = f.input :ftp_user + = f.input :ftp_password + = f.input :ftp_type, collection: Supplier::FTP_TYPES, include_blank: false + = f.input :ftp_regexp, hint: t('.ftp_regexp_hint') + + = f.input :mail_sync + %div#mail_details{style: ('display: none' unless @supplier.mail_sync?)} + .input + %label Send to + - if !ENV["MAILER_DOMAIN"].present? + %i (please set MAILER_DOMAIN to see address) + - elsif @supplier.articles_mail_address.present? + %tt= @supplier.articles_mail_address + - else + %i (save supplier to see address) + = f.input :mail_type, collection: ArticleImport.file_formats.map {|k,o| [o::NAME, k] } + = f.input :mail_from + = f.input :mail_subject + + .form-actions + = f.submit class: 'btn' + = link_to t('ui.or_cancel'), (@supplier.new_record? ? suppliers_path : supplier_path(@supplier)) + + + - content_for :javascript do + :javascript + $(document).on('change', '#supplier_ftp_sync', function() { + $('#ftp_details').toggle(this.checked); + }); + $(document).on('change', '#supplier_mail_sync', function() { + $('#mail_details').toggle(this.checked); + }); diff --git a/app/views/suppliers/edit.haml b/app/views/suppliers/edit.haml new file mode 100644 index 0000000..8ee85c8 --- /dev/null +++ b/app/views/suppliers/edit.haml @@ -0,0 +1,2 @@ +%h1 Lieferantin bearbeiten += render 'form' diff --git a/app/views/suppliers/index.haml b/app/views/suppliers/index.haml new file mode 100644 index 0000000..5850248 --- /dev/null +++ b/app/views/suppliers/index.haml @@ -0,0 +1,23 @@ +%h1 Lieferanten +%table + %tr + %th Name + %th Adresse + %th Telefon + %th Liefertage + %th Notiz + %th Zuletzt aktualisiert + %th + - for supplier in @suppliers + %tr + %td= link_to supplier.name, supplier_path(supplier) + %td= supplier.address + %td= supplier.phone + %td= supplier.delivery_days + %td= supplier.note + %td= I18n.l supplier.articles_updated_at rescue nil + %td= link_to supplier.articles.size.to_s + ' Artikel', supplier_articles_url(supplier) + +%br/ += link_to 'Neuer Lieferant', new_supplier_url + \ No newline at end of file diff --git a/app/views/suppliers/new.haml b/app/views/suppliers/new.haml new file mode 100644 index 0000000..003929b --- /dev/null +++ b/app/views/suppliers/new.haml @@ -0,0 +1,2 @@ +%h1 Neue Lieferantin += render 'form' diff --git a/app/views/suppliers/show.haml b/app/views/suppliers/show.haml new file mode 100644 index 0000000..43e553d --- /dev/null +++ b/app/views/suppliers/show.haml @@ -0,0 +1,43 @@ +%h1 Lieferantin anzeigen +%p + Name: + %br/ + %strong= @supplier.name +%p + Adresse: + %br/ + %strong= @supplier.address +%p + Telefon 1: + %br/ + %strong= @supplier.phone +%p + Telefon 2: + %br/ + %strong= @supplier.phone2 +%p + Fax: + %br/ + %strong= @supplier.fax +%p + E-Mail: + %br/ + %strong= @supplier.email +%p + Webseite: + %br/ + %strong= @supplier.url +%p + Liefertage: + %br/ + %strong= @supplier.delivery_days +%p + Notiz: + %br/ + %strong= @supplier.note + += link_to 'Bearbeiten', edit_supplier_path(@supplier) +| += link_to 'Löschen', supplier_path(@supplier), :confirm => 'Bist du sicher?', :method => :delete +| += link_to 'Zurück', suppliers_path diff --git a/config/routes.rb b/config/routes.rb index c06383a..edeaf24 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,19 @@ Rails.application.routes.draw do - # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html + get 'log_in' => 'sessions#new', :as => :log_in + match 'log_out' => 'sessions#destroy', :as => :log_out, :via => [:get, :post] + resources :sessions, :only => [:new, :create, :destroy] + + get '/' => 'suppliers#index', :as => :root + + resources :suppliers do + resources :articles do # name_prefix => nil + collection do + delete :destroy_all + get :upload + post :parse + end + end + end + + match '/:controller(/:action(/:id))', :via => [:get, :post] end