Initial commit of foodsoft 2
This commit is contained in:
commit
5b9a7e05df
657 changed files with 70444 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
log/*.log
|
||||
tmp/**/*
|
||||
config/*.yml
|
||||
db/*.sqlite3
|
||||
nbproject/
|
||||
|
296
LICENSE
Normal file
296
LICENSE
Normal file
|
@ -0,0 +1,296 @@
|
|||
FoodSoft - a webbased foodcoop management software
|
||||
Copyright (C) 2007 Benni and Lasse
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
|
||||
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
20
README
Normal file
20
README
Normal file
|
@ -0,0 +1,20 @@
|
|||
== FoodSoft
|
||||
|
||||
Web-based software to manage a non-profit food coop (product catalog, ordering, accounting, job scheduling).
|
||||
|
||||
== License
|
||||
|
||||
FoodSoft - a webbased foodcoop management software
|
||||
Copyright (C) 2007 Benni and Lasse
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
(See file LICENSE for the full text of the GPL)
|
61
README_DEVEL
Normal file
61
README_DEVEL
Normal file
|
@ -0,0 +1,61 @@
|
|||
README for DEVELopment Project Setup
|
||||
====================================
|
||||
|
||||
Gratulations, you have successfully checked out the foodsoft project
|
||||
form the subversion repository. Now you are only a few steps away from
|
||||
trying it out and then jumping into development.
|
||||
|
||||
|
||||
Configure datebase
|
||||
------------------
|
||||
Create at least one local databases and then configure database access.
|
||||
|
||||
cp config/database.yml.SAMPLE config/database.yml
|
||||
|
||||
Edit database.yml to specify connection parameters.
|
||||
|
||||
|
||||
Configure Development Environment
|
||||
---------------------------------
|
||||
You need to create your own copy of the development environment configuration:
|
||||
|
||||
cp config/environments/development.rb.SAMPLE config/environments/development.rb
|
||||
|
||||
Edit development.rb to specify your settings (e.g. ActionMailer SMTP settings).
|
||||
|
||||
|
||||
Configure Foodsoft
|
||||
------------------
|
||||
You need to create your own copy of the foodsoft configuration settings:
|
||||
|
||||
cp config/foodsoft.yml.SAMPLE config/foodsoft.yml
|
||||
|
||||
Edit foodsoft.yml to suit your needs.
|
||||
|
||||
|
||||
Create log files
|
||||
----------------
|
||||
The WEBrick server will complain if these do not exist.
|
||||
|
||||
touch log/development.log
|
||||
touch log/test.log
|
||||
touch log/production.log
|
||||
touch log/server.log
|
||||
|
||||
|
||||
Create database schema
|
||||
----------------------
|
||||
rake db:schema:load
|
||||
|
||||
|
||||
Create user admin with password secret
|
||||
--------------------------------------
|
||||
rake foodsoft:create_admin
|
||||
|
||||
|
||||
Try it out
|
||||
----------
|
||||
Start the WEBrick server to try it out:
|
||||
|
||||
script/server
|
||||
|
10
Rakefile
Normal file
10
Rakefile
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
||||
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
||||
|
||||
require(File.join(File.dirname(__FILE__), 'config', 'boot'))
|
||||
|
||||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
|
||||
require 'tasks/rails'
|
239
app/controllers/admin_controller.rb
Normal file
239
app/controllers/admin_controller.rb
Normal file
|
@ -0,0 +1,239 @@
|
|||
class AdminController < ApplicationController
|
||||
before_filter :authenticate_admin
|
||||
filter_parameter_logging :password, :password_confirmation # do not log passwort parameters
|
||||
|
||||
verify :method => :post, :only => [ :destroyUser, :createUser, :updateUser, :destroyGroup, :createGroup, :updateGroup], :redirect_to => { :action => :index }
|
||||
|
||||
# Messages
|
||||
MSG_USER_CREATED = 'Benutzer_in wurde erfolgreich angelegt.'
|
||||
MSG_USER_UPDATED = 'Änderungen wurden gespeichert'
|
||||
MSG_USER_DELETED = 'Benutzer_in wurde gelöscht'
|
||||
ERR_NO_SELF_DELETE = 'Du darfst Dich nicht selbst löschen'
|
||||
MESG_NO_ADMIN_ANYMORE = "Du bist nun kein Admin mehr"
|
||||
|
||||
def index
|
||||
@user = self.current_user
|
||||
@groups = Group.find(:all, :limit => 5, :order => 'created_on DESC')
|
||||
@users = User.find(:all, :limit => 5, :order => 'created_on DESC')
|
||||
end
|
||||
|
||||
# ************************** group actions **************************
|
||||
def listGroups
|
||||
if (params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 100)
|
||||
@per_page = params[:per_page].to_i
|
||||
else
|
||||
@per_page = 20
|
||||
end
|
||||
# if the search field is used
|
||||
conditions = "name LIKE '%#{params[:query]}%'" unless params[:query].nil?
|
||||
|
||||
@total = Group.count(:conditions => conditions)
|
||||
@groups = Group.paginate(:conditions => conditions, :page => params[:page], :per_page => @per_page, :order => 'type DESC, name')
|
||||
|
||||
respond_to do |format|
|
||||
format.html # index.html.erb
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'table', :partial => "listGroups"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def showGroup
|
||||
@group = Group.find(params[:id])
|
||||
end
|
||||
|
||||
def newGroup
|
||||
@group = Group.new
|
||||
render :action => 'newGroup'
|
||||
end
|
||||
|
||||
def newOrderGroup
|
||||
@group = OrderGroup.new
|
||||
render :action => 'newGroup'
|
||||
end
|
||||
|
||||
def createGroup
|
||||
@group = Group.new(params[:group])
|
||||
if @group.save
|
||||
flash[:notice] = 'Neue Gruppe wurde angelegt.'
|
||||
redirect_to :action => 'members', :id => @group.id
|
||||
else
|
||||
flash[:error] = 'Gruppe konnte nicht angelegt werden.'
|
||||
render :action => 'newGroup'
|
||||
end
|
||||
end
|
||||
|
||||
def createOrderGroup
|
||||
@group = OrderGroup.new(params[:group])
|
||||
@group.account_balance = 0
|
||||
@group.account_updated = Time.now
|
||||
if @group.save
|
||||
flash[:notice] = 'Neue Bestellgruppe wurde angelegt.'
|
||||
redirect_to :action => 'members', :id => @group.id
|
||||
else
|
||||
flash[:error] = 'Gruppe konnte nicht angelegt werden.'
|
||||
render :action => 'newGroup'
|
||||
end
|
||||
end
|
||||
|
||||
def editGroup
|
||||
@group = Group.find(params[:id])
|
||||
render :template => 'groups/edit'
|
||||
end
|
||||
|
||||
def updateGroup
|
||||
@group = Group.find(params[:id])
|
||||
if @group.update_attributes(params[:group])
|
||||
flash[:notice] = 'Group was successfully updated.'
|
||||
redirect_to :action => 'showGroup', :id => @group
|
||||
else
|
||||
render :template => 'groups/edit'
|
||||
end
|
||||
end
|
||||
|
||||
def destroyGroup
|
||||
begin
|
||||
group = Group.find(params[:id])
|
||||
group.destroy
|
||||
redirect_to :action => 'listGroups'
|
||||
rescue => error
|
||||
flash[:error] = error.to_s
|
||||
redirect_to :action => "showGroup", :id => group
|
||||
end
|
||||
end
|
||||
|
||||
# ************************** Membership methods ******************************
|
||||
|
||||
# loads the membership-page
|
||||
def members
|
||||
@group = Group.find(params[:id])
|
||||
end
|
||||
|
||||
# adds a new member to the group
|
||||
def addMember
|
||||
@group = Group.find(params[:id])
|
||||
user = User.find(params[:user])
|
||||
Membership.create(:group => @group, :user => user)
|
||||
redirect_to :action => 'memberships_reload', :id => @group
|
||||
end
|
||||
|
||||
# the membership will find an end....
|
||||
def dropMember
|
||||
begin
|
||||
group = Group.find(params[:group])
|
||||
Membership.find(params[:membership]).destroy
|
||||
if User.find(@current_user.id).role_admin?
|
||||
redirect_to :action => 'memberships_reload', :id => group
|
||||
else
|
||||
# If the user drops himself from admin group
|
||||
flash[:notice] = MESG_NO_ADMIN_ANYMORE
|
||||
render(:update) {|page| page.redirect_to :controller => "index"}
|
||||
end
|
||||
rescue => error
|
||||
flash[:error] = error.to_s
|
||||
redirect_to :action => 'memberships_reload', :id => group
|
||||
end
|
||||
end
|
||||
|
||||
# the two boxes 'members' and 'non members' will be reload through ajax
|
||||
def memberships_reload
|
||||
@group = Group.find(params[:id])
|
||||
render :update do |page|
|
||||
page.replace_html 'members', :partial => 'groups/members', :object => @group
|
||||
page.replace_html 'non_members', :partial => 'groups/non_members', :object => @group
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# ****************************** User methdos ******************************
|
||||
def listUsers
|
||||
if (params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 100)
|
||||
@per_page = params[:per_page].to_i
|
||||
else
|
||||
@per_page = 20
|
||||
end
|
||||
# if the search field is used
|
||||
conditions = "first_name LIKE '%#{params[:query]}%' OR last_name LIKE '%#{params[:query]}%'" unless params[:query].nil?
|
||||
|
||||
@total = User.count(:conditions => conditions)
|
||||
@users = User.paginate :page => params[:page], :conditions => conditions, :per_page => @per_page, :order => 'nick'
|
||||
|
||||
respond_to do |format|
|
||||
format.html # listUsers.haml
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'table', :partial => "listUsers"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def showUser
|
||||
@user = User.find(params[:id])
|
||||
end
|
||||
|
||||
def newUser
|
||||
@user = User.new
|
||||
if request.xml_http_request?
|
||||
render :update do |page|
|
||||
page.replace_html 'userForm', :partial => "newUser"
|
||||
page['newUser'].show
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def createUser
|
||||
@user = User.new(params[:user])
|
||||
@user.set_password({:required => true}, params[:user][:password], params[:user][:password_confirmation])
|
||||
if @user.errors.empty? && @user.save
|
||||
for setting in User::setting_keys.keys
|
||||
@user.settings[setting] = (params[:user][:settings] && params[:user][:settings][setting] == '1' ? '1' : nil)
|
||||
end
|
||||
flash[:notice] = MSG_USER_CREATED
|
||||
redirect_to :action => 'listUsers'
|
||||
else
|
||||
render :action => 'newUser'
|
||||
end
|
||||
end
|
||||
|
||||
def editUser
|
||||
@user = User.find(params[:id])
|
||||
if request.xml_http_request?
|
||||
render :update do |page|
|
||||
page.replace_html 'userForm', :partial => "newUser"
|
||||
page['newUser'].show
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def updateUser
|
||||
@user = User.find(params[:id])
|
||||
@user.set_password({:required => false}, params[:user][:password], params[:user][:password_confirmation])
|
||||
@user.attributes = params[:user]
|
||||
for setting in User::setting_keys.keys
|
||||
@user.settings[setting] = (params[:user][:settings] && params[:user][:settings][setting] == '1' ? '1' : nil)
|
||||
end
|
||||
if @user.errors.empty? && @user.save
|
||||
flash[:notice] = MSG_USER_UPDATED
|
||||
redirect_to :action => 'showUser', :id => @user
|
||||
else
|
||||
render :action => 'editUser'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def destroyUser
|
||||
user = User.find(params[:id])
|
||||
if user.nick == @current_user.nick
|
||||
# deny destroying logged-in-user
|
||||
flash[:error] = ERR_NO_SELF_DELETE
|
||||
else
|
||||
user.destroy
|
||||
flash[:notice] = MSG_USER_DELETED
|
||||
end
|
||||
redirect_to :action => 'listUsers'
|
||||
end
|
||||
|
||||
end
|
136
app/controllers/application.rb
Normal file
136
app/controllers/application.rb
Normal file
|
@ -0,0 +1,136 @@
|
|||
require 'user'
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
# Gettext For i18n
|
||||
# locale is chosen through browser http request
|
||||
init_gettext "foodsoft"
|
||||
|
||||
before_filter :select_foodcoop, :authenticate, :store_controller
|
||||
# before_filter :ensureUTF8
|
||||
after_filter :send_email_messages, :remove_controller
|
||||
|
||||
# sends a mail, when an error occurs
|
||||
# see plugins/exception_notification
|
||||
include ExceptionNotifiable
|
||||
|
||||
# Returns the controller handling the current request.
|
||||
def self.current
|
||||
Thread.current[:application_controller]
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def current_user
|
||||
begin
|
||||
# check if there is a valid session and return the logged-in user (its object)
|
||||
if session['user_and_subdomain']
|
||||
id, subdomain = session['user_and_subdomain'].split
|
||||
# for shared-host installations. check if the cookie-subdomain fits to request.
|
||||
return User.current_user = User.find(id) if request.subdomains.first == subdomain
|
||||
end
|
||||
rescue
|
||||
reset_session
|
||||
flash[:error]= _("An error has occurred. Please login again.")
|
||||
redirect_to :controller => 'login'
|
||||
end
|
||||
end
|
||||
|
||||
def current_user=(user)
|
||||
session['user_and_subdomain'] = [user.id, request.subdomains.first].join(" ")
|
||||
end
|
||||
|
||||
def return_to
|
||||
session['return_to']
|
||||
end
|
||||
|
||||
def return_to=(uri)
|
||||
session['return_to'] = uri
|
||||
end
|
||||
|
||||
def deny_access
|
||||
self.return_to = request.request_uri
|
||||
redirect_to :controller => 'login', :action => 'denied'
|
||||
return false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# selects the foodcoop depending on the subdomain
|
||||
def select_foodcoop
|
||||
# get subdomain and set FoodSoft-class-variable (for later config-requests)
|
||||
FoodSoft.subdomain = request.subdomains.first
|
||||
# set database-connection
|
||||
ActiveRecord::Base.establish_connection(FoodSoft.get_database)
|
||||
end
|
||||
|
||||
# Ensures the HTTP content-type encoding is set to "UTF-8" for "text/html" contents.
|
||||
def ensureUTF8
|
||||
content_type = headers["Content-Type"] || "text/html"
|
||||
if /^text\//.match(content_type)
|
||||
headers["Content-Type"] = "#{content_type}; charset=utf-8"
|
||||
end
|
||||
end
|
||||
|
||||
def authenticate(role = 'any')
|
||||
# Attempt to retrieve authenticated user from controller instance or session...
|
||||
if !(user = current_user)
|
||||
# No user at all: redirect to login page.
|
||||
self.return_to = request.request_uri
|
||||
redirect_to :controller => 'login'
|
||||
return false
|
||||
else
|
||||
# We have an authenticated user, now check role...
|
||||
# Roles gets the user through his memberships.
|
||||
hasRole = case role
|
||||
when "admin" then user.role_admin?
|
||||
when "finance" then user.role_finance?
|
||||
when "article_meta" then user.role_article_meta?
|
||||
when "suppliers" then user.role_suppliers?
|
||||
when "orders" then user.role_orders?
|
||||
when "any" then true # no role required
|
||||
else false # any unknown role will always fail
|
||||
end
|
||||
if hasRole
|
||||
@current_user = user
|
||||
else
|
||||
deny_access
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def authenticate_admin
|
||||
authenticate('admin')
|
||||
end
|
||||
|
||||
def authenticate_finance
|
||||
authenticate('finance')
|
||||
end
|
||||
|
||||
def authenticate_article_meta
|
||||
authenticate('article_meta')
|
||||
end
|
||||
|
||||
def authenticate_suppliers
|
||||
authenticate('suppliers')
|
||||
end
|
||||
|
||||
def authenticate_orders
|
||||
authenticate('orders')
|
||||
end
|
||||
|
||||
# Stores this controller instance as a thread local varibale to be accessible from outside ActionController/ActionView.
|
||||
def store_controller
|
||||
Thread.current[:application_controller] = self
|
||||
end
|
||||
|
||||
# Sets the thread local variable that holds a reference to the current controller to nil.
|
||||
def remove_controller
|
||||
Thread.current[:application_controller] = nil
|
||||
end
|
||||
|
||||
# Sends any pending emails that were created during this request.
|
||||
def send_email_messages
|
||||
Message.send_emails if Message.pending?
|
||||
end
|
||||
|
||||
end
|
418
app/controllers/articles_controller.rb
Normal file
418
app/controllers/articles_controller.rb
Normal file
|
@ -0,0 +1,418 @@
|
|||
class ArticlesController < ApplicationController
|
||||
before_filter :authenticate_article_meta
|
||||
verify :method => :post,
|
||||
:only => [ :destroyArticle, :createArticle, :updateArticle,
|
||||
:update_all, :createArticlesFromFile, :update_selected_articles ],
|
||||
:redirect_to => { :action => :list }
|
||||
|
||||
# messages
|
||||
ERROR_NO_SELECTED_ARTICLES = 'Du hast keine Artikel ausgewählt'
|
||||
MSG_ALL_CHECKED_DESTROYED = 'Alle gewählten Artikel wurden gelöscht'
|
||||
MSG_ALL_CHECKED_UNAVAILABLE = 'Alle gewählten Artikel wurden auf "nicht verfügbar" gesetzt'
|
||||
MSG_ALL_CHECKED_AVAILABLE = 'Alle gewählten Artikel wurden auf "verfügbar" gesetzt'
|
||||
ERROR_NO_SELECTED_ACTION = 'Keine Aktion ausgewählt!'
|
||||
ERROR_UPDATE_ARTICLES = 'Ein Fehler ist aufgetreten: '
|
||||
|
||||
def index
|
||||
@suppliers = Supplier.find(:all)
|
||||
end
|
||||
|
||||
def list
|
||||
if params[:id]
|
||||
@supplier = Supplier.find(params[:id])
|
||||
@suppliers = Supplier.find(:all)
|
||||
if (params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 500)
|
||||
@per_page = params[:per_page].to_i
|
||||
else
|
||||
@per_page = 30
|
||||
end
|
||||
|
||||
if params['sort']
|
||||
sort = case params['sort']
|
||||
when "name" then "articles.name"
|
||||
when "unit" then "articles.unit"
|
||||
when "category" then "article_categories.name"
|
||||
when "note" then "articles.note"
|
||||
when "availability" then "articles.availability"
|
||||
when "name_reverse" then "articles.name DESC"
|
||||
when "unit_reverse" then "articles.unit DESC"
|
||||
when "category_reverse" then "article_categories.name DESC"
|
||||
when "note_reverse" then "articles.note DESC"
|
||||
when "availability_reverse" then "articles.availability DESC"
|
||||
end
|
||||
else
|
||||
sort = "article_categories.name, articles.name"
|
||||
end
|
||||
|
||||
# if somebody uses the search field:
|
||||
conditions = ["articles.name LIKE ?", "%#{params[:query]}%"] unless params[:query].nil?
|
||||
|
||||
@total = @supplier.articles.count(:conditions => conditions)
|
||||
@articles = @supplier.articles.paginate(:order => sort,
|
||||
:conditions => conditions,
|
||||
:page => params[:page],
|
||||
:per_page => @per_page,
|
||||
:include => :article_category)
|
||||
|
||||
respond_to do |format|
|
||||
format.html # list.haml
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'table', :partial => "list"
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
end
|
||||
|
||||
def newArticle
|
||||
@supplier = Supplier.find(params[:supplier])
|
||||
@article = Article.new(:supplier => @supplier, :tax => 7.0)
|
||||
render :update do |page|
|
||||
page["edit_article"].replace_html :partial => 'new'
|
||||
page["edit_article"].show
|
||||
end
|
||||
end
|
||||
|
||||
def createArticle
|
||||
@article = Article.new(params[:article])
|
||||
if @article.valid? and @article.save
|
||||
render :update do |page|
|
||||
page.Element.hide('edit_article')
|
||||
page.insert_html :top, 'listbody', :partial => 'new_article_row'
|
||||
page[@article.id.to_s].visual_effect(:highlight,
|
||||
:duration => 2)
|
||||
# highlights article
|
||||
if !@article.availability
|
||||
page[@article.id.to_s].addClassName 'unavailable'
|
||||
else
|
||||
page[@article.id.to_s].addClassName 'just_updated'
|
||||
end
|
||||
end
|
||||
else
|
||||
render :update do |page|
|
||||
page.replace_html 'edit_article', :partial => "new"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# edit an article and its price
|
||||
def editArticle
|
||||
@article = Article.find(params[:id])
|
||||
render :update do |page|
|
||||
page["edit_article"].replace_html :partial => 'edit'
|
||||
page["edit_article"].show
|
||||
end
|
||||
#render :partial => "quick_edit", :layout => false
|
||||
end
|
||||
|
||||
# Updates one Article and highlights the line if succeded
|
||||
def updateArticle
|
||||
@article = Article.find(params[:id])
|
||||
if @article.update_attributes(params[:article])
|
||||
render :update do |page|
|
||||
page["edit_article"].hide
|
||||
page[@article.id.to_s].replace_html :partial => 'article_row'
|
||||
|
||||
# hilights an updated article if the article ist available
|
||||
page[@article.id.to_s].addClassName 'just_updated' if @article.availability
|
||||
|
||||
# highlights an available article and de-highlights in other case
|
||||
if !@article.availability
|
||||
page[@article.id.to_s].addClassName 'unavailable'
|
||||
# remove updated-class
|
||||
page[@article.id.to_s].removeClassName 'just_updated'
|
||||
else
|
||||
page[@article.id.to_s].removeClassName 'unavailable'
|
||||
end
|
||||
|
||||
page[@article.id.to_s].visual_effect(:highlight, :delay => 0.5, :duration => 2)
|
||||
end
|
||||
else
|
||||
render :update do |page|
|
||||
page["edit_article"].replace_html :partial => "edit"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Deletes article from database. send error msg, if article is used in a current order
|
||||
def destroyArticle
|
||||
@article = Article.find(params[:id])
|
||||
@order = @article.inUse #if article is in an active Order, the Order will be returned
|
||||
if @order
|
||||
render :update do |page|
|
||||
page.insert_html :after, @article.id.to_s, :partial => 'destroyActiveArticle'
|
||||
end
|
||||
else
|
||||
@article.destroy
|
||||
render :update do |page|
|
||||
page[@article.id.to_s].remove
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Renders a form for editing all articles from a supplier
|
||||
def edit_all
|
||||
@supplier = Supplier.find(params[:id])
|
||||
end
|
||||
|
||||
# Updates all article of specific supplier
|
||||
# deletes all articles from params[outlisted_articles]
|
||||
def update_all
|
||||
currentArticle = nil # used to find out which article caused a validation exception
|
||||
begin
|
||||
Article.transaction do
|
||||
@supplier = Supplier.find(params[:supplier][:id]) if params[:supplier][:id]
|
||||
unless params[:article].blank?
|
||||
# Update other article attributes...
|
||||
i = 0
|
||||
for id in params[:article].keys
|
||||
currentArticle = Article.find(id)
|
||||
currentArticle.update_attributes!(params[:article].values[i])
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
# delete articles
|
||||
if params[:outlisted_articles]
|
||||
params[:outlisted_articles].keys.each {|id| Article.find(id).destroy }
|
||||
end
|
||||
end
|
||||
# Successfully done.
|
||||
flash[:notice] = 'Alle Artikel und Preise wurden aktalisiert'
|
||||
redirect_to :action => 'list', :id => @supplier
|
||||
|
||||
rescue => e
|
||||
# An error has occurred, transaction has been rolled back.
|
||||
if currentArticle
|
||||
@failedArticle = currentArticle
|
||||
flash[:error] = "Es trat ein Fehler beim Aktualisieren des Artikels '#{currentArticle.name}' auf: #{e.message}"
|
||||
params[:sync] ? redirect_to(:action => "list", :id => @supplier) : render(:action => 'edit_all')
|
||||
else
|
||||
flash[:error] = "Es trat ein Fehler beim Aktualisieren der Artikel auf: #{e.message}"
|
||||
redirect_to :action => "index"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# makes different actions on selected articles
|
||||
def update_selected_articles
|
||||
@supplier = Supplier.find(params[:supplier])
|
||||
articles = Array.new
|
||||
begin
|
||||
raise ERROR_NO_SELECTED_ARTICLES if params[:selected_articles].nil?
|
||||
params[:selected_articles].each do |article|
|
||||
articles << Article.find(article) # put selected articles in an array
|
||||
end
|
||||
|
||||
case params[:selected_action].to_s
|
||||
when 'destroy'
|
||||
articles.each {|a| a.destroy }
|
||||
flash[:notice] = MSG_ALL_CHECKED_DESTROYED
|
||||
when 'setNotAvailable'
|
||||
articles.each {|a| a.update_attribute(:availability, false) }
|
||||
flash[:notice] = MSG_ALL_CHECKED_UNAVAILABLE
|
||||
when 'setAvailable'
|
||||
articles.each {|a| a.update_attribute(:availability, true) }
|
||||
flash[:notice] = MSG_ALL_CHECKED_AVAILABLE
|
||||
else
|
||||
flash[:error] = ERROR_NO_SELECTED_ACTION
|
||||
end
|
||||
# action succeded
|
||||
redirect_to :action => 'list', :id => @supplier
|
||||
|
||||
rescue => e
|
||||
flash[:error] = ERROR_UPDATE_ARTICLES + e
|
||||
redirect_to :action => 'list', :id => @supplier
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
#************** start article categories ************************
|
||||
|
||||
def newCategory
|
||||
@article_category = ArticleCategory.new
|
||||
render :update do |page|
|
||||
page['category_form'].replace_html :partial => 'article_categories/new'
|
||||
page['category_form'].show
|
||||
end
|
||||
end
|
||||
|
||||
def createCategory
|
||||
@article_category = ArticleCategory.new(params[:article_category])
|
||||
if @article_category.save
|
||||
render :update do |page|
|
||||
page['category_form'].hide
|
||||
page['category_list'].replace_html :partial => 'article_categories/list'
|
||||
page['category_'+@article_category.id.to_s].visual_effect(:highlight,
|
||||
:duration => 2)
|
||||
end
|
||||
else
|
||||
render :update do |page|
|
||||
page['category_form'].replace_html :partial => 'article_categories/new'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def editCategory
|
||||
@article_category = ArticleCategory.find(params[:id])
|
||||
render :update do |page|
|
||||
page['category_form'].replace_html :partial => 'article_categories/edit'
|
||||
page['category_form'].show
|
||||
end
|
||||
end
|
||||
|
||||
def updateCategory
|
||||
@article_category = ArticleCategory.find(params[:id])
|
||||
if @article_category.update_attributes(params[:article_category])
|
||||
render :update do |page|
|
||||
page['category_form'].hide
|
||||
page['category_list'].replace_html :partial => 'article_categories/list'
|
||||
page['category_'+@article_category.id.to_s].visual_effect(:highlight,
|
||||
:duration => 2)
|
||||
end
|
||||
else
|
||||
render :update do |page|
|
||||
page['category_form'].replace_html :partial => 'article_categories/edit'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroyCategory
|
||||
@article_category = ArticleCategory.find(params[:id])
|
||||
id = @article_category.id.to_s #save the id before destroying the object
|
||||
if @article_category.destroy
|
||||
render :update do |page|
|
||||
page['category_'+id].visual_effect :drop_out
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# lets start with parsing articles from uploaded file, yeah
|
||||
# Renders the upload form
|
||||
def upload_articles
|
||||
end
|
||||
|
||||
# parses the articles from a csv and creates a form-table with the parsed data.
|
||||
# the csv must have the following format:
|
||||
# status | number | name | note | manufacturer | origin | unit | clear price | unit_quantity | tax | deposit | scale quantity | scale price | category
|
||||
# the first line will be ignored.
|
||||
# field-seperator: ";"
|
||||
# text-seperator: ""
|
||||
def parse_articles
|
||||
begin
|
||||
@articles = Array.new
|
||||
articles, outlisted_articles = FoodsoftFile::parse(params[:articles]["file"])
|
||||
articles.each do |row|
|
||||
# creates a new article and price
|
||||
article = Article.new( :name => row[:name],
|
||||
:note => row[:note],
|
||||
:manufacturer => row[:manufacturer],
|
||||
:origin => row[:origin],
|
||||
:unit => row[:unit],
|
||||
:article_category => ArticleCategory.find_by_name(row[:category]),
|
||||
:net_price => row[:price],
|
||||
:unit_quantity => row[:unit_quantity],
|
||||
:order_number => row[:number],
|
||||
:deposit => row[:deposit],
|
||||
:tax => row[:tax])
|
||||
# stop parsing, when an article isn't valid
|
||||
unless article.valid?
|
||||
raise article.errors.full_messages.join(", ") + _(" ..in line ") + (articles.index(row) + 2).to_s
|
||||
end
|
||||
@articles << article
|
||||
end
|
||||
flash.now[:notice] = @articles.size.to_s + _(" articles are parsed successfully.")
|
||||
rescue => e
|
||||
flash[:error] = _("An error has occurred: ") + e.message
|
||||
redirect_to :action => 'upload_articles'
|
||||
end
|
||||
end
|
||||
|
||||
# creates articles from form
|
||||
def create_articles_from_file
|
||||
@supplier = Supplier.find(params[:supplier][:id])
|
||||
begin
|
||||
Article.transaction do
|
||||
i = 0
|
||||
params[:article].each do
|
||||
@article = Article.new(params[:article][i])
|
||||
@article.supplier = @supplier
|
||||
@article.save!
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
# Successfully done.
|
||||
flash[:notice] = _("The articles are saved successfully")
|
||||
redirect_to :action => 'list', :id => @supplier
|
||||
rescue => e
|
||||
# An error has occurred, transaction has been rolled back.
|
||||
flash[:error] = _("An error occured: ") + " #{e.message}"
|
||||
redirect_to :action => 'upload_articles'
|
||||
end
|
||||
end
|
||||
|
||||
# renders a view to import articles in local database
|
||||
#
|
||||
def list_shared_articles
|
||||
@supplier = Supplier.find(params[:id])
|
||||
conditions = []
|
||||
conditions << "supplier_id = #{@supplier.shared_supplier.id}"
|
||||
# check for keywords
|
||||
conditions << params[:import_query].split(' ').collect { |keyword| "name LIKE '%#{keyword}%'" }.join(' AND ') unless params[:import_query].blank?
|
||||
# check for selected lists
|
||||
conditions << "(" + params[:lists].collect {|list| "list = '#{list[0]}'"}.join(" OR ") + ")" if params[:lists]
|
||||
# check for regional articles
|
||||
conditions << "origin = 'REG'" if params[:regional]
|
||||
|
||||
@articles = SharedArticle.paginate :page => params[:page], :per_page => 10, :conditions => conditions.join(" AND ")
|
||||
render :update do |page|
|
||||
page.replace_html 'search_results', :partial => "import_search_results"
|
||||
end
|
||||
end
|
||||
|
||||
# fills a form whith values of the selected shared_article
|
||||
def new_import
|
||||
shared_article = SharedArticle.find(params[:id])
|
||||
@article = Article.new(
|
||||
:supplier_id => params[:supplier_id],
|
||||
:name => shared_article.name,
|
||||
:unit => shared_article.unit,
|
||||
:note => shared_article.note,
|
||||
:manufacturer => shared_article.manufacturer,
|
||||
:origin => shared_article.origin,
|
||||
:net_price => shared_article.price,
|
||||
:tax => shared_article.tax,
|
||||
:deposit => shared_article.deposit,
|
||||
:unit_quantity => shared_article.unit_quantity,
|
||||
:order_number => shared_article.number,
|
||||
# convert to db-compatible-string
|
||||
:shared_updated_on => shared_article.updated_on.to_formatted_s(:db))
|
||||
|
||||
render :update do |page|
|
||||
page["edit_article"].replace_html :partial => 'new'
|
||||
page["edit_article"].show
|
||||
end
|
||||
end
|
||||
|
||||
# sync all articles with the external database
|
||||
# renders a form with articles, which should be updated
|
||||
def sync_articles
|
||||
@supplier = Supplier.find(params[:id])
|
||||
# check if there is an shared_supplier
|
||||
unless @supplier.shared_supplier
|
||||
flash[:error]= @supplier.name + _(" ist not assigned to an external database.")
|
||||
redirect_to :action => "list", :id => @supplier
|
||||
end
|
||||
# sync articles against external database
|
||||
@updated_articles, @outlisted_articles = @supplier.sync_all
|
||||
# convert to db-compatible-string
|
||||
@updated_articles.each {|a, b| a.shared_updated_on = a.shared_updated_on.to_formatted_s(:db)}
|
||||
if @updated_articles.empty? && @outlisted_articles.empty?
|
||||
flash[:notice] = _("The database is up to date.")
|
||||
redirect_to :action => 'list', :id => @supplier
|
||||
end
|
||||
end
|
||||
end
|
375
app/controllers/finance_controller.rb
Normal file
375
app/controllers/finance_controller.rb
Normal file
|
@ -0,0 +1,375 @@
|
|||
class FinanceController < ApplicationController
|
||||
before_filter :authenticate_finance
|
||||
|
||||
# Messages
|
||||
MSG_TRANSACTION_SUCCESS = 'Transaktion erfolgreich angelegt.'
|
||||
ERROR_TRANSACTION_FAILED = 'Transaktion konnte nicht angelegt werden!'
|
||||
MSG_ORDER_SET_BOOKED = 'Die Bestellung wurde auf "gebucht" gesetzt.'
|
||||
ERROR_ORDER_NOT_FINISHED = 'Die Bestellung ist noch nicht beendet.'
|
||||
MSG_ORDER_BALANCED = "Bestellung wurde erfolgreich abgerechnet, die Kontostände aktualisiert."
|
||||
ERROR_BALANCE_ORDER = "Ein Fehler ist beim Abrechnen aufgetreten: "
|
||||
|
||||
def index
|
||||
@financial_transactions = FinancialTransaction.find(:all, :order => "created_on DESC", :limit => 8)
|
||||
@orders = Order.find(:all, :conditions => 'finished = 1 AND booked = 0', :order => 'ends DESC')
|
||||
end
|
||||
|
||||
#list all ordergroups
|
||||
def listOrdergroups
|
||||
@user = @current_user
|
||||
if (params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 100)
|
||||
@per_page = params[:per_page].to_i
|
||||
else
|
||||
@per_page = 20
|
||||
end
|
||||
if params["sort"]
|
||||
sort = case params["sort"]
|
||||
when "name" then "name"
|
||||
when "size" then "actual_size"
|
||||
when "account_balance" then "account_balance"
|
||||
when "name_reverse" then "name DESC"
|
||||
when "size_reverse" then "actual_size DESC"
|
||||
when "account_balance_reverse" then "account_balance DESC"
|
||||
end
|
||||
else
|
||||
sort = "name"
|
||||
end
|
||||
|
||||
conditions = "name LIKE '%#{params[:query]}%'" unless params[:query].nil?
|
||||
|
||||
@total = OrderGroup.count(:conditions => conditions)
|
||||
@groups = OrderGroup.paginate :conditions => conditions, :page => params[:page], :per_page => @per_page, :order => sort
|
||||
|
||||
respond_to do |format|
|
||||
format.html # listOrdergroups.haml
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'table', :partial => "listOrdergroups" # _listOrdergroups.haml
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#new financial transactions (ordergroups)
|
||||
def newTransaction
|
||||
@group = OrderGroup.find(params[:id])
|
||||
@financial_transaction = FinancialTransaction.new(:order_group => @group)
|
||||
render :template => 'financial_transactions/new'
|
||||
end
|
||||
|
||||
#save the new financial transaction and update the account_balance of the ordergroup
|
||||
def createTransaction
|
||||
@group = OrderGroup.find(params[:id])
|
||||
amount = params[:financial_transaction][:amount]
|
||||
note = params[:financial_transaction][:note]
|
||||
begin
|
||||
@group.addFinancialTransaction(amount, note, @current_user)
|
||||
flash[:notice] = MSG_TRANSACTION_SUCCESS
|
||||
redirect_to :action => 'listTransactions', :id => @group
|
||||
rescue => e
|
||||
@financial_transaction = FinancialTransaction.new(params[:financial_transaction])
|
||||
flash.now[:error] = ERROR_TRANSACTION_FAILED + ' (' + e.message + ')'
|
||||
render :template => 'financial_transactions/new'
|
||||
end
|
||||
end
|
||||
|
||||
# list transactions of a specific ordergroup
|
||||
def listTransactions
|
||||
@group = Group.find(params[:id])
|
||||
|
||||
if params['sort']
|
||||
sort = case params['sort']
|
||||
when "date" then "created_on"
|
||||
when "note" then "note"
|
||||
when "amount" then "amount"
|
||||
when "date_reverse" then "created_on DESC"
|
||||
when "note_reverse" then "note DESC"
|
||||
when "amount_reverse" then "amount DESC"
|
||||
end
|
||||
else
|
||||
sort = "created_on DESC"
|
||||
end
|
||||
|
||||
conditions = ["note LIKE ?", "%#{params[:query]}%"] unless params[:query].nil?
|
||||
|
||||
@total = @group.financial_transactions.count(:conditions => conditions)
|
||||
@financial_transactions = @group.financial_transactions.paginate(:page => params[:page],
|
||||
:per_page => 10,
|
||||
:conditions => conditions,
|
||||
:order => sort)
|
||||
respond_to do |format|
|
||||
format.html {render :template => 'financial_transactions/list'}
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'table', :partial => "financial_transactions/list"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# gives a view to add multiple transactions to different groups
|
||||
# e.g. this is helpful when updating multiple accounts in case of a new statement
|
||||
def new_transactions
|
||||
end
|
||||
|
||||
# creates multiple transactions at once
|
||||
def create_transactions
|
||||
begin
|
||||
note = params[:note]
|
||||
raise _("Note is required!") if note.blank?
|
||||
params[:financial_transactions].each do |trans|
|
||||
# ignore empty amount fields ...
|
||||
unless trans[:amount].blank?
|
||||
OrderGroup.find(trans[:order_group_id]).addFinancialTransaction trans[:amount], note, @current_user
|
||||
end
|
||||
end
|
||||
flash[:notice] = _('Saved all transactions successfully')
|
||||
redirect_to :action => 'index'
|
||||
rescue => error
|
||||
flash[:error] = _("An error occured: ") + error.to_s
|
||||
redirect_to :action => 'new_transactions'
|
||||
end
|
||||
end
|
||||
|
||||
# list finished orders for the order-clearing
|
||||
def listOrders
|
||||
@orders = Order.paginate_all_by_finished true, :page => params[:page], :per_page => 10, :order => 'ends DESC'
|
||||
end
|
||||
|
||||
def editOrder
|
||||
@order = Order.find(params[:id])
|
||||
@comments = @order.comments
|
||||
case params[:view]
|
||||
when 'editResults'
|
||||
render :partial => 'editResults'
|
||||
when 'groupsOverview'
|
||||
render :partial => 'groupsOverview'
|
||||
when 'articlesOverview'
|
||||
render :partial => 'articlesOverview'
|
||||
when "editNote"
|
||||
render :partial => "editNote"
|
||||
end
|
||||
end
|
||||
|
||||
def newArticleResult
|
||||
@order = Order.find(params[:id])
|
||||
@article = @order.order_article_results.build(:tax => 7, :deposit => 0)
|
||||
render :update do |page|
|
||||
page["edit_box"].replace_html :partial => "newArticleResult"
|
||||
page["edit_box"].show
|
||||
end
|
||||
end
|
||||
|
||||
def createArticleResult
|
||||
render :update do |page|
|
||||
@article = OrderArticleResult.new(params[:order_article_result])
|
||||
@article.fc_markup = FoodSoft::getPriceMarkup
|
||||
@article.make_gross if @article.tax && @article.deposit && @article.net_price
|
||||
if @article.valid?
|
||||
@article.save
|
||||
@order = @article.order
|
||||
page["edit_box"].hide
|
||||
page["order_summary"].replace_html :partial => 'summary'
|
||||
page.insert_html :bottom, "result_table", :partial => "articleResults"
|
||||
page["order_article_result_#{@article.id}"].visual_effect :highlight, :duration => 2
|
||||
page["group_order_article_results_#{@article.id}"].show
|
||||
else
|
||||
page["edit_box"].replace_html :partial => "newArticleResult"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def editArticleResult
|
||||
@article = OrderArticleResult.find(params[:id])
|
||||
render :update do |page|
|
||||
page["edit_box"].replace_html :partial => 'editArticleResult'
|
||||
page["edit_box"].show
|
||||
end
|
||||
end
|
||||
|
||||
def updateArticleResult
|
||||
@article = OrderArticleResult.find(params[:id])
|
||||
@article.attributes=(params[:order_article_result]) # update attributes but doesn't save
|
||||
@article.make_gross
|
||||
@order = @article.order
|
||||
@ordered_articles = @order.order_article_results
|
||||
@group_orders = @order.group_order_results
|
||||
render :update do |page|
|
||||
if @article.save
|
||||
page["edit_box"].hide
|
||||
page["order_summary"].replace_html :partial => 'summary'
|
||||
page["order_summary"].visual_effect :highlight, :duration => 2
|
||||
page["order_article_result_#{@article.id}"].replace_html :partial => 'articleResult'
|
||||
page['order_article_result_'+@article.id.to_s].visual_effect :highlight, :delay => 0.5, :duration => 2
|
||||
page["group_order_article_results_#{@article.id}"].replace_html :partial => "groupOrderArticleResults"
|
||||
else
|
||||
page['edit_box'].replace_html :partial => 'editArticleResult'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroyArticleResult
|
||||
if @article = OrderArticleResult.find(params[:id]).destroy
|
||||
@order = @article.order
|
||||
render :update do |page|
|
||||
page["order_article_result_#{@article.id}"].remove
|
||||
page["group_order_article_results_#{@article.id}"].remove
|
||||
page["order_summary"].replace_html :partial => 'summary'
|
||||
page["order_summary"].visual_effect :highlight, :duration => 2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def newGroupResult
|
||||
@result = OrderArticleResult.find(params[:id]).group_order_article_results.build
|
||||
render :update do |page|
|
||||
page["edit_box"].replace_html :partial => "newGroupResult"
|
||||
page["edit_box"].show
|
||||
end
|
||||
end
|
||||
|
||||
# Creates a new GroupOrderArticleResult
|
||||
# If the the chosen OrderGroup hasn't ordered yet, a GroupOrderResult will created
|
||||
def createGroupResult
|
||||
@result = GroupOrderArticleResult.new(params[:group_order_article_result])
|
||||
order = @result.order_article_result.order
|
||||
orderGroup = OrderGroup.find(params[:group_order_article_result][:group_order_result_id])
|
||||
# creates a new GroupOrderResult if necessary
|
||||
unless @result.group_order_result = GroupOrderResult.find(:first,
|
||||
:conditions => ["group_order_results.group_name = ? AND group_order_results.order_id = ?", orderGroup.name, order.id ])
|
||||
@result.group_order_result = GroupOrderResult.create(:order => order, :group_name => orderGroup.name)
|
||||
end
|
||||
render :update do |page|
|
||||
if @result.valid? && @result.save
|
||||
@result.group_order_result.updatePrice #updates the price attribute
|
||||
article = @result.order_article_result
|
||||
page["edit_box"].hide
|
||||
page.insert_html :after, "groups_results_#{article.id}", :partial => "groupResults"
|
||||
page["group_order_article_result_#{@result.id}"].visual_effect :highlight, :duration => 2
|
||||
page["groups_amount"].replace_html number_to_currency(article.order.sumPrice('groups'))
|
||||
page["fcProfit"].replace_html number_to_currency(article.order.fcProfit)
|
||||
page["fcProfit"].visual_effect :highlight, :duration => 2
|
||||
|
||||
# get the new sums for quantity and price and replace it
|
||||
total = article.total
|
||||
page["totalArticleQuantity_#{article.id}"].replace_html total[:quantity]
|
||||
page["totalArticlePrice_#{article.id}"].replace_html number_to_currency(total[:price])
|
||||
else
|
||||
page["edit_box"].replace_html :partial => "newGroupResult"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def updateGroupResult
|
||||
@result = GroupOrderArticleResult.find(params[:id])
|
||||
render :update do |page|
|
||||
if params[:group_order_article_result]
|
||||
if @result.update_attribute(:quantity, params[:group_order_article_result][:quantity])
|
||||
order = @result.group_order_result.order
|
||||
groups_amount = order.sumPrice("groups")
|
||||
article = @result.order_article_result
|
||||
total = article.total
|
||||
|
||||
page["edit_box"].hide
|
||||
page["groups_amount"].replace_html number_to_currency(groups_amount)
|
||||
page["fcProfit"].replace_html number_to_currency(order.fcProfit)
|
||||
page["groups_amount"].visual_effect :highlight, :duration => 2
|
||||
page["fcProfit"].visual_effect :highlight, :duration => 2
|
||||
page["group_order_article_result_#{@result.id}"].replace_html :partial => "groupResult"
|
||||
page["group_order_article_result_#{@result.id}"].visual_effect :highlight, :duration => 2
|
||||
page["totalArticleQuantity_#{article.id}"].replace_html total[:quantity]
|
||||
page["totalArticlePrice_#{article.id}"].replace_html total[:price]
|
||||
page["sum_of_article_#{article.id}"].visual_effect :highlight, :duration => 2
|
||||
end
|
||||
else
|
||||
page["edit_box"].replace_html :partial => 'editGroupResult'
|
||||
page["edit_box"].show
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroyGroupResult
|
||||
@result = GroupOrderArticleResult.find(params[:id])
|
||||
if @result.destroy
|
||||
render :update do |page|
|
||||
article = @result.order_article_result
|
||||
page["group_order_article_result_#{@result.id}"].remove
|
||||
page["groups_amount"].replace_html number_to_currency(article.order.sumPrice('groups'))
|
||||
page["fcProfit"].replace_html number_to_currency(article.order.fcProfit)
|
||||
page["fcProfit"].visual_effect :highlight, :duration => 2
|
||||
total = article.total # get total quantity and price for the ArticleResult
|
||||
page["totalArticleQuantity_#{article.id}"].replace_html total[:quantity]
|
||||
page["totalArticleQuantity_#{article.id}"].visual_effect :highlight, :duration => 2
|
||||
page["totalArticlePrice_#{article.id}"].replace_html number_to_currency(total[:price])
|
||||
page["totalArticlePrice_#{article.id}"].visual_effect :highlight, :duration => 2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def editOrderSummary
|
||||
@order = Order.find(params[:id])
|
||||
render :update do |page|
|
||||
page["edit_box"].replace_html :partial => 'editSummary'
|
||||
page["edit_box"].show
|
||||
end
|
||||
end
|
||||
|
||||
def updateOrderSummary
|
||||
@order = Order.find(params[:id])
|
||||
render :update do |page|
|
||||
if @order.update_attributes(params[:order])
|
||||
page["edit_box"].hide
|
||||
page["order_summary"].replace_html :partial => "summary"
|
||||
page["clear_invoice"].visual_effect :highlight, :duration => 2
|
||||
else
|
||||
page["edit_box"].replace_html :partial => 'editSummary'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def updateOrderNote
|
||||
@order = Order.find(params[:id])
|
||||
render :update do |page|
|
||||
if @order.update_attribute(:note, params[:order][:note])
|
||||
page["note"].replace_html simple_format(@order.note)
|
||||
page["results"].replace_html :partial => "groupsOverview"
|
||||
else
|
||||
page["results"].replace_html :partial => "editNote"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# before the order will booked, a view lists all OrderGroups and its order_prices
|
||||
def confirmOrder
|
||||
@order = Order.find(params[:id])
|
||||
end
|
||||
|
||||
# Balances the Order, Update of the OrderGroup.account_balances
|
||||
def balanceOrder
|
||||
@order = Order.find(params[:id])
|
||||
begin
|
||||
@order.balance(@current_user)
|
||||
flash[:notice] = MSG_ORDER_BALANCED
|
||||
redirect_to :action => "index"
|
||||
rescue => e
|
||||
flash[:error] = ERROR_BALANCE_ORDER + e
|
||||
redirect_to :action =>"editOrder", :id => @order
|
||||
end
|
||||
end
|
||||
|
||||
# Set all GroupOrders that belong to this finished order to status 'booked'.
|
||||
def setAllBooked
|
||||
@order = Order.find(params[:id])
|
||||
if (@order.finished?)
|
||||
@order.booked = true
|
||||
@order.updated_by = @current_user
|
||||
@order.save!
|
||||
flash[:notice] = MSG_ORDER_SET_BOOKED
|
||||
redirect_to :action => 'listOrders', :id => @order
|
||||
else
|
||||
flash[:error] = ERROR_ORDER_NOT_FINISHED
|
||||
redirect_to :action => 'listOrders', :id => @order
|
||||
end
|
||||
end
|
||||
|
||||
end
|
261
app/controllers/index_controller.rb
Normal file
261
app/controllers/index_controller.rb
Normal file
|
@ -0,0 +1,261 @@
|
|||
class IndexController < ApplicationController
|
||||
# Messages
|
||||
MSG_USER_UPDATED = 'Benutzeränderungen wurden gespeichert'
|
||||
ERROR_NO_GROUP_MEMBER = 'Du bist kein Gruppenmitglied.'
|
||||
MSG_GROUP_UPDATED = 'Gruppe wurde erfolgreich bearbeitet'
|
||||
ERR_LAST_MEMBER = "Eine Benutzerin muss der Bestellgruppe erhalten bleiben"
|
||||
MSG_MEMBERSHIP_ENDS = 'Du bist nun nicht mehr Mitglied der Gruppe '
|
||||
ERR_CANNOT_INVITE = 'Du kannst niemanden in diese Gruppe einladen.'
|
||||
MSG_INVITE_SUCCESS = 'Es wurde eine Einladung an %s geschickt.'
|
||||
|
||||
def index
|
||||
@currentOrders = Order.find_current
|
||||
@orderGroup = @current_user.find_ordergroup
|
||||
if @orderGroup
|
||||
@financial_transactions = @orderGroup.financial_transactions.find(:all, :order => 'created_on desc', :limit => 3)
|
||||
end
|
||||
# unread messages
|
||||
@messages = Message.find_all_by_recipient_id_and_read(@current_user.id, false, :order => 'messages.created_on desc', :include => :sender)
|
||||
# unaccepted tasks
|
||||
@unaccepted_tasks = @current_user.unaccepted_tasks
|
||||
# task in next week
|
||||
@next_tasks = @current_user.next_tasks
|
||||
|
||||
# count tasks with no responsible person
|
||||
# tasks for groups the current user is not a member are ignored
|
||||
tasks = Task.find(:all, :conditions => ["assigned = ? and done = ?", false, false])
|
||||
@unassigned_tasks_number = 0
|
||||
for task in tasks
|
||||
(@unassigned_tasks_number += 1) unless task.group && !current_user.is_member_of(task.group)
|
||||
end
|
||||
end
|
||||
|
||||
def myProfile
|
||||
@user = @current_user
|
||||
@user_columns = ["first_name", "last_name", "email", "phone", "address"]
|
||||
end
|
||||
|
||||
def editProfile
|
||||
@user = @current_user
|
||||
end
|
||||
|
||||
def updateProfile
|
||||
@user = @current_user
|
||||
@user.set_password({:required => false}, params[:user][:password], params[:user][:password_confirmation])
|
||||
@user.attributes = params[:user]
|
||||
for setting in User::setting_keys.keys
|
||||
@user.settings[setting] = (params[:user][:settings] && params[:user][:settings][setting] == '1' ? '1' : nil)
|
||||
end
|
||||
if @user.errors.empty? && @user.save
|
||||
flash[:notice] = MSG_USER_UPDATED
|
||||
redirect_to :action => 'myProfile'
|
||||
else
|
||||
render :action => 'editProfile'
|
||||
end
|
||||
end
|
||||
|
||||
def myOrdergroup
|
||||
@user = @current_user
|
||||
@ordergroup = @user.find_ordergroup
|
||||
@ordergroup_column_names = ["Description", "Actual Size", "Balance", "Updated"]
|
||||
@ordergroup_columns = ["description", "actual_size", "account_balance", "account_updated"]
|
||||
|
||||
#listing the financial transactions with ajax...
|
||||
|
||||
if params['sort']
|
||||
sort = case params['sort']
|
||||
when "date" then "created_on"
|
||||
when "note" then "note"
|
||||
when "amount" then "amount"
|
||||
when "date_reverse" then "created_on DESC"
|
||||
when "note_reverse" then "note DESC"
|
||||
when "amount_reverse" then "amount DESC"
|
||||
end
|
||||
else
|
||||
sort = "created_on DESC"
|
||||
end
|
||||
|
||||
# or if somebody uses the search field:
|
||||
conditions = ["note LIKE ?", "%#{params[:query]}%"] unless params[:query].nil?
|
||||
|
||||
@total = @ordergroup.financial_transactions.count(:conditions => conditions)
|
||||
@financial_transactions = @ordergroup.financial_transactions.paginate(:page => params[:page],
|
||||
:per_page => 10,
|
||||
:conditions => conditions,
|
||||
:order => sort)
|
||||
respond_to do |format|
|
||||
format.html # myOrdergroup.haml
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'table', :partial => "financial_transactions/list"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def showGroup
|
||||
@user = @current_user
|
||||
@group = Group.find(params[:id])
|
||||
end
|
||||
|
||||
def showUser
|
||||
@user = User.find(params[:id])
|
||||
end
|
||||
|
||||
def editGroup
|
||||
@group = Group.find(params[:id])
|
||||
authenticate_membership(@group)
|
||||
# unless @group.member?(@current_user)
|
||||
# flash[:error] = ERROR_NO_GROUP_MEMBER
|
||||
# redirect_to :action => 'index'
|
||||
# end
|
||||
end
|
||||
|
||||
# update the Group
|
||||
# only access to description for OrderGroups
|
||||
def updateGroup
|
||||
@group = Group.find(params[:id])
|
||||
authenticate_membership(@group)
|
||||
if @group.is_a?(OrderGroup)
|
||||
@group.update_attribute(:description, params[:group][:description])
|
||||
else
|
||||
@group.update_attributes(params[:group])
|
||||
end
|
||||
if @group.errors.empty?
|
||||
flash[:notice] = MSG_GROUP_UPDATED
|
||||
redirect_to :action => 'showGroup', :id => @group
|
||||
else
|
||||
render :action => 'editGroup'
|
||||
end
|
||||
end
|
||||
|
||||
def members
|
||||
@group = Group.find(params[:id])
|
||||
authenticate_membership(@group)
|
||||
end
|
||||
|
||||
# adds a new member to the group
|
||||
def addMember
|
||||
@group = Group.find(params[:id])
|
||||
authenticate_membership(@group)
|
||||
user = User.find(params[:user])
|
||||
Membership.create(:group => @group, :user => user)
|
||||
redirect_to :action => 'memberships_reload', :id => @group
|
||||
end
|
||||
|
||||
# the membership will find an end....
|
||||
def dropMember
|
||||
begin
|
||||
group = Group.find(params[:group])
|
||||
authenticate_membership(group)
|
||||
membership = Membership.find(params[:membership])
|
||||
if group.is_a?(OrderGroup) && group.memberships.size == 1
|
||||
# Deny dropping member if the group is an OrderGroup and there is only one member left.
|
||||
flash[:error] = ERR_LAST_MEMBER
|
||||
else
|
||||
membership.destroy
|
||||
end
|
||||
redirect_to :action => 'memberships_reload', :id => group
|
||||
rescue => error
|
||||
flash[:error] = error.to_s
|
||||
redirect_to :action => 'memberships_reload', :id => group
|
||||
end
|
||||
end
|
||||
|
||||
# the two boxes 'members' and 'non members' will be reload through ajax
|
||||
def memberships_reload
|
||||
@group = Group.find(params[:id])
|
||||
unless @group.member?(@current_user)
|
||||
flash[:error] = ERROR_NO_GROUP_MEMBER
|
||||
render(:update) {|page| page.redirect_to :action => "myProfile"}
|
||||
else
|
||||
render :update do |page|
|
||||
page.replace_html 'members', :partial => 'groups/members', :object => @group
|
||||
page.replace_html 'non_members', :partial => 'groups/non_members', :object => @group
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# checks if the current_user is member of given group.
|
||||
# if fails the user will redirected to startpage
|
||||
# method used while group/memberships beeing edit
|
||||
def authenticate_membership(group)
|
||||
unless group.member?(@current_user)
|
||||
flash[:error] = ERROR_NO_GROUP_MEMBER
|
||||
if request.xml_http_request?
|
||||
render(:update) {|page| page.redirect_to :action => "index"}
|
||||
else
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# gives a view to list all members of the foodcoop
|
||||
def foodcoop_members
|
||||
|
||||
# sort by ordergroups
|
||||
if params[:sort_by_ordergroups]
|
||||
@users = []
|
||||
OrderGroup.find(:all, :order => "name").each do |group|
|
||||
group.users.each do |user|
|
||||
@users << user
|
||||
end
|
||||
end
|
||||
@total = @users.size
|
||||
else
|
||||
# sort by nick, thats default
|
||||
if (params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 100)
|
||||
@per_page = params[:per_page].to_i
|
||||
else
|
||||
@per_page = 20
|
||||
end
|
||||
|
||||
# if somebody uses the search field:
|
||||
conditions = "first_name LIKE '%#{params[:query]}%' OR last_name LIKE '%#{params[:query]}%'" unless params[:query].blank?
|
||||
|
||||
@total = User.count(:conditions => conditions)
|
||||
@users = User.paginate(:page => params[:page], :per_page => @per_page, :conditions => conditions, :order => "nick", :include => "groups")
|
||||
|
||||
respond_to do |format|
|
||||
format.html # index.html.erb
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'user_table', :partial => "list_members"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# gives an overview for the workgroups and its members
|
||||
def workgroups
|
||||
@groups = Group.find :all, :conditions => "type != 'OrderGroup'", :order => "name"
|
||||
end
|
||||
|
||||
# Invites a new user to join foodsoft in this group.
|
||||
def invite
|
||||
@group = Group.find(params[:id])
|
||||
if (!@group || (!@current_user.is_member_of(@group) && !@current_user.role_admin?))
|
||||
flash[:error] = ERR_CANNOT_INVITE
|
||||
redirect_to(:action => "index")
|
||||
elsif (request.post?)
|
||||
@invite = Invite.new(:user => @current_user, :group => @group, :email => params[:invite][:email])
|
||||
if @invite.save
|
||||
flash[:notice] = format(MSG_INVITE_SUCCESS, @invite.email)
|
||||
redirect_to(:action => 'index')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# cancel personal memberships direct from the myProfile-page
|
||||
def cancel_membership
|
||||
membership = Membership.find(params[:id])
|
||||
if membership.user == current_user
|
||||
membership.destroy
|
||||
flash[:notice] = _("The membership was cancelled.")
|
||||
else
|
||||
flash[:error] = _("You are not allowed to cancel this membership")
|
||||
end
|
||||
redirect_to my_profile_path
|
||||
end
|
||||
end
|
132
app/controllers/login_controller.rb
Normal file
132
app/controllers/login_controller.rb
Normal file
|
@ -0,0 +1,132 @@
|
|||
class LoginController < ApplicationController
|
||||
skip_before_filter :authenticate # no authentication since this is the login page
|
||||
filter_parameter_logging "password" # do not log "password" parameter
|
||||
|
||||
verify :method => :post, :only => [:login, :reset_password, :new], :redirect_to => { :action => :index }
|
||||
|
||||
# Redirects to the login action.
|
||||
def index
|
||||
render :action => 'login'
|
||||
end
|
||||
|
||||
# Logout the current user and deletes the session
|
||||
def logout
|
||||
self.return_to = nil
|
||||
current_user = nil
|
||||
reset_session
|
||||
flash[:notice] = _("Logged out.")
|
||||
render :action => 'login'
|
||||
end
|
||||
|
||||
# Displays a "denied due to insufficient privileges" message and provides the login form.
|
||||
def denied
|
||||
flash[:error] = _("You are not authorized to do this. Please log in as another user or go back.")
|
||||
render :action => 'login'
|
||||
end
|
||||
|
||||
# Login to the foodsoft.
|
||||
def login
|
||||
user = User.find_by_nick(params[:login][:user])
|
||||
if user && user.has_password(params[:login][:password])
|
||||
# Set last_login to Now()
|
||||
user.update_attribute(:last_login, Time.now)
|
||||
self.current_user = user
|
||||
if (redirect = return_to)
|
||||
self.return_to = nil
|
||||
redirect_to redirect
|
||||
else
|
||||
redirect_to :controller => 'index'
|
||||
end
|
||||
else
|
||||
current_user = nil
|
||||
flash[:error] = _("Sorry, login is not possible.")
|
||||
end
|
||||
end
|
||||
|
||||
# Display the form to enter an email address requesting a token to set a new password.
|
||||
def forgot_password
|
||||
end
|
||||
|
||||
# Sends an email to a user with the token that allows setting a new password through action "password".
|
||||
def reset_password
|
||||
if (user = User.find_by_email(params[:login][:email]))
|
||||
user.reset_password_token = user.new_random_password(16)
|
||||
user.reset_password_expires = Time.now.advance(:days => 2)
|
||||
if user.save
|
||||
email = Mailer.deliver_password(user)
|
||||
logger.debug("Sent password reset email to #{user.email}.")
|
||||
end
|
||||
end
|
||||
flash[:notice] = _("If your email address is listed in our system, you will now receive an email with the instructions how to change your password.")
|
||||
render :action => 'login'
|
||||
end
|
||||
|
||||
# Set a new password with a token from the password reminder email.
|
||||
# Called with params :id => User.id and :token => User.reset_password_token to specify a new password.
|
||||
def password
|
||||
@user = User.find_by_id_and_reset_password_token(params[:id], params[:token])
|
||||
if (@user.nil? || @user.reset_password_expires < Time.now)
|
||||
flash[:error] = _("Invalid or expired token, password cannot be changed.")
|
||||
render :action => 'forgot_password'
|
||||
end
|
||||
end
|
||||
|
||||
# Sets a new password.
|
||||
# Called with params :id => User.id and :token => User.reset_password_token to specify a new password.
|
||||
def new
|
||||
@user = User.find_by_id_and_reset_password_token(params[:id], params[:token])
|
||||
if (@user.nil? || @user.reset_password_expires < Time.now)
|
||||
flash[:error] = _("Invalid or expired token, password cannot be changed.")
|
||||
redirect_to :action => 'forgot_password'
|
||||
else
|
||||
@user.set_password({:required => true}, params[:user][:password], params[:user][:password_confirmation])
|
||||
if @user.errors.empty?
|
||||
@user.reset_password_token = nil
|
||||
@user.reset_password_expires = nil
|
||||
if @user.save
|
||||
flash[:notice] = _("New password has been saved, please log in.")
|
||||
render :action => 'login'
|
||||
else
|
||||
@user = User.find(@user.id) # reload to refetch token
|
||||
flash[:error] = _("When trying to save your new password an error has occured. Please try again.")
|
||||
render :action => 'password'
|
||||
end
|
||||
else
|
||||
flash[:error] = _("Error: #{@user.errors.on_base}.")
|
||||
render :action => 'password'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Invited users.
|
||||
def invite
|
||||
@invite = Invite.find_by_token(params[:id])
|
||||
if (@invite.nil? || @invite.expires_at < Time.now)
|
||||
flash[:error] = _("Your invitation is invalid or has expired, sorry!")
|
||||
render :action => 'login'
|
||||
elsif @invite.group.nil?
|
||||
flash[:error] = _("The group you are invited to join doesn't exist any more!")
|
||||
render :action => 'login'
|
||||
elsif (request.post?)
|
||||
User.transaction do
|
||||
@user = User.new(params[:user])
|
||||
@user.email = @invite.email
|
||||
@user.set_password({:required => true}, params[:user][:password], params[:user][:password_confirmation])
|
||||
if (@user.errors.empty? && @user.save)
|
||||
Membership.new(:user => @user, :group => @invite.group).save!
|
||||
for setting in User::setting_keys.keys
|
||||
@user.settings[setting] = (params[:user][:settings] && params[:user][:settings][setting] == '1' ? '1' : nil)
|
||||
end
|
||||
@invite.destroy
|
||||
flash[:notice] = _("Congratulations, your account has been created successfully. You can log in now.")
|
||||
render(:action => 'login')
|
||||
end
|
||||
end
|
||||
else
|
||||
@user = User.new(:email => @invite.email)
|
||||
end
|
||||
rescue
|
||||
flash[:error] = _("An error has occured. Please try again.")
|
||||
end
|
||||
|
||||
end
|
170
app/controllers/messages_controller.rb
Normal file
170
app/controllers/messages_controller.rb
Normal file
|
@ -0,0 +1,170 @@
|
|||
class MessagesController < ApplicationController
|
||||
verify :method => :post, :only => [:create, :destroy], :redirect_to => { :action => :index }
|
||||
|
||||
MESSAGE_SEND_SUCCESS = 'Nachricht erfolgreich abgeschickt.'
|
||||
MESSAGE_DELETE_SUCCESS = 'Nachricht gelöscht.'
|
||||
MESSAGES_DELETE_SUCCESS = 'Nachrichten gelöscht.'
|
||||
ERROR_SEND_FAILED = 'Nachricht konnte nicht verschickt werden.'
|
||||
ERROR_CANNOT_DELETE = 'Nachricht kann nicht gelöscht werden.'
|
||||
ERROR_CANNOT_REPLY = 'Auf diese Nachricht kann nicht geantwortet werden.'
|
||||
ERROR_UNKNOWN_USER = 'Unbekannte_r Empfänger_in.'
|
||||
ERROR_INVALID_GROUP = 'Empfängergruppe ist unbekannt oder hat keine Mitglieder.'
|
||||
ERROR_NO_RECIPIENTS = 'Es sind keine Empfänger_innen ausgewählt.'
|
||||
|
||||
# Renders the "inbox" action.
|
||||
def index
|
||||
inbox
|
||||
render :action => 'inbox'
|
||||
end
|
||||
|
||||
# Shows the user's message inbox.
|
||||
def inbox
|
||||
@messages = Message.find_all_by_recipient_id(@current_user.id, :order => 'messages.created_on desc', :include => :sender)
|
||||
end
|
||||
|
||||
# Creates a new message object.
|
||||
def new
|
||||
@message = Message.new
|
||||
end
|
||||
|
||||
# Creates a new message.
|
||||
def create
|
||||
# Determine recipient(s)...
|
||||
@recipient_nicks = ''
|
||||
if (params[:everyone] == 'yes')
|
||||
@everyone = true
|
||||
recipients = User.find(:all)
|
||||
else
|
||||
recipients = Array.new
|
||||
# users
|
||||
for nick in params[:recipient][:nicks].split(%r{,\s*})
|
||||
if (user = User.find_by_nick(nick))
|
||||
recipients << user
|
||||
@recipient_nicks += "#{nick}, "
|
||||
end
|
||||
end
|
||||
@recipient_nicks = @recipient_nicks[0..-3] unless @recipient_nicks.empty?
|
||||
# group
|
||||
group = Group.find_by_id(params[:recipient][:group_id]) if params[:recipient][:group_id]
|
||||
recipients = recipients | group.users if group
|
||||
end
|
||||
|
||||
# Construct message(s) and save them...
|
||||
if recipients.empty?
|
||||
@message = Message.new(params[:message])
|
||||
@message.sender = @current_user
|
||||
@group = group
|
||||
flash[:error] = ERROR_NO_RECIPIENTS
|
||||
render :action => 'new'
|
||||
else
|
||||
begin
|
||||
if (@everyone)
|
||||
recipients_text = 'alle'
|
||||
else
|
||||
recipients_text = @recipient_nicks
|
||||
recipients_text += (group.nil? ? '' : (recipients_text.empty? ? group.name : ", #{group.name}"))
|
||||
end
|
||||
Message.transaction do
|
||||
for recipient in recipients
|
||||
@message = Message.new(
|
||||
:subject => params[:message][:subject],
|
||||
:body => params[:message][:body],
|
||||
:recipient => recipient,
|
||||
:recipients => recipients_text
|
||||
)
|
||||
@message.sender = @current_user
|
||||
@message.save!
|
||||
end
|
||||
end
|
||||
flash[:notice] = MESSAGE_SEND_SUCCESS
|
||||
redirect_to :action=> 'index'
|
||||
rescue
|
||||
@group = group
|
||||
flash[:error] = ERROR_SEND_FAILED
|
||||
render :action => 'new'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Deletes the message(s) specified by the id/ids param if the current user is the recipient.
|
||||
def destroy
|
||||
ids = Array.new
|
||||
ids << params[:id] if params[:id]
|
||||
ids = ids + params[:ids] if (params[:ids] && params[:ids].is_a?(Array))
|
||||
for id in ids
|
||||
message = Message.find(id)
|
||||
if (message && message.recipient && message.recipient == @current_user)
|
||||
message.destroy
|
||||
else
|
||||
flash[:error] = ERROR_CANNOT_DELETE
|
||||
break
|
||||
end
|
||||
end
|
||||
flash[:notice] = MESSAGE_DELETE_SUCCESS if (flash[:error].blank? && ids.size == 1)
|
||||
flash[:notice] = "#{ids.size} #{MESSAGES_DELETE_SUCCESS}" if (flash[:error].blank? && ids.size > 1)
|
||||
redirect_to :action=> 'index'
|
||||
end
|
||||
|
||||
# Shows a single message.
|
||||
def show
|
||||
@message = Message.find_by_id_and_recipient_id(params[:id], @current_user.id)
|
||||
@message.update_attribute('read', true) if (@message && !@message.read?)
|
||||
end
|
||||
|
||||
# Replys to the message specified through :id.
|
||||
def reply
|
||||
message = Message.find(params[:id])
|
||||
if (message && message.recipient && message.recipient == @current_user && message.sender && message.sender.nick)
|
||||
@message = Message.new(
|
||||
:recipient => message.sender,
|
||||
:subject => "Re: #{message.subject}",
|
||||
:body => "#{message.sender.nick} schrieb am #{FoodSoft::format_date(message.created_on)} um #{FoodSoft::format_time(message.created_on)}:\n"
|
||||
)
|
||||
if (message.body)
|
||||
message.body.each_line{|l| @message.body += "> #{l}"}
|
||||
end
|
||||
@recipient_nicks = message.sender.nick
|
||||
render :action => 'new'
|
||||
else
|
||||
flash[:error] = ERROR_CANNOT_REPLY
|
||||
redirect_to :action=> 'index'
|
||||
end
|
||||
end
|
||||
|
||||
# Shows new-message form with the recipient user specified through :id.
|
||||
def user
|
||||
if (recipient = User.find(params[:id]))
|
||||
@recipient_nicks = recipient.nick
|
||||
@message = Message.new
|
||||
render :action => 'new'
|
||||
else
|
||||
flash[:error] = ERROR_UNKNOWN_USER
|
||||
redirect_to :action=> 'index'
|
||||
end
|
||||
end
|
||||
|
||||
# Shows new-message form with the recipient user specified through :id.
|
||||
def group
|
||||
recipient = Group.find(params[:id], :include => :memberships)
|
||||
if (recipient && !recipient.memberships.empty?)
|
||||
@message = Message.new
|
||||
@group = recipient
|
||||
render :action => 'new'
|
||||
else
|
||||
flash[:error] = ERROR_INVALID_GROUP
|
||||
redirect_to :action=> 'index'
|
||||
end
|
||||
end
|
||||
|
||||
# Auto-complete for recipient user list.
|
||||
def auto_complete_for_recipient_nicks
|
||||
@users = User.find(:all, :conditions => ['LOWER(nick) LIKE ?', '%' + params[:recipient][:nicks].downcase + '%'], :order => :nick, :limit => 8)
|
||||
render :partial => '/shared/auto_complete_users'
|
||||
end
|
||||
|
||||
# Returns list of all users as auto-completion hint.
|
||||
def user_list
|
||||
@users = User.find(:all, :order => :nick)
|
||||
render :partial => '/shared/auto_complete_users'
|
||||
end
|
||||
end
|
220
app/controllers/ordering_controller.rb
Normal file
220
app/controllers/ordering_controller.rb
Normal file
|
@ -0,0 +1,220 @@
|
|||
# Controller for all ordering-actions that are performed by a user who is member of an OrderGroup.
|
||||
# Management actions that require the "orders" role are handled by the OrdersController.
|
||||
class OrderingController < ApplicationController
|
||||
# Security
|
||||
before_filter :ensureOrderGroupMember
|
||||
verify :method => :post, :only => [:saveOrder], :redirect_to => { :action => :index }
|
||||
|
||||
# Messages
|
||||
ERROR_ALREADY_FINISHED = 'Diese Bestellung ist bereits abgeschlossen.'
|
||||
ERROR_NO_ORDERGROUP = 'Sie gehören keiner Bestellgruppe an.'
|
||||
ERROR_INSUFFICIENT_FUNDS = 'Der Bestellwert übersteigt das verfügbare Guthaben.'
|
||||
MSG_ORDER_UPDATED = 'Die Bestellung wurde gespeichert.'
|
||||
MSG_ORDER_CREATED = 'Die Bestellung wurde angelegt.'
|
||||
ERROR_UPDATE_FAILED = 'Die Bestellung konnte nicht aktualisiert werden, da ein Fehler auftrat.'
|
||||
ERROR_UPDATE_CONFLICT = 'In der Zwischenzeit hat jemand anderes auch bestellt, daher konnte die Bestellung nicht aktualisiert werden.'
|
||||
|
||||
# Index page.
|
||||
def index
|
||||
@orderGroup = @current_user.find_ordergroup
|
||||
@currentOrders = Order.find_current
|
||||
@finishedOrders = @orderGroup.findExpiredOrders + @orderGroup.findFinishedNotBooked
|
||||
@bookedOrders = @orderGroup.findBookedOrders(5)
|
||||
|
||||
# Calculate how much the order group has spent on open or nonbooked orders:
|
||||
@currentOrdersValue, @nonbookedOrdersValue = 0, 0
|
||||
@orderGroup.findCurrent.each { |groupOrder| @currentOrdersValue += groupOrder.price}
|
||||
@finishedOrders.each { |groupOrder| @nonbookedOrdersValue += groupOrder.price}
|
||||
end
|
||||
|
||||
# Edit a current order.
|
||||
def order
|
||||
@order = Order.find(params[:id], :include => [:supplier, :order_articles])
|
||||
if !@order.current?
|
||||
flash[:notice] = ERROR_ALREADY_FINISHED
|
||||
redirect_to :action => 'index'
|
||||
elsif !(@order_group = @current_user.find_ordergroup)
|
||||
flash[:notice] = ERROR_NO_ORDERGROUP
|
||||
redirect_to :controller => 'index'
|
||||
else
|
||||
@current_orders = Order.find_current
|
||||
@other_orders = @current_orders.reject{|order| order == @order}
|
||||
# Load order article data...
|
||||
@articles_by_category = @order.get_articles
|
||||
# save results of earlier orders in array
|
||||
ordered_articles = Array.new
|
||||
@group_order = @order.group_orders.find(:first, :conditions => "order_group_id = #{@order_group.id}", :include => :group_order_articles)
|
||||
if @group_order
|
||||
# Group has already ordered, so get the results...
|
||||
for article in @group_order.group_order_articles
|
||||
result = article.orderResult
|
||||
ordered_articles[article.order_article_id] = { 'quantity' => article.quantity,
|
||||
'tolerance' => article.tolerance,
|
||||
'quantity_result' => result[:quantity],
|
||||
'tolerance_result' => result[:tolerance]}
|
||||
end
|
||||
@version = @group_order.lock_version
|
||||
@availableFunds = @order_group.getAvailableFunds(@group_order)
|
||||
else
|
||||
@version = 0
|
||||
@availableFunds = @order_group.getAvailableFunds
|
||||
end
|
||||
|
||||
# load prices ....
|
||||
@price = Array.new; @unit = Array.new;
|
||||
@others_quantity = Array.new; @quantity = Array.new; @quantity_result = Array.new; @used_quantity = Array.new; @unused_quantity = Array.new
|
||||
@others_tolerance = Array.new; @tolerance = Array.new; @tolerance_result = Array.new; @used_tolerance = Array.new; @unused_tolerance = Array.new
|
||||
i = 0;
|
||||
@articles_by_category.each do |category, order_articles|
|
||||
for order_article in order_articles
|
||||
# price/unit size
|
||||
@price[i] = order_article.article.gross_price
|
||||
@unit[i] = order_article.article.unit_quantity
|
||||
# quantity
|
||||
@quantity[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id]['quantity'] : 0)
|
||||
@others_quantity[i] = order_article.quantity - @quantity[i]
|
||||
@used_quantity[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id]['quantity_result'] : 0)
|
||||
# tolerance
|
||||
@tolerance[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id]['tolerance'] : 0)
|
||||
@others_tolerance[i] = order_article.tolerance - @tolerance[i]
|
||||
@used_tolerance[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id]['tolerance_result'] : 0)
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Update changes to a current order.
|
||||
def saveOrder
|
||||
order = Order.find(params[:id], :include => [:supplier, :order_articles])
|
||||
if (!order.current?)
|
||||
flash[:error] = ERROR_ALREADY_FINISHED
|
||||
redirect_to :action => 'index'
|
||||
elsif !(order_group = @current_user.find_ordergroup)
|
||||
flash[:error] = ERROR_NO_ORDERGROUP
|
||||
redirect_to :controller => 'index'
|
||||
elsif (params[:total_balance].to_i < 0)
|
||||
flash[:error] = ERROR_INSUFFICIENT_FUNDS
|
||||
redirect_to :action => 'order'
|
||||
elsif (ordered = params[:ordered])
|
||||
begin
|
||||
Order.transaction do
|
||||
# Create group order if necessary...
|
||||
if (groupOrder = order.group_orders.find(:first, :conditions => "order_group_id = #{order_group.id}", :include => [:group_order_articles]))
|
||||
if (params[:version].to_i != groupOrder.lock_version) # check for conflicts well ahead
|
||||
raise ActiveRecord::StaleObjectError
|
||||
end
|
||||
else
|
||||
groupOrder = GroupOrder.new(:order_group => order_group, :order => order, :updated_by => @current_user, :price => 0)
|
||||
groupOrder.save!
|
||||
end
|
||||
# Create/update GroupOrderArticles...
|
||||
newGroupOrderArticles = Array.new
|
||||
for article in order.order_articles
|
||||
# Find the GroupOrderArticle, create a new one if necessary...
|
||||
groupOrderArticles = groupOrder.group_order_articles.select{ |v| v.order_article_id == article.id }
|
||||
unless (groupOrderArticle = groupOrderArticles[0])
|
||||
groupOrderArticle = GroupOrderArticle.create(:group_order => groupOrder, :order_article_id => article.id, :quantity => 0, :tolerance => 0)
|
||||
end
|
||||
# Get ordered quantities and update GroupOrderArticle/-Quantities...
|
||||
unless (quantities = ordered.delete(article.id.to_s)) && (quantity = quantities['quantity']) && (tolerance = quantities['tolerance'])
|
||||
quantity = tolerance = 0
|
||||
end
|
||||
groupOrderArticle.updateQuantities(quantity.to_i, tolerance.to_i)
|
||||
# Add to new list of GroupOrderArticles:
|
||||
newGroupOrderArticles.push(groupOrderArticle)
|
||||
end
|
||||
groupOrder.group_order_articles = newGroupOrderArticles
|
||||
groupOrder.updatePrice
|
||||
groupOrder.updated_by = @current_user
|
||||
groupOrder.save!
|
||||
order.updateQuantities
|
||||
order.save!
|
||||
end
|
||||
flash[:notice] = MSG_ORDER_UPDATED
|
||||
rescue ActiveRecord::StaleObjectError
|
||||
flash[:error] = ERROR_UPDATE_CONFLICT
|
||||
rescue => exception
|
||||
logger.error('Failed to update order: ' + exception.message)
|
||||
flash[:error] = ERROR_UPDATE_FAILED
|
||||
end
|
||||
redirect_to :action => 'my_order_result', :id => order
|
||||
end
|
||||
end
|
||||
|
||||
# Shows the Result for the OrderGroup the current user belongs to
|
||||
# this method decides between finished and unfinished orders
|
||||
def my_order_result
|
||||
@order= Order.find(params[:id])
|
||||
@current_orders = Order.find_current #.reject{|order| order == @order}
|
||||
if @order.finished?
|
||||
@finished= true
|
||||
@groupOrderResult= @order.group_order_results.find_by_group_name(@current_user.find_ordergroup.name)
|
||||
@order_value= @groupOrderResult.price if @groupOrderResult
|
||||
@comments= @order.comments
|
||||
else
|
||||
@group_order = @order.group_orders.find_by_order_group_id(@current_user.find_ordergroup.id)
|
||||
@order_value= @group_order.price if @group_order
|
||||
end
|
||||
end
|
||||
|
||||
# Shows all Orders of the Ordergroup
|
||||
# if selected, it shows all orders of the foodcoop
|
||||
def myOrders
|
||||
@orderGroup = @current_user.find_ordergroup
|
||||
unless params[:show_all] == "1"
|
||||
# get only orders belonging to the ordergroup
|
||||
@finishedOrders = @orderGroup.findExpiredOrders + @orderGroup.findFinishedNotBooked
|
||||
@bookedOrders = GroupOrderResult.paginate :page => params[:page], :per_page => 10,
|
||||
:include => :order,
|
||||
:conditions => ["group_order_results.group_name = ? AND group_order_results.order_id = orders.id AND orders.finished = ? AND orders.booked = ? ", @orderGroup.name, true, true],
|
||||
:order => "orders.ends DESC"
|
||||
else
|
||||
# get all orders, take care of different models in @finishedOrders
|
||||
@show_all = true
|
||||
@finishedOrders = Order.find_finished
|
||||
@bookedOrders = Order.paginate_all_by_booked true, :page => params[:page], :per_page => 10, :order => 'ends desc'
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html # myOrders.haml
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'bookedOrders', :partial => "bookedOrders"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# sends a form for adding a new comment
|
||||
def newComment
|
||||
@order = Order.find(params[:id])
|
||||
render :update do |page|
|
||||
page.replace_html 'newComment', :partial => 'shared/newComment', :object => @order
|
||||
page["newComment"].show
|
||||
end
|
||||
end
|
||||
|
||||
# adds a Comment to the Order
|
||||
def addComment
|
||||
@order = Order.find(params[:id])
|
||||
@comment = Comment.new(params[:comment])
|
||||
@comment.user = @current_user
|
||||
if @comment.title.length > 3 && @order.comments << @comment
|
||||
flash[:notice] = _("Comment has been created.")
|
||||
redirect_to :action => 'my_order_result', :id => @order
|
||||
else
|
||||
flash[:error] = _("The comment has not been saved. Check the title and try again.")
|
||||
redirect_to :action => 'my_order_result', :id => @order
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Returns true if @current_user is member of an OrderGroup.
|
||||
# Used as a :before_filter by OrderingController.
|
||||
def ensureOrderGroupMember
|
||||
!@current_user.find_ordergroup.nil?
|
||||
end
|
||||
|
||||
end
|
185
app/controllers/orders_controller.rb
Normal file
185
app/controllers/orders_controller.rb
Normal file
|
@ -0,0 +1,185 @@
|
|||
# Controller for managing orders, i.e. all actions that require the "orders" role.
|
||||
# Normal ordering actions of members of order groups is handled by the OrderingController.
|
||||
class OrdersController < ApplicationController
|
||||
# Security
|
||||
before_filter :authenticate_orders
|
||||
verify :method => :post, :only => [:finish, :create, :update, :destroy, :setAllBooked], :redirect_to => { :action => :index }
|
||||
|
||||
# Define layout exceptions for PDF actions:
|
||||
layout "application", :except => [:faxPdf, :matrixPdf, :articlesPdf, :groupsPdf]
|
||||
|
||||
# List orders
|
||||
def index
|
||||
@current_orders = Order.find_current
|
||||
@per_page = 15
|
||||
if params['sort']
|
||||
sort = case params['sort']
|
||||
when "supplier" then "suppliers.name, ends DESC"
|
||||
when "ends" then "ends DESC"
|
||||
when "supplier_reverse" then "suppliers.name DESC, ends DESC"
|
||||
when "ends_reverse" then "ends"
|
||||
end
|
||||
else
|
||||
sort = "ends DESC"
|
||||
end
|
||||
@orders = Order.paginate :page => params[:page], :per_page => @per_page,
|
||||
:order => sort, :conditions => ['ends < ? OR starts > ? OR finished = ?', Time.now, Time.now, true],
|
||||
:include => :supplier
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'orders_table', :partial => "list"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Gives a view for the results to a specific order
|
||||
def show
|
||||
@order= Order.find(params[:id])
|
||||
unless @order.finished?
|
||||
@order_articles= @order.get_articles
|
||||
@group_orders= @order.group_orders
|
||||
else
|
||||
@finished= true
|
||||
@order_articles= @order.order_article_results
|
||||
@group_orders= @order.group_order_results
|
||||
@comments= @order.comments
|
||||
partial = case params[:view]
|
||||
when 'normal' then "showResult"
|
||||
when 'groups'then 'showResult_groups'
|
||||
when 'articles'then 'showResult_articles'
|
||||
end
|
||||
render :partial => partial if partial
|
||||
end
|
||||
end
|
||||
|
||||
# Page to create a new order.
|
||||
def new
|
||||
@supplier = Supplier.find(params[:id])
|
||||
@order = @supplier.orders.build :ends => 4.days.from_now
|
||||
@template_orders = Order.find_all_by_supplier_id_and_finished(@supplier.id, true, :limit => 3, :order => 'starts DESC', :include => "order_article_results")
|
||||
end
|
||||
|
||||
# Save a new order.
|
||||
# order_articles will be saved in Order.article_ids=()
|
||||
def create
|
||||
@order = Order.new(params[:order])
|
||||
if @order.save
|
||||
flash[:notice] = _("The order has been created successfully.")
|
||||
redirect_to :action => 'show', :id => @order
|
||||
else
|
||||
render :action => 'new'
|
||||
end
|
||||
end
|
||||
|
||||
# Page to edit an exsiting order.
|
||||
# editing finished orders is done in FinanceController
|
||||
def edit
|
||||
@order = Order.find(params[:id], :include => :articles)
|
||||
end
|
||||
|
||||
# Update an existing order.
|
||||
def update
|
||||
@order = Order.find params[:id]
|
||||
if @order.update_attributes params[:order]
|
||||
flash[:notice] = _("The order has been updated.")
|
||||
redirect_to :action => 'show', :id => @order
|
||||
else
|
||||
render :action => 'edit'
|
||||
end
|
||||
@order.updateAllGroupOrders #important if ordered articles has been removed
|
||||
end
|
||||
|
||||
# Delete an order.
|
||||
def destroy
|
||||
Order.find(params[:id]).destroy
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
|
||||
# Finish a current order.
|
||||
def finish
|
||||
order = Order.find(params[:id])
|
||||
order.finish(@current_user)
|
||||
flash[:notice] = _("The order has been finished successfully.")
|
||||
redirect_to :action => 'show', :id => order
|
||||
end
|
||||
|
||||
# Renders the groups-orderd PDF.
|
||||
def groupsPdf
|
||||
@order = Order.find(params[:id])
|
||||
@options_for_rfpdf ||= {}
|
||||
@options_for_rfpdf[:file_name] = "#{Date.today}_#{@order.name}_GruppenSortierung.pdf"
|
||||
end
|
||||
|
||||
# Renders the articles-orderd PDF.
|
||||
def articlesPdf
|
||||
@order = Order.find(params[:id])
|
||||
@options_for_rfpdf ||= {}
|
||||
@options_for_rfpdf[:file_name] = "#{Date.today}_#{@order.name}_ArtikelSortierung.pdf"
|
||||
end
|
||||
|
||||
# Renders the fax PDF.
|
||||
def faxPdf
|
||||
@order = Order.find(params[:id])
|
||||
@options_for_rfpdf ||= {}
|
||||
@options_for_rfpdf[:file_name] = "#{Date.today}_#{@order.name}_FAX.pdf"
|
||||
end
|
||||
|
||||
# Renders the fax-text-file
|
||||
# e.g. for easier use with online-fax-software, which don't accept pdf-files
|
||||
def text_fax_template
|
||||
order = Order.find(params[:id])
|
||||
supplier = order.supplier
|
||||
contact = FoodSoft.getFoodcoopContact
|
||||
text = _("Order for") + " #{FoodSoft.getFoodcoopName}"
|
||||
text += "\n" + _("Customer number") + ": #{supplier.customer_number}" unless supplier.customer_number.blank?
|
||||
text += "\n" + _("Delivery date") + ": "
|
||||
text += "\n\n#{supplier.name}\n#{supplier.address}\nFAX: #{supplier.fax}\n\n"
|
||||
text += "****** " + _("Shipping address") + "\n\n"
|
||||
text += "#{FoodSoft.getFoodcoopName}\n#{contact[:street]}\n#{contact[:zip_code]} #{contact[:city]}\n\n"
|
||||
text += "****** " + _("Articles") + "\n\n"
|
||||
text += _("Number") + " " + _("Quantity") + " " + _("Name") + "\n"
|
||||
# now display all ordered articles
|
||||
order.order_article_results.each do |article|
|
||||
text += article.order_number.blank? ? " " : "#{article.order_number} "
|
||||
quantity = article.units_to_order.to_i.to_s
|
||||
quantity = " " + quantity if quantity.size < 2
|
||||
text += "#{quantity} #{article.name}\n"
|
||||
end
|
||||
send_data text,
|
||||
:type => 'text/plain; charset=utf-8; header=present',
|
||||
:disposition => "attachment; filename=#{order.name}"
|
||||
end
|
||||
|
||||
# Renders the matrix PDF.
|
||||
def matrixPdf
|
||||
@order = Order.find(params[:id])
|
||||
@options_for_rfpdf ||= {}
|
||||
@options_for_rfpdf[:file_name] = "#{Date.today}_#{@order.name}_Matrix.pdf"
|
||||
end
|
||||
|
||||
# sends a form for adding a new comment
|
||||
def newComment
|
||||
@order = Order.find(params[:id])
|
||||
render :update do |page|
|
||||
page.replace_html 'newComment', :partial => 'shared/newComment', :object => @order
|
||||
end
|
||||
end
|
||||
|
||||
# adds a Comment to the Order
|
||||
def addComment
|
||||
@order = Order.find(params[:id])
|
||||
@comment = Comment.new(params[:comment])
|
||||
@comment.user = @current_user
|
||||
if @comment.title.length > 3 && @order.comments << @comment
|
||||
flash[:notice] = _("Comment has been created.")
|
||||
redirect_to :action => 'show', :id => @order
|
||||
else
|
||||
flash[:error] = _("The comment has not been saved. Check the title and try again.")
|
||||
redirect_to :action => 'show', :id => @order
|
||||
end
|
||||
end
|
||||
end
|
77
app/controllers/suppliers_controller.rb
Normal file
77
app/controllers/suppliers_controller.rb
Normal file
|
@ -0,0 +1,77 @@
|
|||
class SuppliersController < ApplicationController
|
||||
before_filter :authenticate_suppliers, :except => [:index, :list]
|
||||
|
||||
verify :method => :post, :only => [ :destroy, :create, :update ], :redirect_to => { :action => :list }
|
||||
|
||||
# messages
|
||||
MSG_SUPPLIER_DESTOYED = "Lieferant wurde gelöscht"
|
||||
MSG_SUPPLIER_UPDATED = 'Lieferant wurde aktualisiert'
|
||||
MSG_SUPPLIER_CREATED = "Lieferant wurde erstellt"
|
||||
|
||||
def index
|
||||
list
|
||||
render :action => 'list'
|
||||
end
|
||||
|
||||
def list
|
||||
@supplier_column_names = ["Name", "Telefon", "Email", "Kundennummer"]
|
||||
@supplier_columns = ["name", "phone", "email", "customer_number"]
|
||||
@suppliers = Supplier.find :all
|
||||
end
|
||||
|
||||
def show
|
||||
@supplier = Supplier.find(params[:id])
|
||||
@supplier_column_names = ["Name", "Telefon", "Telefon2", "FAX", "Email", "URL", "Kontakt", "Kundennummer", "Liefertage", "BestellHowTo", "Notiz", "Mindestbestellmenge"]
|
||||
@supplier_columns = ["name", "phone", "phone2", "fax", "email", "url", "contact_person", "customer_number", "delivery_days", "order_howto", "note", "min_order_quantity"]
|
||||
end
|
||||
|
||||
# new supplier
|
||||
# if shared_supplier_id is given, the new supplier will filled whith its attributes
|
||||
def new
|
||||
if params[:shared_supplier_id]
|
||||
shared_supplier = SharedSupplier.find(params[:shared_supplier_id])
|
||||
@supplier = shared_supplier.build_supplier(shared_supplier.attributes)
|
||||
else
|
||||
@supplier = Supplier.new
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
@supplier = Supplier.new(params[:supplier])
|
||||
if @supplier.save
|
||||
flash[:notice] = MSG_SUPPLIER_CREATED
|
||||
redirect_to :action => 'list'
|
||||
else
|
||||
render :action => 'new'
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@supplier = Supplier.find(params[:id])
|
||||
end
|
||||
|
||||
def update
|
||||
@supplier = Supplier.find(params[:id])
|
||||
if @supplier.update_attributes(params[:supplier])
|
||||
flash[:notice] = MSG_SUPPLIER_UPDATED
|
||||
redirect_to :action => 'show', :id => @supplier
|
||||
else
|
||||
render :action => 'edit'
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
Supplier.find(params[:id]).destroy
|
||||
flash[:notice] = MSG_SUPPLIER_DESTOYED
|
||||
redirect_to :action => 'list'
|
||||
rescue => e
|
||||
flash[:error] = _("An error has occurred: ") + e.message
|
||||
redirect_to :action => 'show', :id => params[:id]
|
||||
end
|
||||
|
||||
# gives a list with all available shared_suppliers
|
||||
def shared_suppliers
|
||||
@shared_suppliers = SharedSupplier.find(:all)
|
||||
end
|
||||
|
||||
end
|
128
app/controllers/tasks_controller.rb
Normal file
128
app/controllers/tasks_controller.rb
Normal file
|
@ -0,0 +1,128 @@
|
|||
class TasksController < ApplicationController
|
||||
#auto_complete_for :user, :nick
|
||||
|
||||
def index
|
||||
@non_group_tasks = Task.find :all, :conditions => "group_id IS NULL AND done = 0", :order => "due_date ASC"
|
||||
@groups = Group.find :all, :conditions => "type != 'OrderGroup'"
|
||||
end
|
||||
|
||||
def myTasks
|
||||
@unaccepted_tasks = @current_user.unaccepted_tasks
|
||||
@accepted_tasks = @current_user.accepted_tasks
|
||||
end
|
||||
|
||||
def new
|
||||
if params[:id]
|
||||
group = Group.find(params[:id])
|
||||
@task = group.tasks.build :name => group.task_name,
|
||||
:required_users => group.task_required_users,
|
||||
:description => group.task_description,
|
||||
:due_date => group.next_weekly_tasks[params[:task_from_now].to_i]
|
||||
else
|
||||
@task = Task.new
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
@task = Task.new(params[:task])
|
||||
if @task.errors.empty?
|
||||
@task.save
|
||||
flash[:notice] = "Aufgabe wurde erstellt"
|
||||
if @task.group
|
||||
redirect_to :action => "workgroup", :id => @task.group
|
||||
else
|
||||
redirect_to :action => "index"
|
||||
end
|
||||
else
|
||||
render :template => "tasks/new"
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@task = Task.find(params[:id])
|
||||
end
|
||||
|
||||
def edit
|
||||
@task = Task.find(params[:id])
|
||||
end
|
||||
|
||||
def update
|
||||
@task = Task.find(params[:id])
|
||||
@task.attributes=(params[:task])
|
||||
if @task.errors.empty?
|
||||
@task.save
|
||||
flash[:notice] = "Aufgabe wurde aktualisiert"
|
||||
if @task.group
|
||||
redirect_to :action => "workgroup", :id => @task.group
|
||||
else
|
||||
redirect_to :action => "index"
|
||||
end
|
||||
else
|
||||
render :template => "tasks/edit"
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
Task.find(params[:id]).destroy
|
||||
redirect_to :action => "index"
|
||||
end
|
||||
|
||||
# Delete an given Assignment
|
||||
# currently used in edit-view
|
||||
def drop_assignment
|
||||
ass = Assignment.find(params[:id])
|
||||
task = ass.task
|
||||
ass.destroy
|
||||
redirect_to :action => "show", :id => task
|
||||
end
|
||||
|
||||
# assign current_user to the task and set the assignment to "accepted"
|
||||
# if there is already an assignment, only accepted will be set to true
|
||||
def accept
|
||||
task = Task.find(params[:id])
|
||||
if ass = task.is_assigned?(current_user)
|
||||
ass.update_attribute(:accepted, true)
|
||||
else
|
||||
task.assignments.create(:user => current_user, :accepted => true)
|
||||
end
|
||||
flash[:notice] = "Du hast die Aufgabe übernommen"
|
||||
redirect_to :action => "myTasks"
|
||||
end
|
||||
|
||||
# deletes assignment between current_user and given task
|
||||
def reject
|
||||
Task.find(params[:id]).users.delete(current_user)
|
||||
redirect_to :action => "index"
|
||||
end
|
||||
|
||||
def update_status
|
||||
Task.find(params[:id]).update_attribute("done", params[:task][:done])
|
||||
flash[:notice] = "Aufgabenstatus wurde aktualisiert"
|
||||
redirect_to :action => "index"
|
||||
end
|
||||
|
||||
# Shows all tasks, which are already done
|
||||
def archive
|
||||
@tasks = Task.find :all, :conditions => "done = 1", :order => "due_date DESC"
|
||||
end
|
||||
|
||||
# shows workgroup (normal group) to edit weekly_tasks_template
|
||||
def workgroup
|
||||
@group = Group.find(params[:id])
|
||||
if @group.is_a? OrderGroup
|
||||
flash[:error] = "Keine Arbeitsgruppe gefunden"
|
||||
redirect_to :action => "index"
|
||||
end
|
||||
end
|
||||
|
||||
# this method is uses for the auto_complete-function from script.aculo.us
|
||||
def auto_complete_for_task_user_list
|
||||
@users = User.find(
|
||||
:all,
|
||||
:conditions => [ 'LOWER(nick) LIKE ?', '%' + params[:task][:user_list].downcase + '%' ],
|
||||
:order => 'nick ASC',
|
||||
:limit => 8
|
||||
)
|
||||
render :partial => '/shared/auto_complete_users'
|
||||
end
|
||||
end
|
2
app/helpers/admin_helper.rb
Normal file
2
app/helpers/admin_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module AdminHelper
|
||||
end
|
84
app/helpers/application_helper.rb
Normal file
84
app/helpers/application_helper.rb
Normal file
|
@ -0,0 +1,84 @@
|
|||
# Methods added to this helper will be available to all templates in the application.
|
||||
module ApplicationHelper
|
||||
|
||||
def format_time(time = Time.now)
|
||||
FoodSoft::format_date_time(time) unless time.nil?
|
||||
end
|
||||
|
||||
def format_date(time = Time.now)
|
||||
FoodSoft::format_date(time) unless time.nil?
|
||||
end
|
||||
|
||||
# Creates ajax-controlled-links for pagination
|
||||
# see also the plugin "will_paginate"
|
||||
def pagination_links_remote(collection, per_page = @per_page, params = {})
|
||||
# Translations
|
||||
prev_label = '« ' + _('Previous')
|
||||
next_label = _('Next') + ' »'
|
||||
# Merge other url-options for will_paginate
|
||||
params = params.merge({:per_page => per_page})
|
||||
will_paginate collection, {:params => params, :remote => true, :prev_label => prev_label, :next_label => next_label}
|
||||
end
|
||||
|
||||
# Link-collection for per_page-options when using the pagination-plugin
|
||||
def items_per_page(per_page_options = [20, 50, 100], current = @per_page, action = controller.action_name)
|
||||
links = []
|
||||
per_page_options.each do |per_page|
|
||||
unless per_page == current
|
||||
links << link_to_remote(per_page, {:url => {:action => action, :params => {:per_page => per_page}},
|
||||
:before => "Element.show('loader')",
|
||||
:success => "Element.hide('loader')"})
|
||||
else
|
||||
links << per_page
|
||||
end
|
||||
end
|
||||
return _('Per page: ') + links.join(" ")
|
||||
end
|
||||
|
||||
def sort_td_class_helper(param)
|
||||
result = 'class="sortup"' if params[:sort] == param
|
||||
result = 'class="sortdown"' if params[:sort] == param + "_reverse"
|
||||
return result
|
||||
end
|
||||
|
||||
def sort_link_helper(text, param, per_page = 10)
|
||||
key = param
|
||||
key += "_reverse" if params[:sort] == param
|
||||
options = {
|
||||
:url => {:action => 'list', :params => params.merge({:sort => key, :page => nil, :per_page => per_page})},
|
||||
:before => "Element.show('loader')",
|
||||
:success => "Element.hide('loader')"
|
||||
}
|
||||
html_options = {
|
||||
:title => _('Sort by this field'),
|
||||
:href => url_for(:action => 'list', :params => params.merge({:sort => key, :page => nil, :per_page => per_page}))
|
||||
}
|
||||
link_to_remote(text, options, html_options)
|
||||
end
|
||||
|
||||
# Generates a link to the top of the website
|
||||
def link_to_top
|
||||
link_to image_tag("arrow_up_red.png", :size => "16x16", :border => "0", :alt => "Nach oben"), "#"
|
||||
end
|
||||
|
||||
# Returns the weekday. 0 is sunday, 1 is monday and so on
|
||||
def weekday(dayNumber)
|
||||
weekdays = ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"]
|
||||
return weekdays[dayNumber]
|
||||
end
|
||||
|
||||
# highlights a phrase in given text
|
||||
# based on the rails text-helper 'highlight'
|
||||
def highlight_phrase(text, phrase, highlighter = '<strong class="highlight">\1</strong>')
|
||||
unless phrase.blank? || text.nil?
|
||||
phrase.split(' ').each {|keyword| text.gsub!(/(#{Regexp.escape(keyword)})/i, highlighter)}
|
||||
end
|
||||
return text
|
||||
end
|
||||
|
||||
# to set a title for both the h1-tag and the title in the header
|
||||
def title(page_title)
|
||||
content_for(:title) { page_title }
|
||||
end
|
||||
|
||||
end
|
7
app/helpers/articles_helper.rb
Normal file
7
app/helpers/articles_helper.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
module ArticlesHelper
|
||||
|
||||
# useful for highlighting attributes, when synchronizing articles
|
||||
def highlight_new(unequal_attributes, attribute)
|
||||
unequal_attributes.detect {|a| a == attribute} ? "background-color: yellow" : ""
|
||||
end
|
||||
end
|
2
app/helpers/finance_helper.rb
Normal file
2
app/helpers/finance_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module FinanceHelper
|
||||
end
|
2
app/helpers/groups_helper.rb
Normal file
2
app/helpers/groups_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module GroupsHelper
|
||||
end
|
2
app/helpers/index_helper.rb
Normal file
2
app/helpers/index_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module IndexHelper
|
||||
end
|
2
app/helpers/login_helper.rb
Normal file
2
app/helpers/login_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module LoginHelper
|
||||
end
|
2
app/helpers/messages_helper.rb
Normal file
2
app/helpers/messages_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module MessagesHelper
|
||||
end
|
2
app/helpers/ordering_helper.rb
Normal file
2
app/helpers/ordering_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module OrderingHelper
|
||||
end
|
11
app/helpers/orders_helper.rb
Normal file
11
app/helpers/orders_helper.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
module OrdersHelper
|
||||
require 'iconv'
|
||||
|
||||
# This method is needed to convert special characters into UTF-8 for rendering PDF files correctly.
|
||||
def replace_UTF8(field)
|
||||
ic_ignore = Iconv.new('ISO-8859-15//IGNORE//TRANSLIT', 'UTF-8')
|
||||
field = ic_ignore.iconv(field)
|
||||
ic_ignore.close
|
||||
field
|
||||
end
|
||||
end
|
2
app/helpers/suppliers_helper.rb
Normal file
2
app/helpers/suppliers_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module SuppliersHelper
|
||||
end
|
10
app/helpers/tasks_helper.rb
Normal file
10
app/helpers/tasks_helper.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
module TasksHelper
|
||||
|
||||
# generate colored number of still required users
|
||||
def highlighted_required_users(task)
|
||||
unless task.enough_users_assigned?
|
||||
still_required = task.required_users - task.assignments.select { |ass| ass.accepted }.size
|
||||
"<small style='color:red;font-weight:bold'>(#{still_required})</small>"
|
||||
end
|
||||
end
|
||||
end
|
2
app/helpers/users_helper.rb
Normal file
2
app/helpers/users_helper.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
module UsersHelper
|
||||
end
|
201
app/models/article.rb
Normal file
201
app/models/article.rb
Normal file
|
@ -0,0 +1,201 @@
|
|||
# articles are the internal products which can ordered by ordergroups
|
||||
#
|
||||
# articles have the following attributes:
|
||||
# * name
|
||||
# * supplier_id
|
||||
# * article_category_id
|
||||
# * unit (string, e.g. 500gr, 1liter)
|
||||
# * note
|
||||
# * availability (boolean)
|
||||
# * net_price, decimal (net price, which will be edited by the user)
|
||||
# * gross_price, decimal (gross price (or long price), incl. tax, deposit, price markup ... see environment.rb)
|
||||
# * tax, float (the VAT, value added tax. default is 7.00 which means 7.00%)
|
||||
# * deposit, decimal (deposit, e.g. for bottles)
|
||||
# * unit_quantity, int (the internal(FC) size of trading unit)
|
||||
# * order_number, varchar (for the supplier)
|
||||
# * created_at, timestamp
|
||||
# * updated_at, timestamp
|
||||
#
|
||||
class Article < ActiveRecord::Base
|
||||
belongs_to :supplier
|
||||
belongs_to :article_category
|
||||
|
||||
validates_presence_of :name, :unit, :net_price, :tax, :deposit, :unit_quantity, :supplier_id
|
||||
validates_length_of :name, :in => 4..60
|
||||
validates_length_of :unit, :in => 2..15
|
||||
validates_numericality_of :net_price, :greater_than => 0
|
||||
validates_numericality_of :deposit, :tax
|
||||
|
||||
# calculate the gross_price
|
||||
before_save :calc_gross_price
|
||||
|
||||
# Custom attribute setter that accepts decimal numbers using localized decimal separator.
|
||||
def net_price=(net_price)
|
||||
self[:net_price] = FoodSoft::delocalizeDecimalString(net_price)
|
||||
end
|
||||
|
||||
# Custom attribute setter that accepts decimal numbers using localized decimal separator.
|
||||
def tax=(tax)
|
||||
self[:tax] = FoodSoft::delocalizeDecimalString(tax)
|
||||
end
|
||||
|
||||
# Custom attribute setter that accepts decimal numbers using localized decimal separator.
|
||||
def deposit=(deposit)
|
||||
self[:deposit] = FoodSoft::delocalizeDecimalString(deposit)
|
||||
end
|
||||
|
||||
# calculate the fc price and sets the attribute
|
||||
def calc_gross_price
|
||||
self.gross_price = ((net_price + deposit) * (tax / 100 + 1)) * (FoodSoft::getPriceMarkup / 100 + 1)
|
||||
end
|
||||
|
||||
# Returns true if article has been updated at least 2 days ago
|
||||
def recently_updated
|
||||
updated_at > 2.days.ago
|
||||
end
|
||||
|
||||
# Returns how many units of this article need to be ordered given the specified order quantity and tolerance.
|
||||
# This is determined by calculating how many units can be ordered from the given order quantity, using
|
||||
# the tolerance to order an additional unit if the order quantity is not quiet sufficient.
|
||||
# There must always be at least one item in a unit that is an ordered quantity (no units are ever entirely
|
||||
# filled by tolerance items only).
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# unit_quantity | quantity | tolerance | calculateOrderQuantity
|
||||
# --------------+----------+-----------+-----------------------
|
||||
# 4 | 0 | 2 | 0
|
||||
# 4 | 0 | 5 | 0
|
||||
# 4 | 2 | 2 | 1
|
||||
# 4 | 4 | 2 | 1
|
||||
# 4 | 4 | 4 | 1
|
||||
# 4 | 5 | 3 | 2
|
||||
# 4 | 5 | 4 | 2
|
||||
#
|
||||
def calculateOrderQuantity(quantity, tolerance = 0)
|
||||
unitSize = unit_quantity
|
||||
units = quantity / unitSize
|
||||
remainder = quantity % unitSize
|
||||
units += ((remainder > 0) && (remainder + tolerance >= unitSize) ? 1 : 0)
|
||||
end
|
||||
|
||||
# If the article is used in an active Order, the Order will returned.
|
||||
def inUse
|
||||
Order.find(:all, :conditions => 'finished = 0').each do |order|
|
||||
if order.articles.find_by_id(self)
|
||||
@order = order
|
||||
break
|
||||
end
|
||||
end
|
||||
return @order if @order
|
||||
end
|
||||
|
||||
# Checks if the article is in use before it will deleted
|
||||
def before_destroy
|
||||
raise self.name.to_s + _(" cannot be deleted. The article is used in a current order!") if self.inUse
|
||||
end
|
||||
|
||||
# this method checks, if the shared_article has been changed
|
||||
# unequal attributes will returned in array
|
||||
# if only the timestamps differ and the attributes are equal,
|
||||
# false will returned and self.shared_updated_on will be updated
|
||||
def shared_article_changed?
|
||||
# skip early if the timestamp hasn't changed
|
||||
unless self.shared_updated_on == self.shared_article.updated_on
|
||||
|
||||
# try to convert units
|
||||
# convert supplier's price and unit_quantity into fc-size
|
||||
new_price, new_unit_quantity = self.convert_units
|
||||
new_unit = self.unit
|
||||
unless new_price and new_unit_quantity
|
||||
# if convertion isn't possible, take shared_article-price/unit_quantity
|
||||
new_price, new_unit_quantity, new_unit = self.shared_article.price, self.shared_article.unit_quantity, self.shared_article.unit
|
||||
end
|
||||
|
||||
# check if all attributes differ
|
||||
unequal_attributes = Article.compare_attributes(
|
||||
{
|
||||
:name => [self.name, self.shared_article.name],
|
||||
:manufacturer => [self.manufacturer, self.shared_article.manufacturer.to_s],
|
||||
:origin => [self.origin, self.shared_article.origin],
|
||||
:unit => [self.unit, new_unit],
|
||||
:net_price => [self.net_price, new_price],
|
||||
:tax => [self.tax, self.shared_article.tax],
|
||||
:deposit => [self.deposit, self.shared_article.deposit],
|
||||
# take care of different num-objects.
|
||||
:unit_quantity => [self.unit_quantity.to_s.to_f, new_unit_quantity.to_s.to_f],
|
||||
:note => [self.note.to_s, self.shared_article.note.to_s]
|
||||
}
|
||||
)
|
||||
if unequal_attributes.empty?
|
||||
# when attributes not changed, update timestamp of article
|
||||
self.update_attribute(:shared_updated_on, self.shared_article.updated_on)
|
||||
false
|
||||
else
|
||||
unequal_attributes
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# compare attributes from different articles. used for auto-synchronization
|
||||
# returns array of symbolized unequal attributes
|
||||
def self.compare_attributes(attributes)
|
||||
unequal_attributes = attributes.select { |name, values| values[0] != values[1] }
|
||||
unequal_attributes.collect { |pair| pair[0] }
|
||||
end
|
||||
|
||||
# to get the correspondent shared article
|
||||
def shared_article
|
||||
@shared_article ||= self.supplier.shared_supplier.shared_articles.find_by_number(self.order_number)
|
||||
end
|
||||
|
||||
# convert units in foodcoop-size
|
||||
# uses FoodSoft.get_units_factors to calc the price/unit_quantity
|
||||
# returns new price and unit_quantity in array, when calc is possible => [price, unit_quanity]
|
||||
# returns false if units aren't foodsoft-compatible
|
||||
# returns nil if units are eqal
|
||||
def convert_units
|
||||
if unit != shared_article.unit
|
||||
if shared_article.unit == "KI" and unit == "ST" # 'KI' means a box, with a different amount of items in it
|
||||
# try to match the size out of its name, e.g. "banana 10-12 St" => 10
|
||||
new_unit_quantity = /[0-9\-\s]+(St)/.match(shared_article.name).to_s.to_i
|
||||
if new_unit_quantity and new_unit_quantity > 0
|
||||
new_price = (shared_article.price/new_unit_quantity.to_f).round(2)
|
||||
[new_price, new_unit_quantity]
|
||||
else
|
||||
false
|
||||
end
|
||||
else # get factors for fc and supplier
|
||||
fc_unit_factor, supplier_unit_factor = FoodSoft.get_units_factors[self.unit], FoodSoft.get_units_factors[self.shared_article.unit]
|
||||
if fc_unit_factor and supplier_unit_factor
|
||||
convertion_factor = fc_unit_factor / supplier_unit_factor
|
||||
new_price = BigDecimal((convertion_factor * shared_article.price).to_s).round(2)
|
||||
new_unit_quantity = ( 1 / convertion_factor) * shared_article.unit_quantity
|
||||
[new_price, new_unit_quantity]
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# Returns Articles in a nested Array, grouped by category and ordered by article name.
|
||||
# The array has the following form:
|
||||
# e.g: [["drugs",[teethpaste, toiletpaper]], ["fruits" => [apple, banana, lemon]]]
|
||||
# TODO: force article to belong to a category and remove this complicated implementation!
|
||||
def self.group_by_category(articles)
|
||||
articles_by_category = {}
|
||||
ArticleCategory.find(:all).each do |category|
|
||||
articles_by_category.merge!(category.name.to_s => articles.select {|article| article.article_category and article.article_category.id == category.id })
|
||||
end
|
||||
# add articles without a category
|
||||
articles_by_category.merge!( "--" => articles.select {|article| article.article_category == nil})
|
||||
# return "clean" hash, sorted by category.name
|
||||
return articles_by_category.reject {|category, array| array.empty?}.sort
|
||||
|
||||
# it could be so easy ... but that doesn't work for empty category-ids...
|
||||
# articles.group_by {|a| a.article_category}.sort {|a, b| a[0].name <=> b[0].name}
|
||||
end
|
||||
end
|
7
app/models/article_category.rb
Normal file
7
app/models/article_category.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
class ArticleCategory < ActiveRecord::Base
|
||||
has_many :articles
|
||||
|
||||
validates_length_of :name, :in => 2..20
|
||||
validates_uniqueness_of :name
|
||||
|
||||
end
|
17
app/models/assignment.rb
Normal file
17
app/models/assignment.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
class Assignment < ActiveRecord::Base
|
||||
# gettext-option
|
||||
untranslate_all
|
||||
|
||||
belongs_to :user
|
||||
belongs_to :task
|
||||
|
||||
# after user is assigned mark task as assigned
|
||||
def after_create
|
||||
self.task.update_attribute(:assigned, true)
|
||||
end
|
||||
|
||||
# update assigned-attribute
|
||||
def after_destroy
|
||||
self.task.update_attribute(:assigned, false) if self.task.assignments.empty?
|
||||
end
|
||||
end
|
21
app/models/financial_transaction.rb
Normal file
21
app/models/financial_transaction.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
# financial transactions are the foodcoop internal financial transactions
|
||||
# only order_groups have an account balance and are happy to transfer money
|
||||
#
|
||||
# financial transaction have the following attributes:
|
||||
# * order_group_id (int)
|
||||
# * amount (decimal)
|
||||
# * note (text)
|
||||
# * created_on (datetime)
|
||||
class FinancialTransaction < ActiveRecord::Base
|
||||
belongs_to :order_group
|
||||
belongs_to :user
|
||||
|
||||
validates_presence_of :note, :user_id, :order_group_id
|
||||
validates_numericality_of :amount
|
||||
|
||||
# Custom attribute setter that accepts decimal numbers using localized decimal separator.
|
||||
def amount=(amount)
|
||||
self[:amount] = FoodSoft::delocalizeDecimalString(amount)
|
||||
end
|
||||
|
||||
end
|
90
app/models/group.rb
Normal file
90
app/models/group.rb
Normal file
|
@ -0,0 +1,90 @@
|
|||
# Groups organize the User.
|
||||
#
|
||||
# Group have the following attributes
|
||||
# * name
|
||||
# * description
|
||||
# * type (to specify, if it is a OrderGroup)
|
||||
# * role_admin, role_suppliers, role_article_eta, role_finance, role_orders
|
||||
# * weekly_task (if the group should do a job ervery week)
|
||||
# * weekday (on which weekday should the job be done? 1 means monday and so on)
|
||||
#
|
||||
# A Member gets the roles from the Group
|
||||
class Group < ActiveRecord::Base
|
||||
has_many :memberships, :dependent => :destroy
|
||||
has_many :users, :through => :memberships
|
||||
has_many :tasks
|
||||
# returns all non-finished tasks
|
||||
has_many :open_tasks, :class_name => 'Task', :conditions => ['done = ?', false], :order => 'due_date ASC'
|
||||
|
||||
attr_accessible :name, :description, :role_admin, :role_suppliers, :role_article_meta, :role_finance, :role_orders,
|
||||
:weekly_task, :weekday, :task_name, :task_description, :task_required_users
|
||||
|
||||
validates_length_of :name, :in => 1..25
|
||||
validates_uniqueness_of :name
|
||||
|
||||
# messages
|
||||
ERR_LAST_ADMIN_GROUP_UPDATE = "Der letzten Gruppe mit Admin-Rechten darf die Admin-Rolle nicht entzogen werden"
|
||||
ERR_LAST_ADMIN_GROUP_DELETE = "Die letzte Gruppe mit Admin-Rechten darf nicht gelöscht werden"
|
||||
|
||||
# Returns true if the given user if is an member of this group.
|
||||
def member?(user)
|
||||
memberships.find_by_user_id(user.id)
|
||||
end
|
||||
|
||||
# Returns all NONmembers and a checks for possible multiple OrderGroup-Memberships
|
||||
def non_members
|
||||
nonMembers = Array.new
|
||||
for user in User.find(:all, :order => "nick")
|
||||
unless self.users.include?(user) || ( self.is_a?(OrderGroup) && user.find_ordergroup )
|
||||
nonMembers << user
|
||||
end
|
||||
end
|
||||
return nonMembers
|
||||
end
|
||||
|
||||
# Check before destroy a group, if this is the last group with admin role
|
||||
def before_destroy
|
||||
raise ERR_LAST_ADMIN_GROUP_DELETE if self.role_admin == true && Group.find_all_by_role_admin(true).size == 1
|
||||
end
|
||||
|
||||
# Returns an Array with date-objects to represent the next weekly-tasks
|
||||
def next_weekly_tasks(number = 8)
|
||||
# our system starts from 0 (sunday) to 6 (saturday)
|
||||
# get difference between groups weekday and now
|
||||
diff = self.weekday - Time.now.wday
|
||||
if diff >= 0
|
||||
# weektask is in current week
|
||||
nextTask = diff.day.from_now
|
||||
else
|
||||
# weektask is in the next week
|
||||
nextTask = (diff + 7).day.from_now
|
||||
end
|
||||
# now generate the Array
|
||||
nextTasks = Array.new
|
||||
number.times do
|
||||
nextTasks << nextTask
|
||||
nextTask = 1.week.from_now(nextTask)
|
||||
end
|
||||
return nextTasks
|
||||
end
|
||||
|
||||
# get all groups, which are NOT OrderGroups
|
||||
#TODO: better implement a new model, which inherits from Group, e.g. WorkGroup
|
||||
def self.workgroups
|
||||
Group.find :all, :conditions => "type IS NULL"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# validates uniqueness of the Group.name. Checks groups and order_groups
|
||||
def validate
|
||||
errors.add(:name, "ist schon vergeben") if (group = Group.find_by_name(name) || group = OrderGroup.find_by_name(name)) && self != group
|
||||
end
|
||||
|
||||
# add validation check on update
|
||||
def validate_on_update
|
||||
# error if this is the last group with admin role and role_admin should set to false
|
||||
errors.add(:role_admin, ERR_LAST_ADMIN_GROUP_UPDATE) if self.role_admin == false && Group.find_all_by_role_admin(true).size == 1 && self == Group.find(:first, :conditions => "role_admin = 1")
|
||||
end
|
||||
|
||||
end
|
39
app/models/group_order.rb
Normal file
39
app/models/group_order.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
# A GroupOrder represents an Order placed by an OrderGroup.
|
||||
#
|
||||
# Properties:
|
||||
# * order_id (int): association to the Order
|
||||
# * order_group_id (int): association to the OrderGroup
|
||||
# * group_order_articles: collection of associated GroupOrderArticles
|
||||
# * order_articles: collection of associated OrderArticles (through GroupOrderArticles)
|
||||
# * price (decimal): the price of this GroupOrder (either maximum price if current order or the actual price if finished order)
|
||||
# * lock_version (int): ActiveRecord optimistic locking column
|
||||
# * updated_by (User): the user who last updated this order
|
||||
#
|
||||
class GroupOrder < ActiveRecord::Base
|
||||
# gettext-option
|
||||
untranslate_all
|
||||
|
||||
belongs_to :order
|
||||
belongs_to :order_group
|
||||
has_many :group_order_articles, :dependent => :destroy
|
||||
has_many :order_articles, :through => :group_order_articles
|
||||
has_many :group_order_article_results
|
||||
belongs_to :updated_by, :class_name => "User", :foreign_key => "updated_by_user_id"
|
||||
|
||||
validates_presence_of :order_id
|
||||
validates_presence_of :order_group_id
|
||||
validates_presence_of :updated_by
|
||||
validates_numericality_of :price
|
||||
validates_uniqueness_of :order_group_id, :scope => :order_id # order groups can only order once per order
|
||||
|
||||
# Updates the "price" attribute.
|
||||
# This will be the maximum value of a current order
|
||||
def updatePrice
|
||||
total = 0
|
||||
for article in group_order_articles.find(:all, :include => :order_article)
|
||||
total += article.order_article.article.gross_price * (article.quantity + article.tolerance)
|
||||
end
|
||||
self.price = total
|
||||
end
|
||||
|
||||
end
|
146
app/models/group_order_article.rb
Normal file
146
app/models/group_order_article.rb
Normal file
|
@ -0,0 +1,146 @@
|
|||
# A GroupOrderArticle stores the sum of how many items of an OrderArticle are ordered as part of a GroupOrder.
|
||||
# The chronologically order of the OrderGroup - activity are stored in GroupOrderArticleQuantity
|
||||
#
|
||||
# Properties:
|
||||
# * group_order_id (int): association to the GroupOrder
|
||||
# * order_article_id (int): association to the OrderArticle
|
||||
# * quantity (int): number of items ordered
|
||||
# * tolerance (int): number of items ordered as tolerance
|
||||
# * updated_on (timestamp): updated automatically by ActiveRecord
|
||||
#
|
||||
class GroupOrderArticle < ActiveRecord::Base
|
||||
# gettext-option
|
||||
untranslate_all
|
||||
|
||||
belongs_to :group_order
|
||||
belongs_to :order_article
|
||||
has_many :group_order_article_quantities, :dependent => :destroy
|
||||
|
||||
validates_presence_of :group_order_id
|
||||
validates_presence_of :order_article_id
|
||||
validates_inclusion_of :quantity, :in => 0..99
|
||||
validates_inclusion_of :tolerance, :in => 0..99
|
||||
validates_uniqueness_of :order_article_id, :scope => :group_order_id # just once an article per group order
|
||||
|
||||
# Updates the quantity/tolerance for this GroupOrderArticle by updating both GroupOrderArticle properties
|
||||
# and the associated GroupOrderArticleQuantities chronologically.
|
||||
#
|
||||
# See description of the ordering algorithm in the general application documentation for details.
|
||||
def updateQuantities(quantity, tolerance)
|
||||
logger.debug("GroupOrderArticle[#{id}].updateQuantities(#{quantity}, #{tolerance})")
|
||||
logger.debug("Current quantity = #{self.quantity}, tolerance = #{self.tolerance}")
|
||||
|
||||
# Get quantities ordered with the newest item first.
|
||||
quantities = group_order_article_quantities.find(:all, :order => 'created_on desc')
|
||||
logger.debug("GroupOrderArticleQuantity items found: #{quantities.size}")
|
||||
|
||||
if (quantities.size == 0)
|
||||
# There is no GroupOrderArticleQuantity item yet, just insert with desired quantities...
|
||||
logger.debug("No quantities entry at all, inserting a new one with the desired quantities")
|
||||
quantities.push(GroupOrderArticleQuantity.new(:group_order_article => self, :quantity => quantity, :tolerance => tolerance))
|
||||
self.quantity, self.tolerance = quantity, tolerance
|
||||
else
|
||||
# Decrease quantity/tolerance if necessary by going through the existing items and decreasing their values...
|
||||
i = 0
|
||||
while (i < quantities.size && (quantity < self.quantity || tolerance < self.tolerance))
|
||||
logger.debug("Need to decrease quantities for GroupOrderArticleQuantity[#{quantities[i].id}]")
|
||||
if (quantity < self.quantity && quantities[i].quantity > 0)
|
||||
delta = self.quantity - quantity
|
||||
delta = (delta > quantities[i].quantity ? quantities[i].quantity : delta)
|
||||
logger.debug("Decreasing quantity by #{delta}")
|
||||
quantities[i].quantity -= delta
|
||||
self.quantity -= delta
|
||||
end
|
||||
if (tolerance < self.tolerance && quantities[i].tolerance > 0)
|
||||
delta = self.tolerance - tolerance
|
||||
delta = (delta > quantities[i].tolerance ? quantities[i].tolerance : delta)
|
||||
logger.debug("Decreasing tolerance by #{delta}")
|
||||
quantities[i].tolerance -= delta
|
||||
self.tolerance -= delta
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
# If there is at least one increased value: insert a new GroupOrderArticleQuantity object
|
||||
if (quantity > self.quantity || tolerance > self.tolerance)
|
||||
logger.debug("Inserting a new GroupOrderArticleQuantity")
|
||||
quantities.insert(0, GroupOrderArticleQuantity.new(
|
||||
:group_order_article => self,
|
||||
:quantity => (quantity > self.quantity ? quantity - self.quantity : 0),
|
||||
:tolerance => (tolerance > self.tolerance ? tolerance - self.tolerance : 0)
|
||||
))
|
||||
# Recalc totals:
|
||||
self.quantity += quantities[0].quantity
|
||||
self.tolerance += quantities[0].tolerance
|
||||
end
|
||||
end
|
||||
|
||||
# Check if something went terribly wrong and quantites have not been adjusted as desired.
|
||||
if (self.quantity != quantity || self.tolerance != tolerance)
|
||||
raise 'Invalid state: unable to update GroupOrderArticle/-Quantities to desired quantities!'
|
||||
end
|
||||
|
||||
# Remove zero-only items.
|
||||
quantities = quantities.reject { | q | q.quantity == 0 && q.tolerance == 0}
|
||||
|
||||
# Save
|
||||
transaction do
|
||||
quantities.each { | i | i.save! }
|
||||
self.group_order_article_quantities = quantities
|
||||
save!
|
||||
end
|
||||
end
|
||||
|
||||
# Determines how many items of this article the OrderGroup receives.
|
||||
# Returns a hash with three keys: :quantity / :tolerance / :total
|
||||
#
|
||||
# See description of the ordering algorithm in the general application documentation for details.
|
||||
def orderResult
|
||||
quantity = tolerance = 0
|
||||
|
||||
# Get total
|
||||
total = order_article.units_to_order * order_article.article.unit_quantity
|
||||
logger.debug("unitsToOrder => items ordered: #{order_article.units_to_order} => #{total}")
|
||||
|
||||
if (total > 0)
|
||||
# Get all GroupOrderArticleQuantities for this OrderArticle...
|
||||
orderArticles = GroupOrderArticle.find(:all, :conditions => ['order_article_id = ? AND group_order_id IN (?)', order_article.id, group_order.order.group_orders.collect { | o | o.id }])
|
||||
orderQuantities = GroupOrderArticleQuantity.find(:all, :conditions => ['group_order_article_id IN (?)', orderArticles.collect { | i | i.id }], :order => 'created_on')
|
||||
logger.debug("GroupOrderArticleQuantity records found: #{orderQuantities.size}")
|
||||
|
||||
# Determine quantities to be ordered...
|
||||
totalQuantity = i = 0
|
||||
while (i < orderQuantities.size && totalQuantity < total)
|
||||
q = (orderQuantities[i].quantity <= total - totalQuantity ? orderQuantities[i].quantity : total - totalQuantity)
|
||||
totalQuantity += q
|
||||
if (orderQuantities[i].group_order_article_id == self.id)
|
||||
logger.debug("increasing quantity by #{q}")
|
||||
quantity += q
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
# Determine tolerance to be ordered...
|
||||
if (totalQuantity < total)
|
||||
logger.debug("determining additional items to be ordered from tolerance")
|
||||
i = 0
|
||||
while (i < orderQuantities.size && totalQuantity < total)
|
||||
q = (orderQuantities[i].tolerance <= total - totalQuantity ? orderQuantities[i].tolerance : total - totalQuantity)
|
||||
totalQuantity += q
|
||||
if (orderQuantities[i].group_order_article_id == self.id)
|
||||
logger.debug("increasing tolerance by #{q}")
|
||||
tolerance += q
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# calculate the sum of quantity and tolerance:
|
||||
sum = quantity + tolerance
|
||||
|
||||
logger.debug("determined quantity/tolerance/total: #{quantity} / #{tolerance} / #{sum}")
|
||||
end
|
||||
|
||||
{:quantity => quantity, :tolerance => tolerance, :total => sum}
|
||||
end
|
||||
|
||||
end
|
20
app/models/group_order_article_quantity.rb
Normal file
20
app/models/group_order_article_quantity.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
# stores the quantity, tolerance and timestamp of an GroupOrderArticle
|
||||
# Considers every update of an article-order, so may rows for one group_order_article ar possible.
|
||||
#
|
||||
# properties:
|
||||
# * group_order_article_id (int)
|
||||
# * quantity (int)
|
||||
# * tolerance (in)
|
||||
# * created_on (timestamp)
|
||||
|
||||
class GroupOrderArticleQuantity < ActiveRecord::Base
|
||||
# gettext-option
|
||||
untranslate_all
|
||||
|
||||
belongs_to :group_order_article
|
||||
|
||||
validates_presence_of :group_order_article_id
|
||||
validates_inclusion_of :quantity, :in => 0..99
|
||||
validates_inclusion_of :tolerance, :in => 0..99
|
||||
|
||||
end
|
27
app/models/group_order_article_result.rb
Normal file
27
app/models/group_order_article_result.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
# An GroupOrderArticleResult represents a group-order for a single Article and its quantities,
|
||||
# according to the order quantity/tolerance.
|
||||
# The GroupOrderArticleResult is part of a finished Order, see OrderArticleResult.
|
||||
#
|
||||
# Properties:
|
||||
# * order_article_result_id (int)
|
||||
# * group_order_result_id (int): associated with OrderGroup through GroupOrderResult.group_name
|
||||
# * quantity (int)
|
||||
#
|
||||
class GroupOrderArticleResult < ActiveRecord::Base
|
||||
|
||||
belongs_to :order_article_result
|
||||
belongs_to :group_order_result
|
||||
|
||||
validates_presence_of :order_article_result, :group_order_result, :quantity
|
||||
validates_numericality_of :quantity, :minimum => 0
|
||||
|
||||
# updates the price attribute for the appropriate GroupOrderResult
|
||||
after_update {|result| result.group_order_result.updatePrice }
|
||||
after_destroy {|result| result.group_order_result.updatePrice }
|
||||
|
||||
# Custom attribute setter that accepts decimal numbers using localized decimal separator.
|
||||
def quantity=(quantity)
|
||||
self[:quantity] = FoodSoft::delocalizeDecimalString(quantity)
|
||||
end
|
||||
|
||||
end
|
23
app/models/group_order_result.rb
Normal file
23
app/models/group_order_result.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
# OrderGroups, which participate on a specific order will have a line
|
||||
# Properties:
|
||||
# * order_id, int
|
||||
# * group_name, the name of the group
|
||||
# * price, decimal
|
||||
# * group_order_article_results: collection of associated GroupOrderArticleResults
|
||||
#
|
||||
class GroupOrderResult < ActiveRecord::Base
|
||||
# gettext-option
|
||||
untranslate_all
|
||||
|
||||
belongs_to :order
|
||||
has_many :group_order_article_results, :dependent => :destroy
|
||||
|
||||
# Calculates the Order-Price for the OrderGroup and updates the price-attribute
|
||||
def updatePrice
|
||||
total = 0
|
||||
group_order_article_results.each do |result|
|
||||
total += result.order_article_result.gross_price * result.quantity
|
||||
end
|
||||
update_attribute(:price, total)
|
||||
end
|
||||
end
|
46
app/models/invite.rb
Normal file
46
app/models/invite.rb
Normal file
|
@ -0,0 +1,46 @@
|
|||
require 'digest/sha1'
|
||||
|
||||
# Invites are created by foodcoop users to invite a new user into the foodcoop and their order group.
|
||||
#
|
||||
# Attributes:
|
||||
# * token - the authentication token for this invite
|
||||
# * group - the group the new user is to be made a member of
|
||||
# * user - the inviting user
|
||||
# * expires_at - the time this invite expires
|
||||
# * email - the recipient's email address
|
||||
class Invite < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
belongs_to :group
|
||||
|
||||
validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :message => 'ist keine gültige Email-Adresse'
|
||||
validates_presence_of :user
|
||||
validates_presence_of :group
|
||||
validates_presence_of :token
|
||||
validates_presence_of :expires_at
|
||||
|
||||
attr_accessible :email, :user, :group
|
||||
|
||||
# messages
|
||||
ERR_EMAIL_IN_USE = 'ist bereits in Verwendung'
|
||||
|
||||
protected
|
||||
|
||||
# Before validation, set token and expires_at.
|
||||
def before_validation
|
||||
self.token = Digest::SHA1.hexdigest(Time.now.to_s + rand(100).to_s)
|
||||
self.expires_at = Time.now.advance(:days => 2)
|
||||
end
|
||||
|
||||
# Sends an email to the invited user.
|
||||
def after_create
|
||||
Mailer.deliver_invite(self)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Custom validation: check that email does not already belong to a registered user.
|
||||
def validate_on_create
|
||||
errors.add(:email, ERR_EMAIL_IN_USE) unless User.find_by_email(self.email).nil?
|
||||
end
|
||||
|
||||
end
|
41
app/models/mailer.rb
Normal file
41
app/models/mailer.rb
Normal file
|
@ -0,0 +1,41 @@
|
|||
# ActionMailer class that handles all emails for the FoodSoft.
|
||||
class Mailer < ActionMailer::Base
|
||||
|
||||
# Sends an email with instructions on how to reset the password.
|
||||
# Assumes user.setResetPasswordToken has been successfully called already.
|
||||
def password(user)
|
||||
request = ApplicationController.current.request
|
||||
subject "[#{FoodSoft::getFoodcoopName}] Neues Passwort für/ New password for " + user.nick
|
||||
recipients user.email
|
||||
from "FoodSoft <#{FoodSoft::getEmailSender}>"
|
||||
body :user => user,
|
||||
:link => url_for(:host => FoodSoft::getHost || request.host, :controller => "login", :action => "password", :id => user.id, :token => user.reset_password_token),
|
||||
:foodsoftUrl => url_for(:host => FoodSoft::getHost || request.host, :controller => "index")
|
||||
end
|
||||
|
||||
# Sends an email copy of the given internal foodsoft message.
|
||||
def message(message)
|
||||
request = ApplicationController.current.request
|
||||
subject "[#{FoodSoft::getFoodcoopName}] " + message.subject
|
||||
recipients message.recipient.email
|
||||
from (message.system_message? ? "FoodSoft <#{FoodSoft::getEmailSender}>" : "#{message.sender.nick} <#{message.sender.email}>")
|
||||
body :body => message.body, :sender => (message.system_message? ? 'Foodsoft' : message.sender.nick),
|
||||
:recipients => message.recipients,
|
||||
:reply => url_for(:host => FoodSoft::getHost || request.host, :controller => "messages", :action => "reply", :id => message),
|
||||
:profile => url_for(:host => FoodSoft::getHost || request.host, :controller => "index", :action => "myProfile", :id => message.recipient),
|
||||
:link => url_for(:host => FoodSoft::getHost || request.host, :controller => "messages", :action => "show", :id => message),
|
||||
:foodsoftUrl => url_for(:host => FoodSoft::getHost || request.host, :controller => "index")
|
||||
end
|
||||
|
||||
# Sends an invite email.
|
||||
def invite(invite)
|
||||
request = ApplicationController.current.request
|
||||
subject "Einladung in die Foodcoop #{FoodSoft::getFoodcoopName} - Invitation to the Foodcoop"
|
||||
recipients invite.email
|
||||
from "FoodSoft <#{FoodSoft::getEmailSender}>"
|
||||
body :invite => invite,
|
||||
:link => url_for(:host => FoodSoft::getHost || request.host, :controller => "login", :action => "invite", :id => invite.token),
|
||||
:foodsoftUrl => url_for(:host => FoodSoft::getHost || request.host, :controller => "index")
|
||||
end
|
||||
|
||||
end
|
16
app/models/membership.rb
Normal file
16
app/models/membership.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
class Membership < ActiveRecord::Base
|
||||
|
||||
# gettext-option
|
||||
untranslate_all
|
||||
|
||||
belongs_to :user
|
||||
belongs_to :group
|
||||
|
||||
# messages
|
||||
ERR_NO_ADMIN_MEMBER_DELETE = "Mitgliedschaft kann nicht beendet werden. Du bist die letzte Administratorin"
|
||||
|
||||
# check if this is the last admin-membership and deny
|
||||
def before_destroy
|
||||
raise ERR_NO_ADMIN_MEMBER_DELETE if self.group.role_admin? && self.group.memberships.size == 1 && Group.find_all_by_role_admin(true).size == 1
|
||||
end
|
||||
end
|
101
app/models/message.rb
Normal file
101
app/models/message.rb
Normal file
|
@ -0,0 +1,101 @@
|
|||
# A message within the foodsoft.
|
||||
#
|
||||
# * sender (User) - the sending User (might be nil if it is a system message)
|
||||
# * recipient (User) - the receiving User
|
||||
# * recipients (String) - list of all recipients of this message as User.nick/Group.name
|
||||
# * subject (string) - message subject
|
||||
# * body (string) - message body
|
||||
# * read? (boolean) - message read status
|
||||
# * email_state (integer) - email state, one of EMAIL_STATE.values
|
||||
# * created_on (timestamp) - creation timestamp
|
||||
class Message < ActiveRecord::Base
|
||||
belongs_to :sender, :class_name => "User", :foreign_key => "sender_id"
|
||||
belongs_to :recipient, :class_name => "User", :foreign_key => "recipient_id"
|
||||
|
||||
attr_accessible :recipient_id, :recipient, :subject, :body, :recipients
|
||||
|
||||
# needed for method 'from_template'
|
||||
include GetText::Rails
|
||||
|
||||
# Values for the email_state attribute: :none, :pending, :sent, :failed
|
||||
EMAIL_STATE = {
|
||||
:none => 0,
|
||||
:pending => 1,
|
||||
:sent => 2,
|
||||
:failed => 3
|
||||
}
|
||||
|
||||
validates_presence_of :recipient_id
|
||||
validates_length_of :subject, :in => 1..255
|
||||
validates_presence_of :recipients
|
||||
validates_presence_of :body
|
||||
validates_inclusion_of :email_state, :in => EMAIL_STATE.values
|
||||
|
||||
@@pending = false
|
||||
|
||||
# Automatically determine if this message should be send as an email.
|
||||
def before_validation_on_create
|
||||
if (recipient && recipient.settings["messages.sendAsEmail"] == '1')
|
||||
self.email_state = EMAIL_STATE[:pending]
|
||||
else
|
||||
self.email_state = EMAIL_STATE[:none]
|
||||
end
|
||||
end
|
||||
|
||||
# Determines if this new message is a pending email.
|
||||
def after_create
|
||||
@@pending = @@pending || self.email_state == EMAIL_STATE[:pending]
|
||||
end
|
||||
|
||||
# Returns true if there might be pending emails.
|
||||
def self.pending?
|
||||
@@pending
|
||||
end
|
||||
|
||||
# Returns true if this message is a system message, i.e. was sent automatically by the FoodSoft itself.
|
||||
def system_message?
|
||||
self.sender_id.nil?
|
||||
end
|
||||
|
||||
# Sends all pending messages that are to be send as emails.
|
||||
def self.send_emails
|
||||
transaction do
|
||||
messages = find(:all, :conditions => ["email_state = ?", EMAIL_STATE[:pending]], :lock => true)
|
||||
logger.debug("Sending #{messages.size} pending messages as emails...") unless messages.empty?
|
||||
for message in messages
|
||||
if (message.recipient && message.recipient.email && !message.recipient.email.empty?)
|
||||
begin
|
||||
Mailer.deliver_message(message)
|
||||
message.update_attribute(:email_state, EMAIL_STATE[:sent])
|
||||
logger.debug("Delivered message as email: id = #{message.id}, recipient = #{message.recipient.nick}, subject = \"#{message.subject}\"")
|
||||
rescue => exception
|
||||
message.update_attribute(:email_state, EMAIL_STATE[:failed])
|
||||
logger.warn("Failed to deliver message as email: id = #{message.id}, recipient = #{message.recipient.nick}, subject = \"#{message.subject}\", exception = #{exception.message}")
|
||||
end
|
||||
else
|
||||
message.update_attribute(:email_state, EMAIL_STATE[:failed])
|
||||
logger.warn("Cannot deliver message as email (no user email): id = #{message.id}, recipient = #{message.recipient.nick}, subject = \"#{message.subject}\"")
|
||||
end
|
||||
end
|
||||
logger.debug("Done sending emails.") unless messages.empty?
|
||||
@@pending = false
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a new message object created from the attributes specified (recipient, recipients, subject)
|
||||
# and the body from the given template that can make use of the variables specified.
|
||||
# The templates are to be stored in app/views/messages, i.e. the template name
|
||||
# "order_finished" would invoke template file "app/views/messages/order_finished.rhtml".
|
||||
# Note: you need to set the sender afterwards if this should not be a system message.
|
||||
#
|
||||
# Example:
|
||||
# Message.from_template(
|
||||
# 'order_finished',
|
||||
# {:user => user, :group => order_group, :order => self, :results => results, :total => group_order.price},
|
||||
# {:recipient_id => user.id, :recipients => recipients, :subject => "Bestellung beendet: #{self.name}"}
|
||||
# ).save!
|
||||
def self.from_template(template, vars, attributes)
|
||||
view = ActionView::Base.new(Rails::Configuration.new.view_path, {}, MessagesController.new)
|
||||
new(attributes.merge(:body => view.render(:file => "messages/#{template}.rhtml", :locals => vars)))
|
||||
end
|
||||
end
|
316
app/models/order.rb
Normal file
316
app/models/order.rb
Normal file
|
@ -0,0 +1,316 @@
|
|||
class Order < ActiveRecord::Base
|
||||
has_many :order_articles, :dependent => :destroy
|
||||
has_many :articles, :through => :order_articles
|
||||
has_many :group_orders, :dependent => :destroy
|
||||
has_many :order_groups, :through => :group_orders
|
||||
has_many :order_article_results, :dependent => :destroy
|
||||
has_many :group_order_results, :dependent => :destroy
|
||||
belongs_to :supplier
|
||||
belongs_to :updated_by, :class_name => "User", :foreign_key => "updated_by_user_id"
|
||||
|
||||
validates_length_of :name, :in => 2..50
|
||||
validates_presence_of :starts
|
||||
validates_presence_of :supplier_id
|
||||
validates_inclusion_of :finished, :in => [true, false]
|
||||
validates_numericality_of :invoice_amount, :deposit, :deposit_credit
|
||||
|
||||
validate_on_create :include_articles
|
||||
|
||||
# attr_accessible :name, :supplier, :starts, :ends, :note, :invoice_amount, :deposit, :deposit_credit, :invoice_number, :invoice_date
|
||||
|
||||
#use plugin to make Order commentable
|
||||
acts_as_commentable
|
||||
|
||||
# easyier find of next or previous model
|
||||
acts_as_ordered :order => "ends"
|
||||
|
||||
# Custom attribute setter that accepts decimal numbers using localized decimal separator.
|
||||
def invoice_amount=(amount)
|
||||
self[:invoice_amount] = FoodSoft::delocalizeDecimalString(amount)
|
||||
end
|
||||
|
||||
# Custom attribute setter that accepts decimal numbers using localized decimal separator.
|
||||
def deposit=(deposit)
|
||||
self[:deposit] = FoodSoft::delocalizeDecimalString(deposit)
|
||||
end
|
||||
|
||||
# Custom attribute setter that accepts decimal numbers using localized decimal separator.
|
||||
def deposit_credit=(deposit)
|
||||
self[:deposit_credit] = FoodSoft::delocalizeDecimalString(deposit)
|
||||
end
|
||||
|
||||
# Create or destroy OrderArticle associations on create/update
|
||||
def article_ids=(ids)
|
||||
# fetch selected articles
|
||||
articles_list = Article.find(ids)
|
||||
# create new order_articles
|
||||
(articles_list - articles).each { |article| order_articles.build(:article => article) }
|
||||
# delete old order_articles
|
||||
articles.reject { |article| articles_list.include?(article) }.each do |article|
|
||||
order_articles.detect { |order_article| order_article.article_id == article.id }.destroy
|
||||
end
|
||||
end
|
||||
|
||||
# Returns all current orders, i.e. orders that are not finished and the current time is between the order's start and end time.
|
||||
def self.find_current
|
||||
find(:all, :conditions => ['finished = ? AND starts < ? AND (ends IS NULL OR ends > ?)', false, Time.now, Time.now], :order => 'ends desc', :include => :supplier)
|
||||
end
|
||||
|
||||
# Returns true if this is a current order (not finished and current time matches starts/ends).
|
||||
def current?
|
||||
!finished? && starts < Time.now && (!ends || ends > Time.now)
|
||||
end
|
||||
|
||||
# Returns all finished or expired orders, exclude booked orders
|
||||
def self.find_finished
|
||||
find(:all, :conditions => ['(finished = ? OR ends < ?) AND booked = ?', true, Time.now, false], :order => 'ends desc', :include => :supplier)
|
||||
end
|
||||
|
||||
# Return all booked Orders
|
||||
def self.find_booked
|
||||
find :all, :conditions => ['booked = ?', true], :order => 'ends desc', :include => :supplier
|
||||
end
|
||||
|
||||
# search GroupOrder of given OrderGroup
|
||||
def group_order(ordergroup)
|
||||
unless finished
|
||||
return group_orders.detect {|o| o.order_group_id == ordergroup.id}
|
||||
else
|
||||
return group_order_results.detect {|o| o.group_name == ordergroup.name}
|
||||
end
|
||||
end
|
||||
|
||||
# Returns OrderArticles in a nested Array, grouped by category and ordered by article name.
|
||||
# The array has the following form:
|
||||
# e.g: [["drugs",[teethpaste, toiletpaper]], ["fruits" => [apple, banana, lemon]]]
|
||||
def get_articles
|
||||
articles= order_articles.find :all, :include => :article, :order => "articles.name"
|
||||
articles_by_category= Hash.new
|
||||
ArticleCategory.find(:all).each do |category|
|
||||
articles_by_category.merge!(category.name.to_s => articles.select {|order_article| order_article.article.article_category == category})
|
||||
end
|
||||
# add articles without a category
|
||||
articles_by_category.merge!( "--" => articles.select {|order_article| order_article.article.article_category == nil})
|
||||
# return "clean" hash, sorted by category.name
|
||||
return articles_by_category.reject {|category, order_articles| order_articles.empty?}.sort
|
||||
|
||||
# it could be so easy ... but that doesn't work for empty category-ids...
|
||||
# order_articles.group_by {|a| a.article.article_category}.sort {|a, b| a[0].name <=> b[0].name}
|
||||
end
|
||||
|
||||
# Returns the defecit/benefit for the foodcoop
|
||||
def fcProfit(with_markup = true)
|
||||
groups_sum = with_markup ? sumPrice("groups") : sumPrice("groups_without_markup")
|
||||
groups_sum - invoice_amount + deposit - deposit_credit
|
||||
end
|
||||
|
||||
# Returns the all round price of a finished order
|
||||
# "groups" returns the sum of all GroupOrderResults
|
||||
# "clear" returns the price without tax, deposit and markup
|
||||
# "gross" includes tax and deposit. this amount should be equal to suppliers bill
|
||||
# "fc", guess what...
|
||||
# for unfinished orders it returns the gross price
|
||||
def sumPrice(type = "gross")
|
||||
sum = 0
|
||||
if finished?
|
||||
if type == "groups"
|
||||
for groupResult in group_order_results
|
||||
for result in groupResult.group_order_article_results
|
||||
sum += result.order_article_result.gross_price * result.quantity
|
||||
end
|
||||
end
|
||||
elsif type == "groups_without_markup"
|
||||
for groupResult in group_order_results
|
||||
for result in groupResult.group_order_article_results
|
||||
oar = result.order_article_result
|
||||
sum += (oar.net_price + oar.deposit) * (1 + oar.tax/100) * result.quantity
|
||||
end
|
||||
end
|
||||
else
|
||||
for article in order_article_results
|
||||
case type
|
||||
when 'clear'
|
||||
sum += article.units_to_order * article.unit_quantity * article.net_price
|
||||
when 'gross'
|
||||
sum += article.units_to_order * article.unit_quantity * (article.net_price + article.deposit) * (article.tax / 100 + 1)
|
||||
when "fc"
|
||||
sum += article.units_to_order * article.unit_quantity * article.gross_price
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
for article in order_articles
|
||||
sum += article.units_to_order * article.article.gross_price * article.article.unit_quantity
|
||||
end
|
||||
end
|
||||
sum
|
||||
end
|
||||
|
||||
# Finishes this order. This will set the finish property to "true" and the end property to the current time.
|
||||
# Ignored if the order is already finished.
|
||||
# this will also copied the results into OrderArticleResult, GroupOrderArticleResult and GroupOrderResult
|
||||
def finish(user)
|
||||
unless finished?
|
||||
transaction do
|
||||
#saves ordergroups, which take part in this order
|
||||
self.group_orders.each do |go|
|
||||
group_order_result = GroupOrderResult.create!(:order => self,
|
||||
:group_name => go.order_group.name,
|
||||
:price => go.price)
|
||||
end
|
||||
# saves every article of the order
|
||||
self.get_articles.each do |category, articles|
|
||||
articles.each do |oa|
|
||||
if oa.units_to_order >= 1 # save only successful ordered articles!
|
||||
article_result = OrderArticleResult.new(:order => self,
|
||||
:name => oa.article.name,
|
||||
:unit => oa.article.unit,
|
||||
:net_price => oa.article.net_price,
|
||||
:gross_price => oa.article.gross_price,
|
||||
:tax => oa.article.tax,
|
||||
:deposit => oa.article.deposit,
|
||||
:fc_markup => FoodSoft::getPriceMarkup,
|
||||
:order_number => oa.article.order_number,
|
||||
:unit_quantity => oa.article.unit_quantity,
|
||||
:units_to_order => oa.units_to_order)
|
||||
article_result.save
|
||||
# saves the ordergroup results, belonging to the saved orderd article
|
||||
oa.group_order_articles.each do |goa|
|
||||
result = goa.orderResult
|
||||
# find appropriate GroupOrderResult
|
||||
group_order_result = GroupOrderResult.find(:first, :conditions => ['order_id = ? AND group_name = ?', self.id, goa.group_order.order_group.name])
|
||||
group_order_article_result = GroupOrderArticleResult.new(:order_article_result => article_result,
|
||||
:group_order_result => group_order_result,
|
||||
:quantity => result[:total],
|
||||
:tolerance => result[:tolerance])
|
||||
group_order_article_result.save! if (group_order_article_result.quantity > 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
# set new order state (needed by notifyOrderFinished)
|
||||
self.finished = true
|
||||
self.ends = Time.now
|
||||
self.updated_by = user
|
||||
# delete data, which is no longer required, because everything is now in the result-tables
|
||||
self.group_orders.each do |go|
|
||||
go.destroy
|
||||
end
|
||||
self.order_articles.each do |oa|
|
||||
oa.destroy
|
||||
end
|
||||
self.save!
|
||||
# Update all GroupOrder.price
|
||||
self.updateAllGroupOrders
|
||||
# notify order groups
|
||||
notifyOrderFinished
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Updates the ordered quantites of all OrderArticles from the GroupOrderArticles.
|
||||
def updateQuantities
|
||||
orderArticles = Hash.new # holds the list of updated OrderArticles indexed by their id
|
||||
# Get all GroupOrderArticles for this order and update OrderArticle.quantity/.tolerance/.units_to_order from them...
|
||||
articles = GroupOrderArticle.find(:all, :conditions => ['group_order_id IN (?)', group_orders.collect { | o | o.id }], :include => [:order_article])
|
||||
for article in articles
|
||||
if (orderArticle = orderArticles[article.order_article.id.to_s])
|
||||
# OrderArticle has already been fetched, just update...
|
||||
orderArticle.quantity = orderArticle.quantity + article.quantity
|
||||
orderArticle.tolerance = orderArticle.tolerance + article.tolerance
|
||||
orderArticle.units_to_order = orderArticle.article.calculateOrderQuantity(orderArticle.quantity, orderArticle.tolerance)
|
||||
else
|
||||
# First update to OrderArticle, need to store in orderArticle hash...
|
||||
orderArticle = article.order_article
|
||||
orderArticle.quantity = article.quantity
|
||||
orderArticle.tolerance = article.tolerance
|
||||
orderArticle.units_to_order = orderArticle.article.calculateOrderQuantity(orderArticle.quantity, orderArticle.tolerance)
|
||||
orderArticles[orderArticle.id.to_s] = orderArticle
|
||||
end
|
||||
end
|
||||
# Commit changes to database...
|
||||
OrderArticle.transaction do
|
||||
orderArticles.each_value { | value | value.save! }
|
||||
end
|
||||
end
|
||||
|
||||
# Updates the "price" attribute of GroupOrders or GroupOrderResults
|
||||
# This will be either the maximum value of a current order or the actual order value of a finished order.
|
||||
def updateAllGroupOrders
|
||||
unless finished?
|
||||
group_orders.each do |groupOrder|
|
||||
groupOrder.updatePrice
|
||||
groupOrder.save
|
||||
end
|
||||
else #for finished orders
|
||||
group_order_results.each do |groupOrderResult|
|
||||
groupOrderResult.updatePrice
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Sets "booked"-attribute to true and updates all OrderGroup_account_balances
|
||||
def balance(user)
|
||||
raise "Bestellung wurde schon abgerechnet" if self.booked
|
||||
transaction_note = "Bestellung: #{name}, von #{starts.strftime('%d.%m.%Y')} bis #{ends.strftime('%d.%m.%Y')}"
|
||||
transaction do
|
||||
# update OrderGroups
|
||||
group_order_results.each do |result|
|
||||
price = result.price * -1 # decrease! account balance
|
||||
OrderGroup.find_by_name(result.group_name).addFinancialTransaction(price, transaction_note, user)
|
||||
end
|
||||
self.booked = true
|
||||
self.updated_by = user
|
||||
self.save!
|
||||
end
|
||||
end
|
||||
|
||||
# returns the corresponding message for the status
|
||||
def status
|
||||
if !self.finished? && self.ends > Time.now
|
||||
_("running")
|
||||
elsif !self.finished? && self.ends < Time.now
|
||||
_("expired")
|
||||
elsif self.finished? && !self.booked?
|
||||
_("finished")
|
||||
else
|
||||
_("balanced")
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def validate
|
||||
errors.add(:ends, "muss nach dem Bestellstart liegen (oder leer bleiben)") if (ends && starts && ends <= starts)
|
||||
end
|
||||
|
||||
def include_articles
|
||||
errors.add(:order_articles, _("There must be at least one article selected")) if order_articles.empty?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Sends "order finished" messages to users who have participated in this order.
|
||||
def notifyOrderFinished
|
||||
# Loop through GroupOrderResults for this order:
|
||||
for group_order in self.group_order_results
|
||||
order_group = OrderGroup.find_by_name(group_order.group_name)
|
||||
# Determine group users that want a notification message:
|
||||
users = order_group.users.reject{|u| u.settings["notify.orderFinished"] != '1'}
|
||||
unless (users.empty?)
|
||||
# Assemble the order message text:
|
||||
results = group_order.group_order_article_results.find(:all, :include => [:order_article_result])
|
||||
# Create user notification messages:
|
||||
recipients = users.collect{|u| u.nick}.join(', ')
|
||||
for user in users
|
||||
Message.from_template(
|
||||
'order_finished',
|
||||
{:user => user, :group => order_group, :order => self, :results => results, :total => group_order.price},
|
||||
{:recipient_id => user.id, :recipients => recipients, :subject => "Bestellung beendet: #{self.name}"}
|
||||
).save!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
29
app/models/order_article.rb
Normal file
29
app/models/order_article.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
# An OrderArticle represents a single Article that is part of an Order.
|
||||
#
|
||||
# Properties:
|
||||
# * order_id (int): association to the Order
|
||||
# * article_id (int): association to the Article
|
||||
# * quantity (int): number of items ordered by all OrderGroups for this order
|
||||
# * tolerance (int): number of items ordered as tolerance by all OrderGroups for this order
|
||||
# * units_to_order (int): number of packaging units to be ordered according to the order quantity/tolerance
|
||||
#
|
||||
class OrderArticle < ActiveRecord::Base
|
||||
|
||||
# gettext-option
|
||||
untranslate_all
|
||||
|
||||
belongs_to :order
|
||||
belongs_to :article
|
||||
has_many :group_order_articles, :dependent => :destroy
|
||||
|
||||
validates_presence_of :order_id
|
||||
validates_presence_of :article_id
|
||||
validates_uniqueness_of :article_id, :scope => :order_id # an article can only have one record per order
|
||||
|
||||
private
|
||||
|
||||
def validate
|
||||
errors.add(:article, "muss angegeben sein und einen aktuellen Preis haben") if !(article = Article.find(article_id)) || article.gross_price.nil?
|
||||
end
|
||||
|
||||
end
|
73
app/models/order_article_result.rb
Normal file
73
app/models/order_article_result.rb
Normal file
|
@ -0,0 +1,73 @@
|
|||
# An OrderArticleResult represents a single Article that is part of a *finished* Order.
|
||||
#
|
||||
# Properties:
|
||||
# * order_id (int): association to the Order
|
||||
# * name (string): article name
|
||||
# * unit (string)
|
||||
# * note (string): for post-editing the ordered article. informations like "new tax is ..."
|
||||
# * net_price (decimal): the net price
|
||||
# * gross_price (decimal): incl tax, deposit, fc-markup
|
||||
# * tax (int)
|
||||
# * deposit (decimal)
|
||||
# * fc_markup (float)
|
||||
# * order_number (string)
|
||||
# * unit_quantity (int): the internal(FC) size of trading unit
|
||||
# * units_to_order (int): number of packaging units to be ordered according to the order quantity/tolerance
|
||||
#
|
||||
class OrderArticleResult < ActiveRecord::Base
|
||||
belongs_to :order
|
||||
has_many :group_order_article_results, :dependent => :destroy
|
||||
|
||||
validates_presence_of :name, :unit, :net_price, :gross_price, :tax, :deposit, :fc_markup, :unit_quantity, :units_to_order
|
||||
validates_numericality_of :net_price, :gross_price, :deposit, :unit_quantity, :units_to_order
|
||||
validates_length_of :name, :minimum => 4
|
||||
|
||||
def make_gross # calculate the gross price and sets the attribute
|
||||
self.gross_price = ((net_price + deposit) * (tax / 100 + 1) * (fc_markup / 100 + 1))
|
||||
end
|
||||
|
||||
# Custom attribute setter that accepts decimal numbers using localized decimal separator.
|
||||
def net_price=(net_price)
|
||||
self[:net_price] = FoodSoft::delocalizeDecimalString(net_price)
|
||||
end
|
||||
|
||||
# Custom attribute setter that accepts decimal numbers using localized decimal separator.
|
||||
def tax=(tax)
|
||||
self[:tax] = FoodSoft::delocalizeDecimalString(tax)
|
||||
end
|
||||
|
||||
# Custom attribute setter that accepts decimal numbers using localized decimal separator.
|
||||
def deposit=(deposit)
|
||||
self[:deposit] = FoodSoft::delocalizeDecimalString(deposit)
|
||||
end
|
||||
|
||||
# Custom attribute setter that accepts decimal numbers using localized decimal separator.
|
||||
def units_to_order=(units_to_order)
|
||||
self[:units_to_order] = FoodSoft::delocalizeDecimalString(units_to_order)
|
||||
end
|
||||
|
||||
# counts from every GroupOrderArticleResult for this ArticleResult
|
||||
# Return a hash with the total quantity (in Article-units) and the total (FC) price
|
||||
def total
|
||||
quantity = 0
|
||||
price = 0
|
||||
for result in self.group_order_article_results
|
||||
quantity += result.quantity
|
||||
price += result.quantity * self.gross_price
|
||||
end
|
||||
return {:quantity => quantity, :price => price}
|
||||
end
|
||||
|
||||
|
||||
# updates the price attribute for all appropriate GroupOrderResults
|
||||
def after_update
|
||||
group_order_article_results.each {|result| result.group_order_result.updatePrice}
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def validate
|
||||
errors.add(:net_price, "should be positive") unless net_price.nil? || net_price > 0
|
||||
end
|
||||
|
||||
end
|
119
app/models/order_group.rb
Normal file
119
app/models/order_group.rb
Normal file
|
@ -0,0 +1,119 @@
|
|||
# OrderGroups can order, they are "children" of the class Group
|
||||
#
|
||||
# OrderGroup have the following attributes, in addition to Group
|
||||
# * account_balance (decimal)
|
||||
# * account_updated (datetime)
|
||||
# * actual_size (int) : how many persons are ordering through the OrderGroup
|
||||
class OrderGroup < Group
|
||||
has_many :financial_transactions, :dependent => :destroy
|
||||
has_many :group_orders, :dependent => :destroy
|
||||
has_many :orders, :through => :group_orders
|
||||
has_many :group_order_article_results, :through => :group_orders # TODO: whats this???
|
||||
has_many :group_order_results, :finder_sql => 'SELECT * FROM group_order_results as r WHERE r.group_name = "#{name}"'
|
||||
|
||||
validates_inclusion_of :actual_size, :in => 1..99
|
||||
validates_numericality_of :account_balance, :message => 'ist keine gültige Zahl'
|
||||
|
||||
attr_accessible :actual_size, :account_updated
|
||||
|
||||
# messages
|
||||
ERR_NAME_IS_USED_IN_ARCHIVE = "Der Name ist von einer ehemaligen Gruppe verwendet worden."
|
||||
|
||||
# if the order_group.name is changed, group_order_result.name has to be adapted
|
||||
def before_update
|
||||
ordergroup = OrderGroup.find(self.id)
|
||||
unless (ordergroup.name == self.name) || ordergroup.group_order_results.empty?
|
||||
# rename all finished orders
|
||||
for result in ordergroup.group_order_results
|
||||
result.update_attribute(:group_name, self.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the available funds for this order group (the account_balance minus price of all non-booked GroupOrders of this group).
|
||||
# * excludeGroupOrder (GroupOrder): exclude this GroupOrder from the calculation
|
||||
def getAvailableFunds(excludeGroupOrder = nil)
|
||||
funds = account_balance
|
||||
for order in GroupOrder.find_all_by_order_group_id(self.id)
|
||||
unless order == excludeGroupOrder
|
||||
funds -= order.price
|
||||
end
|
||||
end
|
||||
for order_result in self.findFinishedNotBooked
|
||||
funds -= order_result.price
|
||||
end
|
||||
return funds
|
||||
end
|
||||
|
||||
# Creates a new FinancialTransaction for this OrderGroup and updates the account_balance accordingly.
|
||||
# Throws an exception if it fails.
|
||||
def addFinancialTransaction(amount, note, user)
|
||||
transaction do
|
||||
trans = FinancialTransaction.new(:order_group => self, :amount => amount, :note => note, :user => user)
|
||||
trans.save!
|
||||
self.account_balance += trans.amount
|
||||
self.account_updated = trans.created_on
|
||||
save!
|
||||
notifyNegativeBalance(trans)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns all GroupOrders by this group that are currently running.
|
||||
def findCurrent
|
||||
group_orders.find(:all, :conditions => ["orders.finished = ? AND orders.starts < ? AND (orders.ends IS NULL OR orders.ends > ?)", false, Time.now, Time.now], :include => :order)
|
||||
end
|
||||
|
||||
#find expired (lapsed) but not manually finished orders
|
||||
def findExpiredOrders
|
||||
group_orders.find(:all, :conditions => ["orders.ends < ?", Time.now], :include => :order, :order => 'orders.ends DESC')
|
||||
end
|
||||
|
||||
# Returns all GroupOrderResults by this group that are finished but not booked yet.
|
||||
def findFinishedNotBooked
|
||||
GroupOrderResult.find(:all,
|
||||
:conditions => ["group_order_results.group_name = ? AND group_order_results.order_id = orders.id AND orders.finished = ? AND orders.booked = ? ", self.name, true, false],
|
||||
:include => :order,
|
||||
:order => 'orders.ends DESC')
|
||||
end
|
||||
|
||||
# Returns all GroupOrderResults for booked orders
|
||||
def findBookedOrders(limit = false, offset = 0)
|
||||
GroupOrderResult.find(:all,
|
||||
:conditions => ["group_order_results.group_name = ? AND group_order_results.order_id = orders.id AND orders.finished = ? AND orders.booked = ? ", self.name, true, true],
|
||||
:include => :order,
|
||||
:order => "orders.ends DESC",
|
||||
:limit => limit,
|
||||
:offset => offset)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# If this order group's account balance is made negative by the given/last transaction,
|
||||
# a message is sent to all users who have enabled notification.
|
||||
def notifyNegativeBalance(transaction)
|
||||
# Notify only when order group had a positive balance before the last transaction:
|
||||
if (transaction.amount < 0 && self.account_balance < 0 && self.account_balance - transaction.amount >= 0)
|
||||
users = self.users.reject{|u| u.settings["notify.negativeBalance"] != '1'}
|
||||
unless (users.empty?)
|
||||
recipients = users.collect{|u| u.nick}.join(', ')
|
||||
for user in users
|
||||
Message.from_template(
|
||||
'negative_balance',
|
||||
{:user => user, :group => self, :transaction => transaction},
|
||||
{:recipient_id => user.id, :recipients => recipients, :subject => "Gruppenkonto im Minus"}
|
||||
).save!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# before create or update, check if the name is already used in GroupOrderResults
|
||||
def validate_on_create
|
||||
errors.add(:name, ERR_NAME_IS_USED_IN_ARCHIVE) unless GroupOrderResult.find_all_by_group_name(self.name).empty?
|
||||
end
|
||||
def validate_on_update
|
||||
ordergroup = OrderGroup.find(self.id)
|
||||
errors.add(:name, ERR_NAME_IS_USED_IN_ARCHIVE) unless ordergroup.name == self.name || GroupOrderResult.find_all_by_group_name(self.name).empty?
|
||||
end
|
||||
|
||||
end
|
12
app/models/shared_article.rb
Normal file
12
app/models/shared_article.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
class SharedArticle < ActiveRecord::Base
|
||||
|
||||
# gettext-option
|
||||
untranslate_all
|
||||
|
||||
# connect to database from sharedLists-Application
|
||||
SharedArticle.establish_connection(FoodSoft::get_shared_lists_config)
|
||||
# set correct table_name in external DB
|
||||
set_table_name :articles
|
||||
|
||||
belongs_to :shared_supplier, :foreign_key => :supplier_id
|
||||
end
|
17
app/models/shared_supplier.rb
Normal file
17
app/models/shared_supplier.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
class SharedSupplier < ActiveRecord::Base
|
||||
# used for gettext
|
||||
untranslate_all
|
||||
|
||||
# connect to database from sharedLists-Application
|
||||
SharedSupplier.establish_connection(FoodSoft::get_shared_lists_config)
|
||||
# set correct table_name in external DB
|
||||
set_table_name :suppliers
|
||||
|
||||
|
||||
has_one :supplier
|
||||
has_many :shared_articles, :foreign_key => :supplier_id
|
||||
|
||||
# save the lists as an array
|
||||
serialize :lists
|
||||
|
||||
end
|
67
app/models/supplier.rb
Normal file
67
app/models/supplier.rb
Normal file
|
@ -0,0 +1,67 @@
|
|||
class Supplier < ActiveRecord::Base
|
||||
has_many :articles, :dependent => :destroy
|
||||
has_many :orders
|
||||
attr_accessible :name, :address, :phone, :phone2, :fax, :email, :url, :contact_person, :customer_number, :delivery_days, :order_howto, :note, :shared_supplier_id, :min_order_quantity
|
||||
|
||||
validates_length_of :name, :in => 4..30
|
||||
validates_uniqueness_of :name
|
||||
|
||||
validates_length_of :phone, :in => 8..20
|
||||
validates_length_of :address, :in => 8..50
|
||||
|
||||
# for the sharedLists-App
|
||||
belongs_to :shared_supplier
|
||||
|
||||
# Returns all articles for this supplier that are available and have a valid price, grouped by article category and ordered by name.
|
||||
def getArticlesAvailableForOrdering
|
||||
articles = Article.find(:all, :conditions => ['supplier_id = ? AND availability = ?', self.id, true], :order => 'article_categories.name, articles.name', :include => :article_category)
|
||||
articles.select {|article| article.gross_price}
|
||||
end
|
||||
|
||||
# sync all articles with the external database
|
||||
# returns an array with articles(and prices), which should be updated (to use in a form)
|
||||
# also returns an array with outlisted_articles, which should be deleted
|
||||
def sync_all
|
||||
updated_articles = Array.new
|
||||
outlisted_articles = Array.new
|
||||
for article in articles.find(:all, :order => "article_categories.name", :include => :article_category)
|
||||
# try to find the associated shared_article
|
||||
shared_article = article.shared_article
|
||||
if shared_article
|
||||
# article will be updated
|
||||
|
||||
# skip if shared_article has not been changed
|
||||
unequal_attributes = article.shared_article_changed?
|
||||
unless unequal_attributes.blank?
|
||||
# update objekt but don't save it
|
||||
|
||||
# try to convert different units
|
||||
new_price, new_unit_quantity = article.convert_units
|
||||
if new_price and new_unit_quantity
|
||||
article.net_price = new_price
|
||||
article.unit_quantity = new_unit_quantity
|
||||
else
|
||||
article.net_price = shared_article.price
|
||||
article.unit_quantity = shared_article.unit_quantity
|
||||
article.unit = shared_article.unit
|
||||
end
|
||||
# update other attributes
|
||||
article.attributes = {
|
||||
:name => shared_article.name,
|
||||
:manufacturer => shared_article.manufacturer,
|
||||
:origin => shared_article.origin,
|
||||
:shared_updated_on => shared_article.updated_on,
|
||||
:tax => shared_article.tax,
|
||||
:deposit => shared_article.deposit,
|
||||
:note => shared_article.note
|
||||
}
|
||||
updated_articles << [article, unequal_attributes]
|
||||
end
|
||||
else
|
||||
# article isn't in external database anymore
|
||||
outlisted_articles << article
|
||||
end
|
||||
end
|
||||
return [updated_articles, outlisted_articles]
|
||||
end
|
||||
end
|
60
app/models/task.rb
Normal file
60
app/models/task.rb
Normal file
|
@ -0,0 +1,60 @@
|
|||
class Task < ActiveRecord::Base
|
||||
has_many :assignments, :dependent => :destroy
|
||||
has_many :users, :through => :assignments
|
||||
belongs_to :group
|
||||
|
||||
# form will send user in string. responsibilities will added later
|
||||
attr_protected :users
|
||||
|
||||
validates_length_of :name, :minimum => 3
|
||||
|
||||
|
||||
def is_assigned?(user)
|
||||
self.assignments.detect {|ass| ass.user_id == user.id }
|
||||
end
|
||||
|
||||
def is_accepted?(user)
|
||||
self.assignments.detect {|ass| ass.user_id == user.id && ass.accepted }
|
||||
end
|
||||
|
||||
def enough_users_assigned?
|
||||
assignments.find_all_by_accepted(true).size >= required_users ? true : false
|
||||
end
|
||||
|
||||
# extracts nicknames from a comma seperated string
|
||||
# and makes the users responsible for the task
|
||||
def user_list=(string)
|
||||
@user_list = string.split(%r{,\s*})
|
||||
new_users = @user_list - users.collect(&:nick)
|
||||
old_users = users.reject { |user| @user_list.include?(user.nick) }
|
||||
|
||||
logger.debug "New users: #{new_users}"
|
||||
logger.debug "Old users: #{old_users}"
|
||||
|
||||
self.class.transaction do
|
||||
# delete old assignments
|
||||
if old_users.any?
|
||||
assignments.find(:all, :conditions => ["user_id IN (?)", old_users.collect(&:id)]).each(&:destroy)
|
||||
end
|
||||
# create new assignments
|
||||
new_users.each do |nick|
|
||||
user = User.find_by_nick(nick)
|
||||
if user.blank?
|
||||
errors.add(:user_list)
|
||||
else
|
||||
if user == User.current_user
|
||||
# current_user will accept, when he puts himself to the list of users
|
||||
self.assignments.build :user => user, :accepted => true
|
||||
else
|
||||
# normal assignement
|
||||
self.assignments.build :user => user
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def user_list
|
||||
@user_list ||= users.collect(&:nick).join(", ")
|
||||
end
|
||||
end
|
174
app/models/user.rb
Normal file
174
app/models/user.rb
Normal file
|
@ -0,0 +1,174 @@
|
|||
require 'digest/sha1'
|
||||
|
||||
# A foodsoft user.
|
||||
#
|
||||
# * memberships
|
||||
# * groups
|
||||
# * first_name, last_name, email, phone, address
|
||||
# * nick
|
||||
# * password (stored as a hash)
|
||||
# * settings (user properties via acts_as_configurable plugin)
|
||||
# specific user rights through memberships (see Group)
|
||||
class User < ActiveRecord::Base
|
||||
has_many :memberships, :dependent => :destroy
|
||||
has_many :groups, :through => :memberships
|
||||
has_many :assignments, :dependent => :destroy
|
||||
has_many :tasks, :through => :assignments
|
||||
|
||||
attr_accessible :nick, :first_name, :last_name, :email, :phone, :address
|
||||
|
||||
validates_length_of :nick, :in => 2..25
|
||||
validates_uniqueness_of :nick
|
||||
validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
|
||||
validates_uniqueness_of :email
|
||||
validates_length_of :first_name, :in => 2..50
|
||||
|
||||
# Adds support for configuration settings (through "settings" attribute).
|
||||
acts_as_configurable
|
||||
|
||||
# makes the current_user (logged-in-user) available in models
|
||||
cattr_accessor :current_user
|
||||
|
||||
# User settings keys
|
||||
# returns the User-settings and the translated description
|
||||
def self.setting_keys
|
||||
settings_hash = {
|
||||
"notify.orderFinished" => _('Get message with order result'),
|
||||
"notify.negativeBalance" => _('Get message if negative account balance'),
|
||||
"messages.sendAsEmail" => _('Get messages as emails'),
|
||||
"profile.phoneIsPublic" => _('Phone is visible for foodcoop members'),
|
||||
"profile.emailIsPublic" => _('Email is visible for foodcoop members'),
|
||||
"profile.nameIsPublic" => _('Name is visible for foodcoop members')
|
||||
}
|
||||
return settings_hash
|
||||
end
|
||||
# retuns the default setting for a NEW user
|
||||
# for old records nil will returned
|
||||
# TODO: integrate default behaviour in acts_as_configurable plugin
|
||||
def settings_default(setting)
|
||||
# define a default for the settings
|
||||
defaults = {
|
||||
"messages.sendAsEmail" => true
|
||||
}
|
||||
return true if self.new_record? && defaults[setting]
|
||||
end
|
||||
|
||||
|
||||
# Sets the user's password. It will be stored encrypted along with a random salt.
|
||||
def password=(password)
|
||||
salt = [Array.new(6){rand(256).chr}.join].pack("m").chomp
|
||||
self.password_hash, self.password_salt = Digest::SHA1.hexdigest(password + salt), salt
|
||||
end
|
||||
|
||||
# Returns true if the password argument matches the user's password.
|
||||
def has_password(password)
|
||||
Digest::SHA1.hexdigest(password + self.password_salt) == self.password_hash
|
||||
end
|
||||
|
||||
#Sets the passwort, and if fails it returns error-messages (see above)
|
||||
def set_password(options = {:required => false}, password = nil, confirmation = nil)
|
||||
required = options[:required]
|
||||
if required && (password.nil? || password.empty?)
|
||||
self.errors.add_to_base _('Password is required')
|
||||
elsif !password.nil? && !password.empty?
|
||||
if password != confirmation
|
||||
self.errors.add_to_base _("Passwords doesn't match")
|
||||
elsif password.length < 5 || password.length > 25
|
||||
self.errors.add_to_base _('Password-length has to be between 5 and 25 characters')
|
||||
else
|
||||
self.password = password
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a random password.
|
||||
def new_random_password(size = 3)
|
||||
c = %w(b c d f g h j k l m n p qu r s t v w x z ch cr fr nd ng nk nt ph pr rd sh sl sp st th tr)
|
||||
v = %w(a e i o u y)
|
||||
f, r = true, ''
|
||||
(size * 2).times do
|
||||
r << (f ? c[rand * c.size] : v[rand * v.size])
|
||||
f = !f
|
||||
end
|
||||
r
|
||||
end
|
||||
|
||||
# Checks the admin role
|
||||
def role_admin?
|
||||
groups.detect {|group| group.role_admin?}
|
||||
end
|
||||
|
||||
# Checks the finance role
|
||||
def role_finance?
|
||||
groups.detect {|group| group.role_finance?}
|
||||
end
|
||||
|
||||
# Checks the article_meta role
|
||||
def role_article_meta?
|
||||
groups.detect {|group| group.role_article_meta?}
|
||||
end
|
||||
|
||||
# Checks the suppliers role
|
||||
def role_suppliers?
|
||||
groups.detect {|group| group.role_suppliers?}
|
||||
end
|
||||
|
||||
# Checks the orders role
|
||||
def role_orders?
|
||||
groups.detect {|group| group.role_orders?}
|
||||
end
|
||||
|
||||
# Returns the user's OrderGroup or nil if none found.
|
||||
def find_ordergroup
|
||||
groups.find(:first, :conditions => "type = 'OrderGroup'")
|
||||
end
|
||||
|
||||
# Find all tasks, for which the current user should be responsible
|
||||
# but which aren't accepted yet
|
||||
def unaccepted_tasks
|
||||
# this doesn't work. Produces "undefined method", when later use task.users... Rails Bug?
|
||||
# self.tasks.find :all, :conditions => ["accepted = ?", false], :order => "due_date DESC"
|
||||
Task.find_by_sql ["SELECT t.* FROM tasks t, assignments a, users u
|
||||
WHERE u.id = a.user_id
|
||||
AND t.id = a.task_id
|
||||
AND u.id = ?
|
||||
AND a.accepted = ?
|
||||
AND t.done = ?
|
||||
ORDER BY t.due_date ASC", self.id, false, false]
|
||||
end
|
||||
|
||||
# Find all accepted tasks, which aren't done
|
||||
def accepted_tasks
|
||||
Task.find_by_sql ["SELECT t.* FROM tasks t, assignments a, users u
|
||||
WHERE u.id = a.user_id
|
||||
AND t.id = a.task_id
|
||||
AND u.id = ?
|
||||
AND a.accepted = ?
|
||||
AND t.done = ?
|
||||
ORDER BY t.due_date ASC", self.id, true, false]
|
||||
end
|
||||
|
||||
# find all tasks in the next week (or another number of days)
|
||||
def next_tasks(number = 7)
|
||||
Task.find_by_sql ["SELECT t.* FROM tasks t, assignments a, users u
|
||||
WHERE u.id = a.user_id
|
||||
AND t.id = a.task_id
|
||||
AND u.id = ?
|
||||
AND t.due_date >= ?
|
||||
AND t.due_date <= ?
|
||||
AND t.done = ?
|
||||
AND a.accepted = ?
|
||||
ORDER BY t.due_date ASC", self.id, Time.now, number.days.from_now, false, true]
|
||||
end
|
||||
|
||||
# returns true if user is a member of a given group
|
||||
def is_member_of(group)
|
||||
return true if group.users.detect {|user| user == self}
|
||||
end
|
||||
|
||||
#Returns an array with the users groups (but without the OrderGroups -> because tpye=>"")
|
||||
def member_of_groups()
|
||||
self.groups.find(:all, :conditions => {:type => ""})
|
||||
end
|
||||
|
||||
end
|
27
app/views/admin/_listGroups.haml
Normal file
27
app/views/admin/_listGroups.haml
Normal file
|
@ -0,0 +1,27 @@
|
|||
%p
|
||||
%table{:style => "width:100%"}
|
||||
%tr
|
||||
%td
|
||||
= pagination_links_remote @groups
|
||||
%td{:style => "text-align:right"}
|
||||
- if @total > 20
|
||||
= items_per_page
|
||||
%table.list
|
||||
%thead
|
||||
%tr
|
||||
%th Name
|
||||
%th Typ
|
||||
%th Mitglieder
|
||||
%th
|
||||
%tbody
|
||||
- for group in @groups
|
||||
%tr{:class => cycle('even','odd', :name => 'groups')}
|
||||
%td= link_to group.name, :action => 'showGroup', :id => group
|
||||
%td= group.is_a?(OrderGroup) ? 'Bestellgruppe' : 'Gruppe'
|
||||
%td= group.users.size
|
||||
%td
|
||||
= link_to image_tag('b_users.png', :size => '16x16', :border => "0", :alt => 'Mitlglieder bearbeiten'), :action => 'members', :id => group
|
||||
= link_to(image_tag('b_edit.png', :size => "16x16", :border => "0", :alt => 'Gruppe bearbeiten'), :action => 'editGroup', :id => group)
|
||||
= link_to(image_tag('b_drop.png', :size => "16x16", :border => "0", :alt => 'Gruppe löschen'), {:action => 'destroyGroup', :id => group}, |
|
||||
:confirm => 'Willst du ' + group.name + ' wirklich löschen?', |
|
||||
:method => "post") |
|
39
app/views/admin/_listUsers.haml
Normal file
39
app/views/admin/_listUsers.haml
Normal file
|
@ -0,0 +1,39 @@
|
|||
%p
|
||||
%table{:style => "width:100%"}
|
||||
%tr
|
||||
%td
|
||||
= pagination_links_remote @users
|
||||
%td{:style => "text-align:right"}
|
||||
- if @total > 20
|
||||
= items_per_page
|
||||
%table.list
|
||||
%thead
|
||||
%tr
|
||||
%th= _('Username')
|
||||
%th= _('First name')
|
||||
%th= _('Last name')
|
||||
%th= _('Email')
|
||||
%th= _('Roles')
|
||||
%th= _('Last Login')
|
||||
%th{:style => "width:3em"}
|
||||
%tbody
|
||||
- for user in @users
|
||||
- roles = Array.new
|
||||
- roles << 'Admin' if user.role_admin?
|
||||
- roles << 'Finanzen' if user.role_finance?
|
||||
- roles << 'Lieferanten' if user.role_suppliers?
|
||||
- roles << 'Artikel' if user.role_article_meta?
|
||||
- roles << 'Bestellung' if user.role_orders?
|
||||
%tr{:class => cycle('even','odd', :name => 'users')}
|
||||
%td= link_to user.nick, :action => 'showUser', :id => user
|
||||
%td=h user.first_name
|
||||
%td=h user.last_name
|
||||
%td=h user.email
|
||||
%td=h roles.join(', ')
|
||||
%td=h FoodSoft::format_date_time(user.last_login)
|
||||
%td
|
||||
= link_to(image_tag('b_edit.png', :size => "16x16", :border => "0", :alt => 'Benutzer_in bearbeiten', :title => 'Benutzer_in bearbeiten'), :action => 'editUser', :id => user)
|
||||
= link_to(image_tag('b_drop.png', :size => "16x16", :border => "0", :alt => 'Benutzer_in löschen', :title => 'Benutzer_in löschen'), |
|
||||
{:action => 'destroyUser', :id => user}, |
|
||||
:confirm => 'Willst du ' + user.first_name + ' wirklich löschen?', |
|
||||
:method => "post") |
|
5
app/views/admin/_newUser.rhtml
Normal file
5
app/views/admin/_newUser.rhtml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<% form_tag(:action => 'createUser') do %>
|
||||
<%= render :partial => 'users/form' %>
|
||||
<p style="clear:both;">
|
||||
<%= submit_tag "Speichern" %> | <%= link_to_function('Abbrechen', 'Element.hide("newUser")')%></p>
|
||||
<% end %>
|
12
app/views/admin/editUser.rhtml
Normal file
12
app/views/admin/editUser.rhtml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<h1>Benutzer bearbeiten</h1>
|
||||
<div id="newUser" style="width:65em">
|
||||
<div class="box_title"><h2>Benutzer_in bearbeiten</h2></div>
|
||||
<div class="column_content" id="userForm">
|
||||
<% form_tag(:action => 'updateUser', :id => @user) do %>
|
||||
<%= render :partial => 'users/form' %>
|
||||
<br style="clear:both" />
|
||||
<p>
|
||||
<%= submit_tag 'Speichern' %> | <%= link_to 'Abbrechen', :action => 'listUsers' %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
51
app/views/admin/index.rhtml
Normal file
51
app/views/admin/index.rhtml
Normal file
|
@ -0,0 +1,51 @@
|
|||
<h1>Administration</h1>
|
||||
|
||||
<p><i>Hier kannst Du die Gruppen und Benutzer der Foodsoft verwalten.</i></p>
|
||||
<div class="left_column" style="width:48%">
|
||||
<div class="box_title"><h2>Neuste Benutzer</h2></div>
|
||||
<div class="column_content">
|
||||
<table>
|
||||
<tr>
|
||||
<th>Nick</th>
|
||||
<th>Name</th>
|
||||
<th>Erstellt am</th>
|
||||
</tr>
|
||||
|
||||
<% for user in @users %>
|
||||
<tr class="<%= cycle('even','odd', :name => 'users') %>">
|
||||
<td><%= link_to user.nick, :action => 'showUser', :id => user.id %></td>
|
||||
<td><%= user.first_name %> <%= user.last_name %></td>
|
||||
<td><%= format_date(user.created_on) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
<br />
|
||||
<%= link_to 'Alle Benutzerinnen', :action => 'listUsers' %> |
|
||||
<%= link_to "Neue Benutzerin", :action => "newUser" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right_column" style="width:48%">
|
||||
<div class="box_title"><h2>Neuste Gruppen</h2></div>
|
||||
<div class="column_content">
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Typ</th>
|
||||
<th>Mitglieder</th>
|
||||
</tr>
|
||||
<% for group in @groups %>
|
||||
<tr class="<%= cycle('even','odd', :name => 'groups') %>">
|
||||
<td><%= link_to group.name, :action => 'showGroup', :id => group %></td>
|
||||
<td><%= group.is_a?(OrderGroup) ? 'Bestellgruppe' : 'Gruppe' %></td>
|
||||
<td><%= group.users.size %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
<br />
|
||||
<%= link_to 'Alle Gruppen', :action => 'listGroups' %> |
|
||||
<%= link_to "Neue Bestellgruppe", :action => "newOrderGroup" %> |
|
||||
<%= link_to "Neue Gruppe", :action => "newGroup" %>
|
||||
</div>
|
||||
</div>
|
||||
|
32
app/views/admin/listGroups.haml
Normal file
32
app/views/admin/listGroups.haml
Normal file
|
@ -0,0 +1,32 @@
|
|||
%h1=_ 'Groups'
|
||||
%p
|
||||
%i
|
||||
Hier kannst du
|
||||
= link_to 'neue Gruppen', :action => 'newGroup'
|
||||
und
|
||||
= link_to 'neue Bestellgruppen', :action => 'newOrderGroup'
|
||||
anlegen, Gruppen bearbeiten und löschen.
|
||||
%p
|
||||
Beachte dabei den <em>Unterschied zwischen Gruppe und Bestellgruppe</em>:
|
||||
Eine <em>Bestellgruppe</em> hat ein Konto und kann Essen bestellen, eine <em>Gruppe</em> ist eine Arbeitsgruppe wie z.B. die "Sortiergruppe".
|
||||
Nutzer_innen können immer nur einer Bestellgruppe, aber beliebig vielen anderen Gruppen angehören.
|
||||
.left_column{:style => "width:100%"}
|
||||
.box_title
|
||||
%h2 Gruppenübersicht
|
||||
.column_content
|
||||
#group_filter
|
||||
%form{:name=>"sform", :action=>"", :style=>"display:inline;"}
|
||||
%label{:for => 'article_name'} Suche in Name :
|
||||
= text_field_tag("query", params['query'], :size => 10 )
|
||||
|
||||
= observe_field 'query', :frequency => 2, |
|
||||
:before => "Element.show('loader')", |
|
||||
:success => "Element.hide('loader')", |
|
||||
:url => {:action => 'listGroups'}, |
|
||||
:with => 'query' |
|
||||
#table
|
||||
= render :partial => "listGroups"
|
||||
- if @current_user.role_admin?
|
||||
= link_to 'Neue Gruppe', :action => 'newGroup'
|
||||
|
|
||||
= link_to 'Neue Bestellgruppe', :action => 'newOrderGroup'
|
28
app/views/admin/listUsers.haml
Normal file
28
app/views/admin/listUsers.haml
Normal file
|
@ -0,0 +1,28 @@
|
|||
%h1 Admin/Benutzerinnen
|
||||
%p
|
||||
%i
|
||||
Hier kannst du Benutzer_innen
|
||||
= link_to_remote 'neu Anlegen', :url => {:action => 'newUser'}
|
||||
, bearbeiten und natürlich auch löschen.
|
||||
#newUser{:style => "display:none;"}
|
||||
.box_title
|
||||
%h2 Neue Benutzerinn
|
||||
.column_content#userForm
|
||||
.left_column{:style => "width:100%"}
|
||||
.box_title
|
||||
%h2 Benutzerinnenübersicht
|
||||
.column_content
|
||||
#user_filter
|
||||
%form{:name=>"sform", :action=>"", :style=>"display:inline;"}
|
||||
%label{:for => 'article_name'} Suche in Name :
|
||||
= text_field_tag("query", params['query'], :size => 10 )
|
||||
|
||||
= observe_field 'query', :frequency => 2, |
|
||||
:before => "Element.show('loader')", |
|
||||
:success => "Element.hide('loader')", |
|
||||
:url => {:action => 'listUsers'}, |
|
||||
:with => 'query' |
|
||||
#table
|
||||
= render :partial => "listUsers"
|
||||
- if @current_user.role_admin?
|
||||
%p= link_to 'Neue Benutzer_in', :action => 'newUser'
|
2
app/views/admin/members.rhtml
Normal file
2
app/views/admin/members.rhtml
Normal file
|
@ -0,0 +1,2 @@
|
|||
<%= render :partial => 'groups/edit_members' %>
|
||||
<p style="clear:both"><%= link_to 'Gruppenübersicht', :action => 'listGroups' %></p>
|
8
app/views/admin/newGroup.rhtml
Normal file
8
app/views/admin/newGroup.rhtml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<h1>Neue <%= @group.is_a?(OrderGroup) ? 'Bestellgruppe' : 'Gruppe' %></h1>
|
||||
<div class="edit_form" style="width:50em">
|
||||
<% form_tag(:action => (@group.is_a?(OrderGroup) ? 'createOrderGroup' : 'createGroup')) do %>
|
||||
<%= render :partial => 'groups/form' %>
|
||||
<br style="clear:both" />
|
||||
<%= submit_tag "Speichern" %> | <%= link_to "Abbrechen", :action => "listGroups" %>
|
||||
<% end %>
|
||||
</div>
|
13
app/views/admin/newUser.rhtml
Normal file
13
app/views/admin/newUser.rhtml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<h1>Neuer Benutzer</h1>
|
||||
<div id="newUser">
|
||||
<div class="box_title"><h2>Neue_r Benutzer_in</h2></div>
|
||||
<div class="column_content" id="userForm">
|
||||
<% form_tag(:action => 'createUser') do %>
|
||||
<%= render :partial => 'users/form' %>
|
||||
<p style="clear:both;">
|
||||
<%= submit_tag "Speichern" %> | <%= link_to('Abbrechen', :action => 'listUsers')%></p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<p><%= link_to 'Benutzer_innenübersicht', :action => 'listUsers' %></p>
|
||||
|
54
app/views/admin/showGroup.haml
Normal file
54
app/views/admin/showGroup.haml
Normal file
|
@ -0,0 +1,54 @@
|
|||
%h1
|
||||
= @group.is_a?(OrderGroup) ? "Bestellgruppe:" : "Gruppe:"
|
||||
=h @group.name
|
||||
.left_column{:style => "width:45em"}
|
||||
.box_title
|
||||
%h2 Übersicht
|
||||
.column_content
|
||||
%p
|
||||
%b Beschreibung:
|
||||
=h @group.description
|
||||
- if @group.is_a?(OrderGroup)
|
||||
%p
|
||||
%b Gruppengröße:
|
||||
= @group.actual_size
|
||||
%p
|
||||
%b Kontostand:
|
||||
= @group.account_balance
|
||||
%p
|
||||
%b Kontostand aktualisiert:
|
||||
= @group.account_updated
|
||||
%p
|
||||
%b Gruppe hat Zugriff auf:
|
||||
- roles = Array.new
|
||||
- roles << 'Administration' if @group.role_admin?
|
||||
- roles << 'Finanzen' if @group.role_finance?
|
||||
- roles << 'Lieferanten' if @group.role_suppliers?
|
||||
- roles << 'Artikel' if @group.role_article_meta?
|
||||
- roles << 'Bestellungsverwaltung' if @group.role_orders?
|
||||
=h roles.join(', ')
|
||||
%p
|
||||
- if @group.weekly_task
|
||||
%b wöchentlicher Job:
|
||||
=h @group.task_name
|
||||
am
|
||||
= weekday(@group.weekday)
|
||||
- else
|
||||
kein wöchentlicher Job definiert
|
||||
|
||||
= link_to 'Gruppe bearbeiten', :action => 'editGroup', :id => @group
|
||||
|
|
||||
= link_to 'Löschen', { :action => 'destroyGroup', :id => @group }, :confirm => 'Bist Du sicher?', :method => "post"
|
||||
|
|
||||
= link_to 'Nachricht senden', :controller => 'messages', :action => 'group', :id => @group
|
||||
.box_title
|
||||
%h2 Mitglieder
|
||||
.column_content
|
||||
- if @group.memberships.empty?
|
||||
%i Diese Gruppe hat derzeit keine Mitglieder
|
||||
- else
|
||||
%ul{:style => "list-style-type:decimal;"}
|
||||
- for membership in @group.memberships
|
||||
%li= link_to membership.user.nick, :action => 'showUser', :id => membership.user
|
||||
%p= link_to 'Mitglieder bearbeiten', :action => 'members', :id => @group
|
||||
%p{:style => "clear:both"}= link_to "Gruppenübersicht", :action => "listGroups"
|
17
app/views/admin/showUser.rhtml
Normal file
17
app/views/admin/showUser.rhtml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<h1><%=h @user.nick %></h1>
|
||||
<div class="left_column" style="width:100%">
|
||||
<div class="box_title"><h2>Übersicht</h2></div>
|
||||
<div class="column_content">
|
||||
<%= render :partial => 'users/show'%>
|
||||
<p style="clear:both">
|
||||
<%= link_to 'Bearbeiten', :action => 'editUser', :id => @user %>
|
||||
| <%= link_to 'Löschen', { :action => 'destroyUser', :id => @user }, :confirm => 'Willst du ' + @user.first_name + ' wirklich rausschmeißen?', :method => "post" %>
|
||||
| <%= link_to 'Nachricht senden', :controller => 'messages', :action => 'user', :id => @user %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="box_title"><h2>Gruppenabos</h2></div>
|
||||
<div class="column_content">
|
||||
<%= render :partial => 'users/memberships'%>
|
||||
<p><%= link_to 'Gruppenübersicht', :action => 'listGroups' %></p>
|
||||
</div>
|
||||
</div>
|
7
app/views/article_categories/_edit.rhtml
Normal file
7
app/views/article_categories/_edit.rhtml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<% remote_form_for :article_category, :url => { :action => 'updateCategory', :id => @article_category },
|
||||
:before => "Element.show('loader')",
|
||||
:success => "Element.hide('loader')" do |@f| %>
|
||||
<%= render :partial => '/article_categories/form' %>
|
||||
<br />
|
||||
<%= submit_tag "Speichern" %> | <%= link_to_function("Abbrechen", "Element.hide('category_form')") %>
|
||||
<% end %>
|
17
app/views/article_categories/_form.rhtml
Normal file
17
app/views/article_categories/_form.rhtml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<%= error_messages_for 'article_category' %>
|
||||
|
||||
<!--[form:article_category]-->
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Beschreibung</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label for="article_category_name">
|
||||
<%= @f.text_field 'name', :size => 20 %></label></td>
|
||||
<td><label for="article_category_description">
|
||||
<%= @f.text_field 'description', :size => 30 %></label></td>
|
||||
</tr>
|
||||
</tabel>
|
||||
<!--[eoform:article_category]-->
|
||||
|
22
app/views/article_categories/_list.rhtml
Normal file
22
app/views/article_categories/_list.rhtml
Normal file
|
@ -0,0 +1,22 @@
|
|||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Beschreibung</th>
|
||||
<th colspan="2"></th>
|
||||
</tr>
|
||||
<% for article_category in ArticleCategory.find(:all) %>
|
||||
<tr class="<%= cycle("even","odd", :name => 'category') %>" id="category_<%= article_category.id %>">
|
||||
<td><%=h article_category.name %></td>
|
||||
<td><%=h article_category.description %></td>
|
||||
<td><%= link_to_remote(image_tag('b_edit.png', :size => "16x16", :border => "0", :alt => 'Katgerie bearbeiten'),
|
||||
:url => {:action => 'editCategory', :id => article_category},
|
||||
:before => "Element.show('loader')",
|
||||
:success => "Element.hide('loader')" ) %></td>
|
||||
<td><%= link_to_remote(image_tag('b_drop.png', :size => "16x16", :border => "0", :alt => 'Kategorie löschen'), :url => {:action => 'destroyCategory', :id => article_category }, :confirm => 'Are you sure?' )%></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
<br />
|
||||
<%= link_to_remote('Neue Kategorie', :url => {:action => :newCategory},
|
||||
:before => "Element.show('loader')",
|
||||
:success => "Element.hide('loader')") -%>
|
7
app/views/article_categories/_new.rhtml
Normal file
7
app/views/article_categories/_new.rhtml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<% remote_form_for :article_category, :url => { :action => 'createCategory', :id => @article_category },
|
||||
:before => "Element.show('loader')",
|
||||
:success => "Element.hide('loader')" do |@f| %>
|
||||
<%= render :partial => '/article_categories/form' %>
|
||||
<br />
|
||||
<%= submit_tag "Speichern" %> | <%= link_to_function("Abbrechen", "Element.hide('category_form')") %>
|
||||
<% end %>
|
27
app/views/articles/_article_row.rhtml
Normal file
27
app/views/articles/_article_row.rhtml
Normal file
|
@ -0,0 +1,27 @@
|
|||
<td>
|
||||
<%= check_box_tag 'selected_articles[]', @article.id.to_s, false, {:id => "checkbox_#{@article.id.to_s}", :onclick => "checkRow('#{@article.id.to_s}')"} %>
|
||||
</td>
|
||||
<td><%=h @article.name %></td>
|
||||
<td><%= @article.origin %></td>
|
||||
<td><%=h truncate(@article.article_category.name, 11) if @article.article_category%></td>
|
||||
<td><%=h @article.unit %></td>
|
||||
<td><%=h truncate(@article.note, 15) %></td>
|
||||
<td><%= @article.unit_quantity %></td>
|
||||
<td class="currency">
|
||||
<acronym title="zuletzt geändert: <%= format_date(@article.updated_at) %>
|
||||
| Brutto: <%= number_to_currency(@article.gross_price) %>">
|
||||
<%= number_to_currency(@article.net_price) %>
|
||||
</acronym>
|
||||
</td>
|
||||
<td><%= number_to_percentage(@article.tax) if @article.tax != 0 %></td>
|
||||
<td><%= number_to_currency(@article.deposit) if @article.deposit != 0 %></td>
|
||||
<td><%= link_to_remote(image_tag('b_edit.png', :size => "16x16", :border => 0, :alt => 'Artikel ändern', :onclick => "checkRow('#{@article.id.to_s}')"),
|
||||
:url => {:action => 'editArticle', :id => @article },
|
||||
:before => "Element.show('loader')",
|
||||
:success => "Element.hide('loader')") %>
|
||||
<%= link_to_remote(image_tag('b_drop.png', :size => "16x16", :border => 0, :alt => 'Artikel löschen', :onclick => "checkRow('#{@article.id.to_s}')"),
|
||||
:url => { :action => 'destroyArticle', :id => @article },
|
||||
:confirm => 'Bist du sicher?',
|
||||
:before => "Element.show('loader')",
|
||||
:success => "Element.hide('loader')")%></td>
|
||||
|
9
app/views/articles/_destroyActiveArticle.haml
Normal file
9
app/views/articles/_destroyActiveArticle.haml
Normal file
|
@ -0,0 +1,9 @@
|
|||
%tr.edit_inline{:id=> "edit_"+@article.id.to_s}
|
||||
%td{:colspan=>"10"}
|
||||
=h @article.name
|
||||
wird in laufenden Bestellungen verwendet und kann nicht gelöscht werden.
|
||||
Bitte zuerst den Artikel aus den Bestellungen
|
||||
= link_to "entfernen", :controller => 'orders', :action => 'edit', :id => @order
|
||||
oder
|
||||
= link_to_function 'abbrechen', "Element.remove('edit_#{@article.id.to_s}')"
|
||||
|
14
app/views/articles/_edit.haml
Normal file
14
app/views/articles/_edit.haml
Normal file
|
@ -0,0 +1,14 @@
|
|||
%h2
|
||||
Bearbeiten von
|
||||
= @article.name
|
||||
zuletzt aktualisiert am:
|
||||
= format_time(@article.updated_at)
|
||||
|
||||
- form_remote_tag :url => { :action => 'updateArticle', :id => @article}, :html => {:id => "edit"}, |
|
||||
:before => "Element.show('loader')", :success => "Element.hide('loader')" do |form| |
|
||||
|
||||
= render :partial => "form"
|
||||
|
||||
= submit_tag "Speichern"
|
||||
|
|
||||
= link_to_function "Abbrechen", "Element.hide('edit_article')"
|
69
app/views/articles/_form.rhtml
Normal file
69
app/views/articles/_form.rhtml
Normal file
|
@ -0,0 +1,69 @@
|
|||
<%= error_messages_for 'article' %>
|
||||
<!--[form:article]-->
|
||||
<p>
|
||||
<b>Verfügbar?</b> <label for="article_availability"><%= check_box 'article', 'availability' %></label>
|
||||
</p>
|
||||
<table style="width: 20em">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Herkunft</th>
|
||||
<th>Herstellerin</th>
|
||||
<th>Einheit</th>
|
||||
<th>Notiz</th>
|
||||
<th>Kategorie</th>
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="article_name"><%= text_field 'article', 'name', :size => 15 %></label>
|
||||
</td>
|
||||
<td>
|
||||
<label for="article_origin"><%= text_field 'article', 'origin', :size => 5 %></label>
|
||||
</td>
|
||||
<td>
|
||||
<label for="article_manufacturer"><%= text_field 'article', 'manufacturer', :size => 8 %></label>
|
||||
</td>
|
||||
<td>
|
||||
<label for="article_unit"><%= text_field 'article', 'unit', :size => 5 %></label>
|
||||
</td>
|
||||
<td>
|
||||
<label for="article_note"><%= text_field 'article', 'note', :size => 15 %></label>
|
||||
</td>
|
||||
<td>
|
||||
<label for="article_article_category"><%= select('article', 'article_category_id', ArticleCategory.find(:all, :order => 'name').collect {|a| [ a.name, a.id ] }) %></label>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<br />
|
||||
|
||||
<table style="width:35em">
|
||||
<tr>
|
||||
<th>netto Preis</th>
|
||||
<th>Gebindegröße</th>
|
||||
<th>Artikelnummer</th>
|
||||
<th>Steuer</th>
|
||||
<th>Pfand</th>
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><label for="article_net_price">
|
||||
<%= text_field 'article', 'net_price', :size => 5%></label></td>
|
||||
|
||||
<td><label for="article_unit_quantity">
|
||||
<%= text_field 'article', 'unit_quantity', :size => 5%></label></td>
|
||||
|
||||
<td><label for="article_order_number">
|
||||
<%= text_field 'article', 'order_number', :size => 10%></label></td>
|
||||
|
||||
<td><label for="article_tax">
|
||||
<%= text_field 'article', 'tax', :size => 5%></label></td>
|
||||
|
||||
<td><label for="article_deposit">
|
||||
<%= text_field 'article', 'deposit', :size => 5%></label></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<%= hidden_field 'article', 'shared_updated_on' %>
|
||||
<%= hidden_field 'article', 'supplier_id' %>
|
||||
<!--[eoform:article]-->
|
24
app/views/articles/_import_search_results.haml
Normal file
24
app/views/articles/_import_search_results.haml
Normal file
|
@ -0,0 +1,24 @@
|
|||
%p= pagination_links_remote @articles, 10, {:import_query => params[:import_query], :lists => params[:lists], :regional => params[:regional]}
|
||||
%table.list
|
||||
%thead
|
||||
%tr
|
||||
%th Name
|
||||
%th Herkunft
|
||||
%th Hersteller
|
||||
%th Notiz
|
||||
%th{:style => "width:4em"} Preis
|
||||
%th Einheit
|
||||
%th GebGröße
|
||||
%th
|
||||
%tbody
|
||||
- for article in @articles
|
||||
%tr{:class => cycle('even','odd', :name => 'import_search_results')}
|
||||
%td= highlight_phrase article.name, params[:import_query]
|
||||
%td= article.origin
|
||||
%td= article.manufacturer
|
||||
%td= article.note
|
||||
%td= number_to_currency(article.price)
|
||||
%td= article.unit
|
||||
%td= article.unit_quantity
|
||||
%td= link_to_remote 'importieren', :url => {:action => 'new_import', :id => article, :supplier_id => @supplier.id}
|
||||
|
61
app/views/articles/_list.haml
Normal file
61
app/views/articles/_list.haml
Normal file
|
@ -0,0 +1,61 @@
|
|||
%p
|
||||
=_ 'Number of found articles :'
|
||||
%b= @total
|
||||
|
||||
%p
|
||||
%table{:style => "width:100%"}
|
||||
%tr
|
||||
%td
|
||||
= pagination_links_remote @articles, @per_page, {:sort => params[:sort]}
|
||||
%td{:style => "text-align:right"}
|
||||
- if @total > 30
|
||||
= items_per_page([30, 100, 500])
|
||||
|
||||
%table#articles_table.list.articles
|
||||
%thead
|
||||
%tr
|
||||
%th
|
||||
%th[sort_td_class_helper "name"]
|
||||
= sort_link_helper _("Name"), "name", @supplier.id
|
||||
%th
|
||||
%th[sort_td_class_helper "category"]
|
||||
= sort_link_helper _("Category"), "category", @supplier.id
|
||||
%th[sort_td_class_helper "unit"]
|
||||
= sort_link_helper _("Unit"), "unit", @supplier.id
|
||||
%th[sort_td_class_helper "note"]
|
||||
= sort_link_helper _("Note"), "note", @supplier.id
|
||||
%th{:style => "width: 4em;"}=_ 'UnitQu'
|
||||
%th{:style => "width: 4em;"}=_ 'Price'
|
||||
%th{:style => "width: 3.5em;"}=_ 'Tax'
|
||||
%th{:style => "width: 4em;"}=_ 'Deposit'
|
||||
%th{:style => "width: 3em;"}
|
||||
|
||||
%tbody#listbody
|
||||
|
||||
- if @total > 0
|
||||
- for @article in @articles
|
||||
%tr{ :class => cycle('even','odd') + (!@article.availability ? ' unavailable' : '') + ((@article.recently_updated && @article.availability) ? " just_updated" : ""), |
|
||||
:id => @article.id, :onclick => "checkRow('#{@article.id.to_s}')"} |
|
||||
= render :partial => 'article_row'
|
||||
%tfoot
|
||||
%tr
|
||||
%td{:colspan => '10'}
|
||||
%input{:type => 'checkbox', :name => 'checkall', :onclick => 'checkUncheckAll(this)'}/
|
||||
%select{:name => "selected_action"}
|
||||
%option{:value => '', :selected => 'selected'} Aktion wählen ...
|
||||
%option{:value => "destroy", :onclick => "if (confirm('Willst Du wirklich alle gewählten Artikel löschen?')) { formSubmit(); }; return false;"} Artikel löschen
|
||||
%option{:value => "setNotAvailable", :onclick => 'formSubmit();'} Artikel sind nicht mehr verfügbar
|
||||
%option{:value => "setAvailable", :onclick => 'formSubmit();'} Artikel sind verfügbar
|
||||
|
||||
= hidden_field_tag 'supplier', @supplier.id
|
||||
%p
|
||||
= pagination_links_remote @articles, @per_page, {:sort => params[:sort]}
|
||||
|
||||
:plain
|
||||
<script type="text/javascript">
|
||||
function formSubmit() {
|
||||
$("articlesInListForm").submit()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
5
app/views/articles/_new.haml
Normal file
5
app/views/articles/_new.haml
Normal file
|
@ -0,0 +1,5 @@
|
|||
%h3 Neuer Artikel
|
||||
- form_remote_tag :url => { :action => 'createArticle'} do
|
||||
= render :partial => 'articles/form'
|
||||
= submit_tag "Speichern"
|
||||
= link_to_function('Abbrechen', 'Element.hide("edit_article")')
|
2
app/views/articles/_new_article_row.haml
Normal file
2
app/views/articles/_new_article_row.haml
Normal file
|
@ -0,0 +1,2 @@
|
|||
%tr{:class => cycle('even','odd'), :id => @article.id, :onclick => "checkRow('#{@article.id.to_s}')"}
|
||||
= render :partial => 'article_row'
|
61
app/views/articles/edit_all.rhtml
Normal file
61
app/views/articles/edit_all.rhtml
Normal file
|
@ -0,0 +1,61 @@
|
|||
<h1>Alle Artikel von <%= @supplier.name %> bearbeiten</h1>
|
||||
|
||||
<div class="single_column" style="width:100%">
|
||||
<div class="box_title">
|
||||
<h2>
|
||||
<div id="change_supplier">
|
||||
<% form_tag do -%>
|
||||
<select onchange="redirectTo(this)" style="font-size: 0.9em;margin-left:1em;">
|
||||
<%= options_for_select(Supplier.find(:all).collect {|s| [ s.name, url_for(:action => "edit_all", :id => s)] }, url_for(:action => "edit_all", :id => @supplier.id)) %>
|
||||
</select>
|
||||
<% end %>
|
||||
</div>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="box column_content">
|
||||
<div id="links"><%= link_to 'zurück zur Liste', :action => 'list', :id => @supplier.id %></div>
|
||||
<p>
|
||||
<i>
|
||||
Pflichtfelder sind: Name, Einheit, (netto) Preis und Bestellnummer.
|
||||
</i>
|
||||
</p>
|
||||
<% form_tag(:action => 'update_all', :id => @supplier) do %>
|
||||
<table id="articles_table" class="list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><acronym title="verfügbar">verf.</acronym></th>
|
||||
<th>Name</th>
|
||||
<th>Einheit</th>
|
||||
<th><acronym title="Netto!">Preis</acronym></th>
|
||||
<th><acronym title="Gebindegröße">GebGr</acronym></th>
|
||||
<th>Best.Nr.</th>
|
||||
<th>Notiz</th>
|
||||
<th>Kategorie</th>
|
||||
<th>MwSt.</th>
|
||||
<th>Pfand</th>
|
||||
</tr>
|
||||
<tbody>
|
||||
<% @supplier.articles.find(:all, :order => 'article_categories.name, articles.name', :include => [:article_category]).each do |@article| %>
|
||||
<tr class="<%= cycle('even', 'odd') %>"<%= ' style="background-color: yellow"' if @failedArticle == @article %>>
|
||||
<td colspan="2"><label for="article_availability"><%= check_box 'article[]', 'availability' %></label>
|
||||
<label for="article_name"><%= text_field( 'article[]', 'name', :size => 0) %></label></td>
|
||||
<td><label for="article_unit"><%= text_field 'article[]', 'unit', :size => 5 %></label></td>
|
||||
<td><label for="article_net_price"><%= text_field 'article[]', 'net_price', :size => 4 %></label></td>
|
||||
<td><label for="article_unit_quantity"><%= text_field 'article[]', 'unit_quantity', :size => 4 %></label></td>
|
||||
<td><label for="article_order_number"><%= text_field 'article[]', 'order_number', :size => 6 %></label></td>
|
||||
<td><label for="article_note"><%= text_field 'article[]', 'note', :size => 15 %></label></td>
|
||||
<td><label for="article_article_category"><%= select('article[]', 'article_category_id', ArticleCategory.find(:all).collect {|a| [ a.name, a.id ] }, { :include_blank => true })%></label></td>
|
||||
<td><label for="article_tax"><%= text_field 'article[]', 'tax', :size => 4 %></label></td>
|
||||
<td><label for="article_deposit"><%= text_field 'article[]', 'deposit', :size => 4 %></label></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<!--[eoform:article]-->
|
||||
</tbody>
|
||||
</table><br />
|
||||
<i>Achtung, alle Artikel werden aktualisiert!</i><br />
|
||||
<%= hidden_field 'supplier', 'id' %>
|
||||
<%= submit_tag 'Alle aktualisieren'%>
|
||||
| <%= link_to 'Abbrechen', :action => 'list', :id => @supplier.id %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
29
app/views/articles/index.haml
Normal file
29
app/views/articles/index.haml
Normal file
|
@ -0,0 +1,29 @@
|
|||
%h1 Manage die Artikeldatenbank
|
||||
.left_column{:style => "width:44%"}
|
||||
.box_title
|
||||
%h2 Artikel anzeigen
|
||||
.column_content
|
||||
%p
|
||||
%i
|
||||
Hier kannst du Artikel anzeigen, ändern und neue erstellen
|
||||
%br/
|
||||
Wähle einen Lieferanten:
|
||||
%ul
|
||||
- @suppliers.each do |supplier|
|
||||
%li
|
||||
%b= link_to supplier.name, :action => "list", :id => supplier.id
|
||||
(
|
||||
= supplier.articles.count
|
||||
Artikel )
|
||||
%hr/
|
||||
%p
|
||||
%i
|
||||
hier kannst du neue Artikel aus einer csv-Datei in die Datenbank
|
||||
= link_to 'hochladen', :action => 'upload_articles'
|
||||
|
||||
.right_column{:style => "width:53%"}
|
||||
.box_title
|
||||
%h2 Artikelkategorien
|
||||
.column_content#categories
|
||||
#category_form.box.edit_form{:style => "display:none;margin-bottom:1em;"}
|
||||
#category_list= render :partial => 'article_categories/list'
|
70
app/views/articles/list.haml
Normal file
70
app/views/articles/list.haml
Normal file
|
@ -0,0 +1,70 @@
|
|||
%h1
|
||||
=_ 'Articles from'
|
||||
= @supplier.name
|
||||
|
||||
// import menu
|
||||
- unless @supplier.shared_supplier.nil?
|
||||
.menu{:style => 'width: 14em'}
|
||||
%ul
|
||||
%li
|
||||
=_ 'External database'
|
||||
%ul
|
||||
%li= link_to_function _('search/import'), "Element.toggle('import')"
|
||||
%li= link_to _('sync'), :action => 'sync_articles', :id => @supplier
|
||||
|
||||
|
||||
#change_supplier{:style => "padding:0 0 0.5em 0.7em;"}
|
||||
%span{:style => "float:left"}
|
||||
=_ 'Change supplier:'
|
||||
- form_tag do
|
||||
%select{:onchange => "redirectTo(this)", :style => "font-size: 0.9em;margin-left:1em;"}
|
||||
= options_for_select(@suppliers.collect {|s| [ s.name, url_for(:action => "list", :id => s)] }, url_for(:action => "list", :id => @supplier.id))
|
||||
|
||||
- unless @supplier.shared_supplier.nil?
|
||||
#import.single_column{:style => "display:none; clear:both"}
|
||||
.box_title
|
||||
%h2=_ 'Import articles'
|
||||
.column_content
|
||||
#search{:style => "padding-bottom:3em"}
|
||||
- form_remote_tag :url => {:action => "list_shared_articles", :id => @supplier}, :before => "Element.show('loader')", :success => "Element.hide('loader')" do
|
||||
= text_field_tag :import_query, params['import_query'], :size => 10
|
||||
= submit_tag _('Search articles')
|
||||
- if @supplier.shared_supplier.lists
|
||||
=_ "Search in following lists: "
|
||||
- @supplier.shared_supplier.lists.each do |token, name|
|
||||
= check_box_tag "lists[#{token}]", "1", true
|
||||
= name
|
||||
|
|
||||
=_ "only regional:"
|
||||
= check_box_tag "regional", "1", false
|
||||
#search_results
|
||||
// "import_search_results" will be rendered
|
||||
= link_to_function _('Close'), "Element.hide('import')"
|
||||
|
||||
.single_column{:style => 'width:100%; clear:both'}
|
||||
.box_title
|
||||
.column_content
|
||||
#links
|
||||
%b= link_to_remote( _('New article'), :url => {:action => 'newArticle', :supplier => @supplier}, :before => "Element.show('loader')", :success => "Element.hide('loader')")
|
||||
|
|
||||
= link_to _('Edit all'), :action => 'edit_all', :id => @supplier
|
||||
|
|
||||
= link_to _('Upload articles'), :action => 'upload_articles'
|
||||
|
|
||||
= link_to_if @current_user.role_orders?, _('Create order'), {:controller => 'orders', :action => 'new', :id => @supplier }
|
||||
|
||||
#article_filter
|
||||
#article_search_form{:style=>"display:inline;"}
|
||||
- form_remote_tag :url => {:action => "list", :id => @supplier}, :before => "Element.show('loader')", :success => "Element.hide('loader')" do
|
||||
%label{:for => 'article_name'}=_ "Search in article name: "
|
||||
= text_field_tag("query", params['query'], :size => 10 )
|
||||
= submit_tag _('Search')
|
||||
|
||||
%form{ :method => 'post', :action => url_for(:action => 'update_selected_articles', :only_path => true), :id => 'articlesInListForm'}
|
||||
#table
|
||||
= render :partial => 'list'
|
||||
%br/
|
||||
= link_to _('Back'), :action => 'index'
|
||||
#edit_article{:style => "display:none"}
|
||||
|
||||
|
40
app/views/articles/parse_articles.haml
Normal file
40
app/views/articles/parse_articles.haml
Normal file
|
@ -0,0 +1,40 @@
|
|||
%h1=_ "Upload Articles"
|
||||
%p
|
||||
%i
|
||||
=_ "Please check the parsed articles and choose a supplier at the end of page."
|
||||
%br/
|
||||
=_ "At the moment there is now checking of dublicate articles."
|
||||
|
||||
- form_tag(:action => 'create_articles_from_file') do
|
||||
%table.list
|
||||
%tr
|
||||
%th=_ "Number"
|
||||
%th=_ "Name"
|
||||
%th=_ "Note"
|
||||
%th=_ "Manufacturer"
|
||||
%th=_ "Origin"
|
||||
%th=_ "Unit"
|
||||
%th=_ "Net price"
|
||||
%th=_ "Tax"
|
||||
%th=_ "Deposit"
|
||||
%th=_ "Unit quantity"
|
||||
%th=_ "Category"
|
||||
- for @article in @articles
|
||||
%tr{:class => cycle('even', 'odd')}
|
||||
%td= text_field 'article[]', 'order_number', :size => 6
|
||||
%td= text_field 'article[]', 'name', :size => 0
|
||||
%td= text_field 'article[]', 'note', :size => 15
|
||||
%td= text_field 'article[]', 'manufacturer', :size => 6
|
||||
%td= text_field 'article[]', 'origin', :size => 6
|
||||
%td= text_field 'article[]', 'unit', :size => 5
|
||||
%td= text_field 'article[]', 'net_price', :size => 4
|
||||
%td= text_field 'article[]', 'tax', :size => 4
|
||||
%td= text_field 'article[]', 'deposit', :size => 4
|
||||
%td= text_field 'article[]', 'unit_quantity', :size => 4
|
||||
%td= select('article[]', 'article_category_id', ArticleCategory.find(:all).collect {|a| [ a.name, a.id ] })
|
||||
%p
|
||||
=_ "Choose a supplier:"
|
||||
= select('supplier', 'id', Supplier.find(:all).collect {|s| [ s.name, s.id ] }, :selected => nil)
|
||||
= submit_tag _("Save articles")
|
||||
%p= link_to _("Back"), :action => 'upload_articles'
|
||||
|
70
app/views/articles/sync_articles.haml
Normal file
70
app/views/articles/sync_articles.haml
Normal file
|
@ -0,0 +1,70 @@
|
|||
%h1 Artikel mit externer Datenbank synchronisieren
|
||||
|
||||
- form_tag :action => 'update_all', :id => @supplier, :sync => "1" do
|
||||
%h2 Auslisten ...
|
||||
%p
|
||||
- unless @outlisted_articles.empty?
|
||||
Folgende Artikel wurden ausgelistet und werden
|
||||
%b gelöscht:
|
||||
%ul
|
||||
- for article in @outlisted_articles
|
||||
%li
|
||||
= hidden_field_tag "outlisted_articles[#{article.id}]", '1'
|
||||
= article.name
|
||||
- else
|
||||
%i Es müssen keine Artikel gelöscht werden.
|
||||
%hr/
|
||||
%h2 Aktualisieren ...
|
||||
%p
|
||||
%i
|
||||
%b= @updated_articles.size
|
||||
Artikel müssen aktualisiert werden:
|
||||
%p
|
||||
%i
|
||||
Jeder Artikel wird doppelt angezeigt. Die alten Werte sind grau und die Textfelder sind mit den aktuellen
|
||||
Werten vorausgefüllt.
|
||||
%br/
|
||||
Abweichungen zu den alten Artikeln sind gelb markiert.
|
||||
%table
|
||||
%tr
|
||||
%th Name
|
||||
%th Notiz
|
||||
%th Hersteller
|
||||
%th Herkunft
|
||||
%th Einheit
|
||||
%th GebGr
|
||||
%th Preis
|
||||
%th MwSt.
|
||||
%th Pfand
|
||||
%th Kategorie
|
||||
- @updated_articles.each do |@article, unequal_attributes|
|
||||
- article = Article.find(@article.id)
|
||||
%tr{:style => 'color:grey'}
|
||||
%td= article.name
|
||||
%td= article.note
|
||||
%td= article.manufacturer
|
||||
%td= article.origin
|
||||
%td= article.unit
|
||||
%td= article.unit_quantity
|
||||
%td= article.net_price
|
||||
%td= article.tax
|
||||
%td= article.deposit
|
||||
%td= article.article_category.name if article.article_category
|
||||
%tr
|
||||
%td{:style => highlight_new(unequal_attributes, :name)}
|
||||
= text_field 'article[]', 'name', :size => 0
|
||||
= hidden_field 'article[]', 'shared_updated_on'
|
||||
%td{:style => highlight_new(unequal_attributes, :note)}= text_field 'article[]', 'note', :size => 15
|
||||
%td{:style => highlight_new(unequal_attributes, :manufacturer)}= text_field 'article[]', 'manufacturer', :size => 10
|
||||
%td{:style => highlight_new(unequal_attributes, :origin)}= text_field 'article[]', 'origin', :size => 5
|
||||
%td{:style => highlight_new(unequal_attributes, :unit)}= text_field 'article[]', 'unit', :size => 5
|
||||
%td{:style => highlight_new(unequal_attributes, :unit_quantity)}= text_field 'article[]', 'unit_quantity', :size => 5
|
||||
%td{:style => highlight_new(unequal_attributes, :net_price)}= text_field 'article[]', 'net_price', :size => 5
|
||||
%td{:style => highlight_new(unequal_attributes, :tax)}= text_field 'article[]', 'tax', :size => 4
|
||||
%td{:style => highlight_new(unequal_attributes, :deposit)}= text_field 'article[]', 'deposit', :size => 4
|
||||
%td= select 'article[]', 'article_category_id', ArticleCategory.find(:all).collect {|a| [ a.name, a.id ] }, { :include_blank => true }
|
||||
%hr/
|
||||
= hidden_field 'supplier', 'id'
|
||||
= submit_tag 'Alle löschen/aktualisieren'
|
||||
|
|
||||
= link_to 'Abbrechen', :action => 'list', :id => @supplier
|
18
app/views/articles/upload_articles.haml
Normal file
18
app/views/articles/upload_articles.haml
Normal file
|
@ -0,0 +1,18 @@
|
|||
%h1=_ "Upload articles"
|
||||
%p
|
||||
%i
|
||||
=_ 'The file-type must be "csv" (textfile). Use a semicolon (";") to seperate the fields and double quotes ("Bananas...") for the text.'
|
||||
%br/
|
||||
=_ 'The character-set has to be "UTF-8". The first row, maybe used for headers, will be ignored.'
|
||||
%p
|
||||
%i
|
||||
=_ "Correct order of the columns:"
|
||||
%br/
|
||||
= [_("Status (x=outlistet)"), _("Number"), _("Name"), _("Note"), _("Manufacturer"), _("Origin"), |
|
||||
_("Unit"), _("Net price"), _("Tax"), _("Deposit"), _("Unit quantity"), |
|
||||
_("Scale quantity"), _("Scale price"), _("Category")].join(" | ") |
|
||||
|
||||
#uploadArticles.uploadForm
|
||||
- form_for(:articles, :url => {:action => 'parse_articles'}, :html => { :multipart => true }) do |form|
|
||||
%p= form.file_field("file")
|
||||
%p= submit_tag _("Upload articles")
|
21
app/views/finance/_articleResult.haml
Normal file
21
app/views/finance/_articleResult.haml
Normal file
|
@ -0,0 +1,21 @@
|
|||
%td= @article.name
|
||||
%td= link_to_function image_tag("arrow_down_red.png", :size => "16x16", :border => 0), "Element.toggle('group_order_article_results_#{@article.id}')"
|
||||
%td= @article.order_number
|
||||
%td= @article.units_to_order
|
||||
%td= @article.unit_quantity.to_s + ' * ' + @article.unit.to_s
|
||||
%td= number_to_currency(@article.net_price)
|
||||
%td= number_to_currency(@article.gross_price)
|
||||
%td= @article.tax
|
||||
%td= @article.deposit
|
||||
%td
|
||||
= link_to_remote image_tag('b_edit.png', :size => "16x16", :border => 0, :alt => 'Artikel ändern'), |
|
||||
:url => {:action => 'editArticleResult', :id => @article}, |
|
||||
:before => "Element.show('loader')", |
|
||||
:success => "Element.hide('loader')" |
|
||||
%td
|
||||
= link_to_remote image_tag('b_drop.png', :size => "16x16", :border => 0, :alt => 'Artikel löschen'), |
|
||||
:url => {:action => 'destroyArticleResult', :id => @article}, |
|
||||
:confirm => 'Bist du sicher?', |
|
||||
:method => 'post', |
|
||||
:before => "Element.show('loader')", |
|
||||
:success => "Element.hide('loader')" |
|
29
app/views/finance/_articleResultForm.haml
Normal file
29
app/views/finance/_articleResultForm.haml
Normal file
|
@ -0,0 +1,29 @@
|
|||
= error_messages_for 'article'
|
||||
%p
|
||||
%b Notiz:
|
||||
= @form.text_field 'note', :size => 30
|
||||
%table
|
||||
%tr
|
||||
%th Name
|
||||
%th Nr.
|
||||
%th
|
||||
%abbr{:title=>"Anzahl gelieferter Gebinde"} Menge
|
||||
%th GebGr
|
||||
%th Einheit
|
||||
%th netto
|
||||
%th MwSt.
|
||||
%th Pfand
|
||||
%tr
|
||||
%td= @form.text_field 'name', :size => 20
|
||||
%td= @form.text_field 'order_number', :size => 3
|
||||
%td= @form.text_field 'units_to_order', :size => 5
|
||||
%td= @form.text_field 'unit_quantity', :size => 3
|
||||
%td= @form.text_field 'unit', :size => 5
|
||||
%td= @form.text_field 'net_price', :size => 3
|
||||
%td= @form.text_field 'tax', :size => 3
|
||||
%td= @form.text_field 'deposit', :size => 3
|
||||
= @form.hidden_field "order_id"
|
||||
%br/
|
||||
= submit_tag "Speichern"
|
||||
|
|
||||
= link_to_function 'Abbrechen', "Element.hide('edit_box')"
|
5
app/views/finance/_articleResults.haml
Normal file
5
app/views/finance/_articleResults.haml
Normal file
|
@ -0,0 +1,5 @@
|
|||
%tr{:class => cycle('even', 'odd', :name => 'articles')}[@article]
|
||||
= render :partial => 'articleResult'
|
||||
|
||||
%tr{:id => "group_order_article_results_#{@article.id}", :class => "results", :style => "display:none"}
|
||||
= render :partial => 'groupOrderArticleResults'
|
17
app/views/finance/_articlesOverview.haml
Normal file
17
app/views/finance/_articlesOverview.haml
Normal file
|
@ -0,0 +1,17 @@
|
|||
%p
|
||||
%b
|
||||
Gelieferte Artikel:
|
||||
= @order.order_article_results.size
|
||||
- for article in @order.order_article_results
|
||||
%table{:style=> "margin-bottom:1em; width:40em;"}[article]
|
||||
%thead
|
||||
%tr
|
||||
%th{:colspan => "3"}= article.name + " (" + article.unit + " | " + article.unit_quantity.to_s + " | " + article.gross_price.to_s + ")"
|
||||
%tbody
|
||||
- for result in article.group_order_article_results
|
||||
%tr{ :class => cycle('even', 'odd', :name => 'group')}
|
||||
%td{:style=>"width:70%"}= result.group_order_result.group_name
|
||||
%td= result.quantity
|
||||
%td= article.gross_price * result.quantity
|
||||
- reset_cycle("group")
|
||||
|
8
app/views/finance/_editArticleResult.haml
Normal file
8
app/views/finance/_editArticleResult.haml
Normal file
|
@ -0,0 +1,8 @@
|
|||
%h2
|
||||
Bearbeiten von
|
||||
= @article.name
|
||||
|
||||
- remote_form_for 'order_article_result', @article, :url => {:action => 'updateArticleResult', :id => @article }, |
|
||||
:before => "Element.show('loader')", :success => "Element.hide('loader')" do |@form| |
|
||||
|
||||
= render :partial => "articleResultForm"
|
20
app/views/finance/_editGroupResult.haml
Normal file
20
app/views/finance/_editGroupResult.haml
Normal file
|
@ -0,0 +1,20 @@
|
|||
%h2 Mengenänderung
|
||||
%p
|
||||
= @result.group_order_result.group_name
|
||||
hat von
|
||||
= @result.order_article_result.name
|
||||
bekommen:
|
||||
|
||||
- remote_form_for 'group_order_article_result', @result, :url => {:action => 'updateGroupResult', :id => @result }, |
|
||||
:before => "Element.show('loader')", :success => "Element.hide('loader')" do |form| |
|
||||
|
||||
= error_messages_for 'group_order_article_result'
|
||||
%p
|
||||
%b Menge:
|
||||
(Einheit:
|
||||
= @result.order_article_result.unit
|
||||
)
|
||||
= form.text_field "quantity", :size => "6"
|
||||
= submit_tag "Speichern"
|
||||
|
|
||||
= link_to_function 'Abbrechen', "Element.hide('edit_box')"
|
10
app/views/finance/_editNote.haml
Normal file
10
app/views/finance/_editNote.haml
Normal file
|
@ -0,0 +1,10 @@
|
|||
%h2 Notiz bearbeiten
|
||||
- remote_form_for 'order', @order, :url => {:action => 'updateOrderNote', :id => @order}, |
|
||||
:before => "Element.show('loader')", :success => "Element.hide('loader')" do |form| |
|
||||
|
||||
%br/
|
||||
= form.text_area "note", :size => "60x20"
|
||||
%p
|
||||
= submit_tag "Speichern"
|
||||
|
|
||||
= link_to_remote 'Abbrechen', :update => 'results', :url => { :action => 'editOrder', :id => @order, :view => 'groupsOverview' }, :before => "Element.show('loader')", :success => "Element.hide('loader')"
|
20
app/views/finance/_editResults.haml
Normal file
20
app/views/finance/_editResults.haml
Normal file
|
@ -0,0 +1,20 @@
|
|||
%p{:style => "float:left"}
|
||||
%b Lieferung bearbeiten
|
||||
%p{:style => "float:right"}
|
||||
= link_to_remote "Artikel hinzufügen", :url => {:action => "newArticleResult", :id => @order}
|
||||
|
||||
%table{:class => "ordered_articles", :style => "clear:both"}
|
||||
%thead
|
||||
%tr
|
||||
%th{:colspan => "2"} Artikel
|
||||
%th Nr.
|
||||
%th Menge
|
||||
%th GebGr * Einheit
|
||||
%th netto
|
||||
%th brutto
|
||||
%th MwSt
|
||||
%th Pfand
|
||||
%th{:colspan => "2"}
|
||||
%tbody#result_table
|
||||
- for @article in @order.order_article_results
|
||||
= render :partial => "articleResults"
|
27
app/views/finance/_editSummary.haml
Normal file
27
app/views/finance/_editSummary.haml
Normal file
|
@ -0,0 +1,27 @@
|
|||
%h2 Bestelldaten ändern
|
||||
- remote_form_for 'order', @order, :url => {:action => 'updateOrderSummary', :id => @order}, |
|
||||
:before => "Element.show('loader')", :success => "Element.hide('loader')" do |form| |
|
||||
|
||||
= error_messages_for 'order'
|
||||
%table{:style => "width:10em"}
|
||||
%tr
|
||||
%td Rechnungsnummer:
|
||||
%td= form.text_field "invoice_number", :size => 10
|
||||
%tr
|
||||
%td Rechnungsdatum:
|
||||
%td= form.text_field "invoice_date", :size => 10
|
||||
%tr
|
||||
%td
|
||||
%abbr{:title => "(incl. Pfand/Gutschriften)"} Rechnungsbeitrag
|
||||
%td= form.text_field 'invoice_amount', :size => 10
|
||||
%tr
|
||||
%td
|
||||
%abbr{:title => "z.B Kistepfand"} extra Pfand
|
||||
%td= form.text_field 'deposit', :size => 10
|
||||
%tr
|
||||
%td Pfandgutschrift
|
||||
%td= form.text_field 'deposit_credit', :size => 10
|
||||
%p
|
||||
= submit_tag "Speichern"
|
||||
|
|
||||
= link_to_function 'Abbrechen', "Element.hide('edit_box')"
|
25
app/views/finance/_groupOrderArticleResults.haml
Normal file
25
app/views/finance/_groupOrderArticleResults.haml
Normal file
|
@ -0,0 +1,25 @@
|
|||
%td{:colspan => "7"}
|
||||
%p
|
||||
Notiz:
|
||||
= @article.note
|
||||
%table
|
||||
%thead
|
||||
%tr
|
||||
%td
|
||||
%td{:style => "width:8em"} Gruppe
|
||||
%td Einheiten
|
||||
%td Gesamtpreis
|
||||
%td{:colspan => "3",:style => "width:14em"}
|
||||
= link_to_remote '[Gruppe hinzufügen]', :url => {:action => "newGroupResult", :id => @article}, :before => "Element.show('loader')", :success => "Element.hide('loader')"
|
||||
%tbody{:id => "groups_results_#{@article.id}"}
|
||||
- for @result in @article.group_order_article_results
|
||||
= render :partial => "groupResults"
|
||||
%tfoot
|
||||
%tr{:class => cycle('even', 'odd', :name => 'results'), :id => "sum_of_article_#{@article.id}"}
|
||||
%td
|
||||
%td{:style => "width:8em"} Summe
|
||||
%td{:id => "totalArticleQuantity_#{@article.id}"}= @article.total[:quantity]
|
||||
%td{:id => "totalArticlePrice_#{@article.id}", :class => "currency"}
|
||||
= number_to_currency(@article.total[:price])
|
||||
%td{:colspan => "3"}
|
||||
- reset_cycle('results')
|
17
app/views/finance/_groupResult.haml
Normal file
17
app/views/finance/_groupResult.haml
Normal file
|
@ -0,0 +1,17 @@
|
|||
%td
|
||||
%td{:style=>"width:50%"}= @result.group_order_result.group_name
|
||||
%td{:id => "group_order_article_result_#{@result.id}_quantity"}= @result.quantity
|
||||
%td{:class => "currency"}= number_to_currency(@result.order_article_result.gross_price * @result.quantity)
|
||||
%td{:style=>"width:1em", :class => "actions"}
|
||||
= link_to_remote image_tag('b_edit.png', :size => "16x16", :border => 0, :alt => 'Menge ändern'), |
|
||||
:url => {:action => 'updateGroupResult', :id => @result}, |
|
||||
:before => "Element.show('loader')", |
|
||||
:success => "Element.hide('loader')" |
|
||||
%td{:style=>"width:1em", :class => "actions"}
|
||||
= link_to_remote image_tag('b_drop.png', :size => "16x16", :border => 0, :alt => 'Gruppenergebnis löschen'), |
|
||||
:url => {:action => 'destroyGroupResult', :id => @result}, |
|
||||
:confirm => 'Bist du sicher?', |
|
||||
:method => 'post', |
|
||||
:before => "Element.show('loader')", |
|
||||
:success => "Element.hide('loader')" |
|
||||
%td
|
2
app/views/finance/_groupResults.haml
Normal file
2
app/views/finance/_groupResults.haml
Normal file
|
@ -0,0 +1,2 @@
|
|||
%tr{:class => cycle('even', 'odd', :name => 'results')}[@result]
|
||||
= render :partial => "groupResult"
|
30
app/views/finance/_groupsOverview.haml
Normal file
30
app/views/finance/_groupsOverview.haml
Normal file
|
@ -0,0 +1,30 @@
|
|||
%p
|
||||
%b
|
||||
Gruppenbestellungen:
|
||||
= @order.group_order_results.size
|
||||
- for groupOrderResult in @order.group_order_results
|
||||
%p
|
||||
%table{:style => "width:40em"}[groupOrderResult]
|
||||
%thead
|
||||
%tr
|
||||
%th{:colspan => "6"}= groupOrderResult.group_name
|
||||
%tbody
|
||||
- total = 0
|
||||
- for result in groupOrderResult.group_order_article_results
|
||||
- price = result.order_article_result.gross_price
|
||||
- quantity = result.quantity
|
||||
- subTotal = price * quantity
|
||||
- total += subTotal
|
||||
%tr{:class => cycle('even', 'odd', :name => 'article')}
|
||||
%td= result.order_article_result.name
|
||||
%td= quantity
|
||||
%td{:class => "currency"}= number_to_currency(price)
|
||||
%td= result.order_article_result.unit_quantity
|
||||
%td= result.order_article_result.unit
|
||||
%td= number_to_currency(subTotal)
|
||||
%tfoot
|
||||
%tr{:class => cycle('even', 'odd', :name => 'article')}
|
||||
%td{:colspan => "5"} Summe
|
||||
%td
|
||||
%b= number_to_currency(total)
|
||||
- reset_cycle("article")
|
28
app/views/finance/_listOrdergroups.haml
Normal file
28
app/views/finance/_listOrdergroups.haml
Normal file
|
@ -0,0 +1,28 @@
|
|||
%p
|
||||
= _("Found") + ":"
|
||||
= @total
|
||||
%p
|
||||
%table{:style => "width:100%"}
|
||||
%tr
|
||||
%td
|
||||
= pagination_links_remote @groups, @per_page, {:sort => params[:sort]}
|
||||
%td{:style => "text-align:right"}
|
||||
- if @total > 20
|
||||
= items_per_page
|
||||
%table.list
|
||||
%thead
|
||||
%tr
|
||||
%th= sort_link_helper _("Name"), "name", @per_page
|
||||
%th= sort_link_helper _("Size of group"), "size", @per_page
|
||||
%th= sort_link_helper _("Account balance"), "account_balance", @per_page
|
||||
%th
|
||||
%tbody
|
||||
- for group in @groups
|
||||
%tr{:class => cycle('even','odd', :name => 'groups')}
|
||||
%td= @current_user.role_admin? ? link_to(group.name, {:controller => 'admin', :action => 'showGroup', :id => group}, {:title => _("Show ordergroup")}) : group.name
|
||||
%td= group.actual_size
|
||||
%td{:class => "currency", :style => "width:5em"}= number_to_currency(group.account_balance)
|
||||
%td{:class => "actions"}
|
||||
= link_to image_tag("euro_new.png", :size => "16x16", :alt => _("New transaction"), :border => "0"), {:action => 'newTransaction', :id => group}, {:title => _("New transaction")}
|
||||
= link_to image_tag("b_browse.png", :size => "16x16", :border => "0", :alt => 'Kontoauszug'), {:action => 'listTransactions', :id => group}, {:title => _("List transactions")}
|
||||
|
7
app/views/finance/_newArticleResult.haml
Normal file
7
app/views/finance/_newArticleResult.haml
Normal file
|
@ -0,0 +1,7 @@
|
|||
%h2
|
||||
Neuer gelieferter Artikel die Bestellung
|
||||
|
||||
- remote_form_for 'order_article_result', @article, :url => {:action => 'createArticleResult' }, |
|
||||
:before => "Element.show('loader')", :success => "Element.hide('loader')" do |@form| |
|
||||
|
||||
= render :partial => "articleResultForm"
|
17
app/views/finance/_newGroupResult.haml
Normal file
17
app/views/finance/_newGroupResult.haml
Normal file
|
@ -0,0 +1,17 @@
|
|||
%h2
|
||||
Neue Gruppenmenge für
|
||||
= @result.order_article_result.name
|
||||
- remote_form_for 'group_order_article_result', @result, :url => {:action => 'createGroupResult'}, |
|
||||
:before => "Element.show('loader')", :success => "Element.hide('loader')" do |@form| |
|
||||
= error_messages_for 'result'
|
||||
%p
|
||||
Gruppe:
|
||||
= @form.select "group_order_result_id", OrderGroup.find(:all, :order => "name").collect {|og| [og.name, og.id] }
|
||||
%p
|
||||
Menge:
|
||||
= @form.text_field "quantity", :size => 5
|
||||
= @form.hidden_field "order_article_result_id"
|
||||
%p
|
||||
= submit_tag "Speichern"
|
||||
|
|
||||
= link_to_function 'Abbrechen', "Element.hide('edit_box')"
|
6
app/views/finance/_order_group.haml
Normal file
6
app/views/finance/_order_group.haml
Normal file
|
@ -0,0 +1,6 @@
|
|||
%tr.transaction
|
||||
%td
|
||||
%select{:name => 'financial_transactions[][order_group_id]'}
|
||||
= options_for_select OrderGroup.find(:all, :order => 'name').collect { |g| [ g.name, g.id ] }
|
||||
%td= text_field_tag 'financial_transactions[][amount]'
|
||||
%td= link_to_function image_tag("b_drop.png", :size => "16x16", :alt => _("Remove"), :border => "0"), "$(this).up('.transaction').remove()", {:title => _("Remove ordergroup")}
|
60
app/views/finance/_summary.haml
Normal file
60
app/views/finance/_summary.haml
Normal file
|
@ -0,0 +1,60 @@
|
|||
%p
|
||||
%b=h @order.supplier ? @order.supplier.name : _('nonexistent')
|
||||
| Rechnungsnummer:
|
||||
= @order.invoice_number
|
||||
| Rechnungsdatum:
|
||||
= @order.invoice_date
|
||||
|
|
||||
= link_to_remote image_tag('b_edit.png', :size => "16x16", :border => 0, :alt => 'Rechnungsbetrag ändern'), |
|
||||
:url => {:action => "editOrderSummary", :id => @order}, |
|
||||
:before => "Element.show('loader')", |
|
||||
:success => "Element.hide('loader')" |
|
||||
%table
|
||||
%tr
|
||||
%td{:colspan => "2"}
|
||||
%b Foodcoop
|
||||
%td{:colspan => "2"}
|
||||
%b Lieferant
|
||||
%tr
|
||||
%td
|
||||
%abbr{:title => "gelieferten Artikel x Nettopreis"} Nettobetrag:
|
||||
%td= number_to_currency(@order.sumPrice("clear"))
|
||||
%td
|
||||
Rechnungsbetrag
|
||||
%small (incl. Pfand/Gutschriften)
|
||||
%td#invoice_amount= number_to_currency(@order.invoice_amount)
|
||||
%tr
|
||||
%td
|
||||
%abbr{:title => "Nettobetrag mit Pfand und MwSt."} Bruttobetrag:
|
||||
%td= number_to_currency(@order.sumPrice("gross"))
|
||||
%td
|
||||
%span - extra Pfand
|
||||
%small (Kistenpfand etc.)
|
||||
%td#deposit= number_to_currency(@order.deposit)
|
||||
%tr
|
||||
%td
|
||||
%abbr{:title => "Bruttobetrag mit Foodcoop Aufschlag"} FC Summe:
|
||||
%td= number_to_currency(@order.sumPrice("fc"))
|
||||
%td{:style => "border-bottom: 1px solid grey"}
|
||||
+ Pfandgutschriften
|
||||
%td{:style => "border-bottom: 1px solid grey"}
|
||||
#deposit_credit= number_to_currency(@order.deposit_credit)
|
||||
%tr
|
||||
%td
|
||||
%abbr{:title => "Zugeteilte Mengen x Bruttopreise (inkl. Aufschlag)"} Gruppenbeträge:
|
||||
%td#groups_amount= number_to_currency(@order.sumPrice("groups"))
|
||||
%td
|
||||
Summe
|
||||
%small (Rechungsbetrag ohne Pfand)
|
||||
%td#clear_invoice= number_to_currency(@order.invoice_amount - @order.deposit + @order.deposit_credit)
|
||||
%tr
|
||||
%td{:colspan => "4"}
|
||||
%abbr{:title => "Gruppenbeträge ohne Aufschlag minus Rechnung ohne Pfand. |
|
||||
Im Idealfall sollte hier 0.00 stehen."} Differenz ohne Aufschlag: |
|
||||
%span#fcProfit= number_to_currency(@order.fcProfit(false))
|
||||
%tr
|
||||
%td{:colspan => "4"}
|
||||
%b
|
||||
%abbr{:title => "= Gruppenbeträge - Rechnung ohne Pfand"} Differenz mit Aufschlag
|
||||
= "(#{number_to_percentage(FoodSoft.getPriceMarkup)}):"
|
||||
%span#fcProfit= number_to_currency(@order.fcProfit)
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue