2021-12-20 12:32:51 +00:00
|
|
|
# SPDX-License-Identifier: Apache-2.0
|
2022-02-02 14:21:36 +00:00
|
|
|
# Copyright (C) 2021-2022 OKTET Labs Ltd. All rights reserved.
|
2021-12-20 12:32:51 +00:00
|
|
|
|
|
|
|
#
|
|
|
|
# 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")
|
2022-01-17 12:07:54 +00:00
|
|
|
elsif @time.class == Time
|
|
|
|
@time.to_time.localtime.strftime("%Y-%m-%d %H:%M:%S")
|
2021-12-20 12:32:51 +00:00
|
|
|
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
|
2022-01-17 12:07:54 +00:00
|
|
|
tlag = (created -((Time.strptime(ddate.to_s + " 00:00:00", "%Y-%m-%d %H:%M:%S")) + 20*60*60))/60/60/24
|
|
|
|
if tlag > 0
|
|
|
|
@lag += tlag
|
|
|
|
end
|
2021-12-20 12:32:51 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
2022-02-02 14:21:36 +00:00
|
|
|
@approval_note = DataMapper.database.query("select * from approval_note where id = '%s'" % @id).collect { |row| row.to_h }
|
2021-12-20 12:32:51 +00:00
|
|
|
return @approval_note
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.approve_all(prj, who)
|
2022-02-02 14:21:36 +00:00
|
|
|
DataMapper.database.query("update diary set state = '%s' where prj_id = '%s' AND who = '%s' AND state IN ('%s','%s')" %
|
|
|
|
[
|
|
|
|
DiaryState::APPROVED,
|
|
|
|
DataMapper.database.escape(prj.id.to_s),
|
|
|
|
DataMapper.database.escape(who.to_s),
|
|
|
|
DiaryState::REQ_APPROVAL, DiaryState::REJECTED
|
|
|
|
])
|
2021-12-20 12:32:51 +00:00
|
|
|
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
|
2022-02-02 14:21:36 +00:00
|
|
|
last_diary = DataMapper.database.query("SELECT prj_id FROM diary WHERE who='%s' ORDER BY ddate DESC LIMIT 1" % who.uid ).first
|
|
|
|
return last_diary ? last_diary["prj_id"].to_i : nil
|
2021-12-20 12:32:51 +00:00
|
|
|
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
|
2022-02-02 14:21:36 +00:00
|
|
|
query_clause = " where ddate >= '%s' and ddate <= '%s'"
|
2021-12-20 12:32:51 +00:00
|
|
|
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 (" +
|
2022-02-02 14:21:36 +00:00
|
|
|
prj_list.collect { "'%s'" }.join(",") + ")"
|
2021-12-20 12:32:51 +00:00
|
|
|
query_args += prj_list
|
|
|
|
elsif prj_id
|
2022-02-02 14:21:36 +00:00
|
|
|
query_clause += " AND prj_id = '%s'"
|
2021-12-20 12:32:51 +00:00
|
|
|
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 (" +
|
2022-02-02 14:21:36 +00:00
|
|
|
eng_list.collect { "'%s'" }.join(",") + ")"
|
2021-12-20 12:32:51 +00:00
|
|
|
query_args += eng_list
|
|
|
|
elsif who
|
2022-02-02 14:21:36 +00:00
|
|
|
query_clause += " AND who = '%s'"
|
2021-12-20 12:32:51 +00:00
|
|
|
query_args = query_args << who
|
|
|
|
end
|
|
|
|
|
|
|
|
if customer
|
2022-02-02 14:21:36 +00:00
|
|
|
query_clause += " AND customer = '%s'"
|
2021-12-20 12:32:51 +00:00
|
|
|
query_args = query_args << customer
|
|
|
|
end
|
|
|
|
|
2022-02-02 14:21:36 +00:00
|
|
|
DataMapper.database.query("select diary.*, project.customer, " +
|
2021-12-20 12:32:51 +00:00
|
|
|
" project.leader, project.manager, diary.id as id" +
|
|
|
|
", diary.state as state" +
|
|
|
|
" from diary left join project on diary.prj_id = project.id" +
|
2022-02-02 14:21:36 +00:00
|
|
|
query_clause % (query_args) + " order by ddate, prj_id, who"
|
|
|
|
).collect do |row|
|
2021-12-20 12:32:51 +00:00
|
|
|
self.new(row["id"], row.to_h)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.for_approve(who)
|
2022-02-02 14:21:36 +00:00
|
|
|
DataMapper.database.query("select *, diary.id as id from" +
|
2021-12-20 12:32:51 +00:00
|
|
|
" diary inner join approval on approval.who = diary.who and" +
|
|
|
|
" approval.prj_id = diary.prj_id left join project on" +
|
2022-02-02 14:21:36 +00:00
|
|
|
" diary.prj_id = project.id where approval.approver = '%s'" % who.uid +
|
|
|
|
" and diary.state = '%s' order by diary.ddate" % DiaryState::REQ_APPROVAL
|
|
|
|
).collect do |row|
|
2021-12-20 12:32:51 +00:00
|
|
|
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
|
2022-02-02 14:21:36 +00:00
|
|
|
DataMapper.database.query("insert into approval_note(id,created,author,note) values ('%s',%s,'%s','%s')" %
|
|
|
|
[
|
|
|
|
DataMapper.database.escape(id.to_s),
|
|
|
|
"STR_TO_DATE('%s'," % DbTime.now + " '%Y-%m-%dT%h:%i:%s+00:00')",
|
|
|
|
DataMapper.database.escape(DiaryEnv.instance.user.uid),
|
|
|
|
DataMapper.database.escape(message)
|
|
|
|
])
|
2021-12-20 12:32:51 +00:00
|
|
|
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
|
2022-02-02 14:21:36 +00:00
|
|
|
total = DataMapper.database.query("SELECT SUM(hours) as sum FROM diary WHERE who='%s' AND ddate='%s'" %
|
|
|
|
[
|
|
|
|
data["who"],
|
|
|
|
data["ddate"]
|
|
|
|
] + (data.include?("id") ? " AND id<>#{data["id"]}" : "")
|
|
|
|
).first["sum"].to_i +
|
2021-12-20 12:32:51 +00:00
|
|
|
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
|
2022-02-02 14:21:36 +00:00
|
|
|
DataMapper.database.query(
|
|
|
|
"SELECT COUNT(*) as count FROM diary WHERE " +
|
|
|
|
"who = '%s' and descr = '%s' and hours = '%s' and ddate = '%s'" %
|
|
|
|
[
|
|
|
|
DataMapper.database.escape(data["who"].to_s),
|
|
|
|
DataMapper.database.escape(data["descr"].to_s),
|
|
|
|
DataMapper.database.escape(data["hours"].to_s),
|
|
|
|
DataMapper.database.escape(data["ddate"].to_s)
|
|
|
|
]
|
|
|
|
).first["count"].to_i > 0
|
2021-12-20 12:32:51 +00:00
|
|
|
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
|