replace skeleton files by previous version; apply obvious fixes
This commit is contained in:
parent
1f5cbcd6d1
commit
2133be2436
35 changed files with 1086 additions and 39 deletions
|
@ -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;
|
||||
}
|
||||
|
|
74
app/assets/stylesheets/scaffold.css
Normal file
74
app/assets/stylesheets/scaffold.css
Normal file
|
@ -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%;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
147
app/controllers/articles_controller.rb
Normal file
147
app/controllers/articles_controller.rb
Normal file
|
@ -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
|
24
app/controllers/sessions_controller.rb
Normal file
24
app/controllers/sessions_controller.rb
Normal file
|
@ -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
|
89
app/controllers/suppliers_controller.rb
Normal file
89
app/controllers/suppliers_controller.rb
Normal file
|
@ -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
|
2
app/helpers/articles_helper.rb
Normal file
2
app/helpers/articles_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module ArticlesHelper
|
||||
end
|
2
app/helpers/sessions_helper.rb
Normal file
2
app/helpers/sessions_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module SessionsHelper
|
||||
end
|
2
app/helpers/suppliers_helper.rb
Normal file
2
app/helpers/suppliers_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module SuppliersHelper
|
||||
end
|
|
@ -1,3 +0,0 @@
|
|||
class ApplicationRecord < ActiveRecord::Base
|
||||
self.abstract_class = true
|
||||
end
|
62
app/models/article.rb
Normal file
62
app/models/article.rb
Normal file
|
@ -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
|
146
app/models/supplier.rb
Normal file
146
app/models/supplier.rb
Normal file
|
@ -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
|
40
app/models/user.rb
Normal file
40
app/models/user.rb
Normal file
|
@ -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
|
4
app/models/user_access.rb
Normal file
4
app/models/user_access.rb
Normal file
|
@ -0,0 +1,4 @@
|
|||
class UserAccess < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
belongs_to :supplier
|
||||
end
|
54
app/views/articles/_form.haml
Normal file
54
app/views/articles/_form.haml
Normal file
|
@ -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
|
7
app/views/articles/edit.haml
Normal file
7
app/views/articles/edit.haml
Normal file
|
@ -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)
|
57
app/views/articles/index.haml
Normal file
57
app/views/articles/index.haml
Normal file
|
@ -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
|
5
app/views/articles/new.haml
Normal file
5
app/views/articles/new.haml
Normal file
|
@ -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)
|
18
app/views/articles/parse_errors.haml
Normal file
18
app/views/articles/parse_errors.haml
Normal file
|
@ -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)
|
57
app/views/articles/show.haml
Normal file
57
app/views/articles/show.haml
Normal file
|
@ -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)
|
26
app/views/articles/upload.haml
Normal file
26
app/views/articles/upload.haml
Normal file
|
@ -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)
|
45
app/views/articles/verify_upload.haml
Normal file
45
app/views/articles/verify_upload.haml
Normal file
|
@ -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'
|
||||
|
23
app/views/layouts/application.haml
Normal file
23
app/views/layouts/application.haml
Normal file
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1 +0,0 @@
|
|||
= yield
|
13
app/views/sessions/new.html.haml
Normal file
13
app/views/sessions/new.html.haml
Normal file
|
@ -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"
|
49
app/views/suppliers/_form.haml
Normal file
49
app/views/suppliers/_form.haml
Normal file
|
@ -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 <tt>MAILER_DOMAIN</tt> 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);
|
||||
});
|
2
app/views/suppliers/edit.haml
Normal file
2
app/views/suppliers/edit.haml
Normal file
|
@ -0,0 +1,2 @@
|
|||
%h1 Lieferantin bearbeiten
|
||||
= render 'form'
|
23
app/views/suppliers/index.haml
Normal file
23
app/views/suppliers/index.haml
Normal file
|
@ -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
|
||||
|
2
app/views/suppliers/new.haml
Normal file
2
app/views/suppliers/new.haml
Normal file
|
@ -0,0 +1,2 @@
|
|||
%h1 Neue Lieferantin
|
||||
= render 'form'
|
43
app/views/suppliers/show.haml
Normal file
43
app/views/suppliers/show.haml
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue