774 lines
27 KiB
Ruby
774 lines
27 KiB
Ruby
|
# SPDX-License-Identifier: Apache-2.0
|
||
|
# Copyright (C) 2021 OKTET Labs Ltd. All rights reserved.
|
||
|
|
||
|
#
|
||
|
# Class Diary for Diary Management Application.
|
||
|
#
|
||
|
|
||
|
require_relative 'ldap_record'
|
||
|
require_relative 'project'
|
||
|
require_relative 'diary_policy'
|
||
|
require_relative 'diary_datamapper'
|
||
|
|
||
|
DIARY_TEMPLATE = AmritaTemplate.new("diary.html")
|
||
|
|
||
|
def debug_log(msg)
|
||
|
STDERR.puts "DEBUG: " + msg
|
||
|
end
|
||
|
|
||
|
def show_action_button(value, name)
|
||
|
button = "<button type=\"submit\" name=\"diary:action:verb\" "
|
||
|
button += "value=\"" + value + "\""
|
||
|
button += " class=\"btn btn-primary btn-sm\" _id=\"edit\">" + name + "</button>"
|
||
|
|
||
|
return button
|
||
|
end
|
||
|
|
||
|
def show_checkbox(name, checked)
|
||
|
button = "<input type=\"checkbox\" name=\"diary:list:" + name + "\" class=\"form-check-input\" _id=\"" + name + "\" "
|
||
|
button += "checked" if checked[0] == "on"
|
||
|
button += ">"
|
||
|
|
||
|
return noescape { button }
|
||
|
end
|
||
|
|
||
|
def transform_links(prj, text)
|
||
|
if prj and text
|
||
|
text = prj.transform_links(text)
|
||
|
end
|
||
|
|
||
|
return noescape { text }
|
||
|
end
|
||
|
|
||
|
# All timestamps in database are in UTC, so we convert them to localtime
|
||
|
# before printing if they are valid
|
||
|
class DbTime
|
||
|
@@offset = DateTime.now.offset
|
||
|
|
||
|
def DbTime.now
|
||
|
# DBI used previously DBI::Timestamp for mapping SQL DATETIME,
|
||
|
# now it is deprecated, DateTime is used
|
||
|
if DBI::VERSION >= "0.4"
|
||
|
DateTime.now.new_offset(0)
|
||
|
else
|
||
|
Time.now.utc
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def initialize(t)
|
||
|
@time = t
|
||
|
end
|
||
|
|
||
|
def to_s
|
||
|
if @time.is_a? DBI::Timestamp
|
||
|
@time.to_time.localtime.strftime("%Y-%m-%d %H:%M:%S")
|
||
|
elsif @time.is_a? DateTime and @time.mjd > 0
|
||
|
@time.new_offset(@@offset).strftime("%Y-%m-%d %H:%M:%S")
|
||
|
else
|
||
|
"<N/A>"
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class DiaryState
|
||
|
NORMAL = 0
|
||
|
REQ_APPROVAL = 1
|
||
|
APPROVED = 2
|
||
|
REJECTED = 3
|
||
|
DENIED = 4
|
||
|
|
||
|
def DiaryState.valid(value)
|
||
|
value.to_i == NORMAL || value.to_i == APPROVED
|
||
|
end
|
||
|
|
||
|
def initialize(x)
|
||
|
@value = x
|
||
|
end
|
||
|
|
||
|
def to_s
|
||
|
case @value
|
||
|
when NORMAL then "regular"
|
||
|
when REQ_APPROVAL then "requires approval"
|
||
|
when APPROVED then "approved"
|
||
|
when REJECTED then "rejected"
|
||
|
when DENIED then "denied"
|
||
|
else "unknown"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def to_html
|
||
|
state_name = to_s
|
||
|
@value == NORMAL ? state_name :
|
||
|
Amrita.e(:span, :class =>
|
||
|
"state-" + state_name.tr_s(' ','-')){ state_name }
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class DiaryMapper < DataMapper
|
||
|
def initialize
|
||
|
super("diary")
|
||
|
end
|
||
|
|
||
|
def instantiate(id, attributes)
|
||
|
Diary.new(id, attributes)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class DiaryPerson
|
||
|
attr_reader :person
|
||
|
attr_accessor :hours
|
||
|
attr_accessor :records
|
||
|
attr_accessor :lag
|
||
|
attr_accessor :week
|
||
|
|
||
|
def initialize(person)
|
||
|
@person = person
|
||
|
@hours = 0
|
||
|
@records = 0
|
||
|
@lag = 0
|
||
|
@week = Hash.new
|
||
|
Date::ABBR_DAYNAMES.each { |x| @week[x] = {:hours => 0, :records => 0} }
|
||
|
end
|
||
|
|
||
|
def register(hours, ddate, created)
|
||
|
@hours += hours.to_i
|
||
|
@records += 1
|
||
|
|
||
|
# We assume that 20:00 is the rough time people submit the diary
|
||
|
@lag += (created - (ddate + Rational(20, 24)))
|
||
|
|
||
|
week_day_name = Date::ABBR_DAYNAMES[ddate.wday]
|
||
|
@week[week_day_name][:hours] += hours.to_i
|
||
|
@week[week_day_name][:records] += 1
|
||
|
end
|
||
|
|
||
|
def lag_to_html
|
||
|
average_lag = (@lag / @records).round(1).to_f
|
||
|
average_lag = 0 if average_lag < 0
|
||
|
|
||
|
if average_lag > 5
|
||
|
css_style = 'text-danger'
|
||
|
elsif average_lag > 2
|
||
|
css_style = 'text-warning'
|
||
|
elsif average_lag < 0.3
|
||
|
css_style = 'text-muted'
|
||
|
else
|
||
|
css_style = 'text-body'
|
||
|
end
|
||
|
|
||
|
noescape { "<span class=\"" + css_style + "\">" + average_lag.to_s + "</span>" }
|
||
|
end
|
||
|
|
||
|
def week_to_html
|
||
|
out = ""
|
||
|
@week.each { |key, record|
|
||
|
if record[:hours] > 0
|
||
|
out = out + key + ": " + record[:hours].to_s + " "
|
||
|
end
|
||
|
}
|
||
|
out
|
||
|
end
|
||
|
|
||
|
def day_to_html(day, d_start, d_end)
|
||
|
# calculate number of days between s_date and e_date
|
||
|
day_number = DateTime.parse(day).wday
|
||
|
days_list = (d_start..d_end).to_a.select { |d| day_number == d.wday }
|
||
|
number_of_days = days_list.count
|
||
|
|
||
|
if not @week.has_key?(day) or @week[day][:records] == 0
|
||
|
""
|
||
|
else
|
||
|
Rational(@week[day][:hours], number_of_days).round(+1).to_f.to_s
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class Diary
|
||
|
@@mapper = nil
|
||
|
def self.mapper
|
||
|
return @@mapper unless @@mapper.nil?
|
||
|
@@mapper = DiaryMapper.new
|
||
|
end
|
||
|
|
||
|
def initialize(id = nil, values = nil)
|
||
|
@id = id
|
||
|
@attributes = values ? values : Hash.new
|
||
|
@approval_note = nil
|
||
|
end
|
||
|
|
||
|
def self.get(id)
|
||
|
mapper.find(id)
|
||
|
end
|
||
|
|
||
|
def project
|
||
|
@attributes.has_key?("prj_id") ?
|
||
|
Project.get(@attributes["prj_id"]) : nil
|
||
|
end
|
||
|
|
||
|
def who
|
||
|
Person.find_or_create(@attributes["who"])
|
||
|
end
|
||
|
|
||
|
def approval_note
|
||
|
policy = DiaryEnv.instance.policy
|
||
|
return [] if (not policy.can_edit?(project, who) and
|
||
|
not policy.can_approve?(project, who)) or
|
||
|
DiaryState.valid(@attributes["state"].to_i)
|
||
|
|
||
|
@approval_note = DataMapper.database.select_all(
|
||
|
"select * from approval_note where id = ?", @id
|
||
|
).collect { |row| row.to_h }
|
||
|
return @approval_note
|
||
|
end
|
||
|
|
||
|
def self.approve_all(prj, who)
|
||
|
DataMapper.database.do("update diary set state = ? where prj_id = ? " +
|
||
|
"AND who = ? AND state IN (?,?)", DiaryState::APPROVED,
|
||
|
prj.id, who, DiaryState::REQ_APPROVAL, DiaryState::REJECTED)
|
||
|
end
|
||
|
|
||
|
def destroy
|
||
|
end
|
||
|
|
||
|
def [](tag)
|
||
|
@attributes[tag]
|
||
|
end
|
||
|
|
||
|
def []=(tag, value)
|
||
|
@attributes[tag] = value
|
||
|
end
|
||
|
|
||
|
def self.predict_project(who)
|
||
|
# TODO: select the most used of N last entries
|
||
|
last_diary = DataMapper.database.select_one(
|
||
|
"SELECT prj_id FROM diary " +
|
||
|
"WHERE who=? ORDER BY ddate DESC " +
|
||
|
"LIMIT 1", who.uid)
|
||
|
return last_diary ? last_diary[0].to_i : nil
|
||
|
end
|
||
|
|
||
|
def self.find(args)
|
||
|
who = args[:who]
|
||
|
prj_id = args[:prj_id]
|
||
|
customer = args[:customer]
|
||
|
policy = DiaryEnv.instance.policy
|
||
|
|
||
|
dates = args[:ddate]
|
||
|
raise ArgumentError unless dates.is_a? Range
|
||
|
query_clause = " where ddate >= ? and ddate <= ?"
|
||
|
query_args = [dates.first, dates.last]
|
||
|
|
||
|
if policy.restriction.include?(:project)
|
||
|
prj_list = policy.project_list.collect {|prj| prj.id }
|
||
|
prj_list = prj_list & [prj_id] if prj_id
|
||
|
return [] if prj_list.empty?
|
||
|
query_clause += " AND prj_id IN (" +
|
||
|
prj_list.collect { "?" }.join(",") + ")"
|
||
|
query_args += prj_list
|
||
|
elsif prj_id
|
||
|
query_clause += " AND prj_id = ?"
|
||
|
query_args = query_args << prj_id
|
||
|
end
|
||
|
|
||
|
if policy.restriction.include?(:engineer)
|
||
|
eng_list = policy.engineer_list.collect {|eng| eng.uid }
|
||
|
eng_list = eng_list & [who] if who
|
||
|
return [] if eng_list.empty?
|
||
|
query_clause += " AND who IN (" +
|
||
|
eng_list.collect { "?" }.join(",") + ")"
|
||
|
query_args += eng_list
|
||
|
elsif who
|
||
|
query_clause += " AND who = ?"
|
||
|
query_args = query_args << who
|
||
|
end
|
||
|
|
||
|
if customer
|
||
|
query_clause += " AND customer = ?"
|
||
|
query_args = query_args << customer
|
||
|
end
|
||
|
|
||
|
DataMapper.database.select_all("select diary.*, project.customer, " +
|
||
|
" project.leader, project.manager, diary.id as id" +
|
||
|
", diary.state as state" +
|
||
|
" from diary left join project on diary.prj_id = project.id" +
|
||
|
query_clause + " order by ddate, prj_id, who",
|
||
|
*query_args).collect do |row|
|
||
|
self.new(row["id"], row.to_h)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def self.for_approve(who)
|
||
|
DataMapper.database.select_all("select *, diary.id as id from" +
|
||
|
" diary inner join approval on approval.who = diary.who and" +
|
||
|
" approval.prj_id = diary.prj_id left join project on" +
|
||
|
" diary.prj_id = project.id where approval.approver = ?" +
|
||
|
" and diary.state = ? order by diary.ddate",
|
||
|
who.uid, DiaryState::REQ_APPROVAL).collect do |row|
|
||
|
self.new(row["id"], row.to_h)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def self.add_approval_note(id, message)
|
||
|
if message.is_a?(String) and message.length > 0
|
||
|
DataMapper.database.do(
|
||
|
"insert into approval_note(id,created,author,note) " +
|
||
|
"values (?,?,?,?)", id, DbTime.now,
|
||
|
DiaryEnv.instance.user.uid, message)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class DiaryUI
|
||
|
def initialize(cgi)
|
||
|
@cgi = cgi
|
||
|
@user = DiaryEnv.instance.user
|
||
|
@policy = DiaryEnv.instance.policy
|
||
|
@diary_table = SQL_Cache.new(DataMapper.database, "diary", "")
|
||
|
@projects = nil
|
||
|
end
|
||
|
|
||
|
# Array of projects suitable for diary entries
|
||
|
def projects
|
||
|
return @projects unless @projects.nil?
|
||
|
@projects = Project.all(:prj_status => 'ACTIVE')
|
||
|
end
|
||
|
|
||
|
def project_list
|
||
|
projects.sort.collect do |prj|
|
||
|
[prj.id, prj.to_s]
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def customer_list
|
||
|
projects.collect {|prj| prj.customer}.uniq.sort.collect do |org|
|
||
|
[org.uid, org.to_s]
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def show_total(list, d_start, d_end, detailed)
|
||
|
totals = Hash.new
|
||
|
totals[:customer] = Array.new
|
||
|
totals[:person] = Array.new
|
||
|
totals[:hours] = 0
|
||
|
|
||
|
persons = Hash.new
|
||
|
projects = Hash.new
|
||
|
customers = Hash.new
|
||
|
list.each do |x|
|
||
|
who = Person.find_or_create(x["who"])
|
||
|
prj = Project.get(x["prj_id"])
|
||
|
next if not @policy.can_account?(prj)
|
||
|
if DiaryState.valid(x["state"].to_i)
|
||
|
projects[prj] =
|
||
|
projects[prj].to_i + x["hours"].to_i
|
||
|
if not persons[who]
|
||
|
persons[who] = DiaryPerson.new(who)
|
||
|
end
|
||
|
persons[who].register(x["hours"], x["ddate"], x["created"])
|
||
|
totals[:hours] += x["hours"].to_i
|
||
|
end
|
||
|
end
|
||
|
projects.sort.each do |item|
|
||
|
prj = item[0]
|
||
|
org = Organization.find(prj["customer"])
|
||
|
customers[org] = Array.new unless customers[org]
|
||
|
customers[org].push({:name => prj["name"],
|
||
|
:customer => prj["customer"],
|
||
|
:tag => prj["tag"],
|
||
|
:hours => item[1]})
|
||
|
end
|
||
|
customers.sort.each do |item|
|
||
|
hours = item[1].inject(0) { |sum, value| sum += value[:hours] }
|
||
|
totals[:customer].push({:name => item[0].to_s,
|
||
|
:hours => hours,
|
||
|
:project => item[1]})
|
||
|
end
|
||
|
if detailed
|
||
|
persons.sort.each do |item|
|
||
|
dp = item[1]
|
||
|
totals[:person].push({:name => item[0].to_html,
|
||
|
:hours => dp.hours,
|
||
|
:delay => dp.lag_to_html,
|
||
|
:mon_stat => dp.day_to_html('Mon', d_start, d_end),
|
||
|
:tue_stat => dp.day_to_html('Tue', d_start, d_end),
|
||
|
:wed_stat => dp.day_to_html('Wed', d_start, d_end),
|
||
|
:thu_stat => dp.day_to_html('Thu', d_start, d_end),
|
||
|
:fri_stat => dp.day_to_html('Fri', d_start, d_end),
|
||
|
:sat_stat => dp.day_to_html('Sat', d_start, d_end),
|
||
|
:sun_stat => dp.day_to_html('Sun', d_start, d_end),
|
||
|
})
|
||
|
end
|
||
|
else
|
||
|
persons.sort.each do |item|
|
||
|
totals[:person].push({:name => item[0].to_html,
|
||
|
:hours => item[1].hours,
|
||
|
})
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return totals[:hours] > 0 ? totals : nil
|
||
|
end
|
||
|
private :show_total
|
||
|
|
||
|
def show_approval_notes(entry)
|
||
|
return nil if entry.approval_note.empty?
|
||
|
entry.approval_note.collect do |note|
|
||
|
{
|
||
|
:created => DbTime.new(note["created"]).to_s,
|
||
|
:author => note["author"],
|
||
|
:note => note["note"]
|
||
|
}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def show_one(values = nil, edit = false, approve = false)
|
||
|
if (values == nil)
|
||
|
edit = add = true
|
||
|
values = Diary.new
|
||
|
else
|
||
|
add = false
|
||
|
edit = false if values["state"].to_i == DiaryState::DENIED
|
||
|
end
|
||
|
edit ||= add
|
||
|
|
||
|
# Set project to the last edited one (for new entries)
|
||
|
if not values.project
|
||
|
values["prj_id"] = Diary.predict_project(@user)
|
||
|
end
|
||
|
|
||
|
prj = Project.get(values["prj_id"].to_i)
|
||
|
if edit
|
||
|
edit_data = {
|
||
|
:hiddens => @cgi.hiddens(@cgi.tree_flatten(["diary",
|
||
|
"list"]).delete_if do |x|
|
||
|
x == ["diary:list:edit", values["id"].to_s]
|
||
|
end),
|
||
|
:date => MyCGI::select_date_e("diary:edit:ddate",
|
||
|
values["ddate"] || Date.today, edit),
|
||
|
:person => MyCGI::list_options(
|
||
|
@policy.engineer_list.collect {|x| [x.uid, x.to_s]},
|
||
|
(values["who"] || @user.uid)),
|
||
|
:hours => a(:value => values["hours"]),
|
||
|
:project => MyCGI::list_options(project_list,
|
||
|
prj ? prj.id : ""),
|
||
|
:descr => values["descr"]
|
||
|
}
|
||
|
edit_data[:id] = a(:value => values["id"]) if values["id"]
|
||
|
if prj and prj.approvals.has_key?(values["who"])
|
||
|
edit_data[:approval] = Hash.new
|
||
|
edit_data[:approval][:appnote] = show_approval_notes(values)
|
||
|
end
|
||
|
return { :edit => edit_data }
|
||
|
else
|
||
|
view = {
|
||
|
:id => a(:name => values["id"]),
|
||
|
:hiddens => @cgi.hiddens(@cgi.tree_flatten(["diary", "list"])),
|
||
|
:date => MyCGI::select_date_e("diary:edit:ddate",
|
||
|
values["ddate"] || Date.today, edit),
|
||
|
:person => Person.find_or_create(values["who"]).to_html,
|
||
|
:project_name => prj["name"],
|
||
|
:project => prj["customer"]+ "/" + prj["tag"],
|
||
|
:descr => transform_links(prj, values["descr"]),
|
||
|
:edit_id => a(:value => values["id"]),
|
||
|
:modified => DbTime.new(values["modified"]).to_s,
|
||
|
:created => DbTime.new(values["created"]).to_s,
|
||
|
:state => DiaryState.new(values["state"].to_i).to_html
|
||
|
}
|
||
|
view[:hours] = values["hours"].to_s + " h" if
|
||
|
@policy.can_account?(prj)
|
||
|
view[:appnote] = show_approval_notes(values) if
|
||
|
not values.approval_note.empty?
|
||
|
if @policy.can_edit?(prj, Person.find(values["who"])) or approve
|
||
|
view[:edit] = { }
|
||
|
return { :view => a(:action => "#" +
|
||
|
MyCGI::element_id(values["id"])){ view }}
|
||
|
else
|
||
|
return { :view_only => view }
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
private :show_one
|
||
|
|
||
|
def menu
|
||
|
{
|
||
|
:start_date => MyCGI::select_date_e("diary:list:start",
|
||
|
@cgi.tree_params["diary"]["list"]["start"]),
|
||
|
:end_date => MyCGI::select_date_e("diary:list:end",
|
||
|
@cgi.tree_params["diary"]["list"]["end"]),
|
||
|
:project => MyCGI::list_options([["*", "- All -"]] +
|
||
|
project_list,
|
||
|
@cgi.tree_params["diary"]["list"]["prj_id"]),
|
||
|
:customer => MyCGI::list_options([["*", "Customer: all"]] +
|
||
|
customer_list,
|
||
|
@cgi.tree_params["diary"]["list"]["customer"]),
|
||
|
:who => MyCGI::list_options([["*", "Engineer: all"]] +
|
||
|
@policy.engineer_list.collect {|x| [x.uid, x.to_s]},
|
||
|
@cgi.tree_params["diary"]["list"]["who"]),
|
||
|
:newest_first => show_checkbox("newest_first", @cgi.tree_params["diary"]["list"]["newest_first"]),
|
||
|
:just_stats => show_checkbox("just_stats", @cgi.tree_params["diary"]["list"]["just_stats"])
|
||
|
}
|
||
|
end
|
||
|
|
||
|
def show
|
||
|
diary_data = Hash.new
|
||
|
MyCGI::add_commons(diary_data)
|
||
|
diary_data[:menu] = menu
|
||
|
diary_data[:new] = show_one() if @user.local?
|
||
|
|
||
|
if @cgi.tree_params["do"] == "diary"
|
||
|
d_start = MyCGI::hash2date(
|
||
|
@cgi.tree_params["diary"]["list"]["start"])
|
||
|
d_end = MyCGI::hash2date(
|
||
|
@cgi.tree_params["diary"]["list"]["end"])
|
||
|
prj_id = @cgi.tree_params["diary"]["list"]["prj_id"][0].to_i
|
||
|
prj_id = nil if prj_id <= 0
|
||
|
customer = @cgi.tree_params["diary"]["list"]["customer"][0]
|
||
|
customer = nil if not Organization.exists?(customer)
|
||
|
who = @cgi.tree_params["diary"]["list"]["who"][0].strip.downcase
|
||
|
who = nil if who == "" or who == "*"
|
||
|
|
||
|
list_approval = Diary.for_approve(@user)
|
||
|
if list_approval.length > 0
|
||
|
diary_data[:massapproval] = a(:__id__)
|
||
|
|
||
|
diary_data[:approvals] = { :entries =>
|
||
|
list_approval.inject([]) do |data, item|
|
||
|
data << a(:__id__ => MyCGI::element_id(item["id"])){
|
||
|
show_one(item, false, true)}
|
||
|
end
|
||
|
}
|
||
|
end
|
||
|
|
||
|
list = Diary.find(:who => who,
|
||
|
:prj_id => prj_id,
|
||
|
:customer => customer,
|
||
|
:ddate => (d_start..d_end))
|
||
|
list.delete_if do |entry|
|
||
|
not DiaryState.valid(entry["state"]) and
|
||
|
not @policy.can_edit?(entry.project, entry.who)
|
||
|
end
|
||
|
if list.length > 0
|
||
|
if @policy.is_director?
|
||
|
diary_data[:detailed_totals] = show_total(list, d_start, d_end, @policy.is_director?)
|
||
|
else
|
||
|
diary_data[:totals] = show_total(list, d_start, d_end, false)
|
||
|
end
|
||
|
|
||
|
if @cgi.tree_params["diary"]["list"]["just_stats"][0] != "on"
|
||
|
if @cgi.tree_params["diary"]["list"]["newest_first"][0] == "on"
|
||
|
list.reverse!
|
||
|
end
|
||
|
diary_data[:details] = {
|
||
|
:entries =>
|
||
|
list.inject([]) do |data, item|
|
||
|
data << a(:__id__ => MyCGI::element_id(item["id"])){
|
||
|
show_one(item,
|
||
|
@cgi.tree_params["diary"]["list"]["edit"].
|
||
|
include?(item["id"].to_s)) }
|
||
|
end
|
||
|
}
|
||
|
else
|
||
|
diary_data[:nodata] = {}
|
||
|
end
|
||
|
else
|
||
|
diary_data[:nodata] = {}
|
||
|
end
|
||
|
end
|
||
|
s = String.new
|
||
|
DIARY_TEMPLATE.expand(s, diary_data)
|
||
|
s
|
||
|
end
|
||
|
|
||
|
def approve_item(id, action)
|
||
|
entry = @diary_table[id].dup
|
||
|
raise "Entry does not exist" if not entry
|
||
|
raise "You have no rights to approve diary entries for " +
|
||
|
"#{Person.find(entry["who"]).to_s} in this project." if
|
||
|
not @policy.can_approve?(Project.get(entry["prj_id"]),
|
||
|
Person.find_or_create(entry["who"]))
|
||
|
|
||
|
entry["state"] = case action
|
||
|
when "Approve" then DiaryState::APPROVED
|
||
|
when "Deny" then DiaryState::DENIED
|
||
|
when "Reject" then DiaryState::REJECTED
|
||
|
end
|
||
|
@diary_table.modify(id, entry)
|
||
|
|
||
|
return if not @cgi.tree_params["diary"].include?("edit")
|
||
|
data = @cgi.tree_params["diary"]["edit"]
|
||
|
Diary.add_approval_note(id, data["approval"])
|
||
|
end
|
||
|
|
||
|
def approval_action
|
||
|
id = @cgi.tree_params["diary"]["action"]["id"].to_i
|
||
|
action = @cgi.tree_params["diary"]["action"]["verb"]
|
||
|
|
||
|
approve_item(id, action)
|
||
|
end
|
||
|
|
||
|
def list_approval_action
|
||
|
list_action = @cgi.tree_params["diary"]["action"]["verb"]
|
||
|
action = case list_action
|
||
|
when "ApproveAll" then "Approve"
|
||
|
when "DenyAll" then "Deny"
|
||
|
when "RejectAll" then "Reject"
|
||
|
end
|
||
|
|
||
|
list_approval = Diary.for_approve(@user)
|
||
|
if list_approval.length > 0
|
||
|
list_approval.each do |item|
|
||
|
approve_item(item["id"], action)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def action
|
||
|
debug_log(@cgi.tree_params.inspect)
|
||
|
|
||
|
# Delete an entry
|
||
|
if @cgi.tree_params["diary"]["action"]["verb"] == "Delete"
|
||
|
id = @cgi.tree_params["diary"]["action"]["id"].to_i
|
||
|
entry = Diary.get(id)
|
||
|
raise "You are trying to delete diary entry #{id} " +
|
||
|
"which does not exist." if not entry
|
||
|
|
||
|
raise "You have no rights to delete diary entries for " +
|
||
|
"#{entry.who.to_s} in this project." if
|
||
|
not @policy.can_edit?(entry.project, entry.who)
|
||
|
|
||
|
raise NeedConfirm.new("delete"),
|
||
|
"You are deleting diary entry of " +
|
||
|
"#{entry.who.to_s} for #{entry["ddate"]}!" if
|
||
|
not DiaryEnv.confirmed?("delete")
|
||
|
|
||
|
@diary_table.delete(id)
|
||
|
return
|
||
|
end
|
||
|
|
||
|
if @cgi.tree_params["diary"]["action"]["verb"] == "ApproveAll" or
|
||
|
@cgi.tree_params["diary"]["action"]["verb"] == "RejectAll" or
|
||
|
@cgi.tree_params["diary"]["action"]["verb"] == "DenyAll"
|
||
|
list_approval_action
|
||
|
return
|
||
|
end
|
||
|
|
||
|
|
||
|
if @cgi.tree_params["diary"]["action"]["verb"] == "Approve" or
|
||
|
@cgi.tree_params["diary"]["action"]["verb"] == "Reject" or
|
||
|
@cgi.tree_params["diary"]["action"]["verb"] == "Deny"
|
||
|
approval_action
|
||
|
return
|
||
|
end
|
||
|
|
||
|
# After adding/editing entry
|
||
|
return if not @cgi.tree_params["diary"].include?("edit")
|
||
|
data = @cgi.tree_params["diary"]["edit"].dup
|
||
|
# normalize input
|
||
|
data["who"].strip!
|
||
|
data["who"].downcase!
|
||
|
raise "No such person #{data["who"]} " if not Person.exists?(data["who"])
|
||
|
data["prj_id"] = data["prj_id"].to_i
|
||
|
prj = Project.get(data["prj_id"])
|
||
|
who = Person.find(data["who"])
|
||
|
raise "No such project #{data["prj_id"]}" unless prj
|
||
|
|
||
|
raise "Project #{prj.to_s} is terminated" if
|
||
|
prj["prj_status"] == "TERMINATED"
|
||
|
|
||
|
raise "You have no rights to add/change diary records " +
|
||
|
"for #{who.to_s}." if not @policy.can_edit?(prj, who)
|
||
|
|
||
|
data["hours"] = data["hours"].to_i
|
||
|
data["ddate"] = MyCGI::hash2date(data["ddate"])
|
||
|
|
||
|
raise NeedConfirm.new("future"),
|
||
|
"You're posting diary for the future" if
|
||
|
data["ddate"] > Date.today and not DiaryEnv.confirmed?("future")
|
||
|
|
||
|
raise "Don't use non-English symbols - not all customers can read Russian" if
|
||
|
not data["descr"].ascii_only?
|
||
|
|
||
|
raise "Please use at least 10 characters in diary entry description" if
|
||
|
data["descr"].length < 10
|
||
|
|
||
|
# Check total hours
|
||
|
total = DataMapper.database.select_one(
|
||
|
"SELECT SUM(hours) FROM diary " +
|
||
|
"WHERE who=? AND ddate=?" +
|
||
|
(data.include?("id") ?
|
||
|
" AND id<>#{data["id"]}" : ""),
|
||
|
*([data["who"], data["ddate"]]))[0].to_i +
|
||
|
data["hours"]
|
||
|
raise "You have more than 24 hours " +
|
||
|
"for #{data["ddate"]}" if total > 24
|
||
|
|
||
|
raise NeedConfirm.new("overwork"),
|
||
|
"You have more (#{total}) than 15 hours " +
|
||
|
"for #{data["ddate"]}" if
|
||
|
total > 15 and not DiaryEnv.confirmed?("overwork")
|
||
|
|
||
|
if @policy.needs_confirmation?(prj, who)
|
||
|
raise NeedConfirm.new("past"), "You are " +
|
||
|
(data.include?("id") ? "editing" : "adding") +
|
||
|
" a diary record for #{data["ddate"]} that is in the past" if
|
||
|
data["ddate"] < Date.today and not DiaryEnv.confirmed?("past")
|
||
|
end
|
||
|
|
||
|
raise NeedConfirm.new("progressive"),
|
||
|
"You are trying to add #{total} hours " +
|
||
|
"for today, while it is just #{Time.now.hour} hours now" if
|
||
|
data["ddate"] == Date.today and total > Time.now.hour and
|
||
|
not DiaryEnv.confirmed?("progressive")
|
||
|
|
||
|
data["state"] = (prj.approvals.has_key?(data["who"]) ?
|
||
|
DiaryState::REQ_APPROVAL : DiaryState::NORMAL)
|
||
|
|
||
|
# Automatically reject creating (not editing) too old entry
|
||
|
if not data.include?("id") and
|
||
|
data["state"] == DiaryState::REQ_APPROVAL and
|
||
|
Date.today - data["ddate"] > DiaryEnv::DIARY_MAX_AGE
|
||
|
if DiaryEnv.confirmed?("too_old")
|
||
|
data["state"] = DiaryState::REJECTED
|
||
|
else
|
||
|
raise NeedConfirm.new("too_old"),
|
||
|
"Date is too old, entry will be rejected"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Insert/replace DB entry
|
||
|
data["modified"] = DbTime.now
|
||
|
if data.include?("id")
|
||
|
data.delete("id")
|
||
|
if data["hours"].to_i == 0
|
||
|
@diary_table.delete(@cgi.tree_params["diary"]["edit"]["id"])
|
||
|
else
|
||
|
@diary_table.modify(@cgi.tree_params["diary"]["edit"]["id"],
|
||
|
data)
|
||
|
end
|
||
|
else
|
||
|
raise "You should specify at least 1 hour" if data["hours"] < 1
|
||
|
raise "You are inserting a duplicate diary record!" if
|
||
|
DataMapper.database.select_one(
|
||
|
"SELECT COUNT(*) FROM diary WHERE " +
|
||
|
"who = ? and descr = ? and hours = ? " +
|
||
|
"and ddate =?",
|
||
|
*([data["who"], data["descr"],
|
||
|
data["hours"], data["ddate"]]))[0].to_i > 0
|
||
|
data["created"] = data["modified"]
|
||
|
@diary_table.create(data)
|
||
|
end
|
||
|
|
||
|
Diary.add_approval_note(@cgi.tree_params["diary"]["edit"]["id"].to_i,
|
||
|
data["approval"]) if
|
||
|
data["approval"] and data["approval"].length > 0
|
||
|
|
||
|
# Adjust start/end day of the list to make new record visible
|
||
|
@cgi.tree_params["diary"]["list"]["start"] =
|
||
|
MyCGI::date2hash(data["ddate"]) if data["ddate"] <
|
||
|
MyCGI::hash2date(@cgi.tree_params["diary"]["list"]["start"])
|
||
|
@cgi.tree_params["diary"]["list"]["end"] =
|
||
|
MyCGI::date2hash(data["ddate"]) if data["ddate"] >
|
||
|
MyCGI::hash2date(@cgi.tree_params["diary"]["list"]["end"])
|
||
|
end
|
||
|
end
|