Commit 0cb752ee by Nguyen Vo Huy Hoang

Add a plugin: Redmine Work Time

parent 1fe8a08d
Pipeline #1527 failed with stages
in 0 seconds
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 Lesser 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
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
WorkTime is a plugin of Redmine to view and update Spent time by each user.
### Installation notes ###
0. Setup Redmine
1. Download redmine_work_time-*.zip from https://bitbucket.org/tkusukawa/redmine_work_time/downloads
2. Expand the plugin into the plugins directory
3. Migrate plugin: rake redmine:plugins:migrate RAILS_ENV=production
4. Restart Redmine
5. Enable the module on the project setting page.
6. Check the permissions on the Roles and permissions(Administration)
### Links ###
* http://www.redmine.org/plugins/redmine_work_time
* https://bitbucket.org/tkusukawa/redmine_work_time
* http://www.r-labs.org/projects/worktime/
\ No newline at end of file
= work_time
Description goes here
class WorkTimeController < ApplicationController
unloadable
# before_filter :find_project, :authorize
accept_api_auth :relay_total
helper :custom_fields
include CustomFieldsHelper
NO_ORDER = -1
def index
@message = ""
require_login || return
@project = nil
prepare_values
ticket_pos
prj_pos
ticket_del
hour_update
make_pack
update_daily_memo(params[:memo]) if params.key?(:memo)
set_holiday
@custom_fields = TimeEntryCustomField.all
@link_params.merge!(:action=>"index")
if !params.key?(:user) then
redirect_to @link_params
else
render "show"
end
end
def show
@message = ""
require_login || return
find_project
authorize
prepare_values
if @this_user.nil? || !@this_user.allowed_to?(:view_work_time_tab, @project)
@link_params.merge!(:action=>"relay_total")
redirect_to @link_params
return
end
ticket_pos
prj_pos
ticket_del
hour_update
make_pack
member_add_del_check
update_daily_memo(params[:memo]) if params.key?(:memo)
set_holiday
@custom_fields = TimeEntryCustomField.all
@link_params.merge!(:action=>"show")
if !params.key?(:user) then
redirect_to @link_params
end
end
def member_monthly_data
require_login || return
if params.key?(:id) then
find_project
end
prepare_values
make_pack
csv_data = %Q|"user","date","project","ticket","spent time"\n|
(@first_date..@last_date).each do |date|
@month_pack[:odr_prjs].each do |prj_pack|
next if prj_pack[:count_issues] == 0
prj_pack[:odr_issues].each do |issue_pack|
next if issue_pack[:count_hours] == 0
issue = issue_pack[:issue]
if issue_pack[:total_by_day][date] then
csv_data << %Q|"#{@this_user}","#{date}","#{issue.project}","##{issue.id} #{issue.subject}",#{issue_pack[:total_by_day][date]}\n|
end
end
end
if @month_pack[:other_by_day].has_key?(date) then
csv_data << %Q|"#{@this_user}","#{date}","PRIVATE","PRIVATE",#{@month_pack[:other_by_day][date]}\n|
end
end
send_data Redmine::CodesetUtil.from_utf8(csv_data, l(:general_csv_encoding)), :type=>"text/csv", :filename=>"member_monthly.csv"
end
def total
@message = ""
find_project
authorize
prepare_values
change_member_position
change_ticket_position
change_project_position
member_add_del_check
calc_total
@link_params.merge!(:action=>"total")
end
def total_data
find_project
authorize
prepare_values
change_member_position
change_ticket_position
change_project_position
member_add_del_check
calc_total
csv_data = %Q|"user","relayed project","relayed ticket","project","ticket","spent time"\n|
#-------------------------------------- メンバーのループ
@members.each do |mem_info|
user = mem_info[1]
#-------------------------------------- プロジェクトのループ
prjs = WtProjectOrders.where("uid=-1").
order("dsp_pos").
all
prjs.each do |po|
dsp_prj = po.dsp_prj
dsp_pos = po.dsp_pos
next unless @prj_cost.key?(dsp_prj) # 値の無いプロジェクトはパス
next unless @prj_cost[dsp_prj].key?(-1) # 値の無いプロジェクトはパス
next if @prj_cost[dsp_prj][-1] == 0 # 値の無いプロジェクトはスパ
prj =Project.find_by_id(dsp_prj)
#-------------------------------------- チケットのループ
tickets = WtTicketRelay.order("position").all
tickets.each do |tic|
issue_id = tic.issue_id
next unless @issue_cost.key?(issue_id) # 値の無いチケットはパス
next unless @issue_cost[issue_id].key?(-1) # 値の無いチケットはパス
next if @issue_cost[issue_id][-1] == 0 # 値の無いチケットはパス
next unless @issue_cost[issue_id].key?(user.id) # 値の無いチケットはパス
next if @issue_cost[issue_id][user.id] == 0 # 値の無いチケットはパス
issue = Issue.find_by_id(issue_id)
next if issue.nil? # チケットが削除されていたらパス
next if issue.project_id != dsp_prj # このプロジェクトに表示するチケットでない場合はパス
parent_issue = Issue.find_by_id(@issue_parent[issue_id])
next if parent_issue.nil? # チケットが削除されていたらパス
csv_data << %Q|"#{user}","#{parent_issue.project}","##{parent_issue.id} #{parent_issue.subject}",|
csv_data << %Q|"#{prj}","##{issue.id} #{issue.subject}",#{@issue_cost[issue_id][user.id]}\n|
end
end
if @issue_cost.has_key?(-1) && @issue_cost[-1].has_key?(user.id) then
csv_data << %Q|"#{user}","private","private","private","private",#{@issue_cost[-1][user.id]}\n|
end
end
send_data Redmine::CodesetUtil.from_utf8(csv_data, l(:general_csv_encoding)), :type=>"text/csv", :filename=>"monthly_report_raw.csv"
end
def total_data_with_act
find_project
authorize
prepare_values
change_member_position
change_ticket_position
change_project_position
member_add_del_check
calc_total
csv_data = %Q|"user","relayed project","relayed ticket","project","ticket","activity","spent time"\n|
@issue_act_cost.each do |issue_id, user_act_cost|
if issue_id >0
issue = Issue.find_by_id(issue_id)
next if issue.nil? # チケットが削除されていたらパス
parent_issue = Issue.find_by_id(@issue_parent[issue_id])
next if parent_issue.nil? # チケットが削除されていたらパス
prj = issue.project
user_act_cost.each do |user_id, act_cost|
user = User.find_by_id(user_id)
act_cost.each do |act_id, cost|
act = TimeEntryActivity.find_by_id(act_id)
unless act.nil?
csv_data << %Q|"#{user}","#{parent_issue.project}","##{parent_issue.id} #{parent_issue.subject}",|
csv_data << %Q|"#{prj}","##{issue.id} #{issue.subject}","#{act.name}",|
csv_data << %Q|#{cost}\n|
else # can not find activity
csv_data << %Q|"#{user}","#{parent_issue.project}","##{parent_issue.id} #{parent_issue.subject}",|
csv_data << %Q|"#{prj}","##{issue.id} #{issue.subject}","nil",|
csv_data << %Q|#{cost}\n|
end
end
end
else # 表示権限の無い工数があった場合
user_act_cost.each do |user_id, act_cost|
user = User.find_by_id(user_id)
act_cost.each do |act_id, cost|
csv_data << %Q|"#{user}","private","private","private","private","private",|
csv_data << %Q|#{cost}\n|
end
end
end
end
send_data Redmine::CodesetUtil.from_utf8(csv_data, l(:general_csv_encoding)), :type=>"text/csv", :filename=>"monthly_report_raw_with_act.csv"
end
def edit_relay
@message = ""
find_project
authorize
prepare_values
change_member_position
change_ticket_position
change_project_position
member_add_del_check
calc_total
@link_params.merge!(:action=>"edit_relay")
end
def relay_total
@message = ""
find_project || return
authorize
prepare_values
change_member_position
change_ticket_position
change_project_position
member_add_del_check
calc_total
respond_to do |format|
format.html {
@link_params.merge!(:action=>"relay_total")
}
format.api {}
end
end
def relay_total_data
find_project
authorize
prepare_values
change_member_position
change_ticket_position
change_project_position
member_add_del_check
calc_total
csv_data = %Q|"user","project","ticket","spent time"\n|
#-------------------------------------- メンバーのループ
@members.each do |mem_info|
user = mem_info[1]
#-------------------------------------- プロジェクトのループ
prjs = WtProjectOrders.where("uid=-1").
order("dsp_pos").
all
prjs.each do |po|
dsp_prj = po.dsp_prj
dsp_pos = po.dsp_pos
next unless @r_prj_cost.key?(dsp_prj) # 値の無いプロジェクトはパス
next unless @r_prj_cost[dsp_prj].key?(-1) # 値の無いプロジェクトはパス
next if @r_prj_cost[dsp_prj][-1] == 0 # 値の無いプロジェクトはスパ
prj =Project.find_by_id(dsp_prj)
#-------------------------------------- チケットのループ
tickets = WtTicketRelay.order("position").all
tickets.each do |tic|
issue_id = tic.issue_id
next unless @r_issue_cost.key?(issue_id) # 値の無いチケットはパス
next unless @r_issue_cost[issue_id].key?(-1) # 値の無いチケットはパス
next if @r_issue_cost[issue_id][-1] == 0 # 値の無いチケットはパス
next unless @r_issue_cost[issue_id].key?(user.id) # 値の無いチケットはパス
next if @r_issue_cost[issue_id][user.id] == 0 # 値の無いチケットはパス
issue = Issue.find_by_id(issue_id)
next if issue.nil? # チケットが削除されていたらパス
next if issue.project_id != dsp_prj # このプロジェクトに表示するチケットでない場合はパス
csv_data << %Q|"#{user}","#{prj}","##{issue.id} #{issue.subject}",#{@r_issue_cost[issue_id][user.id]}\n|
end
end
if @r_issue_cost.has_key?(-1) && @r_issue_cost[-1].has_key?(user.id) then
csv_data << %Q|"#{user}","private","private",#{@r_issue_cost[-1][user.id]}\n|
end
end
send_data Redmine::CodesetUtil.from_utf8(csv_data, l(:general_csv_encoding)), :type=>"text/csv", :filename=>"monthly_report.csv"
end
def relay_total_data_with_act
find_project
authorize
prepare_values
change_member_position
change_ticket_position
change_project_position
member_add_del_check
calc_total
csv_data = %Q|"user","project","ticket","activity","spent time"\n|
@r_issue_act_cost.each do |issue_id, user_act_cost|
if issue_id >0
issue = Issue.find_by_id(issue_id)
next if issue.nil?
prj = issue.project
user_act_cost.each do |user_id, act_cost|
user = User.find_by_id(user_id)
act_cost.each do |act_id, cost|
act = TimeEntryActivity.find_by_id(act_id)
unless act.nil?
csv_data << %Q|"#{user}","#{prj}","##{issue.id} #{issue.subject}",|
csv_data << %Q|"#{act.name}",#{cost}\n|
else # can not find activity
csv_data << %Q|"#{user}","#{prj}","##{issue.id} #{issue.subject}",|
csv_data << %Q|"nil",#{cost}\n|
end
end
end
else # 表示権限の無い工数があった場合
user_act_cost.each do |user_id, act_cost|
user = User.find_by_id(user_id)
act_cost.each do |act_id, cost|
csv_data << %Q|"#{user}","private","private",|
csv_data << %Q|"private",#{cost}\n|
end
end
end
end
send_data Redmine::CodesetUtil.from_utf8(csv_data, l(:general_csv_encoding)), :type=>"text/csv", :filename=>"monthly_report_with_act.csv"
end
def ajax_relay
if !params.key?(:issue_id)
render :layout=>false, :text=>'ERROR: no issue_id'
return
end
@issue_id = params[:issue_id].to_i
find_project
@message = ''
@parent_disp = ''
@relay_modified = false
if params.key?(:parent_id)
@parent_id = params[:parent_id].to_i
if @parent_id >= 0
update_relay @issue_id, @parent_id
else
# parent_id == -1 by set_ticket_relay_by_issue_relation
redmine_parent_id = Issue.find_by_id(@issue_id).parent_id
if redmine_parent_id && redmine_parent_id >= 1 # has parent
update_relay @issue_id, redmine_parent_id
end
end
end
relay = WtTicketRelay.where(["issue_id=:i",{:i=>@issue_id}]).first
@parent_id = relay.parent
if @parent_id != 0 && !((parent = Issue.find_by_id(@parent_id)).nil?) then
@parent_disp = parent.closed? ? '<del>'+parent.to_s+'</del>' : parent.to_s
end
render :layout=>false
end
def update_relay(issue_id, parent_id)
if !User.current.allowed_to?(:edit_work_time_total, @project)
@message ||= ''
@message += l(:wt_no_permission)
return
end
# loop relay check
route = ''
search_id = parent_id
while search_id != 0 do
route += "->#{search_id}"
if search_id == issue_id
@message ||= ''
@message += l(:wt_loop_relay)+route
return
end
relay = WtTicketRelay.where(["issue_id=:i",{:i=>search_id}]).first
break if !relay
search_id = relay.parent
end
relay = WtTicketRelay.where(["issue_id=:i",{:i=>issue_id}]).first
if relay then
relay.parent = parent_id
relay.save
@relay_modified = true
else
@message ||= ''
@message += "Internal Error: no WtTicketRelay for ##{issue_id}"
end
end
def ajax_relay_input # チケット選択の内容を返すアクション
@issue_id = params[:issue_id]
@projects = Project.joins("INNER JOIN wt_project_orders ON wt_project_orders.dsp_prj=projects.id AND wt_project_orders.uid=-1").
select("projects.*, wt_project_orders.dsp_pos as pos").
order("pos").
all
render(:layout=>false)
end
def ajax_relay_input_select # チケット選択ウィンドウにAjaxで挿入(Update)される内容を返すアクション
@issue_id = params[:issue_id]
@issues = Issue.includes(:assigned_to).
where(["project_id=:p",{:p=>params[:prj]}]).
order("id DESC").
all
render(:layout=>false)
end
def ajax_add_tickets_input
prepare_values
@select_projects = Project.
joins("LEFT JOIN wt_project_orders ON wt_project_orders.dsp_prj=projects.id AND wt_project_orders.uid=#{User.current.id}").
select("projects.*, coalesce(wt_project_orders.dsp_pos,100000) as pos").
order("pos,name").
all
render(:layout=>false)
end
def ajax_add_tickets_input_select # 複数チケット選択ウィンドウにAjaxで挿入(Update)される内容を返すアクション
prepare_values
@issues = Issue.
includes(:assigned_to).
where(["project_id=:p",{:p=>params[:prj]}]).
order("id DESC").
all
render(:layout=>false)
end
def ajax_add_tickets_insert # 日毎工数に挿入するAjaxアクション
prepare_values
uid = params[:user]
@add_issue_id = params[:add_issue]
@add_count = params[:count]
if @this_uid==@crnt_uid then
add_issue = Issue.find_by_id(@add_issue_id)
@add_issue_children_cnt = Issue.where(["parent_id = ?", add_issue.id.to_s]).count
if add_issue && add_issue.visible? then
prj = add_issue.project
if User.current.allowed_to?(:log_time, prj) then
if add_issue.closed? then
@issueHtml = "<del>"+add_issue.to_s+"</del>"
else
@issueHtml = add_issue.to_s
end
@activities = []
@activity_default = nil
prj.activities.each do |act|
@activities.push([act.name, act.id])
@activity_default = act.id if act.is_default
end
@custom_fields = TimeEntryCustomField.all
@custom_fields.each do |cf|
def cf.custom_field
return self
end
def cf.value
return self.default_value
end
def cf.true?
return self.default_value
end
end
@add_issue = add_issue
unless UserIssueMonth.exists?(["uid=:u and issue=:i",{:u=>uid, :i=>@add_issue_id}]) then
# 既存のレコードが存在していなければ追加
UserIssueMonth.create(:uid=>uid, :issue=>@add_issue_id,
:odr => UserIssueMonth.where(["uid = ?", uid]).count + 1
)
end
end
end
end
render(:layout=>false)
end
def ajax_memo_edit # 日毎のメモ入力フォームを出力するAjaxアクション
render(:layout=>false)
end
def ajax_done_ratio_input # 進捗%更新ポップアップ
prepare_values
issue_id = params[:issue_id]
@issue = Issue.find_by_id(issue_id)
if @issue.nil? || @issue.closed? || !@issue.visible? then
@issueHtml = "<del>"+@issue.to_s+"</del>"
else
@issueHtml = @issue.to_s
end
render(:layout=>false)
end
def ajax_done_ratio_update
prepare_values
issue_id = params[:issue_id]
done_ratio = params[:done_ratio]
@issue = Issue.find_by_id(issue_id)
if User.current.allowed_to?(:edit_issues, @issue.project) then
@issue.init_journal(User.current)
@issue.done_ratio = done_ratio
@issue.save
end
render(:layout=>false)
end
def register_project_settings
@message = ""
require_login || return
find_project
authorize
@settings = Setting.plugin_redmine_work_time
@settings = Hash.new unless @settings.is_a?(Hash)
@settings['account_start_days'] = Hash.new unless @settings['account_start_days'].is_a?(Hash)
@settings['account_start_days'][@project.id.to_s] = params['account_start_day']
Setting.plugin_redmine_work_time = @settings
redirect_to :controller => 'projects',
:action => 'settings', :id => @project, :tab => 'work_time'
end
private
def find_project
# Redmine Pluginとして必要らしいので@projectを設定
@project = Project.find(params[:id])
rescue ActiveRecord::RecordNotFound
render_404
end
def prepare_values
# ************************************* 値の準備
@crnt_uid = User.current.id
@this_uid = (params.key?(:user) && User.current.allowed_to?(:view_work_time_other_member, @project)) ? params[:user].to_i : @crnt_uid
@this_user = User.find_by_id(@this_uid)
if @project &&
Setting.plugin_redmine_work_time.is_a?(Hash) &&
Setting.plugin_redmine_work_time['account_start_days'].is_a?(Hash) &&
Setting.plugin_redmine_work_time['account_start_days'].has_key?(@project.id.to_s)
@account_start_day = Setting.plugin_redmine_work_time['account_start_days'][@project.id.to_s].to_i
else
@account_start_day = 1
end
@today = Date.today
year = params.key?(:year) ? params[:year].to_i : @today.year
month = params.key?(:month) ? params[:month].to_i : @today.month
day = params.key?(:day) ? params[:day].to_i : @today.day
@this_date = Date.new(year, month, day)
display_date = @this_date
display_date <<= 1 if day < @account_start_day
@display_year = display_date.year
@display_month = display_date.month
@last_month = @this_date << 1
@next_month = @this_date >> 1
@restrict_project = (params.key?(:prj) && params[:prj].to_i > 0) ? params[:prj].to_i : false
@first_date = Date.new(@display_year, @display_month, @account_start_day)
@last_date = (@first_date >> 1) - 1
@month_names = l(:wt_month_names).split(',')
@wday_name = l(:wt_week_day_names).split(',')
@wday_color = ["#faa", "#eee", "#eee", "#eee", "#eee", "#eee", "#aaf"]
@link_params = {:controller=>"work_time", :id=>@project,
:year=>year, :month=>month, :day=>day,
:user=>@this_uid, :prj=>@restrict_project}
@is_registerd_backlog = false
begin
Redmine::Plugin.find :redmine_backlogs
@is_registerd_backlog = true
rescue Exception => exception
end
end
def ticket_pos
return if @this_uid != @crnt_uid
# 重複削除と順序の正規化
if order_normalization(UserIssueMonth, :issue, :order=>"odr", :conditions=>["uid=:u",{:u=>@this_uid}]) then
@message ||= ''
#@message += '<div style="background:#faa;">Warning: normalize UserIssueMonth</div>'
return
end
# 表示チケット順序変更求処理
if params.key?("ticket_pos") && params[:ticket_pos] =~ /^(.*)_(.*)$/ then
tid = $1.to_i
dst = $2.to_i
src = UserIssueMonth.where(["uid=:u and issue=:i", {:u=>@this_uid,:i=>tid}]).first
if src then # ポジション変更の場合
if src.odr > dst then # チケットを前にもっていく場合
tgts = UserIssueMonth.
where(["uid=:u and odr>=:o1 and odr<:o2", {:u=>src.uid, :o1=>dst, :o2=>src.odr}]).
all
tgts.each do |tgt|
tgt.odr += 1; tgt.save# 順位をひとつずつ後へ
end
src.odr = dst; src.save
else # チケットを後に持っていく場合
tgts = UserIssueMonth.
where(["uid=:u and odr<=:o1 and odr>:o2",{:u=>src.uid, :o1=>dst, :o2=>src.odr}]).
all
tgts.each do |tgt|
tgt.odr -= 1; tgt.save# 順位をひとつずつ後へ
end
src.odr = dst; src.save
end
else
# 新規のポジションの場合
tgts = UserIssueMonth.
where(["uid=:u and odr>=:o1", {:u=>@this_uid, :o1=>dst}]).
all
tgts.each do |tgt|
tgt.odr += 1; tgt.save# 順位をひとつずつ後へ
end
UserIssueMonth.create(:uid=>@this_uid, :issue=>tid, :odr=>dst) # 追加
end
end
end
def prj_pos
return if @this_uid != @crnt_uid
# 重複削除と順序の正規化
if order_normalization(WtProjectOrders, :dsp_prj, :order=>"dsp_pos", :conditions=>["uid=:u",{:u=>@this_uid}]) then
@message ||= ''
#@message += '<div style="background:#faa;">Warning: normalize WtProjectOrders</div>'
return
end
# 表示プロジェクト順序変更求処理
if params.key?("prj_pos") && params[:prj_pos] =~ /^(.*)_(.*)$/ then
tid = $1.to_i
dst = $2.to_i
src = WtProjectOrders.
where(["uid=:u and dsp_prj=:d",{:u=>@this_uid, :d=>tid}]).
first
if src then # ポジション変更の場合
if src.dsp_pos > dst then # チケットを前にもっていく場合
tgts = WtProjectOrders.
where(["uid=:u and dsp_pos>=:o1 and dsp_pos<:o2",{:u=>@this_uid, :o1=>dst, :o2=>src.dsp_pos}]).
all
tgts.each do |tgt|
tgt.dsp_pos += 1; tgt.save# 順位をひとつずつ後へ
end
src.dsp_pos = dst; src.save
else # チケットを後に持っていく場合
tgts = WtProjectOrders.
where(["uid=:u and dsp_pos<=:o1 and dsp_pos>:o2",{:u=>@this_uid, :o1=>dst, :o2=>src.dsp_pos}]).
all
tgts.each do |tgt|
tgt.dsp_pos -= 1; tgt.save# 順位をひとつずつ後へ
end
src.dsp_pos = dst; src.save
end
else
# 新規のポジションの場合
tgts = WtProjectOrders.
where(["uid=:u and dsp_pos>=:o1",{:u=>@this_uid, :o1=>dst}]).
all
tgts.each do |tgt|
tgt.dsp_pos += 1; tgt.save# 順位をひとつずつ後へ
end
WtProjectOrders.create(:uid=>@this_uid, :dsp_prj=>tid, :dsp_pos=>dst)
end
end
end
def ticket_del # チケット削除処理
if params.key?("ticket_del") then
if params["ticket_del"]=="closed" then # 終了チケット全削除の場合
issues = Issue.
joins("INNER JOIN user_issue_months ON user_issue_months.issue=issues.id").
where(["user_issue_months.uid=:u",{:u=>@this_uid}]).
all
# byebug
issues.each do |issue|
if issue.closed? then
tgt = UserIssueMonth.
where(["uid=:u and issue=:i",{:u=>@this_uid,:i=>issue.id}]).first
tgt.destroy
end
end
return
end
# チケット番号指定の削除の場合
src = UserIssueMonth.
where(["uid=:u and issue=:i",{:u=>@this_uid,:i=>params["ticket_del"]}]).
first
if src && src.uid == @crnt_uid then
tgts = UserIssueMonth.
where(["uid=:u and odr>:o",{:u=>src.uid, :o=>src.odr}]).
all
tgts.each do |tgt|
tgt.odr -= 1; tgt.save# 当該チケット表示より後ろの全チケットの順位をアップ
end
src.destroy# 当該チケット表示を削除
end
end
end
def hour_update # *********************************** 工数更新要求の処理
by_other = false
if @this_uid != @crnt_uid
if User.current.allowed_to?(:edit_work_time_other_member, @project)
by_other = true
else
return
end
end
# 新規工数の登録
if params["new_time_entry"] then
params["new_time_entry"].each do |issue_id, valss|
issue = Issue.find_by_id(issue_id)
next if issue.nil? || !issue.visible?
next if !User.current.allowed_to?(:log_time, issue.project)
valss.each do |count, vals|
tm_vals = vals.slice! "remaining_hours", "status_id"
tm_vals.merge!(params["new_time_entry_#{issue_id}_#{count}"]) if params.has_key?("new_time_entry_#{issue_id}_#{count}")
next if tm_vals["hours"].blank? && vals["remaining_hours"].blank? && vals["status_id"].blank?
if tm_vals["hours"].present? then
if !tm_vals[:activity_id] then
append_error_message_html(@message, 'Error: Issue'+issue_id+': No Activities!')
next
end
if by_other
append_text = "\n[#{Time.now.localtime.strftime("%Y-%m-%d %H:%M")}] #{User.current.to_s}"
append_text += " add time entry of ##{issue.id.to_s}: #{tm_vals[:hours].to_f}h"
update_daily_memo(append_text, true)
end
new_entry = TimeEntry.new(:project => issue.project, :issue => issue, :user => @this_user, :spent_on => @this_date)
new_entry.safe_attributes = tm_vals
new_entry.save
append_error_message_html(@message, hour_update_check_error(new_entry, issue_id))
end
if vals["remaining_hours"].present? || vals["status_id"].present? then
append_error_message_html(@message, issue_update_to_remain_and_more(issue_id, vals))
end
end
end
end
# 既存工数の更新
if params["time_entry"] then
params["time_entry"].each do |id, vals|
tm = TimeEntry.find_by_id(id)
issue_id = tm.issue.id
tm_vals = vals.slice! "remaining_hours", "status_id"
tm_vals.merge!(params["time_entry_"+id.to_s]) if params.has_key?("time_entry_"+id.to_s)
if tm_vals["hours"].blank? then
# 工数指定が空文字の場合は工数項目を削除
if by_other
append_text = "\n[#{Time.now.localtime.strftime("%Y-%m-%d %H:%M")}] #{User.current.to_s}"
append_text += " delete time entry of ##{issue_id.to_s}: -#{tm.hours.to_f}h-"
update_daily_memo(append_text, true)
end
tm.destroy
else
if by_other && tm_vals.key?(:hours) && tm.hours.to_f != tm_vals[:hours].to_f
append_text = "\n[#{Time.now.localtime.strftime("%Y-%m-%d %H:%M")}] #{User.current.to_s}"
append_text += " update time entry of ##{issue_id.to_s}: -#{tm.hours.to_f}h- #{tm_vals[:hours].to_f}h"
update_daily_memo(append_text, true)
end
tm.safe_attributes = tm_vals
tm.save
append_error_message_html(@message, hour_update_check_error(tm, issue_id))
end
if vals["remaining_hours"].present? || vals["status_id"].present? then
append_error_message_html(@message, issue_update_to_remain_and_more(issue_id, vals))
end
end
end
end
def issue_update_to_remain_and_more(issue_id, vals)
issue = Issue.find_by_id(issue_id)
return 'Error: Issue'+issue_id+': Private!' if issue.nil? || !issue.visible?
return if vals["remaining_hours"].blank? && vals["status_id"].blank?
journal = issue.init_journal(User.current)
# update "0.0" is changed
vals["remaining_hours"] = 0 if vals["remaining_hours"] == "0.0"
if vals['status_id'] =~ /^M+(.*)$/
vals['status_id'] = $1.to_i
else
vals.delete 'status_id'
end
issue.safe_attributes = vals
return if !issue.changed?
issue.save
hour_update_check_error(issue, issue_id)
end
def append_error_message_html(html, msg)
@message ||= ''
@message += '<div style="background:#faa;">' + msg + '</div><br>' if !msg.blank?
end
def hour_update_check_error(obj, issue_id)
return "" if obj.errors.empty?
str = l("field_issue")+"#"+issue_id.to_s+"<br>"
fm = obj.errors.full_messages
fm.each do |msg|
str += msg+"<br>"
end
str.html_safe
end
def member_add_del_check
# プロジェクトのメンバーを取得
mem = Member.where(["project_id=:prj", {:prj=>@project.id}]).all
mem_by_uid = {}
mem.each do |m|
next if m.nil? || m.user.nil? || ! m.user.allowed_to?(:view_work_time_tab, @project)
mem_by_uid[m.user_id] = m
end
# メンバーの順序を取得
odr = WtMemberOrder.where(["prj_id=:p", {:p=>@project.id}]).order("position").all
# 当月のユーザ毎の工数入力数を取得
entry_count = TimeEntry.
where(["spent_on>=:first_date and spent_on<=:last_date",
{:first_date=>@first_date, :last_date=>@last_date}]).
select("user_id, count(hours)as cnt").
group("user_id").
all
cnt_by_uid = {}
entry_count.each do |ec|
cnt_by_uid[ec.user_id] = ec.cnt
end
@members = []
pos = 1
# 順序情報にあってメンバーに無いものをチェック
odr.each do |o|
if mem_by_uid.has_key?(o.user_id) then
user=mem_by_uid[o.user_id].user
if ! user.nil? then
# 順位の確認と修正
if o.position != pos then
o.position=pos
o.save
end
# 表示メンバーに追加
if user.active? || cnt_by_uid.has_key?(user.id) then
@members.push([pos, user])
end
pos += 1
# 順序情報に存在したメンバーを削っていく
mem_by_uid.delete(o.user_id)
next
end
end
# メンバーに無い順序情報は削除する
o.destroy
end
# 残ったメンバーを順序情報に加える
mem_by_uid.each do |k,v|
user = v.user
next if user.nil?
n = WtMemberOrder.new(:user_id=>user.id,
:position=>pos,
:prj_id=>@project.id)
n.save
if user.active? || cnt_by_uid.has_key?(user.id) then
@members.push([pos, user])
end
pos += 1
end
end
def update_daily_memo(text, append = false) # 日ごとメモの更新
year = params[:year] || return
month = params[:month] || return
day = params[:day] || return
user_id = params[:user] || return
# ユーザと日付で既存のメモを検索
date = Date.new(year.to_i,month.to_i,day.to_i)
memo = WtDailyMemo.where(["day=:d and user_id=:u",{:d=>date,:u=>user_id}]).first
if memo then
# 既存のメモがあれば
text = memo.description + text if append
memo.description = text
memo.updated_on = Time.now
memo.save # 更新
else
# 既存のメモがなければ新規作成
now = Time.now
WtDailyMemo.create(:user_id=>user_id,
:day=>date,
:created_on=>now,
:updated_on=>now,
:description=>text)
end
end
################################ 休日設定
def set_holiday
user_id = params["user"] || return
if set_date = params['set_holiday'] then
WtHolidays.create(:holiday=>set_date, :created_on=>Time.now, :created_by=>user_id)
end
if del_date = params['del_holiday'] then
holidays = WtHolidays.where(["holiday=:h and deleted_on is null",{:h=>del_date}]).all
holidays.each do |h|
h.deleted_on = Time.now
h.deleted_by = user_id
h.save
end
end
end
def change_member_position
################################### メンバー順序変更処理
if params.key?("member_pos") && params[:member_pos]=~/^(.*)_(.*)$/ then
if User.current.allowed_to?(:edit_work_time_total, @project) then
uid = $1.to_i
dst = $2.to_i
mem = WtMemberOrder.where(["prj_id=:p and user_id=:u",{:p=>@project.id, :u=>uid}]).first
if mem then
if mem.position > dst then # メンバーを前に持っていく場合
tgts = WtMemberOrder.
where(["prj_id=:p and position>=:p1 and position<:p2",{:p=>@project.id, :p1=>dst, :p2=>mem.position}]).
all
tgts.each do |mv|
mv.position+=1; mv.save # 順位を一つずつ後へ
end
mem.position=dst; mem.save
end
if mem.position < dst then # メンバーを後に持っていく場合
tgts = WtMemberOrder.
where(["prj_id=:p and position<=:p1 and position>:p2",{:p=>@project.id, :p1=>dst, :p2=>mem.position}]).
all
tgts.each do |mv|
mv.position-=1; mv.save # 順位を一つずつ前へ
end
mem.position=dst; mem.save
end
end
else
@message ||= ''
@message += '<div style="background:#faa;">'+l(:wt_no_permission)+'</div>'
return
end
end
end
def change_ticket_position
# 重複削除と順序の正規化
if order_normalization(WtTicketRelay, :issue_id, :order=>"position") then
@message ||= ''
#@message += '<div style="background:#faa;">Warning: normalize WtTicketRelay</div>'
return
end
################################### チケット表示順序変更処理
if params.key?("ticket_pos") && params[:ticket_pos]=~/^(.*)_(.*)$/ then
if User.current.allowed_to?(:edit_work_time_total, @project) then
issue_id = $1.to_i
dst = $2.to_i
relay = WtTicketRelay.where(["issue_id=:i",{:i=>issue_id}]).first
if relay then
if relay.position > dst then # 前に持っていく場合
tgts = WtTicketRelay.
where(["position>=:p1 and position<:p2",{:p1=>dst, :p2=>relay.position}]).
all
tgts.each do |mv|
mv.position+=1; mv.save # 順位を一つずつ後へ
end
relay.position=dst; relay.save
end
if relay.position < dst then # 後に持っていく場合
tgts = WtTicketRelay.
where(["position<=:p1 and position>:p2",{:p1=>dst, :p2=>relay.position}]).
all
tgts.each do |mv|
mv.position-=1; mv.save # 順位を一つずつ前へ
end
relay.position=dst; relay.save
end
end
else
@message ||= ''
@message += '<div style="background:#faa;">'+l(:wt_no_permission)+'</div>'
return
end
end
end
def change_project_position
# 重複削除と順序の正規化
if order_normalization(WtProjectOrders, :dsp_prj, :order=>"dsp_pos", :conditions=>"uid=-1") then
@message ||= ''
#@message += '<div style="background:#faa;">Warning: normalize WtProjectOrders</div>'
return
end
################################### プロジェクト表示順序変更処理
return if !params.key?("prj_pos") # 位置変更パラメータが無ければパス
return if !(params[:prj_pos]=~/^(.*)_(.*)$/) # パラメータの形式が正しくなければパス
dsp_prj = $1.to_i
dst = $2.to_i
if !User.current.allowed_to?(:edit_work_time_total, @project) then
# 権限が無ければパス
@message ||= ''
@message += '<div style="background:#faa;">'+l(:wt_no_permission)+'</div>'
return
end
po = WtProjectOrders.where(["uid=-1 and dsp_prj=:d",{:d=>dsp_prj}]).first
return if po == nil # 対象の表示プロジェクトが無ければパス
if po.dsp_pos > dst then # 前に持っていく場合
tgts = WtProjectOrders.where(["uid=-1 and dsp_pos>=:o1 and dsp_pos<:o2",{:o1=>dst, :o2=>po.dsp_pos}]).all
tgts.each do |mv|
mv.dsp_pos+=1; mv.save # 順位を一つずつ後へ
end
po.dsp_pos=dst; po.save
end
if po.dsp_pos < dst then # 後に持っていく場合
tgts = WtProjectOrders.where(["uid=-1 and dsp_pos<=:o1 and dsp_pos>:o2",{:o1=>dst, :o2=>po.dsp_pos}]).all
tgts.each do |mv|
mv.dsp_pos-=1; mv.save # 順位を一つずつ前へ
end
po.dsp_pos=dst; po.save
end
end
def calc_total
################################################ 合計集計計算ループ ########
@total_cost = 0
@member_cost = Hash.new
WtMemberOrder.where(["prj_id=:p",{:p=>@project.id}]).all.each do |i|
@member_cost[i.user_id] = 0
end
@issue_parent = Hash.new # clear cash
@issue_cost = Hash.new
@r_issue_cost = Hash.new
@prj_cost = Hash.new
@r_prj_cost = Hash.new
@issue_act_cost = Hash.new
@r_issue_act_cost = Hash.new
relay = Hash.new
WtTicketRelay.all.each do |i|
relay[i.issue_id] = i.parent
end
#当月の時間記録を抽出
TimeEntry.
where(["spent_on>=:t1 and spent_on<=:t2 and hours>0",{:t1 => @first_date, :t2 => @last_date}]).
all.
each do |time_entry|
iid = time_entry.issue_id
uid = time_entry.user_id
cost = time_entry.hours
act = time_entry.activity_id
# 本プロジェクトのユーザの工数でなければパス
next unless @member_cost.key?(uid)
issue = Issue.find_by_id(iid)
next if issue.nil? # チケットが削除されていたらパス
pid = issue.project_id
# プロジェクト限定の対象でなければパス
next if @restrict_project && pid != @restrict_project
@total_cost += cost
@member_cost[uid] += cost
parent_iid = get_parent_issue(relay, iid)
if !Issue.find_by_id(iid) || !Issue.find_by_id(iid).visible?
# 表示権限の無い工数があった場合
iid = -1 # private
pid = -1 # private
act = -1 # private
end
@issue_cost[iid] ||= Hash.new
@issue_cost[iid][-1] ||= 0
@issue_cost[iid][-1] += cost
@issue_cost[iid][uid] ||= 0
@issue_cost[iid][uid] += cost
@prj_cost[pid] ||= Hash.new
@prj_cost[pid][-1] ||= 0
@prj_cost[pid][-1] += cost
@prj_cost[pid][uid] ||= 0
@prj_cost[pid][uid] += cost
@issue_act_cost[iid] ||= Hash.new
@issue_act_cost[iid][uid] ||= Hash.new
@issue_act_cost[iid][uid][act] ||= 0
@issue_act_cost[iid][uid][act] += cost
parent_issue = Issue.find_by_id(parent_iid)
if parent_issue && parent_issue.visible?
parent_pid = parent_issue.project_id
else
parent_iid = -1
parent_pid = -1
end
@r_issue_cost[parent_iid] ||= Hash.new
@r_issue_cost[parent_iid][-1] ||= 0
@r_issue_cost[parent_iid][-1] += cost
@r_issue_cost[parent_iid][uid] ||= 0
@r_issue_cost[parent_iid][uid] += cost
@r_prj_cost[parent_pid] ||= Hash.new
@r_prj_cost[parent_pid][-1] ||= 0
@r_prj_cost[parent_pid][-1] += cost
@r_prj_cost[parent_pid][uid] ||= 0
@r_prj_cost[parent_pid][uid] += cost
@r_issue_act_cost[parent_iid] ||= Hash.new
@r_issue_act_cost[parent_iid][uid] ||= Hash.new
@r_issue_act_cost[parent_iid][uid][act] ||= 0
@r_issue_act_cost[parent_iid][uid][act] += cost
end
end
def get_parent_issue(relay, iid)
@issue_parent ||= Hash.new
return @issue_parent[iid] if @issue_parent.has_key?(iid)
issue = Issue.find_by_id(iid)
return 0 if issue.nil? # issueが削除されていたらそこまで
@issue_cost[iid] ||= Hash.new
if relay.has_key?(iid)
parent_id = relay[iid]
if parent_id != 0 && parent_id != iid
parent_id = get_parent_issue(relay, parent_id)
end
parent_id = iid if parent_id == 0
else
# 関連が登録されていない場合は登録する
WtTicketRelay.create(:issue_id=>iid, :position=>relay.size, :parent=>0)
parent_id = iid
end
# iid に対する初めての処理
pid = issue.project_id
unless @prj_cost.has_key?(pid)
check = WtProjectOrders.where(["uid=-1 and dsp_prj=:p",{:p=>pid}]).all
if check.size == 0
WtProjectOrders.create(:uid=>-1, :dsp_prj=>pid, :dsp_pos=>@prj_cost.size)
end
end
@issue_parent[iid] = parent_id # return
end
def make_pack
# 月間工数表のデータを作成
@month_pack = {:ref_prjs=>{}, :odr_prjs=>[],
:total=>0, :total_by_day=>{},
:other=>0, :other_by_day=>{},
:count_prjs=>0, :count_issues=>0}
@month_pack[:total_by_day].default = 0
# 日毎工数のデータを作成
@day_pack = {:ref_prjs=>{}, :odr_prjs=>[],
:total=>0, :total_by_day=>{},
:other=>0, :other_by_day=>{},
:count_prjs=>0, :count_issues=>0}
@day_pack[:total_by_day].default = 0
# プロジェクト順の表示データを作成
dsp_prjs = Project.joins("INNER JOIN wt_project_orders ON wt_project_orders.dsp_prj=projects.id").
where(["wt_project_orders.uid=:u",{:u=>@this_uid}]).
select("projects.*, wt_project_orders.dsp_pos as dsp_pos").
order("wt_project_orders.dsp_pos").
all
dsp_prjs.each do |prj|
next if @restrict_project && @restrict_project!=prj.id
make_pack_prj(@month_pack, prj, prj.dsp_pos)
make_pack_prj(@day_pack, prj, prj.dsp_pos)
end
@prj_odr_max = dsp_prjs.length
# チケット順の表示データを作成
dsp_issues = Issue.joins("INNER JOIN user_issue_months ON user_issue_months.issue=issues.id").
where(["user_issue_months.uid=:u",{:u=>@this_uid}]).
order("user_issue_months.odr").
select("issues.*, user_issue_months.odr").
all
dsp_issues.each do |issue|
next if @restrict_project && @restrict_project!=issue.project.id
month_prj_pack = make_pack_prj(@month_pack, issue.project)
make_pack_issue(month_prj_pack, issue, issue.odr)
day_prj_pack = make_pack_prj(@day_pack, issue.project)
make_pack_issue(day_prj_pack, issue, issue.odr)
end
@issue_odr_max = dsp_issues.length
# 月内の工数を集計
hours = TimeEntry.
includes(:issue).
where(["user_id=:uid and spent_on>=:day1 and spent_on<=:day2",
{:uid => @this_uid, :day1 => @first_date, :day2 => @last_date}]).
all
hours.each do |hour|
next if @restrict_project && @restrict_project!=hour.project.id
work_time = hour.hours
if hour.issue && hour.issue.visible? then
# 表示項目に工数のプロジェクトがあるかチェック→なければ項目追加
prj_pack = make_pack_prj(@month_pack, hour.project)
# 表示項目に工数のチケットがあるかチェック→なければ項目追加
issue_pack = make_pack_issue(prj_pack, hour.issue)
issue_pack[:count_hours] += 1
# 合計時間の計算
@month_pack[:total] += work_time
prj_pack[:total] += work_time
issue_pack[:total] += work_time
# 日毎の合計時間の計算
date = hour.spent_on
@month_pack[:total_by_day][date] += work_time
prj_pack[:total_by_day][date] += work_time
issue_pack[:total_by_day][date] += work_time
if date==@this_date then # 表示日の工数であれば項目追加
# 表示項目に工数のプロジェクトがあるかチェック→なければ項目追加
day_prj_pack = make_pack_prj(@day_pack, hour.project)
# 表示項目に工数のチケットがあるかチェック→なければ項目追加
day_issue_pack = make_pack_issue(day_prj_pack, hour.issue, NO_ORDER)
day_issue_pack[:each_entries][hour.id] = hour # 工数エントリを追加
day_issue_pack[:total] += work_time
day_prj_pack[:total] += work_time
@day_pack[:total] += work_time
end
else
# 合計時間の計算
@month_pack[:total] += work_time
@month_pack[:other] += work_time
# 日毎の合計時間の計算
date = hour.spent_on
@month_pack[:total_by_day][date] ||= 0
@month_pack[:total_by_day][date] += work_time
@month_pack[:other_by_day][date] ||= 0
@month_pack[:other_by_day][date] += work_time
if date==@this_date then # 表示日の工数であれば項目追加
@day_pack[:total] += work_time
@day_pack[:other] += work_time
end
end
end
# この日のチケット作成を洗い出す
next_date = @this_date+1
t1 = Time.local(@this_date.year, @this_date.month, @this_date.day)
t2 = Time.local(next_date.year, next_date.month, next_date.day)
isu = Issue.arel_table
jnl = Journal.arel_table
union_sql = Issue.where((isu[:author_id].eq(@this_uid))
.and( isu[:created_on].gteq(t1))
.and( isu[:created_on].lt(t2)))
.union(
Issue.joins(isu.join(jnl).on(isu[:id].eq(jnl[:journalized_id])).join_sources.first)
.where(jnl[:journalized_type].eq('Issue')
.and( jnl[:user_id].eq(@this_uid))
.and( jnl[:created_on].gteq(t1))
.and( jnl[:created_on].lt(t2))
).uniq
)
issues = Issue.from("#{union_sql.to_sql} issues" )
issues.each do |issue|
next if @restrict_project && @restrict_project!=issue.project.id
next if !@this_user.allowed_to?(:log_time, issue.project)
next if !issue.visible?
prj_pack = make_pack_prj(@day_pack, issue.project)
issue_pack = make_pack_issue(prj_pack, issue)
if issue_pack[:css_classes] == 'wt_iss_overdue'
issue_pack[:css_classes] = 'wt_iss_overdue_worked'
else
issue_pack[:css_classes] = 'wt_iss_worked'
end
end
issues = Issue.
joins("INNER JOIN issue_statuses ist on ist.id = issues.status_id ").
joins("LEFT JOIN groups_users on issues.assigned_to_id = group_id").
where(["1 = 1 and
( (issues.assigned_to_id = :u or groups_users.user_id = :u) and
issues.start_date < :t2 and
ist.is_closed = :closed
)", {:u => @this_uid, :t2 => t2, :closed => false}]).
all
issues.each do |issue|
next if @restrict_project && @restrict_project!=issue.project.id
next if !@this_user.allowed_to?(:log_time, issue.project)
next if !issue.visible?
prj_pack = make_pack_prj(@day_pack, issue.project)
issue_pack = make_pack_issue(prj_pack, issue)
if issue_pack[:css_classes] == 'wt_iss_default'
issue_pack[:css_classes] = 'wt_iss_assigned'
elsif issue_pack[:css_classes] == 'wt_iss_worked'
issue_pack[:css_classes] = 'wt_iss_assigned_worked'
elsif issue_pack[:css_classes] == 'wt_iss_overdue'
issue_pack[:css_classes] = 'wt_iss_assigned_overdue'
elsif issue_pack[:css_classes] == 'wt_iss_overdue_worked'
issue_pack[:css_classes] = 'wt_iss_assigned_overdue_worked'
end
end
# 月間工数表から工数が無かった項目の削除と項目数のカウント
@month_pack[:count_issues] = 0
@month_pack[:odr_prjs].each do |prj_pack|
prj_pack[:odr_issues].each do |issue_pack|
if issue_pack[:count_hours]==0 then
prj_pack[:count_issues] -= 1
end
end
if prj_pack[:count_issues]==0 then
@month_pack[:count_prjs] -= 1
else
@month_pack[:count_issues] += prj_pack[:count_issues]
end
end
end
def make_pack_prj(pack, new_prj, odr=NO_ORDER)
# 表示項目に当該プロジェクトがあるかチェック→なければ項目追加
unless pack[:ref_prjs].has_key?(new_prj.id) then
prj_pack = {:odr=>odr, :prj=>new_prj,
:total=>0, :total_by_day=>{},
:ref_issues=>{}, :odr_issues=>[], :count_issues=>0}
pack[:ref_prjs][new_prj.id] = prj_pack
pack[:odr_prjs].push prj_pack
pack[:count_prjs] += 1
prj_pack[:total_by_day].default = 0
end
pack[:ref_prjs][new_prj.id]
end
def make_pack_issue(prj_pack, new_issue, odr=NO_ORDER)
id = new_issue.nil? ? -1 : new_issue.id
# 表示項目に当該チケットがあるかチェック→なければ項目追加
unless prj_pack[:ref_issues].has_key?(id) then
issue_pack = {:odr=>odr, :issue=>new_issue,
:total=>0, :total_by_day=>{},
:count_hours=>0, :each_entries=>{},
:cnt_childrens=>0}
issue_pack[:total_by_day].default = 0
if !new_issue.due_date.nil? && new_issue.due_date < @this_date.to_datetime
issue_pack[:css_classes] = 'wt_iss_overdue'
else
issue_pack[:css_classes] = 'wt_iss_default'
end
prj_pack[:ref_issues][id] = issue_pack
prj_pack[:odr_issues].push issue_pack
prj_pack[:count_issues] += 1
cnt_childrens = Issue.where(["parent_id = ?", new_issue.id.to_s]).count
issue_pack[:cnt_childrens] = cnt_childrens
end
prj_pack[:ref_issues][id]
end
def sum_or_nil(v1, v2)
if v2.blank?
v1
else
if v1.blank?
v2
else
v1 + v2
end
end
end
# 重複削除と順序の正規化
def order_normalization(table, key_column, find_params)
raise "need table" unless table
order = find_params[:order]
raise "need :order" unless order
update = false
tgts = table.
where(find_params[:conditions]).
order(order).
all
keys = []
tgts.each do |tgt|
if keys.include?(tgt[key_column]) then
tgt.destroy
update = true
else
keys.push(tgt[key_column])
if tgt[order] != keys.length then
tgt[order] = keys.length
tgt.save
update = true
end
end
end
update
end
end
module WorkTimeHelper
def print_issue_cost(issue)
return "" unless issue
if issue.tracker_id == 12 # Userstory
issue_cost_est = issue.total_estimated_hours
issue_cost = issue.total_spent_hours
else
issue_cost_est = issue.estimated_hours || 'NG'
issue_cost = TimeEntry.where(:issue_id => issue.id).sum(:hours).to_f
end
due_date = issue.due_date || 'NG'
return sprintf("(%1.1f/%s/%s)",issue_cost, issue_cost_est, due_date)
end
def print_issue_cost_rate(issue)
return "" unless issue
issue_cost_est = issue.estimated_hours
return "" unless issue_cost_est
issue_cost = TimeEntry.where(:issue_id => issue.id).sum(:hours).to_f
return sprintf("%1.0f",issue_cost/issue_cost_est*100)
end
def wk_pretty_issue_name(issue, issue_id = issue.id)
if issue.nil? || !issue.visible?
content_tag :del, issue_id
elsif issue.closed?
content_tag :del, issue.to_s
else
issue.to_s
end
end
end
class UserIssueMonth < ActiveRecord::Base
attr_accessible :uid, :issue, :odr
end
class WtDailyMemo < ActiveRecord::Base
attr_accessible :user_id, :day, :created_on, :updated_on, :description
end
class WtHolidays < ActiveRecord::Base
attr_accessible :holiday, :created_on, :created_by
end
class WtMemberOrder < ActiveRecord::Base
attr_accessible :user_id, :position, :prj_id
end
class WtProjectOrders < ActiveRecord::Base
attr_accessible :uid, :dsp_prj, :dsp_pos
end
class WtTicketRelay < ActiveRecord::Base
attr_accessible :issue_id, :position, :parent
end
<%
settings = Setting.plugin_redmine_work_time
if settings.is_a?(Hash) &&
settings['account_start_days'].is_a?(Hash) &&
settings['account_start_days'].has_key?(@project.id.to_s)
day = settings['account_start_days'][@project.id.to_s]
else
day = 1
end
%>
<%= form_tag(:controller=>"work_time", :id=>@project, :action => "register_project_settings") do %>
<p>
<%=content_tag(:label, l(:wt_account_start_day))%>
<%=number_field_tag 'account_start_day', day , in: 1..31 %>
</p>
<%= submit_tag l(:wt_update) %>
<%end%>
<%
@settings = Hash.new unless @settings.is_a?(Hash)
%>
<p>
<%= label_tag :settings_show_account_menu, 'show account menu' %>
<%= check_box_tag 'settings[show_account_menu]', true, @settings['show_account_menu'] %>
</p>
<% (@settings['account_start_days'] || {}).each do |k,v| %>
<%= hidden_field_tag "settings[account_start_days][#{k}]", v %>
<% end %>
<%#*************************************************** プロジェクトセレクタ %>
<%= @restrict_project ? Project.find(@restrict_project).name : ""%>
<select onchange="if (this.value != '') {window.location = this.value;}">
<option selected="selectes"><%=l(:wt_select_project)%></option>
<option value="<%= url_for(@link_params.merge(:prj=>false));%>">---</option>
<%
prjs = Project.
joins("LEFT JOIN wt_project_orders ON wt_project_orders.dsp_prj=projects.id AND wt_project_orders.uid=#{User.current.id}").
select("projects.*, coalesce(wt_project_orders.dsp_pos,100000) as pos").
order("pos,name").
all
Project.project_tree(prjs) do |prj, level|
next unless prj.active?
next if !prj.visible?
name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ': '').html_safe
%>
<option value="<%= url_for(@link_params.merge(:prj=>prj.id));%>"> <%=name_prefix + prj.name%> </option>
<%end%>
</select>
<%#**************************************************** ユーザセレクタ %>
<%if @project then%>
<%=l(:wt_select_user)%>
<select onchange="if (this.value != '') {window.location = this.value;}">
<option value="">---</option>
<%
members_name_id = Array.new
@members.each {|mem| members_name_id << [mem[1].to_s, mem[1].id]}
members_name_id.sort!
members_name_id.each do |member_name_id|
sel = (member_name_id[1] == @this_uid) ? 'selected' : ''
%>
<option value="<%= url_for(link_params.merge(:user=>member_name_id[1]));%>" <%=sel%>>
<%=member_name_id[0]%>
</option>
<%
end
%>
</select>
<%end%>
<table border="0" id="time_input_table">
<tr style="background:#fff;font-size:14px">
<td>&nbsp;</td>
<td><%=l(:work_time)%></td>
<% if @is_registerd_backlog then %>
<td><%=l(:field_remaining_hours)%></td>
<% end %>
<td><%=l(:field_activity)%></td>
<td><%=l(:field_status)%></td>
<td><%=l(:field_comments)%></td>
<% @custom_fields.each do |cf| %>
<td><%= cf.name %></td>
<% end %>
</tr>
<%
@day_pack[:odr_prjs].each do |prj_pack|
next if prj_pack[:ref_issues].length==0
prj = prj_pack[:prj]
next if !prj.visible?
dsp_prj = prj.id
dsp_pos = prj_pack[:odr]
activities = []
activity_default = nil
prj.activities.each do |act|
activities.push([act.name, act.id])
activity_default = act.id if act.is_default
end
%>
<tr style="background:#000;color:#fff;">
<td>
<%if @this_uid==@crnt_uid then%>
<a href="#"
style="color:#8ff;"
onclick="prj_pos('<%=url_for(@link_params) %>',
<%=dsp_prj%>,
<%=dsp_pos%>,
<%=@prj_odr_max+((dsp_pos.to_i<0)?1:0)%>);
return false;">
<%= (dsp_pos.to_i<0) ? "*" : dsp_pos %>:
</a>
<%end%>
<%=prj.name%>
</td>
<td><%=sprintf("%1.2f", prj_pack[:total])%></td>
<% if @is_registerd_backlog then %>
<td>&nbsp;</td>
<% end %>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<% @custom_fields.each do |cf| %>
<td>&nbsp;</td>
<% end %>
</tr>
<%
prj_pack[:odr_issues].each do |issue_pack|
issue = issue_pack[:issue]
issue_id = issue.nil? ? -1 : issue.id
issue_odr = issue_pack[:odr].to_i
# issue_class = issue_pack[:worked] ? "#cfc" : "#fff"
issue_css_classes = issue_pack[:css_classes]
issue = Issue.find_by_id(issue_id)
issue_html = wk_pretty_issue_name issue, issue_id
if issue_pack[:each_entries].length==0 then
%>
<!-- 工数エントリなし -->
<tr id="time_entry_pos<%=issue_id%>_0">
<td class="<%=issue_css_classes%>">
<div style="position:relative;width:300px;">
<%if @this_uid==@crnt_uid then%>
<a href="#"
onclick="ticket_pos('<%=url_for(@link_params) %>',
<%=issue_id%>,
<%=issue_odr%>,
<%=@issue_odr_max+((issue_odr.to_i<0)?1:0)%>);
return false;">
<%= (issue_odr.to_i<0) ? "*" : issue_odr %>
</a>
<%end%>
<%= link_to(issue_html,
{:controller=>"issues", :action=>"show", :id=>issue_id},
:popup=>true, :class=>'wt_iss_link', :'data-issue'=>issue_id) %>
<span class="tooltip">
<strong><%= print_issue_cost(issue) %></strong>
<span class="tip">Logtime/Estimation/Due_Date</span>
</span>
<%if User.current.allowed_to?(:edit_issues, issue.project)%>
<a name="<%='done_ratio'+issue.id.to_s%>"
class="wt_done_ratio"
data-issue="<%= issue_id %>"
href="#"
onclick="input_done_ratio(
'<%=url_for(@link_params.merge(:action=>"ajax_done_ratio_input"))%>',
<%=issue_id%>);
return false;">
[<%=issue.done_ratio%>&#37;]
</a>
<%else%>
[<%=issue.done_ratio%>&#37;]
<%end%>
<%if @this_uid==@crnt_uid then%>
&nbsp;&nbsp;&nbsp;
<a style="position:absolute;bottom:1px;right:10px;"
href="#"
onclick="dup_ticket(
'<%=url_for(@link_params.merge(:action=>"ajax_add_tickets_insert"))%>',
'time_entry_pos<%=issue_id%>_0',
<%=issue_id%>);
return false;">
+
</a>
<%if issue_odr>0 then%>
<a style="position:absolute;bottom:1px;right:3px;" href="<%=url_for(@link_params.merge(:ticket_del=>issue_id))%>">x</a>
<%end%>
<%end%>
</div>
</td>
<td>
<%= text_field_tag("new_time_entry["+issue_id.to_s+"][0][hours]", "", :size=>5, :oninput => "sumDayTimes()") %>
</td>
<% if @is_registerd_backlog then %>
<td>
<% if issue_pack[:cnt_childrens] != 0 || issue.closed? then %>
<%= issue[:remaining_hours] %>
<% else %>
<%= text_field_tag("new_time_entry["+issue_id.to_s+"][0][remaining_hours]", issue[:remaining_hours], :size=>5, :oninput => "sumDayTimes()") %>
<% end %>
</td>
<% end %>
<td>
<%= select_tag "new_time_entry["+issue_id.to_s+"][0][activity_id]", options_for_select(activities,activity_default) %>
</td>
<td>
<% if issue.new_statuses_allowed_to(User.current).length == 0 then %>
<%= issue.status %>
<% else %>
<% select_name="new_time_entry["+issue_id.to_s+"][0][status_id]" %>
<%= select_tag select_name,
options_for_select(issue.new_statuses_allowed_to(User.current).collect {|p| [p.name, p.id]},
issue.status_id),
:onchange => "statusUpdateOnDailyTable('#{select_name}')",
:required => true %>
<% end %>
</td>
<td>
<%= text_field_tag("new_time_entry["+issue_id.to_s+"][0][comments]", "", :size=>80)%>
</td>
<%if @custom_fields.length != 0
dummy_hour = TimeEntry.new(:project => issue.project, :issue => issue, :user => @this_user, :spent_on => @this_date)
dummy_hour.custom_field_values.each do |cfv|
%>
<td><%= custom_field_tag "new_time_entry_"+issue_id.to_s+"_0", cfv %></td>
<%end
end%>
</tr>
<%
else
issue_pack[:each_entries].each do |hour_id, hour|
%>
<!-- 工数エントリあり -->
<tr id="time_entry_pos<%=hour_id%>">
<td class="<%=issue_css_classes%>">
<div style="position:relative;width:300px;">
<%if !issue.nil? then%>
<%if @this_uid==@crnt_uid then%>
<a href="#"
onclick="ticket_pos('<%=url_for(@link_params) %>',
<%=issue_id%>,
<%=issue_odr%>,
<%=@issue_odr_max+((issue_odr.to_i<0)?1:0)%>);
return false;">
<%= (issue_odr.to_i<0) ? "*" : issue_odr %>
</a>
<%end%>
<%= link_to(issue_html,
{:controller=>"issues", :action=>"show", :id=>issue_id},
:popup=>true, :class=>'wt_iss_link', :'data-issue'=>issue_id) %>
<%= print_issue_cost(issue) %>
<%if User.current.allowed_to?(:edit_issues, issue.project)%>
<a name="<%='done_ratio'+issue_id.to_s%>"
class="wt_done_ratio"
data-issue="<%= issue_id %>"
href="#"
onclick="input_done_ratio(
'<%=url_for(@link_params.merge(:action=>"ajax_done_ratio_input"))%>',
<%=issue_id%>);
return false;">
[<%=issue.done_ratio%>&#37;]
</a>
<%else%>
[<%=issue.done_ratio%>&#37;]
<%end%>
<%if @this_uid==@crnt_uid then%>
&nbsp;&nbsp;&nbsp;
<a style="position:absolute;bottom:1px;right:10px;"
href="#"
onclick="dup_ticket(
'<%=url_for(@link_params.merge(:action=>"ajax_add_tickets_insert"))%>',
'time_entry_pos<%=hour_id%>',
<%=issue_id%>);
return false;">
+
</a>
<%if issue_odr>0 then%>
<a style="position:absolute;bottom:1px;right:3px;" href="<%=url_for(@link_params.merge(:ticket_del=>issue_id))%>">x</a>
<%end%>
<%end%>
<%end%>
</div>
</td>
<td>
<%= text_field_tag("time_entry["+hour_id.to_s+"][hours]", sprintf("%1.2f", hour.hours), :size=>5, :oninput => "sumDayTimes()") %>
</td>
<% if @is_registerd_backlog then %>
<td>
<% if issue_pack[:cnt_childrens] != 0 || hour.issue.closed? then %>
<%= hour.issue[:remaining_hours] %>
<% else %>
<%= text_field_tag("time_entry["+hour_id.to_s+"][remaining_hours]", hour.issue[:remaining_hours], :size=>5, :oninput => "sumDayTimes()") %>
<% end %>
</td>
<% end %>
<td>
<%= select_tag "time_entry["+hour_id.to_s+"][activity_id]", options_for_select(activities, hour.activity_id), :required => true %>
</td>
<td>
<% if issue.new_statuses_allowed_to(User.current).length == 0 then %>
<%= issue.status %>
<% else %>
<% select_name="time_entry["+hour_id.to_s+"][status_id]" %>
<%= select_tag select_name,
options_for_select(issue.new_statuses_allowed_to(User.current).collect {|p| [p.name, p.id]},
issue.status_id),
:onchange => "statusUpdateOnDailyTable('#{select_name}')",
:required => true %>
<% end %>
</td>
<td>
<%= text_field_tag("time_entry["+hour_id.to_s+"][comments]", hour.comments, :size=>80)%>
</td>
<% hour.custom_field_values.each do |cfv| %>
<td><%= custom_field_tag "time_entry_"+hour_id.to_s, cfv %></td>
<% end %>
</tr>
<%
end
end
end
end
%>
<% if @day_pack[:other]!=0 then %>
<tr style="background:#000;color:#fff;">
<td>private</td>
<td><%=sprintf("%1.2f", @day_pack[:other])%></td>
<% if @is_registerd_backlog then %>
<td><%=sprintf("%1.2f", @day_pack[:other_remain]) if @day_pack[:other_remain]%></td>
<% end %>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<% @custom_fields.each do |cf| %>
<td>&nbsp;</td>
<% end %>
</tr>
<% end %>
<tr align="center" id="time_input_table_bottom" style="background:#ddd;">
<td>&nbsp;</td>
<td><span id='currentTotal' title='<%=l(:wt_saved_value)%> <%=sprintf("%1.2f", @day_pack[:total])%>'><%=sprintf("%1.2f", @day_pack[:total])%></span></td>
<% if @is_registerd_backlog then %>
<td>&nbsp;</td>
<% end %>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<% @custom_fields.each do |cf| %>
<td>&nbsp;</td>
<% end %>
</tr>
</table>
<%
win_w = 35 # 小窓の幅
win_h = 31 # 小窓の高さ
win_hs = 20 # 小さい小窓の高さ
box_w = 300
box_h = win_hs*3+win_h*@month_pack[:count_issues]+win_hs*@month_pack[:count_prjs]+20
if @month_pack[:other]!=0 then
box_h += win_hs
end
%>
<h2><%=l(:wt_monthly_report)%> (<%=@this_user%>)</h2>
<table width="100%" style="border:0;border-collapse:collapse;">
<tr>
<!-- ************************************************ 左側を表示 -->
<td style="width:<%=box_w%>px;padding:0px;">
<div style="background: #ccc;">
<div style="position:relative;height:<%=box_h%>px;overflow:auto;">
<div style="position:absolute;top:1px;left:1px;">
<div style="background: #fff;font-size:1.5em;">
<div style="text-align:center;position:relative;top:1px;left:1px;width:<%=box_w-2%>px;height:<%=win_hs*2-1%>px;">
<%= @first_date.strftime("%Y/%m/%d") %> - <%= @last_date.strftime("%Y/%m/%d") %>
</div>
</div>
<div style="text-align:right;position:absolute;top:1px;left:1px;width:<%=box_w-2%>px;height:<%=win_hs*2-1%>px;">
<%= link_to(">>", @link_params.merge(:day=>@next_month.day, :month=>@next_month.month, :year=>@next_month.year))%>
</div>
<div style="text-align:left;position:absolute;top:1px;left:1px;">
<%= link_to("<<", @link_params.merge(:day=>@last_month.day, :month=>@last_month.month, :year=>@last_month.year))%>
</div>
</div>
<%
ofst_t = win_hs*3-5
@month_pack[:odr_prjs].each do |prj_pack|
next if prj_pack[:count_issues] == 0
prj = prj_pack[:prj]
dsp_pos = prj_pack[:odr]
prj_ofst_t = ofst_t
ofst_t += win_hs
prj_pack[:odr_issues].each do |issue_pack|
next if issue_pack[:count_hours] == 0
issue = issue_pack[:issue]
issue_id = issue.nil? ? -1 : issue.id
issue_odr = issue_pack[:odr]
%>
<div style="position:absolute;top:<%=ofst_t%>px;left:1px;">
<div style="background: #fff;font-size:0.8em;">
<div style="position:relative;top:1px;left:1px;width:<%=box_w-2%>px;height:<%=win_h-1%>px;">
<%if !issue.nil? then%>
<%if @this_uid==@crnt_uid then%>
<a href="#"
onclick="JavaScript:
ticket_pos(
'<%=url_for(@link_params) %>',
<%=issue_id%>,
<%=issue_odr%>,
<%=@issue_odr_max+((issue_odr.to_i<0)?1:0)%>);
return false;">
<%= (issue_odr.to_i<0) ? "*" : issue_odr %>
</a>
<%end%>
<%= link_to(wk_pretty_issue_name(issue, issue_id), {:controller=>"issues", :action=>"show", :id=>issue_id}, :popup=>true, :class=>'wt_iss_link', :'data-issue'=>issue_id) %>
<%= print_issue_cost(issue) %>
<%end%>
<div style="position:absolute;top:15px;right:15px;">
<%=sprintf("%1.2f", issue_pack[:total])%>
</div>
</div>
</div>
</div>
<%
ofst_t += win_h
end
%>
<div style="position:absolute;top:<%=prj_ofst_t%>px;left:2px;">
<div style="background:#000;color:#fff;font-size:1.0em;">
<div style="position:relative;top:1px;left:1px;width:<%=box_w-3%>px;height:<%=win_hs-1%>px;">
<%if @this_uid==@crnt_uid then%>
<a href="#"
style="color:#8ff;"
onclick="JavaScript:
prj_pos(
'<%=url_for(@link_params) %>',
<%=prj.id%>,
<%=dsp_pos%>,
<%=@prj_odr_max+((dsp_pos.to_i<0)?1:0)%>);
return false;">
<%= (dsp_pos.to_i<0) ? "*" : dsp_pos %>:
</a>
<%end%>
<%= prj.name%>
<div style="position:absolute;top:1px;right:10px;">
<%=sprintf("%1.2f",prj_pack[:total])%>
</div>
</div>
</div>
</div>
<%
end
%>
<%if @month_pack[:other]!=0 then%>
<div style="position:absolute;top:<%=ofst_t%>px;left:2px;">
<div style="background:#000;color:#fff;font-size:1.0em;">
<div style="position:relative;top:1px;left:1px;width:<%=box_w-4%>px;height:<%=win_hs-2%>px;">
private
<div style="position:absolute;top:1px;right:10px;">
<%=sprintf("%1.2f", @month_pack[:other])%>
</div>
</div>
</div>
</div>
<%end%>
<div style="position:absolute;bottom:10px;right:10px;">
<%=sprintf("%1.2f", @month_pack[:total])%>
</div>
<div style="position:absolute;top:<%=win_hs*2%>px;right:10px;">
<%=sprintf("%1.2f", @month_pack[:total])%>
</div>
</div>
</div>
</td>
<!-- ************************************************ 右側を表示 -->
<td>
<div style="background: #ccc;">
<div style="position:relative;height:<%=box_h%>px;overflow:auto;" id="day_scroll">
<% # チケット毎の合計時間を計算&表示
day_pos = 0
(@first_date..@last_date).each do |date|
day_pos += 1
month = date.month
day = date.day
wday = date.wday
ofst_l = win_w*(day_pos-1)
holidayEntry = WtHolidays.
where(["holiday=:h and deleted_on is null",{:h=>date}]).
all
if holidayEntry.size == 0 then
bcolor = @wday_color[wday]
else
bcolor = "#fbc"
end
if day==@this_date.day then
bcolor_day = "#cfc"
today_id = 'id=day_scroll_today'
else
bcolor_day = bcolor
today_id = ''
end
%>
<div style="position:absolute;top:1px;left:<%=ofst_l%>px;" <%=today_id%> >
<div style="background:<%=bcolor_day%>;">
<div style="text-align:center;position:relative;top:1px;left:1px;width:<%=win_w-1%>px;height:<%=win_hs-1%>px;">
<%= link_to(day, @link_params.merge(:day=>day, :month=>month)) %>
</div>
</div>
</div>
<div style="position:absolute;top:<%=win_hs+1%>px;left:<%=ofst_l%>px;">
<div style="background:<%=bcolor%>;">
<div style="text-align:center;position:relative;top:1px;left:1px;width:<%=win_w-1%>px;height:<%=win_hs-1%>px;">
<%= @wday_name[wday]%>
</div>
</div>
</div>
<%
ofst_t = win_hs*3-5
day_cost = 0
@month_pack[:odr_prjs].each do |prj_pack|
next if prj_pack[:count_issues] == 0
dsp_prj = prj_pack[:prj].id
dsp_pos = prj_pack[:odr]
prj_ofst_t = ofst_t
ofst_t += win_hs
prj_pack[:odr_issues].each do |issue_pack|
next if issue_pack[:count_hours] == 0
issue = issue_pack[:issue]
issue_id = issue.nil? ? -1 : issue.id
issue_odr = issue_pack[:odr]
%>
<div style="position:absolute;top:<%=ofst_t%>px;left:<%=ofst_l%>px;">
<div style="background:<%=bcolor%>;font-size:0.8em;">
<div style="text-align:center;position:relative;top:5px;left:1px;width:<%=win_w-1%>px;height:<%=win_h-1%>px;">
<%
if issue_pack[:total_by_day].has_key?(date) then
%>
<%=sprintf("%1.2f", issue_pack[:total_by_day][date])%>
<%
end
%>
</div>
</div>
</div>
<%
ofst_t += win_h
end
%>
<div style="position:absolute;top:<%=prj_ofst_t%>px;left:<%=ofst_l%>px;">
<div style="background:#000;color:#fff;font-size:0.8em;">
<div style="text-align:center;position:relative;top:5px;left:1px;width:<%=win_w-1%>px;height:<%=win_hs-1%>px;">
<%if prj_pack[:total_by_day].has_key?(date) then%>
<%=sprintf("%1.2f", prj_pack[:total_by_day][date])%>
<%end%>
</div>
</div>
</div>
<%
end
%>
<!--日毎の合計時間表示-->
<div style="position:absolute;top:<%=win_hs*2%>px;left:<%=ofst_l%>px;">
<div style="font-size:0.8em;">
<div style="text-align:center;position:relative;top:1px;left:1px;width:<%=win_w-2%>px;height:<%=win_hs-2%>px;">
<%if @month_pack[:total_by_day].has_key?(date) then%>
<%= sprintf("%1.2f", @month_pack[:total_by_day][date])%>
<%end%>
</div>
</div>
</div>
<!--otherの時間表示-->
<%if @month_pack[:other]!=0 then%>
<div style="position:absolute;top:<%=ofst_t%>px;left:<%=ofst_l%>px;">
<div style="background:#000;color:#fff;font-size:0.8em;">
<div style="text-align:center;position:relative;top:5px;left:1px;width:<%=win_w-2%>px;height:<%=win_hs-2%>px;">
<%if @month_pack[:other_by_day].has_key?(date) then%>
<%= sprintf("%1.2f", @month_pack[:other_by_day][date])%>
<%end%>
</div>
</div>
</div>
<%end%>
<%end%>
</div>
</div>
</td>
</tr>
</table>
<input type="button"
onclick="location.href='<%=url_for(@link_params.merge(:action=>"member_monthly_data"))%>'"
value="<%=l(:wt_data_download)%>"
/>
<br/>
<script type="text/javascript">
<!--
var scr = document.getElementById("day_scroll");
var tgt = document.getElementById("day_scroll_today");
scr.scrollLeft = tgt.offsetLeft - scr.offsetLeft;
-->
</script>
<div class="wt_add_ticket_block">
<h2><%=l(:wt_add_ticket)%></h2>
<select onchange="if (this.value != '') {
jQuery.ajax({
url: this.value,
data: {asynchronous: true, method: 'get'},
success: function (response) {
jQuery('#input_tickets').replaceWith(response);}
})
return false;}">
<option value=''><%=l(:wt_select_project)%></option>
<option disabled="disabled">---</option>
<%
Project.project_tree(@select_projects) do |prj, level|
next unless prj.active?
next if !User.current.allowed_to?(:log_time, prj)
members = Member.
where(["user_id=:u and project_id=:p",{:u=>User.current.id, :p=>prj.id}]).
all
next if members.size==0
name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ': '').html_safe
%>
<option value="<%= url_for(:action=>"ajax_add_tickets_input_select", :prj=>prj.id) %>">
<%= name_prefix + prj.name %>
</option>
<%
end
%>
</select>
<div id="input_tickets" style="display: inline-block;_display: inline;">
<%=l(:wt_input_ticket_numbers)%>
<form action="">
<input type="text"
id="input_ids"
size=32
onKeyPress="if(checkEnter(event)) {
tickets_inputed('<%=url_for(@link_params.merge(:action=>"ajax_add_tickets_insert"))%>');
return false;
}
return event;"
/>
<input type="button"
value="<%=l(:button_apply)%>"
onclick="tickets_inputed('<%=url_for(@link_params.merge(:action=>"ajax_add_tickets_insert"))%>');"
/>
</form>
</div>
</div>
<script>
$('#input_ids').focus();
</script>
\ No newline at end of file
<div id="input_tickets">
<form style="font-size:14px;" action="">
<%
opt = {
:action => 'ajax_add_tickets_input_select',
:prj => params[:prj],
:all => params[:all] || '0',
:other => params[:other] || '0'
}
opt[:all] = opt[:all] == '0' ? '1' : '0' if params.key?(:all_toggle)
opt[:other] = opt[:other] == '0' ? '1' : '0' if params.key?(:other_toggle)
url1 = url_for(opt.merge({:all_toggle => ''}))
url2 = url_for(opt.merge({:other_toggle => ''}) )
link_label1 = opt[:all] == '0' ? l(:wt_opt_disp_ticket_with_closed) : l(:wt_opt_disp_ticket_opened_only)
link_label2 = opt[:other] == '0' ? l(:wt_opt_disp_ticket_with_other_member) : l(:wt_opt_disp_ticket_mine_only)
%>
<a href="#"
onclick="jQuery.ajax({
url: '<%=url1%>',
data: {asynchronous: true, method: 'get'},
success: function (response) {
jQuery('#input_tickets').replaceWith(response);}
});
return false;">
<%= link_label1 %>
</a>
<a href="#"
onclick="jQuery.ajax({
url: '<%=url2%>',
data: {asynchronous: true, method: 'get'},
success: function (response) {
jQuery('#input_tickets').replaceWith(response);}
});return false;">
<%= link_label2 %>
</a>
<br/>
<input type="button"
onclick="tickets_checked('<%=url_for(@link_params.merge(:action=>"ajax_add_tickets_insert"))%>');"
value="<%=l(:wt_apply_checked)%>"/>
<br/>
<div id="tickets_check">
<%
@issues.each do |issue|
next if issue.nil? || !issue.visible? || (opt[:other] == '0' && issue.assigned_to_id != User.current.id) || (issue.closed? && opt[:all] == '0')
%>
<input type="checkbox"
name="ticket_select_check"
value="<%=issue.id%>"
/>
<input type="button"
onclick="tickets_selected('<%=url_for(@link_params.merge(:action=>"ajax_add_tickets_insert"))%>', '<%=issue.id%>');"
value="<%=l(:button_apply)%>"
/>
(<%= link_to_user(issue.assigned_to || '-') %>): <%= wk_pretty_issue_name issue %>
<a href="#"
onclick="window.open('<%=url_for(:controller=>"issues", :action=>"show", :id=>issue.id)%>');">
&#63;
</a>
<br/>
<%end%>
</div>
</form>
<input type="button"
onclick="tickets_checked('<%=url_for(@link_params.merge(:action=>"ajax_add_tickets_insert"))%>');"
value="<%=l(:wt_apply_checked)%>"/>
<br/>
<br/>
</div>
<%if @add_issue then%>
<tr style="background:#ddf;" id="time_entry_pos<%=@add_issue_id%>_<%=@add_count%>">
<td>
<div style="position:relative;width:300px;">
<%=@add_issue.project.name%>
<br/>
<%= link_to(@issueHtml.html_safe, {:controller=>"issues", :action=>"show", :id=>@add_issue.id},
:popup=>true, :class=>'wt_iss_link', :'data-issue'=>@add_issue.id) %>
<%= print_issue_cost(@add_issue) %>
<%if User.current.allowed_to?(:edit_issues, @add_issue.project)%>
<a name="<%='done_ratio'+@add_issue.id.to_s%>"
class="wt_done_ratio"
data-issue="<%= @add_issue.id %>"
href="#"
onclick="JavaScript:
input_done_ratio(
'<%=url_for(@link_params.merge(:action=>"ajax_done_ratio_input"))%>',
<%=@add_issue.id%>);
return false;">
[<%=@add_issue.done_ratio%>&#37;]
</a>
<%else%>
[<%=@add_issue.done_ratio%>&#37;]
<%end%>
<a style="position:absolute;bottom:1px;right:10px;"
href="#"
onclick="JavaScript:
dup_ticket(
'<%=url_for(@link_params.merge(:action=>"ajax_add_tickets_insert"))%>',
'time_entry_pos<%=@add_issue_id%>_<%=@add_count%>',
<%=@add_issue.id%>);
return false;">
+
</a>
</div>
</td>
<td>
<%= text_field_tag("new_time_entry["+@add_issue_id+"]["+@add_count+"][hours]", "", :size=>5, :oninput => "sumDayTimes()") %>
</td>
<% if @is_registerd_backlog then %>
<td>
<% if @add_issue_children_cnt != 0 || @add_issue.closed? then %>
<%= @add_issue.remaining_hours %>
<% else %>
<%= text_field_tag("new_time_entry["+@add_issue_id+"]["+@add_count+"][remaining_hours]", @add_issue.remaining_hours, :size=>5, :oninput => "sumDayTimes()") %>
<% end %>
</td>
<% end %>
<td>
<%= select_tag "new_time_entry["+@add_issue_id+"]["+@add_count+"][activity_id]", options_for_select(@activities,@activity_default), :required => true %>
</td>
<td>
<%
if @add_issue.new_statuses_allowed_to(User.current).length == 0
%>
<%= @add_issue.status %>
<% else %>
<%= select_tag "new_time_entry["+@add_issue_id.to_s+"][0][status_id]", options_for_select(@add_issue.new_statuses_allowed_to(User.current), @add_issue.status), :required => true %>
<% end %>
</td>
<td>
<%= text_field_tag("new_time_entry["+@add_issue_id+"]["+@add_count+"][comments]", "", :size=>80)%>
</td>
<%if @custom_fields.length != 0
dummy_hour = TimeEntry.new(:project => @add_issue.project, :issue => @add_issue, :user => @this_user, :spent_on => @this_date)
dummy_hour.custom_field_values.each do |cfv|
%>
<td><%= custom_field_tag "new_time_entry_"+@add_issue_id+"_"+@add_count, cfv %></td>
<%end end%>
</tr>
<%end%>
<div name="<%='done_ratio'+@issue.id.to_s%>">
<% if @issue.estimated_hours then %>
<%= print_issue_cost(@issue) %>
&#61;
<%= print_issue_cost_rate(@issue) %>
&#37;
<br/>
<% end %>
<form action="">
<%= @issue.done_ratio %>
&#37;
&rarr;
<input type="text"
id="<%='input_ratio'+@issue.id.to_s%>"
size="4"
onKeyPress="if(checkEnter(event)) {
update_done_ratio('<%=url_for(@link_params.merge(:action=>"ajax_done_ratio_update"))%>', <%=@issue.id.to_s%>);
return false;
}
return event; "
value="<%= print_issue_cost_rate(@issue) %>"/>
&#37;
<input type="button"
value="<%=l(:button_apply)%>"
onclick="update_done_ratio('<%=url_for(@link_params.merge(:action=>"ajax_done_ratio_update"))%>',
<%=@issue.id.to_s%>);" />
</form>
</div>
<script>
$('#<%='input_ratio'+@issue.id.to_s%>').focus();
</script>
\ No newline at end of file
<%#
# チケットの進捗%をAjaxUpdateする
%>
<%if User.current.allowed_to?(:edit_issues, @issue.project)%>
<a name="<%='done_ratio'+@issue.id.to_s%>"
href="#"
onclick="input_done_ratio(
'<%=url_for(@link_params.merge(:action=>"ajax_done_ratio_input"))%>',
<%=@issue.id.to_s%>);
return false;">
[<%=@issue.done_ratio%>&#37;]
</a>
<%else%>
[<%=@issue.done_ratio%>&#37;]
<%end%>
\ No newline at end of file
<%
year = params["year"]
month = params["month"]
day = params["day"]
user_id = params["user"]
date = Date.new(year.to_i,month.to_i,day.to_i)
wday_name = l(:wt_week_day_names).split(',')
memo=WtDailyMemo.where(["day=:d and user_id=:u",{:d=>date,:u=>user_id}]).first
if memo then
%>
<textarea id="work_time_memo" name="memo" rows="15" cols="100"><%=memo.description -%></textarea>
<%else%>
<textarea id="work_time_memo" name="memo" rows="15" cols="100"></textarea>
<%end%>
<br/>
<%= submit_tag l(:wt_update) %>
<%= wikitoolbar_for 'work_time_memo' %>
<% if @message != '' %>
<div class="wt_error">
<%=@message.html_safe%>
</div>
<% end %>
<div class="<%=@relay_modified ? 'wt_modified' : ''%>">
=>
<%=@parent_disp.html_safe%>
<%
if User.current.allowed_to?(:edit_work_time_total, @project)
if @parent_disp != ''
%>
<a href="#"
onclick="jQuery.ajax({
url: '<%=url_for(:action=>"ajax_relay_input", :issue_id=>@issue_id.to_s)%>',
data: {asynchronous: true, method: 'get'},
success: function (response) {
jQuery('#ticket_relay_<%=@issue_id.to_s%>').html(response);
}
});
return false;">
[<%=l(:button_change)%>]
</a>
<a href="#"
onclick="jQuery.ajax({
url:'<%=url_for(:action=>'ajax_relay',:issue_id=>@issue_id, :parent_id=>0)%>',
data:{asynchronous:true, method:'get'},
success: function(response) {
jQuery('#ticket_relay_<%=@issue_id%>').html(response);}
});
return false;">
[<%=l(:button_delete)%>]
</a>
<%else%>
<a href="#"
onclick="jQuery.ajax({
url: '<%=url_for(:action=>"ajax_relay_input", :issue_id=>@issue_id.to_s)%>',
data: {asynchronous: true, method: 'get'},
success: function (response) {
jQuery('#ticket_relay_<%=@issue_id.to_s%>').html(response);
}
});
return false;">
[<%=l(:button_add)%>]
</a>
<%
end
end
%>
</div>
<select onchange="JavaScript:if (this.value != '') {
jQuery.ajax({
url:this.value,
data:{asynchronous:true, method:'get'},
success:function(response){
jQuery('#input_area_<%=@issue_id%>').replaceWith(response);
}
});
return false;}">
<option value=''><%=l(:wt_select_project)%></option>
<option disabled="disabled">---</option>
<%
@projects.each do |prj|
next unless prj.active?
next if !prj.visible?
%>
<option value="<%= url_for(:action=>"ajax_relay_input_select", :prj=>prj.id, :issue_id=>@issue_id) %>">
<%= (prj.pos.to_i < 100000) ? prj.pos : '*' %>: <%= prj.name %>
</option>
<%
end
%>
</select>
<div id="input_area_<%=@issue_id%>" style="display: inline-block;_display: inline;">
<%=l(:wt_input_ticket_numbers)%>
<form action="">
<input type="text"
id="input_id_<%=@issue_id%>"
size="8"
onKeyPress="if(checkEnter(event)) {
jQuery.ajax({
url:'<%=url_for(:action=>'ajax_relay',:issue_id=>@issue_id)%>' +
'&parent_id=' + $('#input_id_<%=@issue_id%>').val(),
data:{asynchronous:true, method:'get'},
success: function(response) {
jQuery('#ticket_relay_<%=@issue_id%>').html(response);}
});
return false;
}
return event;"
/>
<input type="button"
value="<%=l(:button_apply)%>"
onclick="jQuery.ajax({
url:'<%=url_for(:action=>'ajax_relay',:issue_id=>@issue_id)%>' +
'&parent_id=' + $('#input_id_<%=@issue_id%>').val(),
data:{asynchronous:true, method:'get'},
success: function(response) {
jQuery('#ticket_relay_<%=@issue_id%>').html(response);}
});
return false;"
/>
</form>
</div>
<a href="#"
onclick="jQuery.ajax({
url: '<%=url_for(:action=>"ajax_relay", :issue_id=>@issue_id.to_s)%>',
data: {asynchronous: true, method: 'get'},
success: function (response) {
jQuery('#ticket_relay_<%=@issue_id.to_s%>').html(response);
}
});
return false;">
<%=l(:button_cancel)%>
</a>
<script>
$('#input_id_<%=@issue_id%>').focus();
</script>
<div id="input_area_<%=@issue_id%>">
<%
opt = {
:action => 'ajax_relay_input_select',
:issue_id => @issue_id,
:prj => params[:prj],
:all => params[:all] || '0'
}
opt[:all] = opt[:all] == '0' ? '1' : '0' if params.key?(:all_toggle)
url1 = url_for(opt.merge({:all_toggle => ''}))
link_label1 = opt[:all] == '0' ? l(:wt_opt_disp_ticket_with_closed) : l(:wt_opt_disp_ticket_opened_only)
%>
<a href="#"
onclick="jQuery.ajax({
url: '<%=url1%>',
data: {asynchronous: true, method: 'get'},
success: function (response) {
jQuery('#input_area_<%=@issue_id%>').html(response);}
});JavaScript:
return false;">
<%= link_label1 %>
</a>
<br/>
<%
@issues.each do |issue|
next if issue.nil? || !issue.visible? || (issue.closed? && opt[:all] == '0')
%>
<div style="font-size:14px;">
<input type="button"
onclick="jQuery.ajax({
url:'<%=url_for(:action=>'ajax_relay', :issue_id=>@issue_id, :parent_id=>issue.id)%>',
data:{asynchronous:true, method:'get'},
success: function(response) {
jQuery('#ticket_relay_<%=@issue_id%>').html(response);}
});
"
value="<%=l(:button_apply)%>"
/>
(<%= link_to_user(issue.assigned_to || '-') %>): <%= wk_pretty_issue_name issue %>
<a href="#"
onclick="window.open('<%=url_for(:controller=>"issues", :action=>"show", :id=>issue.id)%>');
return false;">
&#63;
</a>
<br/>
</div>
<%end%>
</div>
<%=javascript_include_tag 'work_time', :plugin=>'redmine_work_time' %>
<%=stylesheet_link_tag 'work_time', :plugin => 'redmine_work_time' %>
<%= @message.html_safe %>
<div style="text-align:right;">
<%= render :partial=>'select_project' %> <br/>
[<%= link_to(l(:wt_each_member_report), @link_params.merge(:action=>"show")) %>]
[<%= link_to(l(:wt_raw_total), @link_params.merge(:action=>"total")) %>]
[<%= link_to(l(:wt_relay_total), @link_params.merge(:action=>"relay_total")) %>]
</div>
<%
###################################################### チケット関係テーブルの表示
%>
<br/>
<h2>
<%=l(:wt_edit_relay)%>
<%= link_to("<<", @link_params.merge(:day=>@last_month.day, :month=>@last_month.month, :year=>@last_month.year))%>
<%= @first_date.strftime("%Y/%m/%d") %> - <%= @last_date.strftime("%Y/%m/%d") %>
<%= link_to(">>", @link_params.merge(:day=>@next_month.day, :month=>@next_month.month, :year=>@next_month.year))%>
</h2>
<table border="1" id="relay_table">
<%
prjs = WtProjectOrders.where("uid=-1").order("dsp_pos").all
tickets = WtTicketRelay.order("position").all
tic_max = tickets.size
prjs.each do |po|
dsp_prj = po.dsp_prj
dsp_pos = po.dsp_pos
next if (!@prj_cost.has_key?(dsp_prj) || @prj_cost[dsp_prj][-1]==0.0) && (!@r_prj_cost.has_key?(dsp_prj) || @r_prj_cost[dsp_prj][-1]==0.0)
prj =Project.find_by_id(dsp_prj)
%>
<tr style="background:#000;color:#fff;">
<td>
<%if User.current.allowed_to?(:edit_work_time_total, @project) then%>
<a href="#"
style="color:#8ff;"
onclick="JavaScript:
prj_pos(
'<%=url_for(@link_params)%>',
<%=dsp_prj%>,
<%=dsp_pos%>,
<%=prjs.size%>);
return false;">
<%=dsp_pos%>:
</a>
<%end%>
<%=prj.name%>
</td>
<td style="text-align:right;">
<%if @prj_cost.has_key?(dsp_prj) && @prj_cost[dsp_prj][-1]!=0%>
<%=sprintf('%1.2f', @prj_cost[dsp_prj][-1])%>
<%else%>
&nbsp;
<%end%>
</td>
<td style="text-align:right;"><b>
<%if @r_prj_cost.has_key?(dsp_prj) && @r_prj_cost[dsp_prj][-1]!=0%>
<%=sprintf('%1.2f', @r_prj_cost[dsp_prj][-1]) %>
<%else%>
&nbsp;
<%end%>
</b></td>
<td>&nbsp;</td>
</tr>
<%
tic_num = 0
tickets.each do |tic|
issue_id = tic.issue_id
tic_num += 1
if tic.position != tic_num then # 番号が間違っていたらつけなおし
tic.position = tic_num; tic.save
end
# 当該チケットに集計が無かったらスキップ
next unless @issue_cost.has_key?(issue_id) || @r_issue_cost.has_key?(issue_id)
issue = Issue.find_by_id(issue_id)
parent_id = ""
next if issue.nil? # チケットが削除されていたらパス
next if issue.project_id != dsp_prj
if tic.parent && tic.parent != 0 then
parent = Issue.find_by_id(tic.parent)
if parent.nil? then
parentHtml = "<del>Issue:"+tic.parent.to_s+"</del>"
else
parent_id = parent.id
parentHtml = parent.closed? ? "<del>"+parent.to_s+"</del>" : parent.to_s
end
else
parentHtml = ""
end
if @issue_cost.key?(issue_id) &&
@issue_cost[issue_id].key?(-1) &&
@issue_cost[issue_id][-1]!=0 then
cost=sprintf("%.2f",@issue_cost[issue_id][-1])
else
cost="&nbsp"
end
if @r_issue_cost.key?(issue_id) &&
@r_issue_cost[issue_id].key?(-1) &&
@r_issue_cost[issue_id][-1]!=0 then
r_cost=sprintf("%.2f",@r_issue_cost[issue_id][-1])
else
r_cost="&nbsp"
end
%>
<tr>
<td>
<%if User.current.allowed_to?(:edit_work_time_total, @project) then%>
<a href="#"
onclick="JavaScript:
ticket_pos('<%=url_for(@link_params)%>',
<%=issue_id%>,
<%=tic.position%>,
<%=tic_max%>);
return false;">
<%=tic.position%>
</a>
<%end%>
<%= link_to(wk_pretty_issue_name(issue), {:controller=>"issues", :action=>"show", :id=>issue.id}, :popup=>true) %>
<%= print_issue_cost(issue) %>
</td>
<td style="text-align:right;"><%=cost.html_safe%></td>
<td style="text-align:right;"><%=r_cost.html_safe%></td>
<td id="ticket_relay_<%=issue_id%>"></td>
<script>
jQuery.ajax({
url: '<%=url_for(:action=>"ajax_relay", :issue_id=>issue_id.to_s)%>',
data: {asynchronous: true, method: 'get'},
success: function (response) {
jQuery('#ticket_relay_<%=issue_id.to_s%>').html(response);
}
});
</script>
</tr>
<%
end
end
%>
<% if @prj_cost.has_key?(-1) || @r_prj_cost.has_key?(-1) then%>
<tr style="background:#000;color:#fff;">
<td>private</td>
<td style="text-align:right;">
<%if @prj_cost.has_key?(-1) then%>
<%=sprintf('%1.2f', @prj_cost[-1][-1])%>
<%else%>
&nbsp;
<%end%>
</td>
<td style="text-align:right;">
<%if @r_prj_cost.has_key?(-1) then%>
<b><%=sprintf('%1.2f', @r_prj_cost[-1][-1]) if @r_prj_cost[-1][-1]%></b>
<%else%>
&nbsp;
<%end%>
</td>
<td>&nbsp;</td>
</tr>
<% end %>
</table>
<%= submit_tag(l('wt_bulkupdate_relations'),
{:type => 'button',
:onclick => "set_ticket_relay_by_issue_relation('#{url_for(:action=>'ajax_relay', :parent_id=>-1)}')".html_safe}) %>
<div style="text-align:right;">
<%= render :partial=>'select_project' %> <br/>
[<%= link_to(l(:wt_each_member_report), @link_params.merge(:action=>"show")) %>]
[<%= link_to(l(:wt_raw_total), @link_params.merge(:action=>"total")) %>]
[<%= link_to(l(:wt_relay_total), @link_params.merge(:action=>"relay_total")) %>]
</div>
api.total_relay do
api.total_cost @total_cost
api.array :member_cost do
@members.each do |mem_info|
if @member_cost.key?(mem_info[1].id) && @member_cost[mem_info[1].id] > 0 then
api.member_cost do
api.user(:id => mem_info[1].id, :firstname => mem_info[1].firstname, :lastname => mem_info[1].lastname) unless mem_info[1].nil?
api.cost @member_cost[mem_info[1].id]
end
end
end
end
api.array :issues_cost do
@r_issue_cost.each do |issue,cost|
iss = Issue.find_by_id(issue)
api.issue_cost do
api.issue do
api.id iss.id unless iss.nil?
api.tracker(:id => iss.tracker.id, :name => iss.tracker.name) unless iss.tracker.nil?
api.subject iss.subject
end
api.cost cost[-1]
end
end
end
end
<%=javascript_include_tag "work_time", :plugin=>'redmine_work_time' %>
<%=stylesheet_link_tag 'work_time', :plugin => 'redmine_work_time' %>
<%= @message.html_safe %>
<div style="text-align:right;">
<%= render :partial=>'select_project' %> <br/>
[<%= link_to(l(:wt_each_member_report), @link_params.merge(:action=>"show")) %>]
[<%= link_to(l(:wt_edit_relay), @link_params.merge(:action=>"edit_relay")) %>]
[<%= link_to(l(:wt_raw_total), @link_params.merge(:action=>"total")) %>]
</div>
<h2>
<%=l(:wt_relay_total)%>
<%= link_to("<<", @link_params.merge(:day=>@last_month.day, :month=>@last_month.month, :year=>@last_month.year))%>
<%= @first_date.strftime("%Y/%m/%d") %> - <%= @last_date.strftime("%Y/%m/%d") %>
<%= link_to(">>", @link_params.merge(:day=>@next_month.day, :month=>@next_month.month, :year=>@next_month.year))%>
</h2>
<%
if !User.current.allowed_to?(:view_work_time_other_member, @project) then
return
end
############################################## 集計表 #####################
%>
<table border="1">
<tr valign="top">
<td>
</td>
<td style="background:#ddd;"></td>
<%
max = @members.size
@members.each do |mem_info|
pos = mem_info[0]
user = mem_info[1]
%>
<td align="center">
<%if User.current.allowed_to?(:edit_work_time_total, @project) then%>
<a href="#"
onclick="JavaScript:
member_pos(
'<%=url_for(@link_params)%>',
<%=user.id%>,
<%=pos%>,
<%=max%>);
return false;">
<%=pos%>
</a>
<%end%>
<div style="top:2px;width:56px;font-size:10px;">
<%= link_to(user, @link_params.merge(:action=>"show", :user=>user.id))%>
</div>
</td>
<%
end
%>
</tr>
<tr>
<td style="background:#ddd;">
</td>
<td style="background:#ddd;text-align:right;">
<%=sprintf('%1.2f', @total_cost)%>
</td>
<%
#---------------------------------------各人の合計を表示
@members.each do |mem_info|
user=mem_info[1]
%>
<td style="background:#ddd;text-align:right;">
<%= (@member_cost.key?(user.id) && @member_cost[user.id] !=0 ) ? sprintf('%1.2f', @member_cost[user.id]) : " "%>
</td>
<%
end
%>
</tr>
<%
prjs = WtProjectOrders.where("uid=-1").order("dsp_pos").all
tickets = WtTicketRelay.order("position").all
tic_max = tickets.size
#-------------------------------------- チケット行のループ
prjs.each do |po|
dsp_prj = po.dsp_prj
dsp_pos = po.dsp_pos
next unless @r_prj_cost.key?(dsp_prj) # 値の無いプロジェクトはパス
next unless @r_prj_cost[dsp_prj].key?(-1) # 値の無いプロジェクトはパス
next if @r_prj_cost[dsp_prj][-1] == 0 # 値の無いプロジェクトはスパ
prj =Project.find(dsp_prj)
%>
<tr style="background:#000;color:#fff;">
<td>
<%if User.current.allowed_to?(:edit_work_time_total, @project) then%>
<a href="#"
style="color:#8ff;"
onclick="JavaScript:
prj_pos(
'<%=url_for(@link_params)%>',
<%=dsp_prj%>,
<%=dsp_pos%>,
<%=prjs.size%>);
return false;">
<%=dsp_pos%>:
</a>
<%end%>
<%=prj.name%>
</td>
<td style="text-align:right;"> <%=sprintf('%1.2f', (@r_prj_cost[dsp_prj])[-1])%> </td>
<%
@members.each do |mem_info|
user=mem_info[1]
uid=user.id
%>
<td style="text-align:right;">
<%= @r_prj_cost[dsp_prj].key?(uid) ? sprintf('%1.2f', (@r_prj_cost[dsp_prj])[uid]) : " "%>
</td>
<%
end
%>
</tr>
<%
tickets.each do |tic|
issue_id = tic.issue_id
next unless @r_issue_cost.key?(issue_id) # 値の無いチケットはパス
next unless @r_issue_cost[issue_id].key?(-1) # 値の無いチケットはパス
next if @r_issue_cost[issue_id][-1] == 0 # 値の無いチケットはパス
issue = Issue.find_by_id(issue_id)
next if issue.nil? # チケットが削除されていたらパス
next if issue.project_id != dsp_prj # このプロジェクトに表示するチケットでない場合はパス
%>
<tr>
<td>
<%if User.current.allowed_to?(:edit_work_time_total, @project) then%>
<a href="#"
onclick="JavaScript:
ticket_pos(
'<%=url_for(@link_params)%>',
<%=issue_id%>,
<%=tic.position%>,
<%=tic_max%>);
return false;">
<%=tic.position%>
</a>
<%end%>
<%= link_to( wk_pretty_issue_name(issue), {:controller=>"issues", :action=>"show", :id=>issue.id}, :popup=>true) %>
<%= print_issue_cost(issue) %>
</td>
<td style="background:#ddd;text-align:right;">
<%=sprintf('%1.2f', (@r_issue_cost[issue_id])[-1])%>
</td>
<%
@members.each do |mem_info|
user=mem_info[1]
%>
<td style="text-align:right;">
<% if @r_issue_cost.key?(issue_id) && @r_issue_cost[issue_id].key?(user.id) then %>
<%= sprintf('%1.2f', (@r_issue_cost[issue_id])[user.id]) %>
<%else%>
&nbsp;
<%end%>
</td>
<%
end
%>
</tr>
<%
end
end
if @r_prj_cost.has_key?(-1) then
%>
<tr style="background:#000;color:#fff;">
<td>private</td>
<td style="text-align:right;"> <%=sprintf('%1.2f', (@r_prj_cost[-1])[-1])%> </td>
<%
@members.each do |mem_info|
user=mem_info[1]
uid=user.id
%>
<td style="text-align:right;">
<%= @r_prj_cost[-1].key?(uid) ? sprintf('%1.2f', (@r_prj_cost[-1])[uid]) : " "%>
</td>
<%end%>
</tr>
<%end%>
</table>
<input type="button"
onclick="location.href='<%=url_for(@link_params.merge(:action=>"relay_total_data"))%>'"
value="<%=l(:wt_data_download)%>"
/>
<input type="button"
onclick="location.href='<%=url_for(@link_params.merge(:action=>"relay_total_data_with_act"))%>'"
value="<%=l(:wt_data_download_with_act)%>"
/>
<div style="text-align:right;">
<%= render :partial=>'select_project' %> <br/>
[<%= link_to(l(:wt_each_member_report), @link_params.merge(:action=>"show")) %>]
[<%= link_to(l(:wt_edit_relay), @link_params.merge(:action=>"edit_relay")) %>]
[<%= link_to(l(:wt_raw_total), @link_params.merge(:action=>"total")) %>]
</div>
<%=javascript_include_tag "work_time", :plugin=>'redmine_work_time' %>
<%=stylesheet_link_tag 'work_time', :plugin => 'redmine_work_time' %>
<%= @message.html_safe %>
<div class="contextual">
<%= render :partial=>'select_project' %>
<%if User.current.allowed_to?(:view_work_time_other_member, @project) then%>
<%= render :partial=>'select_user', :locals=>{:link_params=>@link_params} %>
<%= link_to(l(:wt_relay_total), @link_params.merge(:action=>"relay_total")) %>
<%end%>
<%if User.current.admin then%>
<%if WtHolidays.where(["holiday=:h and deleted_on is null",{:h=>@this_date}]).all.size == 0 then %>
<input type="button"
onclick="location.href='<%=url_for(@link_params.merge(:set_holiday=>@this_date))%>'"
value="<%=l(:wt_set_holiday)%>"
/>
<%else%>
<input type="button"
onclick="location.href='<%=url_for(@link_params.merge(:del_holiday=>@this_date))%>'"
value="<%=l(:wt_del_holiday)%>"
/>
<%end%>
<%end%>
</div>
<%= render :partial=>'user_month_table' %>
<br/>
<hr/>
<%= form_tag @link_params do %>
<h2>
<%=l(:wt_daily_report)%>
<%=@this_date.strftime("%Y-%m-%d")%>
</h2>
<%= hidden_field_tag('year', @this_date.year) %>
<%= hidden_field_tag('month', @this_date.month) %>
<%= hidden_field_tag('day', @this_date.day) %>
<%= hidden_field_tag('user', @this_uid) %>
<%= hidden_field_tag('prj', @restrict_project) %>
<table style="background: #eeeeee;"><tr><td>
<table style="display:inline-block; border:0;">
<tr>
<td><%=l(:wt_legend)%>:</td>
<td class='wt_iss_default' width='100px' align='center'><%=l(:wt_style_default)%></td>
<td class='wt_iss_assigned' width='100px' align='center'><%=l(:wt_style_assigned)%></td>
<td class='wt_iss_worked' width='100px' align='center'><%=l(:wt_style_worked)%></td>
<td class='wt_iss_assigned_worked' width='100px' align='center'><%=l(:wt_style__assigned_worked)%></td>
<td class='wt_iss_overdue' width='100px' align='center'><%=l(:wt_style_overdue)%>
<td class='wt_iss_assigned_overdue' width='100px' align='center'><%=l(:wt_style_assigned_overdue)%>
</td><td class='wt_iss_overdue_worked' width='100px' align='center'><%=l(:wt_style_overdue_worked)%></td>
<td class='wt_iss_assigned_overdue_worked' width='100px' align='center'><%=l(:wt_style_assigned_overdue_worked)%></td>
</tr>
</table>
</td></tr></table>
<%= render :partial=>'user_day_table' %>
<%if @this_uid==@crnt_uid || User.current.allowed_to?(:edit_work_time_other_member, @project) %>
<%= submit_tag l(:wt_update) %>
&#58;
<input type="button"
onclick="location.href='<%=url_for(@link_params.merge(:ticket_del=>"closed"))%>'"
value="<%=l(:wt_delete_closed_tickets)%>"
/>
<div id="add_ticket_area" style="display: inline-block;_display: inline;">
<input type="button"
onclick="jQuery.ajax({
url: '<%=url_for(:action=>"ajax_add_tickets_input")%>',
data:{asynchronous:true, method:'get'},
success: function(response){
jQuery('#add_ticket_area').replaceWith(response);
}
});
return false;"
value="<%=l(:wt_add_ticket)%>"
/>
</div>
<%end%>
<%#------------------------- Legend for the daily table%>
<br/>
<br/>
<hr/>
<h2 id="memo">
<%=link_to(@this_date.strftime("%Y-%m-%d")+'('+@wday_name[@this_date.wday]+') '+@this_user.to_s,
@link_params.merge(:anchor=>"memo"))
%>
</h2>
<%#------------------------------------------- Wiki表示#%>
<div id="memo-wiki">
<%
memo=WtDailyMemo.where(["day=:d and user_id=:u",{:d=>@this_date,:u=>@this_uid}]).first
if memo then # この日のメモがある場合
%>
<div class="wiki wt_memo_wiki_block">
<%=textilizable(memo.description) %>
<div style="text-align:right;">
(update:<%=memo.updated_on.localtime.strftime("%Y-%m-%d %H:%M")%>)
</div>
</div>
<%end%>
<%
memo=WtDailyMemo.where(["day<:d and user_id=:u",{:d=>@this_date,:u=>@this_uid}]).order("day DESC").first
if memo then
d = memo.day
%>
[<%=link_to(l(:wt_pre_memo), @link_params.merge(:anchor=>"memo", :day=>d.day, :month=>d.month, :year=>d.year))%>]
<%end%>
<%if @this_uid==@crnt_uid then%>
[<a href="#"
onclick="jQuery.ajax({
url: '<%=url_for(@link_params.merge(:action=>"ajax_memo_edit"))%>',
data:{asynchronous:true, method:'get'},
success: function(response){
jQuery('#memo-wiki').html(response);
}
});
return false;">
<%=l(:wt_edit_memo)%>
</a>]
<%end%>
<%
memo=WtDailyMemo.where(["day>:d and user_id=:u",{:d=>@this_date,:u=>@this_uid}]).order("day").first
if memo then
d = memo.day
%>
[<%= link_to(l(:wt_next_memo), @link_params.merge(:anchor=>"memo", :day=>d.day, :month=>d.month, :year=>d.year))%>]
<%end%>
</div>
<br/>
<%if User.current.allowed_to?(:view_work_time_other_member, @project) then%>
<%= render :partial=>'select_user', :locals=>{:link_params=>@link_params.merge(:anchor=>"memo")} %>
<%end%>
<%end%>
<%=javascript_include_tag "work_time", :plugin=>'redmine_work_time' %>
<%=stylesheet_link_tag 'work_time', :plugin => 'redmine_work_time' %>
<%= @message.html_safe %>
<div style="text-align:right;">
<%= render :partial=>'select_project' %> <br/>
[<%= link_to(l(:wt_each_member_report), @link_params.merge(:action=>"show")) %>]
[<%= link_to(l(:wt_edit_relay), @link_params.merge(:action=>"edit_relay")) %>]
[<%= link_to(l(:wt_relay_total), @link_params.merge(:action=>"relay_total")) %>]
</div>
<h2>
<%=l(:wt_raw_total)%>
<%=link_to("<<", @link_params.merge(:day=>@last_month.day, :month=>@last_month.month, :year=>@last_month.year))%>
<%= @first_date.strftime("%Y/%m/%d") %> - <%= @last_date.strftime("%Y/%m/%d") %>
<%=link_to(">>", @link_params.merge(:day=>@next_month.day, :month=>@next_month.month, :year=>@next_month.year))%>
</h2>
<%
if !User.current.allowed_to?(:view_work_time_other_member, @project) then
return
end
############################################## 集計表 #####################
%>
<table border="1">
<tr valign="top">
<td>
</td>
<td style="background:#ddd;"></td>
<%
max = @members.size
@members.each do |mem_info|
pos=mem_info[0]
user=mem_info[1]
%>
<td align="center">
<%if User.current.allowed_to?(:edit_work_time_total, @project) then%>
<a href="#"
onclick="JavaScript:
member_pos(
'<%=url_for(@link_params)%>',
<%=user.id%>,
<%=pos%>,
<%=max%>);
return false;">
<%=pos%>
</a>
<%end%>
<div style="top:2px;width:56px;font-size:10px;">
<%= link_to(user, @link_params.merge(:action=>"show", :user=>user.id))%>
</div>
</td>
<%
end
%>
</tr>
<tr>
<td style="background:#ddd;">
</td>
<td style="background:#ddd;text-align:right;">
<%=sprintf('%1.2f', @total_cost)%>
</td>
<%
#---------------------------------------各人の合計を表示
@members.each do |mem_info|
user=mem_info[1]
%>
<td style="background:#ddd;text-align:right;">
<%= (@member_cost.key?(user.id) && @member_cost[user.id] !=0 ) ? sprintf('%1.2f', @member_cost[user.id]) : " "%>
</td>
<%
end
%>
</tr>
<%
prjs = WtProjectOrders.where("uid=-1").order("dsp_pos").all
tickets = WtTicketRelay.order("position").all
tic_max = tickets.size
#-------------------------------------- チケット行のループ
prjs.each do |po|
dsp_prj = po.dsp_prj
dsp_pos = po.dsp_pos
next unless @prj_cost.key?(dsp_prj) # 値の無いプロジェクトはパス
next unless @prj_cost[dsp_prj].key?(-1) # 値の無いプロジェクトはパス
next if @prj_cost[dsp_prj][-1] == 0 # 値の無いプロジェクトはスパ
prj =Project.find(dsp_prj)
%>
<tr style="background:#000;color:#fff;">
<td>
<%if User.current.allowed_to?(:edit_work_time_total, @project) then%>
<a href="#"
style="color:#8ff;"
onclick="JavaScript:
prj_pos(
'<%=url_for(@link_params)%>',
<%=dsp_prj%>,
<%=dsp_pos%>,
<%=prjs.size%>);
return false;">
<%=dsp_pos%>:
</a>
<%end%>
<%=prj.name%>
</td>
<td style="text-align:right;"> <%=sprintf('%1.2f', (@prj_cost[dsp_prj])[-1])%> </td>
<%
@members.each do |mem_info|
user=mem_info[1]
uid=user.id
%>
<td style="text-align:right;">
<%= @prj_cost[dsp_prj].key?(uid) ? sprintf('%1.2f', (@prj_cost[dsp_prj])[uid]) : " "%>
</td>
<%
end
%>
</tr>
<%
tickets.each do |tic|
issue_id = tic.issue_id
next unless @issue_cost.key?(issue_id) # 値の無いチケットはパス
next unless @issue_cost[issue_id].key?(-1) # 値の無いチケットはパス
next if @issue_cost[issue_id][-1] == 0 # 値の無いチケットはパス
issue = Issue.find_by_id(issue_id)
next if issue.nil? # チケットが削除されていたらパス
next if issue.project_id != dsp_prj # このプロジェクトに表示するチケットでない場合はパス
%>
<tr>
<td>
<%if User.current.allowed_to?(:edit_work_time_total, @project) then%>
<a href="#"
onclick="JavaScript:
ticket_pos(
'<%=url_for(@link_params)%>',
<%=issue_id%>,
<%=tic.position%>,
<%=tic_max%>);
return false;">
<%=tic.position%>
</a>
<%end%>
<%= link_to(wk_pretty_issue_name(issue), {:controller=>"issues", :action=>"show", :id=>issue.id}, :popup=>true) %>
<%= print_issue_cost(issue) %>
</td>
<td style="background:#ddd;text-align:right;">
<%=sprintf('%1.2f', (@issue_cost[issue_id])[-1])%>
</td>
<%
@members.each do |mem_info|
user=mem_info[1]
%>
<td style="text-align:right;">
<% if @issue_cost.key?(issue_id) && @issue_cost[issue_id].key?(user.id) then %>
<%= sprintf('%1.2f', (@issue_cost[issue_id])[user.id]) %>
<%else%>
&nbsp;
<%end%>
</td>
<%
end
%>
</tr>
<%
end
end
if @prj_cost.has_key?(-1) then
%>
<tr style="background:#000;color:#fff;">
<td>private</td>
<td style="text-align:right;"> <%=sprintf('%1.2f', (@prj_cost[-1])[-1])%> </td>
<%
@members.each do |mem_info|
user=mem_info[1]
uid=user.id
%>
<td style="text-align:right;">
<%= @prj_cost[-1].key?(uid) ? sprintf('%1.2f', (@prj_cost[-1])[uid]) : " "%>
</td>
<%end%>
</tr>
<%end%>
</table>
<input type="button"
onclick="location.href='<%=url_for(@link_params.merge(:action=>"total_data"))%>'"
value="<%=l(:wt_data_download)%>"
/>
<input type="button"
onclick="location.href='<%=url_for(@link_params.merge(:action=>"total_data_with_act"))%>'"
value="<%=l(:wt_data_download_with_act)%>"
/>
<div align="right">
<%= render :partial=>'select_project' %> <br/>
[<%= link_to(l(:wt_each_member_report), @link_params.merge(:action=>"show")) %>]
[<%= link_to(l(:wt_edit_relay), @link_params.merge(:action=>"edit_relay")) %>]
[<%= link_to(l(:wt_relay_total), @link_params.merge(:action=>"relay_total")) %>]
</div>
function ticket_pos(url, issue, pos, max)
{
var new_pos = prompt("Destination No.", pos);
if(new_pos != null) {
if((new_pos>=1) && (new_pos<=max))
location.replace(url+"&ticket_pos="+issue+"_"+new_pos);
else
alert("Out of range!");
}
}
function prj_pos(url, prj, pos, max)
{
var new_pos = prompt("Destination No.", pos);
if(new_pos != null) {
if((new_pos>=1) && (new_pos<=max))
location.replace(url+"&prj_pos="+prj+"_"+new_pos);
else
alert("Out of range!");
}
}
function member_pos(url, user_id, pos, max)
{
var new_pos = prompt("Distination No.", pos);
if(new_pos != null) {
if((new_pos>=1) && (new_pos<=max))
location.replace(url+"&member_pos="+user_id+"_"+new_pos);
else
alert("Out of rnage!");
}
}
function set_ticket_relay_by_issue_relation(ajax_url) {
$('[id^=ticket_relay_]').each(function(){
var issue_id = $(this).attr('id').replace(/.*([^_]+)$/, "$1");
jQuery.ajax({
url: ajax_url + '&issue_id=' + issue_id,
data:{asynchronous:true, method:'get'},
success: function(response) {
jQuery('#ticket_relay_'+issue_id).html(response);
}
});
})
}
function input_done_ratio(ajax_url, issue_id) {
jQuery.ajax({
url: ajax_url + "&issue_id=" + issue_id,
data: {asynchronous: true, method: 'get'},
success: function (response) {
jQuery('[name="done_ratio'+ issue_id+'"]:first').replaceWith(response);
}
});
}
function update_done_ratio(ajax_url, issue_id) {
var done_ratio = $('#input_ratio'+issue_id).val();
jQuery.ajax({
url:ajax_url+"&issue_id="+issue_id+"&done_ratio="+done_ratio,
data:{asynchronous:true, method:'get'},
success:function(response){
jQuery('[name="done_ratio'+ issue_id+'"]').replaceWith(response);
}
});
}
function checkEnter(e)
{
if (!e) var e = window.event;
if(e.keyCode == 13)
return true;
else
return false;
}
//------------------------------------------------- for show.html.erb
var add_ticket_count = 1;
function dup_ticket(ajax_url, insert_pos, id)
{
jQuery.ajax({
url:ajax_url+"&add_issue="+id+"&count="+add_ticket_count,
data:{asynchronous:true, method:'get'},
success:function(response){
jQuery('#'+insert_pos).after(response);
}
});
add_ticket_count ++;
}
function tickets_insert(ajax_url, tickets)
{
for(i=0; i<tickets.length;i++) {
jQuery.ajax({
url:ajax_url+"&add_issue="+tickets[i]+"&count="+add_ticket_count,
data:{asynchronous:true, method:'get'},
success:function(response){
jQuery('#time_input_table_bottom').before(response);
}
});
add_ticket_count ++;
}
}
function tickets_inputed(ajax_url)
{
var vals = document.getElementById("input_ids").value;
var tickets = vals.split(',');
tickets_insert(ajax_url, tickets);
}
function tickets_selected(ajax_url, issue_id)
{
var tickets = [issue_id];
tickets_insert(ajax_url, tickets);
}
function tickets_checked(ajax_url)
{
var $checked = $('[name="ticket_select_check"]:checked');
var tickets = $checked.map(function(i,e){return $(this).val()});
tickets_insert(ajax_url, tickets);
}
function statusUpdateOnDailyTable(name) {
obj = document.getElementsByName(name)[0];
obj.style.backgroundColor = '#cfc';
index = obj.selectedIndex;
v = obj.options[index].value;
obj.options[index].value = 'M'+v;
}
//------------- for user_day_table.html.erb
function sumDayTimes() {
var total=0;
var dayInputs;
// List all Input elemnets of the page
dayInputs = document.getElementsByTagName("input");
for (var i=0; i<dayInputs.length; i++) {
// Consider only those with an id containing the strings 'time_entry' and 'hours'
if ((dayInputs[i].id.indexOf("time_entry") >= 0) && (dayInputs[i].id.indexOf("hours") >= 0)) {
var val = dayInputs[i].value;
if (val) {
var vals = val.match(/^([\d\.]+)$/);
if (vals) {
// add the number to the total if it is a valid number
total = total + parseFloat(vals[1]);
}
else {
vals = val.match(/^(\d+)m$/);
if(vals) {
total = total + parseFloat(vals[1])/60;
}
else {
vals = val.match(/^(\d+):(\d+)$/);
if(vals) {
total = total + parseFloat(vals[1]) + parseFloat(vals[2])/60;
}
}
}
}
}
}
// Set the total value to the new number, changing the style to indicate
// it is not saved, and adding the saved value as a flyover indication
var originalValue;
document.getElementById("currentTotal").innerHTML = total.toFixed(2);
document.getElementById("currentTotal").style = 'color:#FF0000;';
return true;
}
h1 {
margin-top: 0;
margin-bottom: 0
}
h2 {
margin-top: 0;
margin-bottom: 0
}
div.wt_modified {
background: #cfc;
}
div.wt_error {
background: #faa;
}
div.wt_memo_wiki_block {
background: #ffb;
}
div.wt_add_ticket_block {
background: #ddd;
border: 1px solid black;
}
/* use day table */
td.wt_iss_default {
background: #fff;
}
td.wt_iss_worked {
background: #cfc;
}
td.wt_iss_overdue {
background: #ffc;
}
td.wt_iss_overdue_worked {
background: #ccc;
}
td.wt_iss_assigned {
background: #cff;
}
td.wt_iss_assigned_worked {
background: #ccf;
}
td.wt_iss_assigned_overdue {
background: #fcc;
}
td.wt_iss_assigned_overdue_worked {
background: #fcf;
}
ca:
work_time: "WorkTime"
wt_update: "Update"
wt_month_names: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec"
wt_week_day_names: "Sun,Mon,Tue,Wed,Thu,Fri,Sat"
wt_monthly_report: "Monthly Report"
wt_daily_report: "Daily Report"
wt_each_member_report: "Member Report"
wt_raw_total: "Monthly Report(not relayed)"
wt_relay_total: "Monthly Report(relayed)"
wt_edit_relay: "Ticket Relay Editor"
wt_ticket: "Ticket"
wt_add_ticket: "Add ticket"
wt_no_permission: "No permission"
wt_loop_relay: "Ticket relation looped"
wt_set_holiday: "set holiday on this date"
wt_del_holiday: "del holiday on this date"
wt_apply_checked: "apply all checked"
wt_select_project: "Restrict project..."
wt_select_user: "Select user..."
wt_edit_memo: "Edit memo"
wt_pre_memo: "pre. memo"
wt_next_memo: "next memo"
wt_data_list: "Data list"
wt_input_ticket_numbers: "or input ticket numbers"
wt_delete_closed_tickets: "delete closed tickets"
wt_data_download: "data download"
wt_data_download_with_act: "data download(each activity)"
wt_opt_disp_ticket_with_closed: "[display closed ticket]"
wt_opt_disp_ticket_opened_only: "[display opened ticket only]"
wt_opt_disp_ticket_with_other_member: "[display other members ticket]"
wt_opt_disp_ticket_mine_only: "[display my ticket only]"
wt_bulkupdate_relations: "bulkupdate to parent"
permission_view_work_time_tab: "View Work time tab"
permission_view_work_time_other_member: "View other members' work time"
permission_edit_work_time_total: "Edit work time totals"
permission_edit_work_time_other_member: "Edit other members' work time"
wt_saved_value: "Saved timed: "
wt_legend: "Legend"
wt_style_default: "By default"
wt_style_assigned: "Assigned"
wt_style_worked: "Worked on"
wt_style__assigned_worked: "Assigned and worked on"
wt_style_overdue: "Overdue"
wt_style_assigned_overdue: "Assigned and overdue"
wt_style_overdue_worked: "Overdue and worked on"
wt_style_assigned_overdue_worked: "Assigned, overdue and worked on"
wt_account_start_day: "Company start day"
de:
work_time: "Zeiterfassung"
wt_update: "Aktualisieren"
wt_month_names: "Jan,Feb,Mar,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez"
wt_week_day_names: "So,Mo,Di,Mi,Do,Fr,Sa"
wt_monthly_report: "Monatsbericht"
wt_daily_report: "Tagesbericht"
wt_each_member_report: "Bericht pro Benutzer"
wt_raw_total: "Monatsbericht (ohne Fortschreibung)"
wt_edit_relay: "Aufwände fortschreiben"
wt_relay_total: "Monatsbericht"
wt_ticket: "Ticket"
wt_add_ticket: "Ticket hinzufügen"
wt_no_permission: "Keine Berechtigung"
wt_loop_relay: "Zyklus bei der Aufwandsfortschreibung!"
wt_set_holiday: "Tag zum Urlaubs-/Feiertag machen"
wt_del_holiday: "Tag zum Arbeitstag machen"
wt_apply_checked: "Alle ausgewählten Einträge übernehmen"
wt_select_project: "Projektauswahl"
wt_select_user: "Benutzerauswahl"
wt_edit_memo: "Memo ändern"
wt_pre_memo: "Vorheriges Memo"
wt_next_memo: "Nächstes Memo"
wt_data_list: "Datenliste"
wt_input_ticket_numbers: "oder Ticket-Nr. direkt eingeben"
wt_delete_closed_tickets: "delete closed tickets"
wt_data_download: "Daten laden"
wt_data_download_with_act: "Daten laden(each activity)"
wt_opt_disp_ticket_with_closed: "[display closed ticket]"
wt_opt_disp_ticket_opened_only: "[display opened ticket only]"
wt_opt_disp_ticket_with_other_member: "[display other members ticket]"
wt_opt_disp_ticket_mine_only: "[display my ticket only]"
wt_bulkupdate_relations: "bulkupdate to parent"
permission_view_work_time_tab: "View Work time tab"
permission_view_work_time_other_member: "View other members' work time"
permission_edit_work_time_total: "Edit work time totals"
permission_edit_work_time_other_member: "Edit other members' work time"
wt_saved_value: "Saved timed: "
wt_legend: "Legend"
wt_style_default: "By default"
wt_style_assigned: "Assigned"
wt_style_worked: "Worked on"
wt_style__assigned_worked: "Assigned and worked on"
wt_style_overdue: "Overdue"
wt_style_assigned_overdue: "Assigned and overdue"
wt_style_overdue_worked: "Overdue and worked on"
wt_style_assigned_overdue_worked: "Assigned, overdue and worked on"
wt_account_start_day: "Company start day"
en:
work_time: "WorkTime"
wt_update: "Update"
wt_month_names: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec"
wt_week_day_names: "Sun,Mon,Tue,Wed,Thu,Fri,Sat"
wt_monthly_report: "Monthly Report"
wt_daily_report: "Daily Report"
wt_each_member_report: "Member Report"
wt_raw_total: "Monthly Report(not relayed)"
wt_relay_total: "Monthly Report(relayed)"
wt_edit_relay: "Ticket Relay Editor"
wt_ticket: "Ticket"
wt_add_ticket: "Add ticket"
wt_no_permission: "No permission"
wt_loop_relay: "Ticket relation looped"
wt_set_holiday: "set holiday on this date"
wt_del_holiday: "del holiday on this date"
wt_apply_checked: "apply all checked"
wt_select_project: "Restrict project..."
wt_select_user: "Select user..."
wt_edit_memo: "Edit memo"
wt_pre_memo: "pre. memo"
wt_next_memo: "next memo"
wt_data_list: "Data list"
wt_input_ticket_numbers: "or input ticket numbers"
wt_delete_closed_tickets: "delete closed tickets"
wt_data_download: "data download"
wt_data_download_with_act: "data download(each activity)"
wt_opt_disp_ticket_with_closed: "[display closed ticket]"
wt_opt_disp_ticket_opened_only: "[display opened ticket only]"
wt_opt_disp_ticket_with_other_member: "[display other members ticket]"
wt_opt_disp_ticket_mine_only: "[display my ticket only]"
wt_bulkupdate_relations: "bulkupdate to parent"
permission_view_work_time_tab: "View Work time tab"
permission_view_work_time_other_member: "View other members' work time"
permission_edit_work_time_total: "Edit work time totals"
permission_edit_work_time_other_member: "Edit other members' work time"
wt_saved_value: "Saved timed: "
wt_legend: "Legend"
wt_style_default: "By default"
wt_style_assigned: "Assigned"
wt_style_worked: "Worked on"
wt_style__assigned_worked: "Assigned and worked on"
wt_style_overdue: "Overdue"
wt_style_assigned_overdue: "Assigned and overdue"
wt_style_overdue_worked: "Overdue and worked on"
wt_style_assigned_overdue_worked: "Assigned, overdue and worked on"
wt_account_start_day: "Company start day"
es:
work_time: "Agenda"
wt_update: "Actualizar"
wt_month_names: "Ene,Feb,Mar,Abr,May,Jun,Jul,Ago,Sep,Oct,Nov,Dic"
wt_week_day_names: "Dom,Lun,Mar,Mie,Jue,Vie,Sab"
wt_monthly_report: "Informe mensual"
wt_daily_report: "Informe diario"
wt_each_member_report: "Informe de miembro"
wt_raw_total: "Informe mensual(no se retransmite)"
wt_edit_relay: "Editor transmisión de peticiones"
wt_relay_total: "Informe mensual de peticiones"
wt_ticket: "Petición"
wt_add_ticket: "Añade petición"
wt_no_permission: "Sin permiso"
wt_loop_relay: "Transmisión de petición en bucle"
wt_set_holiday: "Establece vacaciones en esta fecha"
wt_del_holiday: "Elimina vacaciones en esta fecha"
wt_apply_checked: "Aplica a todos los seleccionados"
wt_select_project: "Seleccione proyecto..."
wt_select_user: "Seleccione usuario..."
wt_edit_memo: "Edita memorandum"
wt_pre_memo: "Anterior memorandum"
wt_next_memo: "Siguiente memorandum"
wt_data_list: "Lista de datos"
wt_input_ticket_numbers: "o introduzca numero de petición"
wt_delete_closed_tickets: "delete closed tickets"
wt_data_download: "data download"
wt_data_download_with_act: "data download(each activity)"
wt_opt_disp_ticket_with_closed: "[display closed ticket]"
wt_opt_disp_ticket_opened_only: "[display opened ticket only]"
wt_opt_disp_ticket_with_other_member: "[display other members ticket]"
wt_opt_disp_ticket_mine_only: "[display my ticket only]"
wt_bulkupdate_relations: "bulkupdate to parent"
permission_view_work_time_tab: "View Work time tab"
permission_view_work_time_other_member: "View other members' work time"
permission_edit_work_time_total: "Edit work time totals"
permission_edit_work_time_other_member: "Edit other members' work time"
wt_saved_value: "Saved timed: "
wt_legend: "Legend"
wt_style_default: "By default"
wt_style_assigned: "Assigned"
wt_style_worked: "Worked on"
wt_style__assigned_worked: "Assigned and worked on"
wt_style_overdue: "Overdue"
wt_style_assigned_overdue: "Assigned and overdue"
wt_style_overdue_worked: "Overdue and worked on"
wt_style_assigned_overdue_worked: "Assigned, overdue and worked on"
wt_account_start_day: "Company start day"
fr:
work_time: "Temps de travail"
wt_update: "Mettre à jour"
wt_month_names: "Jan,Fév,Mar,Avr,Mai,Juin,Juil,Août,Sep,Oct,Nov,Déc"
wt_week_day_names: "Dim,Lun,Mar,Mer,Jeu,Ven,Sam"
wt_monthly_report: "Rapport mensuel"
wt_daily_report: "Rapport journalier"
wt_each_member_report: "Rapport utilisateur"
wt_raw_total: "Rapport mensuel(non relayé)"
wt_relay_total: "Rapport mensuel (relayé)"
wt_edit_relay: "Ticket Relay Editor"
wt_ticket: "Demande"
wt_add_ticket: "Ajouter une demande"
wt_no_permission: "Vous n'avez pas les droits"
wt_loop_relay: "Boucle infinie sur une demande"
wt_set_holiday: "Placer cette date en jour férié"
wt_del_holiday: "Retirer cette date des jours fériés"
wt_apply_checked: "Appliquer pour toutes les cases cochées"
wt_select_project: "Restreindre à un projet"
wt_select_user: "Choisir un utilisateur"
wt_edit_memo: "Éditer la note"
wt_pre_memo: "Note précédente"
wt_next_memo: "Note suivante"
wt_data_list: "Liste brute"
wt_input_ticket_numbers: "ou entrez le numéro de la demande"
wt_delete_closed_tickets: "Supprimer les demandes fermées"
wt_data_download: "Téléchargement des données"
wt_data_download_with_act: "Téléchargement des données(each activity)"
wt_opt_disp_ticket_with_closed: "[afficher les demandes fermés]"
wt_opt_disp_ticket_opened_only: "[n'afficher que les demandes ouvertes]"
wt_opt_disp_ticket_with_other_member: "[afficher les demandes des autres membres]"
wt_opt_disp_ticket_mine_only: "[n'afficher que mes demandes]"
wt_bulkupdate_relations: "mise à jour en bloc du parent"
permission_view_work_time_tab: "Afficher l'onglet Temps de travail"
permission_view_work_time_other_member: "Afficher les temps des autres membres"
permission_edit_work_time_total: "Modifier les totaux de temps de travail"
permission_edit_work_time_other_member: "Modifier les temps des autres membres"
wt_saved_value: "Temps sauvegardé : "
wt_legend: "Légende"
wt_style_default: "Par défaut"
wt_style_assigned: "Affecté"
wt_style_worked: "Travail en cours"
wt_style__assigned_worked: "Affecté et travail en cours"
wt_style_overdue: "En retard"
wt_style_assigned_overdue: "Affecté et en retard"
wt_style_overdue_worked: "En retard et travail en cours"
wt_style_assigned_overdue_worked: "Affecté, en retard et travail en cours"
wt_account_start_day: "Company start day"
it:
work_time: "Tempo di lavoro"
wt_update: "Aggiorna"
wt_month_names: "Gen,Feb,Mar,Apr,Mag,Giu,Lug,Ago,Set,Ott,Nov,Dic"
wt_week_day_names: "Dom,Lun,Mar,Mer,Gio,Ven,Sab"
wt_monthly_report: "Report Mensile"
wt_daily_report: "Report Giornaliero"
wt_each_member_report: "Report Utente"
wt_raw_total: "Report Mensile (not relayed)"
wt_edit_relay: "Editor segnalazioni trasmesse"
wt_relay_total: "Report Mensile Segnalazioni Relayed"
wt_ticket: "Segnalazione"
wt_add_ticket: "Aggiungi segnalazione"
wt_no_permission: "Non autorizzato"
wt_loop_relay: "Ticket relation looped"
wt_set_holiday: "Imposta giorno come festivo"
wt_del_holiday: "Canc giorno come festivo"
wt_apply_checked: "Applica a selezionati"
wt_select_project: "Seleziona progetto..."
wt_select_user: "Seleziona utente..."
wt_edit_memo: "Modifica nota"
wt_pre_memo: "Nota precedente"
wt_next_memo: "Prossima nota"
wt_data_list: "Lista Dati"
wt_input_ticket_numbers: "o inserisci i numeri delle segnalazioni"
wt_delete_closed_tickets: "Elimina segnalazioni chiuse"
wt_data_download: "download di dati"
wt_data_download_with_act: "download di dati(each activity)"
wt_opt_disp_ticket_with_closed: "[mostra segnalazioni chiuse]"
wt_opt_disp_ticket_opened_only: "[mostra solo segnalazioni aperte]"
wt_opt_disp_ticket_with_other_member: "[mostra segnalazioni di altri membri]"
wt_opt_disp_ticket_mine_only: "[mostra solo le mie segnalazioni]"
wt_bulkupdate_relations: "bulkupdate to parent"
permission_view_work_time_tab: "View Work time tab"
permission_view_work_time_other_member: "View other members' work time"
permission_edit_work_time_total: "Edit work time totals"
permission_edit_work_time_other_member: "Edit other members' work time"
wt_saved_value: "Saved timed: "
wt_legend: "Legend"
wt_style_default: "By default"
wt_style_assigned: "Assigned"
wt_style_worked: "Worked on"
wt_style__assigned_worked: "Assigned and worked on"
wt_style_overdue: "Overdue"
wt_style_assigned_overdue: "Assigned and overdue"
wt_style_overdue_worked: "Overdue and worked on"
wt_style_assigned_overdue_worked: "Assigned, overdue and worked on"
wt_account_start_day: "Company start day"
ja:
work_time: "工数"
wt_update: "更新"
wt_month_names: "1,2,3,4,5,6,7,8,9,10,11,12"
wt_week_day_names: "日,月,火,水,木,金,土"
wt_monthly_report: "月間工数表"
wt_daily_report: "日毎工数"
wt_each_member_report: "個人工数集計"
wt_raw_total: "月間集計(付替なし)"
wt_relay_total: "月間集計(付替あり)"
wt_edit_relay: "チケット付替設定"
wt_ticket: "チケット"
wt_add_ticket: "チケット追加"
wt_no_permission: "権限がありません"
wt_loop_relay: "チケット関係がループしています"
wt_set_holiday: "この日を休日に設定"
wt_del_holiday: "この日の休日を解除"
wt_apply_checked: "チェックを適用"
wt_select_project: "プロジェクト限定"
wt_select_user: "ユーザを選択"
wt_edit_memo: "メモを編集"
wt_pre_memo: "前のメモ"
wt_next_memo: "次のメモ"
wt_data_list: "データリスト"
wt_input_ticket_numbers: "またはチケット番号を入力"
wt_delete_closed_tickets: "終了チケット削除"
wt_data_download: "データダウンロード"
wt_data_download_with_act: "活動別データダウンロード"
wt_opt_disp_ticket_with_closed: "[終了したチケットも表示]"
wt_opt_disp_ticket_opened_only: "[オープン中のチケットのみ表示]"
wt_opt_disp_ticket_with_other_member: "[他のメンバーのチケットも表示]"
wt_opt_disp_ticket_mine_only: "[自分のチケットのみ表示]"
wt_bulkupdate_relations: "親チケットへ一括更新"
permission_view_work_time_tab: "工数入力集計対象者"
permission_view_work_time_other_member: "他のメンバーの工数の表示"
permission_edit_work_time_total: "工数集計の設定"
permission_edit_work_time_other_member: "他のメンバーの工数の入力"
wt_saved_value: "保存時間: "
wt_legend: "チケット表示凡例"
wt_style_default: "通常表示"
wt_style_assigned: "担当期間"
wt_style_worked: "書込"
wt_style__assigned_worked: "担当期間,書込"
wt_style_overdue: "期限超過"
wt_style_assigned_overdue: "担当,期限超過"
wt_style_overdue_worked: "期限超過,書込"
wt_style_assigned_overdue_worked: "担当,超過,書込"
wt_account_start_day: "集計開始日"
# Translation by Ki Won Kim (http://xyz37.blog.me, xyz37@naver.com)
ko:
work_time: "작업 시간"
wt_update: "수정"
wt_month_names: "1월,2월,3월,4월,5월,6월,7월,8월,9월,10월,11월,12월"
wt_week_day_names: "일,월,화,수,목,금,토"
wt_monthly_report: "월간 보고"
wt_daily_report: "일간 보고"
wt_each_member_report: "사용자 보고"
wt_raw_total: "월간 보고(연동 없음)"
wt_edit_relay: "티켓 연동 편집기"
wt_relay_total: "연동된 티켓 월간 보고"
wt_ticket: "티켓"
wt_add_ticket: "티켓 추가"
wt_no_permission: "권한 없음"
wt_loop_relay: "티켓 관계가 반복(Looping) 됩니다."
wt_set_holiday: " 날짜를 휴일로 지정"
wt_del_holiday: " 날짜를 휴일에서 제거"
wt_apply_checked: "선택 항목 적용"
wt_select_project: "프로젝트 선택..."
wt_select_user: "사용자 선택..."
wt_edit_memo: "메모 수정"
wt_pre_memo: "이전 메모"
wt_next_memo: "다음 메모"
wt_data_list: "데이터 목록"
wt_input_ticket_numbers: "또는 티켓 번호를 입력하세요."
wt_delete_closed_tickets: "완료된 티켓 삭제"
wt_data_download: "데이터 다운로드"
wt_data_download_with_act: "데이터 다운로드(each activity)"
wt_opt_disp_ticket_with_closed: "[display closed ticket]"
wt_opt_disp_ticket_opened_only: "[display opened ticket only]"
wt_opt_disp_ticket_with_other_member: "[display other members ticket]"
wt_opt_disp_ticket_mine_only: "[display my ticket only]"
wt_bulkupdate_relations: "bulkupdate to parent"
permission_view_work_time_tab: "View Work time tab"
permission_view_work_time_other_member: "View other members' work time"
permission_edit_work_time_total: "Edit work time totals"
permission_edit_work_time_other_member: "Edit other members' work time"
wt_saved_value: "Saved timed: "
wt_legend: "Legend"
wt_style_default: "By default"
wt_style_assigned: "Assigned"
wt_style_worked: "Worked on"
wt_style__assigned_worked: "Assigned and worked on"
wt_style_overdue: "Overdue"
wt_style_assigned_overdue: "Assigned and overdue"
wt_style_overdue_worked: "Overdue and worked on"
wt_style_assigned_overdue_worked: "Assigned, overdue and worked on"
wt_account_start_day: "Company start day"
"no":
work_time: "Arbeidstimer"
wt_update: "Oppdater"
wt_month_names: "Jan,Feb,Mar,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Des"
wt_week_day_names: "Søn,Man,Tir,Ons,Tor,Fre,Lør"
wt_monthly_report: "Månedsrapport"
wt_daily_report: "Dagsrapport"
wt_each_member_report: "Medlemsrapport"
wt_raw_total: "Månedsrapport (ikke samlet)"
wt_relay_total: "Månedsrapport (samlet)"
wt_edit_relay: "Samle poster"
wt_ticket: "Poster"
wt_add_ticket: "Legg til post"
wt_no_permission: "Ikke tilgang"
wt_loop_relay: "Samlekobling i løkke"
wt_set_holiday: "merk dato som feriedag"
wt_del_holiday: "fjern feriedagmerking dat"
wt_apply_checked: "Legg til valgte poster"
wt_select_project: "Begrens prosjekt..."
wt_select_user: "Velg bruker..."
wt_edit_memo: "Endre notat"
wt_pre_memo: "forr. notat"
wt_next_memo: "neste notat"
wt_data_list: "Data list"
wt_input_ticket_numbers: "eller legg inn saksnummer"
wt_delete_closed_tickets: "Slett lukkede poster"
wt_data_download: "Last ned data"
wt_data_download_with_act: "Last ned data(each activity)"
wt_opt_disp_ticket_with_closed: "[vis lukkede saker]"
wt_opt_disp_ticket_opened_only: "[vis bare åpne saker]"
wt_opt_disp_ticket_with_other_member: "[vis andre brukeres saker]"
wt_opt_disp_ticket_mine_only: "[vis bare mine saker]"
wt_bulkupdate_relations: "Samle alle til sin forelder"
permission_view_work_time_tab: "Vise arbeidstime-fane"
permission_view_work_time_other_member: "Vise andres arbeidstimer"
permission_edit_work_time_total: "Endre oppsummerte arbeidstimer"
permission_edit_work_time_other_member: "Endre andres arbeidstimer"
wt_saved_value: "Lagret timer: "
wt_legend: "Nøkkel"
wt_style_default: "Forvalgt"
wt_style_assigned: "Tildelt"
wt_style_worked: "Jobbet med"
wt_style__assigned_worked: "Tildelt og jobbet med"
wt_style_overdue: "Over tidsfrist"
wt_style_assigned_overdue: "Tildelt og over tidsfrist"
wt_style_overdue_worked: "Over tidsfrist og jobbet med"
wt_style_assigned_overdue_worked: "Tildelt, over tidsfrist og jobbet med"
wt_account_start_day: "Company start day"
po:
work_time: "WorkTime"
wt_update: "Update"
wt_month_names: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec"
wt_week_day_names: "Sun,Mon,Tue,Wed,Thu,Fri,Sat"
wt_monthly_report: "Monthly Report"
wt_daily_report: "Daily Report"
wt_each_member_report: "Member Report"
wt_raw_total: "Monthly Report(not relayed)"
wt_relay_total: "Rapport mensuel (relayed)"
wt_edit_relay: "Ticket Relay Editor"
wt_ticket: "Ticket"
wt_add_ticket: "Add ticket"
wt_no_permission: "No permission"
wt_loop_relay: "Ticket relation looped"
wt_set_holiday: "set holiday on this date"
wt_del_holiday: "del holiday on this date"
wt_apply_checked: "apply all checked"
wt_select_project: "Restrict project..."
wt_select_user: "Select user..."
wt_edit_memo: "Edit memo"
wt_pre_memo: "pre. memo"
wt_next_memo: "next memo"
wt_data_list: "Data list"
wt_input_ticket_numbers: "or input ticket numbers"
wt_delete_closed_tickets: "delete closed tickets"
wt_data_download: "data download"
wt_data_download_with_act: "data download(each activity)"
wt_opt_disp_ticket_with_closed: "[display closed ticket]"
wt_opt_disp_ticket_opened_only: "[display opened ticket only]"
wt_opt_disp_ticket_with_other_member: "[display other members ticket]"
wt_opt_disp_ticket_mine_only: "[display my ticket only]"
wt_bulkupdate_relations: "bulkupdate to parent"
permission_view_work_time_tab: "View Work time tab"
permission_view_work_time_other_member: "View other members' work time"
permission_edit_work_time_total: "Edit work time totals"
permission_edit_work_time_other_member: "Edit other members' work time"
wt_saved_value: "Saved timed: "
wt_legend: "Legend"
wt_style_default: "By default"
wt_style_assigned: "Assigned"
wt_style_worked: "Worked on"
wt_style__assigned_worked: "Assigned and worked on"
wt_style_overdue: "Overdue"
wt_style_assigned_overdue: "Assigned and overdue"
wt_style_overdue_worked: "Overdue and worked on"
wt_style_assigned_overdue_worked: "Assigned, overdue and worked on"
wt_account_start_day: "Company start day"
pt-BR:
work_time: "Agenda"
wt_update: "Atualização"
wt_month_names: "Jan,Fev,Mar,Abr,Mai,Jun,Jul,Ago,Set,Out,Nov,Dez"
wt_week_day_names: "Dom,Seg,Ter,Qua,Qui,Sex,Sab"
wt_monthly_report: "Relatório Mensal"
wt_daily_report: "Relatório Diário"
wt_each_member_report: "Relatório Membro"
wt_raw_total: "Relatório Mensal(não reenviado)"
wt_edit_relay: "Editor de etarefas enviada"
wt_relay_total: "Relatório mensal de tarefas reenviadas"
wt_ticket: "Tarefa"
wt_add_ticket: "Nova tarefa"
wt_no_permission: "Sem permissão"
wt_no_permission_del: "Não é possível excluir porque pedidos de horas-homem"
wt_loop_relay: "Ticket relation looped"
wt_set_holiday: "define feriado nesta data"
wt_del_holiday: "define data normal"
wt_apply_checked: "Aplicar selecionados"
wt_select_project: "Selecione projeto..."
wt_select_user: "Selecione usuário..."
wt_edit_memo: "Edita anotação"
wt_pre_memo: "prev anotação"
wt_next_memo: "prox anotação"
wt_data_list: "Lista de data"
wt_input_ticket_numbers: "ou inclua o número da tarefa"
wt_delete_closed_tickets: "apagar tarefas fechadas"
wt_data_download: "download de dados"
wt_data_download_with_act: "download de dados(each activity)"
wt_opt_disp_ticket_with_closed: "[exibe tarefa fechada]"
wt_opt_disp_ticket_opened_only: "[exibe somente tafera aberta]"
wt_opt_disp_ticket_with_other_member: "[exibe tarefa de outros membros]"
wt_opt_disp_ticket_mine_only: "[existe somente minha tarefa]"
wt_bulkupdate_relations: "bulkupdate to parent"
permission_view_work_time_tab: "Ver a tab tempo de trabalho"
permission_view_work_time_other_member: "Ver o tempo de trabalho de outros membros"
permission_edit_work_time_total: "Edit work time totals"
permission_edit_work_time_other_member: "Editar o tempo de trabalho de outros membros"
wt_saved_value: "Tempo salvo: "
wt_legend: "Legenda"
wt_style_default: "Por padrão"
wt_style_assigned: "Atribuído"
wt_style_worked: "Trabalhando em"
wt_style__assigned_worked: "Atrasado e trabalhando em"
wt_style_overdue: "Atrasada"
wt_style_assigned_overdue: "Atribuído e em atraso"
wt_style_overdue_worked: "Atrasada e trabalhado"
wt_style_assigned_overdue_worked: "Atribuída, atrasada e trabalhando em"
wt_account_start_day: "Empresa começar dia"
ru:
work_time: "Рабочий отчёт"
wt_update: "Обновить"
wt_month_names: "Янв,Фев,Мар,Апр,Май,Июн,Июл,Авг,Сен,Окт,Ноя,Дек"
wt_week_day_names: "Вск,Пон,Втр,Срд,Чет,Пят,Суб"
wt_monthly_report: "Ежемесячный отчёт"
wt_daily_report: "Ежедневный отчёт"
wt_each_member_report: "Отчёт по пользователям"
wt_raw_total: "Отчёт за месяц (не связанный)"
wt_relay_total: "Отчёт за месяц (связанный)"
wt_edit_relay: "Редактор связей задач"
wt_ticket: "Задача"
wt_add_ticket: "Добавить задачу"
wt_no_permission: "Ограничение прав"
wt_loop_relay: "Взаимосвязь задач"
wt_set_holiday: "установить выходной на дату"
wt_del_holiday: "удалить выходной на дату"
wt_apply_checked: "применить все отмеченные"
wt_select_project: "Отфильтровать по проектам..."
wt_select_user: "Выбор пользователя..."
wt_edit_memo: "Редактировать примечание"
wt_pre_memo: "предыдущее примечание"
wt_next_memo: "следующе примечание"
wt_data_list: "Список дат"
wt_input_ticket_numbers: "или введите номера задач (через запятую)"
wt_delete_closed_tickets: "удалить закрытые задачи"
wt_data_download: "экспорт данных"
wt_data_download_with_act: "экспорт данных(each activity)"
wt_opt_disp_ticket_with_closed: "[отобразить закрытые задачи]"
wt_opt_disp_ticket_opened_only: "[отобразить только открытые задачи]"
wt_opt_disp_ticket_with_other_member: "[отобразить задачи других участников]"
wt_opt_disp_ticket_mine_only: "[отобразить только мои задачи]"
wt_bulkupdate_relations: "массовое обновление связанных"
permission_view_work_time_tab: "Просмотр закладки рабочего времени"
permission_view_work_time_other_member: "Просмотр рабочего времени других участников"
permission_edit_work_time_total: "Редактирование рабочего времени"
permission_edit_work_time_other_member: "Редактирование рабочего времени других участников"
wt_saved_value: "Сохранённое время: "
wt_legend: "Соответствие цветов"
wt_style_default: "По умолчанию"
wt_style_assigned: "Назначенные"
wt_style_worked: "В работе"
wt_style__assigned_worked: "Назначенные и в работе"
wt_style_overdue: "Просроченные"
wt_style_assigned_overdue: "Назначенные и просроченные"
wt_style_overdue_worked: "Просроченные и в работе"
wt_style_assigned_overdue_worked: "Назначенные, в работе и просроченные"
wt_account_start_day: "Company start day"
tr:
work_time: "ÇalışmaZamanı"
wt_update: "Güncelle"
wt_month_names: "Oca,Şub,Mar,Nis,May,Haz,Tem,Ağu,Eyl,Eki,Kas,Ara"
wt_week_day_names: "Paz,Pzt,Sal,Çar,Per,Cum,Cmt"
wt_monthly_report: "Aylık Rapor"
wt_daily_report: "Günlük Rapor"
wt_each_member_report: "Kullanıcı Raporu"
wt_raw_total: "Aylık Rapor(aktarımsız)"
wt_edit_relay: "İş Aktarım Düzenleyici"
wt_relay_total: "Aktarılmış İş Aylık Rapor"
wt_ticket: "İş"
wt_add_ticket: "İş ekle"
wt_no_permission: "Yetki yok"
wt_loop_relay: "İş ilişkileri dögülü"
wt_set_holiday: "bu tarihe tatil koy"
wt_del_holiday: "bu tarihteki tatili sil"
wt_apply_checked: "seçililere uygula"
wt_select_project: "Birim seç..."
wt_select_user: "Kullanıcı seç..."
wt_edit_memo: "Not düzenle"
wt_pre_memo: "önceki not"
wt_next_memo: "sonraki not"
wt_data_list: "Veri Liste"
wt_input_ticket_numbers: "veya numarası gir"
wt_delete_closed_tickets: "kapalı işleri sil"
wt_data_download: "veri indir"
wt_data_download_with_act: "veri indir(each activity)"
wt_opt_disp_ticket_with_closed: "[kapalı işleri göster]"
wt_opt_disp_ticket_opened_only: "[sadece açık işleri göster]"
wt_opt_disp_ticket_with_other_member: "[başkalarının işlerini göster]"
wt_opt_disp_ticket_mine_only: "[sadece kendi işlerimi göster]"
wt_bulkupdate_relations: "üst işi toplu güncelle"
permission_view_work_time_tab: "ÇalışmaZamanı sekmesini gör"
permission_view_work_time_other_member: "Başka kişilerin çalışma zamanını gör"
permission_edit_work_time_total: "Çalışma zamanı toplamlarını düzenle"
permission_edit_work_time_other_member: "Başka kiçinin çalışma zamanını düzenle"
wt_saved_value: "Saklanmış değer: "
wt_legend: "İşaretler"
wt_style_default: "Öntanımlı"
wt_style_assigned: "Atanmış"
wt_style_worked: "Çalışılmış"
wt_style__assigned_worked: "Atanmış ve çalışılmış"
wt_style_overdue: "Zamanı geçmiş"
wt_style_assigned_overdue: "Atanmış ve zamanı geçmiş"
wt_style_overdue_worked: "Zamanı geçmiş ve çalışılmış"
wt_style_assigned_overdue_worked: "Atanmış, zamanı geçmiş, çalışılmış"
wt_account_start_day: "Company start day"
zh:
work_time: "工时日志"
wt_update: "更新"
wt_month_names: "一月,二月,三月,四月,五月,六月,七月,八月,九月,十月,十一月,十二月"
wt_week_day_names: "周日,周一,周二,周三,周四,周五,周六"
wt_monthly_report: "月度报告"
wt_daily_report: "日报告"
wt_each_member_report: "成员报告"
wt_raw_total: "月度报告(not relayed)"
wt_relay_total: "月度报告(relayed)"
wt_edit_relay: "工作 Relay 编辑器"
wt_ticket: "工作"
wt_add_ticket: "新增工作"
wt_no_permission: "无权限"
wt_loop_relay: "工作关系循环"
wt_set_holiday: "设置当前日期为假期"
wt_del_holiday: "设置当期日期为工作日"
wt_apply_checked: "应用所有已选项"
wt_select_project: "选择项目..."
wt_select_user: "选择用户..."
wt_edit_memo: "编辑备忘录"
wt_pre_memo: "前一备忘录"
wt_next_memo: "后一备忘录"
wt_data_list: "数据清单"
wt_input_ticket_numbers: "请输入编号"
wt_delete_closed_tickets: "删除已关闭的工作"
wt_data_download: "数据下载"
wt_data_download_with_act: "数据下载(按活动)"
wt_opt_disp_ticket_with_closed: "[显示已关闭的工作]"
wt_opt_disp_ticket_opened_only: "[只显示打开的工作]"
wt_opt_disp_ticket_with_other_member: "[显示其他成员的工作]"
wt_opt_disp_ticket_mine_only: "[只显示我的工作]"
wt_bulkupdate_relations: "批量更新到上一级"
permission_view_work_time_tab: "查看工时日志"
permission_view_work_time_other_member: "查看其他成员的工时日志"
permission_edit_work_time_total: "编辑工时日志总计"
permission_edit_work_time_other_member: "编辑其他成员的工时日志"
wt_saved_value: "已节省: "
wt_legend: "图例"
wt_style_default: "按默认"
wt_style_assigned: "已分配"
wt_style_worked: "已处理"
wt_style__assigned_worked: "已分配并已耗费"
wt_style_overdue: "逾期"
wt_style_assigned_overdue: "已分配并已逾期"
wt_style_overdue_worked: "已逾期并已耗费"
wt_style_assigned_overdue_worked: "已分配、已逾期并已耗费"
wt_account_start_day: "统计开始日"
RedmineApp::Application.routes.draw do
match 'work_time/:action', :to => 'work_time#index', :via => [:get, :post]
match 'work_time/:action/:id', :to => 'work_time#show', :via => [:get, :post]
end
class CreateUserIssueMonths < ActiveRecord::Migration
def self.up
create_table :user_issue_months do |t|
t.column :uid, :integer
t.column :issue, :integer
t.column :month, :string
t.column :odr, :integer
end
end
def self.down
drop_table :user_issue_months
end
end
class CreateWtMemberOrders < ActiveRecord::Migration
def self.up
create_table :wt_member_orders do |t|
t.column :user_id, :integer
t.column :position, :integer
end
end
def self.down
drop_table :wt_member_orders
end
end
class CreateWtTicketRelays < ActiveRecord::Migration
def self.up
create_table :wt_ticket_relays do |t|
t.column :issue_id, :integer
t.column :position, :integer
t.column :parent, :integer
end
end
def self.down
drop_table :wt_ticket_relays
end
end
class AddPrjToMemOdr < ActiveRecord::Migration
def self.up
add_column :wt_member_orders, :prj_id, :integer, :default => nil
end
def self.down
remove_column :wt_member_orders, :prj_id
end
end
class CreateWtDailyMemos < ActiveRecord::Migration
def self.up
create_table :wt_daily_memos do |t|
t.column :day, :date
t.column :user_id, :integer
t.column :created_on, :timestamp
t.column :updated_on, :timestamp
t.column :description, :text
end
end
def self.down
drop_table :wt_daily_memos
end
end
class CreateWtProjectOrders < ActiveRecord::Migration
def self.up
create_table :wt_project_orders do |t|
t.column :prj, :integer
t.column :uid, :integer
t.column :dsp_prj, :integer
t.column :dsp_pos, :integer
end
end
def self.down
drop_table :wt_project_orders
end
end
class CreateWtHolidays < ActiveRecord::Migration
def self.up
create_table :wt_holidays do |t|
t.column :holiday, :date
t.column :created_on, :datetime
t.column :created_by, :integer
t.column :deleted_on, :datetime
t.column :deleted_by, :integer
end
end
def self.down
drop_table :wt_holidays
end
end
class RemoveMonthFromUserIssueMonth < ActiveRecord::Migration
def self.up
remove_column :user_issue_months, :month
end
def self.down
add_column :user_issue_months, :month, :string, :default => nil
end
end
class RemovePrjFromWtProjectOrders < ActiveRecord::Migration
def self.up
remove_column :wt_project_orders, :prj
end
def self.down
add_column :wt_project_orders, :prj, :integer, :default => nil
end
end
require 'redmine'
Redmine::Plugin.register :redmine_work_time do
name 'Redmine Work Time plugin'
author 'Tomohisa Kusukawa'
description 'A plugin to view and update TimeEntry by each user'
version '0.3.4'
url 'http://www.redmine.org/plugins/redmine_work_time'
author_url 'http://about.me/tkusukawa'
project_module :work_time do
permission :view_work_time_tab, {:work_time =>
[:show,:member_monthly_data,
:total,:total_data,:edit_relay,:relay_total,:relay_total_data,
:total_data_with_act, :relay_total_data_with_act,
:register_project_settings,
]}
permission :view_work_time_other_member, {:work_time =>
[:show,:member_monthly_data,
:total,:total_data,:edit_relay,:relay_total,:relay_total_data,
:total_data_with_act, :relay_total_data_with_act,
:register_project_settings,
]}
permission :edit_work_time_total, {}
permission :edit_work_time_other_member, {}
end
menu :account_menu, :work_time,
{:controller => 'work_time', :action => 'index'},
:before => :my_account,
:caption => :work_time,
:if => Proc.new{User.current.logged? && Setting.plugin_redmine_work_time['show_account_menu']}
menu :project_menu, :work_time,
{:controller => 'work_time', :action => 'show'}, :caption => :work_time,
:after => :gantt
settings :default => {'account_start_days' => {}, 'show_account_menu' => 'true'},
:partial => 'settings/work_time_settings'
Rails.configuration.to_prepare do
require_dependency 'projects_helper'
unless ProjectsHelper.included_modules.include? WorkTimeProjectsHelperPatch
ProjectsHelper.send(:include, WorkTimeProjectsHelperPatch)
end
end
end
require_dependency 'projects_helper'
module WorkTimeProjectsHelperPatch
def self.included base # :nodoc:
base.send :include, ProjectsHelperMethodsWorkTime
base.class_eval do
alias_method_chain :project_settings_tabs, :work_time
end
end
end
module ProjectsHelperMethodsWorkTime
def project_settings_tabs_with_work_time
tabs = project_settings_tabs_without_work_time
action = {:name => 'work_time',
:controller => 'work_time',
:action => :show,
:partial => 'settings/work_time_project_settings', :label => :work_time}
tabs << action if User.current.allowed_to?(:edit_work_time_total, @project)
tabs
end
end
Redmineプラグイン(WorkTime) 作成覚書
cd $RAILS_ROOT
***プラグインの雛形生成***
# ruby script/generate redmine_plugin work_time
***バージョン管理に登録***
# cd vendor/plugins/redmine_work_time
# hg init
# hg commit -A
***init.rbの編集***
# vi init.rb
# cat init.rb
require 'redmine'
Redmine::Plugin.register :redmine_work_time do
name 'Redmine Work Time plugin'
author 'Tomohisa Kusukawa'
description 'A plugin to view and update TimeEntry by each user'
version '0.0.1'
project_module :work_time do
permission :view_work_time_tab, {:work_time => [:show]}, :public => true
end
menu :project_menu, :work_time, {:controller => 'work_time', :action => 'show'}, :caption => :work_time
end
*** モデル(DBテーブル)の作成 ***
# ruby script/generate redmine_plugin_model work_time user_issue_month uid:integer issue:integer month:string odr:integer
exists app/models/
create test/unit/
create test/fixtures/
create app/models/user_issue_month.rb
create test/unit/user_issue_month_test.rb
create test/fixtures/user_issue_months.yml
exists db/migrate
create db/migrate/20090104004624_create_user_issue_months.rb
# mv db/migrate/20090104004624_create_user_issue_months.rb db/migrate/001_create_user_issue_months.rb
# ruby script/generate redmine_plugin_model work_time wt_member_order user_id:integer position:integer
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/wt_member_order.rb
create test/unit/wt_member_order_test.rb
create test/fixtures/wt_member_orders.yml
exists db/migrate
create db/migrate/20090131105945_create_wt_member_orders.rb
# mv db/migrate/20090131105945_create_wt_member_orders.rb db/migrate/002_create_wt_member_orders.rb
# ruby script/generate redmine_plugin_model work_time wt_ticket_relay issue_id:integer position:integer parent:integer
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/wt_ticket_relay.rb
create test/unit/wt_ticket_relay_test.rb
create test/fixtures/wt_ticket_relays.yml
exists db/migrate
create db/migrate/20090131110021_create_wt_ticket_relays.rb
# mv db/migrate/20090131110021_create_wt_ticket_relays.rb db/migrate/003_create_wt_ticket_relays.rb
# vi db/migrate/004_add_prj_to_mem_odr.rb
# ruby script/generate redmine_plugin_model work_time wt_daily_memo day:date user_id:integer created_on:timestamp updated_on:timestamp description:text
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/wt_daily_memo.rb
create test/unit/wt_daily_memo_test.rb
create test/fixtures/wt_daily_memos.yml
exists db/migrate
create db/migrate/20090221151021_create_wt_daily_memos.rb
# mv db/migrate/20090221151021_create_wt_daily_memos.rb db/migrate/005_create_wt_daily_memos.rb
# rake db:migrate_plugins RAILS_ENV=production
*** コントローラの作成 ***
# ruby script/generate redmine_plugin_controller work_time work_time show
exists app/controllers/
exists app/helpers/
create app/views/work_time
create test/functional/
create app/controllers/work_time_controller.rb
create test/functional/work_time_controller_test.rb
create app/helpers/work_time_helper.rb
create app/views/work_time/show.html.erb
# cd $RAILS_ROOT/vendor/plugins/redmine_work_time/app/controllers/
# vi work_time_controller.rb
*** ビューの編集 ***
# cd $RAILS_ROOT/vendor/plugins/redmine_work_time/app/view/work_time/
# vi show.html.erb
######################################################################### version 0.0.33
ユーザーの月毎表示におけるチケットトータル工数の表示を左側に変更
######################################################################### version 0.0.34
ユーザ毎の工数表示をプロジェクト毎にまとめて表示するように変更
# cd $RAILS_ROOT
# ruby script/generate redmine_plugin_model work_time wt_project_orders prj:integer uid:integer dsp_prj:integer dsp_pos:integer
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/wt_project_orders.rb
create test/unit/wt_project_orders_test.rb
create test/fixtures/wt_project_orders.yml
exists db/migrate
create db/migrate/20090531095136_create_wt_project_orders.rb
# cd vendor/plugins/redmine_work_time/db/migrate/
# mv 20090531095136_create_wt_project_orders.rb 006_create_wt_project_orders.rb
# cd $RAILS_ROOT
# rake db:migrate_plugins RAILS_ENV=production
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
one:
id: 1
uid: 1
issue: 1
month: MyString
odr: 1
two:
id: 2
uid: 1
issue: 1
month: MyString
odr: 1
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
one:
id: 1
day: 2009-02-22
user_id: 1
created_on: 2009-02-22 00:10:21
updated_on: 2009-02-22 00:10:21
description: MyText
two:
id: 2
day: 2009-02-22
user_id: 1
created_on: 2009-02-22 00:10:21
updated_on: 2009-02-22 00:10:21
description: MyText
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
one:
id: 1
holiday: 2009-09-12
created_on: 2009-09-12 18:48:19
created_by: 1
deleted_on: 2009-09-12 18:48:19
deleted_by: 1
two:
id: 2
holiday: 2009-09-12
created_on: 2009-09-12 18:48:19
created_by: 1
deleted_on: 2009-09-12 18:48:19
deleted_by: 1
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
one:
id: 1
user_id: 1
position: 1
prj_id: 1
two:
id: 2
user_id: 1
position: 1
prj_id: 1
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
one:
id: 1
prj: 1
uid: 1
dsp_prj: 1
dsp_pos: 1
two:
id: 2
prj: 1
uid: 1
dsp_prj: 1
dsp_pos: 1
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
one:
id: 1
issue_id: 1
position: 1
parent: 1
two:
id: 2
issue_id: 1
position: 1
parent: 1
require File.dirname(__FILE__) + '/../test_helper'
class WorkTimeControllerTest < ActionController::TestCase
# Replace this with your real tests.
def test_truth
assert true
end
end
# Load the normal Rails helper
require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')
# Ensure that we are using the temporary fixture path
Engines::Testing.set_fixture_path
require File.dirname(__FILE__) + '/../test_helper'
class UserIssueMonthTest < Test::Unit::TestCase
fixtures :user_issue_months
# Replace this with your real tests.
def test_truth
assert true
end
end
require File.dirname(__FILE__) + '/../test_helper'
class WtDailyMemoTest < Test::Unit::TestCase
fixtures :wt_daily_memos
# Replace this with your real tests.
def test_truth
assert true
end
end
require File.dirname(__FILE__) + '/../test_helper'
class WtHolidaysTest < Test::Unit::TestCase
fixtures :wt_holidays
# Replace this with your real tests.
def test_truth
assert true
end
end
require File.dirname(__FILE__) + '/../test_helper'
class WtMemberOrderTest < Test::Unit::TestCase
fixtures :wt_member_orders
# Replace this with your real tests.
def test_truth
assert true
end
end
require File.dirname(__FILE__) + '/../test_helper'
class WtProjectOrdersTest < Test::Unit::TestCase
fixtures :wt_project_orders
# Replace this with your real tests.
def test_truth
assert true
end
end
require File.dirname(__FILE__) + '/../test_helper'
class WtTicketRelayTest < Test::Unit::TestCase
fixtures :wt_ticket_relays
# Replace this with your real tests.
def test_truth
assert true
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment