461 lines
16 KiB
Ruby
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
|