diary/project.rb

461 lines
16 KiB
Ruby

# SPDX-License-Identifier: Apache-2.0
# Copyright (C) 2021-2022 OKTET Labs Ltd. All rights reserved.
#
# Class Project for Diary Management Application.
#
PROJECT_TEMPLATE = AmritaTemplate.new("project.html")
require 'dbi'
require_relative 'diary_datamapper'
require_relative 'diary_policy'
class Project
STATUS = ["NEW", "ACTIVE", "TERMINATED"]
STATUS_DEFAULT = "ACTIVE"
@@mapper = nil
def initialize(id = nil, values = nil)
@id = id
@attributes = values ? values : Hash.new
@cc = nil
@approvals = nil
end
def self.get(id)
mapper.find(id)
end
def self.where(clause, *args)
mapper.where(clause, *args)
end
def save_approvals
self.class.mapper.save_approvals(@id, @approvals)
end
def approvals
return @approvals unless @approvals.nil?
@approvals = self.class.mapper.find_approvals(@id)
end
def id
@id
end
def [](key)
@attributes[key]
end
def <=>(rhs)
to_s <=> rhs.to_s
end
def to_s
@attributes["customer"] + "/" + @attributes["tag"]
end
def self.mapper
return @@mapper unless @@mapper.nil?
@@mapper = ProjectMapper.new
end
def customer
Organization.find_or_create(@attributes["customer"])
end
# we should not hardcode things
def transform_links(text)
return text
end
def self.all(args)
policy = DiaryEnv.instance.policy
res = mapper.all(args)
res = res & policy.project_list if
policy.restriction.include?(:project)
return res
end
end
class ProjectMapper < DataMapper
def initialize
super("project")
end
def instantiate(id, values)
Project.new(id, values)
end
def insert(prj)
super(prj)
if prj.cc and prj.cc.length > 0
@@database.do("insert into cc_list (prj_id, person) values " +
(["(#{prj.id}, ?)"] * prj.cc.length).join(", "),
*prj.cc);
end
save_approvals(prj.id, prj.approvals)
end
def find_approvals(id)
app = Hash.new
@@database.query("select * from approval where prj_id = '%s'" % id).collect { |row| app[row["who"]] = row["approver"] }
app
end
def save_approvals(id, app)
cmd = "delete from approval where prj_id = '%s'" % id.to_s
@@database.query(cmd)
app.each do |who, approver|
cmd = "insert into approval (prj_id, who, approver) values ('%s', '%s', '%s')" %
[@@database.escape(id.to_s), @@database.escape(who.to_s), @@database.escape(approver.to_s)]
@@database.query(cmd)
end
end
end
class ProjectUI < Hash
PERSONS = %w(manager leader cust_man cust_tech)
MANDATORY = %w(tag name prj_status manager customer cust_man prj_desc hide_hrs)
OPTIONAL = %w(status_desc issues leader cust_tech maillists svndir
storagedir bugzilla_prod extra_rights)
def status_list(selected)
MyCGI::list_options(
Project::STATUS.collect {|status| [status.to_s, status.to_s]},
selected)
end
def update(id = @id)
@id = id
self.clear
if @id
DataMapper.database.query("select * from project where id = #{@id}") do |q|
### For some reason MySQL driver always returns 0 in q.rows
# if q.rows != 1
# raise "Project #{@id} has #{q.rows} instances"
# end
row = q.fetch_hash
raise "Project #{@id} has no instances" if not row
self.replace(row)
self.delete("id")
end
self["cc"] = Array.new
DataMapper.database.query("select person from cc_list where " +
"prj_id = #{@id}").collect do |row|
self["cc"].push(row["person"])
end
end
true
end
def initialize(cgi)
@id = nil
@cgi = cgi
@user = DiaryEnv.instance.user
@policy = DiaryEnv.instance.policy
end
def create(data)
return unless @policy.can_edit_project?
#raise "Create project #{data["name"]}"
inst = data.keys
inst -= ["cc"]
cmd = ("insert into project (" + inst.join(", ") + ") values (" + (inst.collect do "'%s'" end).join(", ") + ")") %
(inst.collect do |field| DataMapper.database.escape(data[field]) end)
DataMapper.database.query(cmd)
@id = DataMapper.database.query("select last_insert_id() as id").first["id"]
if data["cc"].length > 0
DataMapper.database.do("insert into cc_list (prj_id, person) values " +
(["(#{@id}, ?)"] * data["cc"].length).join(", "),
*data["cc"]);
end
# Send notifications XXX
end
def modify(current, data)
return if not @policy.can_edit_project?(@id)
changes = Array.new
data.each_key do |field|
if data[field] != current[field]
changes.push(field)
end
end
if (changes - ["cc"]).length > 0
cmd = ("update project set " + (changes.collect do |field| field + " = %s" if field != "cc" end).join(", ") + " where id = #{@id}") %
(changes.collect do |field|
if data[field].class == DateTime
t = "STR_TO_DATE('%s'" % data[field] + ", '%Y-%m-%dT%H:%i:%s+00:00')"
else
t = "'%s'" % DataMapper.database.escape(data[field].to_s)
end
t
end)
DataMapper.database.query(cmd);
end
if (current["cc"] - data["cc"]).length > 0
DataMapper.database.do("delete from cc_list where " +
"prj_id = #{@id} and (" +
(["person = ?"] * (current["cc"] - data["cc"]).length
).join(" or ") + ")",
*(current["cc"] - data["cc"]))
end
if (data["cc"] - current["cc"]).length > 0
DataMapper.database.do("insert into cc_list (prj_id, person) " +
"values " +
(["(#{@id}, ?)"] * (data["cc"] - current["cc"]).length
).join(", "),
*(data["cc"] - current["cc"]))
end
# Send notifications XXX
#@people.notify("mis", "Project '#{data["name"]}' is changed")
end
# Set project data to SQL tables.
def set(data)
MANDATORY.each do |field|
if not data[field] or data[field].length == 0
raise "Mandatory field #{field} is not set"
end
end
data.each_key do |field|
if not MANDATORY.include?(field) and
not OPTIONAL.include?(field) and field != "cc"
raise "Field #{field} is unknown"
end
end
data["svndir"].sub!(/^\/*/, '')
data["storagedir"].sub!(/^\/*/, '')
data = data.delete_if do |field, value|
value.length == 0
end
data.each do |field, value|
data[field] = value
end
PERSONS.each do |pers|
if data[pers] and not Person.find(data[pers])
raise "No such person #{pers}: #{data[pers]} "
end
end
if not data["cc"]
data["cc"] = Array.new
end
if @id
modify(self, data)
else
create(data)
end
end
attr_reader :id
def list
return [] if @cgi.tree_params["prj"]["list"]["prj_status"].empty?
args = Hash.new
customer = @cgi.tree_params["prj"]["list"]["customer"]
args[:customer] = customer if
customer.is_a?(Array) and not customer.include?("*")
args[:prj_status] = @cgi.tree_params["prj"]["list"]["prj_status"] ||
[Project::STATUS_DEFAULT]
Project.all(args).collect do |prj|
a(:__id__ => MyCGI::element_id(prj.id)){
list_item(prj, @cgi.tree_params["prj"]["list"]["edit"].
include?(prj.id.to_s))}
end
end
def list_item(values = nil, edit = false)
if (values == nil)
edit = add = true
values = Hash.new
else
add = false
end
if not @policy.can_edit_project?(@id)
edit = add = false
end
if edit
edit_data = {
:hiddens => @cgi.hiddens(@cgi.tree_flatten(["prj", "list"]).
delete_if do |x|
x == ["prj:list:edit", values["id"].to_s]
end),
:name => a(:value => values["name"]),
:tag => a(:value => values["tag"],
:size => Person::MAX_LENGTH),
:customer => MyCGI::list_options(Organization.all.collect { |org|
[org.uid, org.o] },
values["customer"]),
:cust_man => a(:value => values["cust_man"],
:size => Person::MAX_LENGTH),
:cust_tech => a(:value => values["cust_tech"],
:size => Person::MAX_LENGTH),
:manager => a(:value => values["manager"] || DiaryEnv.director[0].uid,
:size => Person::MAX_LENGTH),
:leader => a(:value => values["leader"],
:size => Person::MAX_LENGTH),
:prj_desc => values["prj_desc"] || "",
:prj_status => status_list(@cgi.tree_params["prj"]["list"]["prj_status"]),
:status_desc => values["status_desc"] || "",
:issues => values["issues"] || "",
:maillists => a(:value => values["maillists"]),
:storagedir => a(:value => values["storagedir"]),
:svndir => a(:value => values["svndir"]),
:extra_rights => a(:value => values["extra_rights"]),
:bugzilla_prod => a(:value => values["bugzilla_prod"]),
# :cclist => MyCGI::list_options(values["cc"] ? values["cc"] : [])
:hide_hrs => a(:checked => values["hide_hrs"] ? "checked" : nil)
}
edit_data[:id] = a(:value => values["id"]) if values["id"]
return { :edit => edit_data }
else
view = {
:id => a(:name => values["id"]),
:edit_id => a(:value => values["id"]),
:hiddens => @cgi.hiddens(@cgi.tree_flatten(["prj","list"])),
:name => values["name"],
:tag => values["tag"],
:customer => Organization.find_or_create(values["customer"]).to_html,
:cust_man => Person.find_or_create(values["cust_man"]).to_html,
:manager => Person.find_or_create(values["manager"]).to_html,
:prj_desc => values["prj_desc"],
:prj_status => values["prj_status"]
}
if @policy.can_edit_project?(@id)
approval_items = MyCGI::list_options(
values.approvals.collect do |who, approver|
[ who, "#{Person.find_or_create(who).to_s} => " +
"#{Person.find_or_create(approver).to_s}"]
end)
view[:approval] = {
:person => MyCGI::list_options(
Person.find_by_org(DiaryEnv::HOME_ORGANIZATION).sort.collect { |x| [x.uid, x.to_s] })
}
view[:approval][:approval_list] = {
:approval_item => approval_items
} if not approval_items.empty?
end
view[:edit] = {} if @policy.can_edit_project?(@id)
view[:cust_tech] = MyCGI::optional(
Person.find_or_create(values["cust_tech"]).to_html) if
values["cust_tech"]
view[:leader] = MyCGI::optional(Person.find_or_create(values["leader"]).to_html) if
values["leader"]
view[:status_desc] = MyCGI::optional(values["status_desc"]) if
values["status_desc"]
view[:issues] = MyCGI::optional(values["issues"]) if values["issues"]
view[:maillists] = MyCGI::optional(values["maillists"]) if
values["maillists"]
view[:storagedir] = MyCGI::optional(a(:href => "/storage/" +
values["storagedir"]){values["storagedir"]}) if
values["storagedir"]
view[:svndir] = MyCGI::optional(a(:href => "/svnroot/" +
values["svndir"]){ values["svndir"] }) if
values["svndir"]
view[:extra_rights] = MyCGI::optional(values["extra_rights"]) if
values["extra_rights"]
return { :view => a(:action => "#" +
MyCGI::element_id(values["id"])){ view }}
end
end
def menu
return {
:status => status_list(
@cgi.tree_params["prj"]["list"]["prj_status"].empty? ?
Project::STATUS_DEFAULT :
@cgi.tree_params["prj"]["list"]["prj_status"]),
:customer => MyCGI::list_options([["*", "- All -"]] +
@policy.customer_list.collect { |org|
[org.uid, org.to_s] },
@cgi.tree_params["prj"]["list"]["customer"])
}
end
def add_approval(id, who, approver)
# TODO: user rights
prj = Project.get(id)
raise "No project #{id}" unless prj
raise "You have no rights to edit project" unless
@policy.can_edit_approval?(prj)
prj.approvals[who] = approver
prj.save_approvals
end
def delete_approval(id, who)
who = [who] unless who.is_a? Array
# TODO: user rights
prj = Project.get(id)
raise "No project #{id}" unless prj
raise "You have no rights to edit project" unless
@policy.can_edit_approval?(prj)
raise NeedConfirm.new("cancel_approval"),
"Cancelling the approval mode will approve " +
"ALL diary entries of " +
"#{who.collect{ |x| Person.find(x).to_s }.join(", ")} " +
"in project \"#{prj["name"]} (#{prj.to_s})\"!" if
not DiaryEnv.confirmed?("cancel_approval")
who.each do |x|
prj.approvals.delete(x)
Diary.approve_all(prj, x)
end
prj.save_approvals
end
def action
if @cgi.tree_params["prj"].include?("approve")
id = @cgi.tree_params["prj"]["action"]["id"]
case @cgi.tree_params["prj"]["action"]["verb"]
when "Add"
add_approval(id,
@cgi.tree_params["prj"]["approve"]["who"],
@cgi.tree_params["prj"]["approve"]["approver"])
@cgi.tree_params["prj"]["list"]["edit"].delete(id.to_s)
return
when "Delete"
delete_approval(id,
@cgi.tree_params["prj"]["approve"]["list"])
@cgi.tree_params["prj"]["list"]["edit"].delete(id.to_s)
return
end
end
if @cgi.tree_params["prj"].include?("edit")
attrs = @cgi.tree_params["prj"]["edit"]
attrs["hide_hrs"] = attrs.include?("hide_hrs") ? "1" : "0"
if attrs.include?("id")
update(attrs["id"])
attrs.delete("id")
set(attrs)
else
attrs["prj_status"] =
attrs.include?("make_active") ? "ACTIVE" : "NEW"
attrs.delete("make_active")
set(attrs)
end
end
end
def show
project_data = Hash.new
MyCGI::add_commons(project_data)
project_data[:menu] = menu
project_data[:new] = list_item() if @policy.can_edit_project?
prj_list = list
if prj_list.length == 0
project_data[:nodata] = {}
else
project_data[:list] = list
end
s = String.new
PROJECT_TEMPLATE.expand(s, project_data)
s
end
end