--- a/src/main/resources/db/db-data.sql Tue Jun 05 15:40:44 2012 +0200
+++ b/src/main/resources/db/db-data.sql Tue Jun 05 15:40:45 2012 +0200
@@ -3,8 +3,14 @@
(code_list, id, i18n, name, rank, dflt, i1,
i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
VALUES
-('project_state', nextval('code_list_item_id_seq'), true, 'project.state.assigned', 10, true, 0,
-0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+('project_state', nextval('code_list_item_id_seq'), true, 'project.state.new', 1, true, 0,
+0, 0, 0, 0, 0, 'new', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('project_state', nextval('code_list_item_id_seq'), true, 'project.state.assigned', 10, false, 0,
+0, 0, 0, 0, 0, 'assigned', '', '', current_timestamp, current_timestamp, false);
INSERT INTO code_list_item
(code_list, id, i18n, name, rank, dflt, i1,
i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
@@ -28,14 +34,26 @@
i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
VALUES
('project_state', nextval('code_list_item_id_seq'), true, 'project.state.cancelled', 50, false, 1,
+0, 0, 0, 0, 0, 'closed', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('project_state', nextval('code_list_item_id_seq'), true, 'project.state.realized', 40, false, 1,
+0, 0, 0, 0, 0, 'closed', '', '', current_timestamp, current_timestamp, false);
+-- Project phases
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('project_phase', nextval('code_list_item_id_seq'), true, 'project.phase.offer', 10, true, 0,
0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
INSERT INTO code_list_item
(code_list, id, i18n, name, rank, dflt, i1,
i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
VALUES
-('project_state', nextval('code_list_item_id_seq'), true, 'project.state.realized', 40, false, 1,
+('project_phase', nextval('code_list_item_id_seq'), true, 'project.phase.realization', 20, false, 0,
0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
-
-- Task states
INSERT INTO code_list_item
(code_list, id, i18n, name, rank, dflt, i1,
--- a/src/main/resources/db/db-schema.sql Tue Jun 05 15:40:44 2012 +0200
+++ b/src/main/resources/db/db-schema.sql Tue Jun 05 15:40:45 2012 +0200
@@ -175,6 +175,7 @@
create sequence "attachment_id_seq";
create table "project" (
"id" bigint primary key not null,
+ "phase" bigint not null,
"name" varchar(100) not null,
"description" varchar(40960) not null,
"responsible" bigint not null,
@@ -182,6 +183,8 @@
"ident_s" varchar(256) not null,
"state" bigint not null,
"product_line" bigint,
+ "location_a" varchar(1024) not null,
+ "location_b" varchar(1024) not null,
"note" varchar(10240),
"created_at" timestamp not null,
"created_by" bigint,
@@ -266,6 +269,7 @@
alter table "project" add foreign key ("responsible") references "user"("id");
alter table "project" add foreign key ("product_line") references "code_list_item"("id") on delete set null;
alter table "project" add foreign key ("state") references "code_list_item"("id");
+alter table "project" add foreign key ("phase") references "code_list_item"("id");
alter table "task" add foreign key ("responsible") references "user"("id");
alter table "task" add foreign key ("task_type") references "code_list_item"("id");
alter table "task" add foreign key ("state") references "code_list_item"("id");
--- a/src/main/resources/db/schema-changes-0.2-0.3.sql Tue Jun 05 15:40:44 2012 +0200
+++ b/src/main/resources/db/schema-changes-0.2-0.3.sql Tue Jun 05 15:40:45 2012 +0200
@@ -87,3 +87,17 @@
DROP RULE rtask ON vtask;
DROP VIEW vtask;
ALTER TABLE task ALTER COLUMN num SET NOT NULL;
+
+-- project extensions
+UPDATE code_list_item SET s1 = 'closed', i1 = 0 WHERE code_list = 'project_state' AND i1 = 1;
+UPDATE code_list_item SET s1 = 'assigned' WHERE code_list = 'project_state' AND name LIKE '%assigned';
+ALTER TABLE project ADD COLUMN phase bigint;
+alter table "project" add foreign key ("phase") references "code_list_item"("id");
+UPDATE project SET phase = (select id from code_list_item where name = 'project.phase.offer');
+ALTER TABLE project ALTER COLUMN phase SET NOT NULL;
+ALTER TABLE project ADD COLUMN "location_a" varchar(1024);
+ALTER TABLE project ADD COLUMN "location_b" varchar(1024);
+UPDATE project SET location_a = '', location_b = '';
+ALTER TABLE project ALTER COLUMN "location_a" SET NOT NULL ;
+ALTER TABLE project ALTER COLUMN "location_b" SET NOT NULL ;
+
--- a/src/main/scala/fis/pm/model/PmSchema.scala Tue Jun 05 15:40:44 2012 +0200
+++ b/src/main/scala/fis/pm/model/PmSchema.scala Tue Jun 05 15:40:45 2012 +0200
@@ -37,6 +37,9 @@
val projectState = oneToManyRelation(cli, projectT).
via((i, p) => (i.id === p.stateFld))
+ val projectPhase = oneToManyRelation(cli, projectT).
+ via((i, p) => (i.id === p.phaseFld))
+
Project.users.default.set(activeUsersF)
def projects: Iterable[Project] = from(projectT)(p =>
--- a/src/main/scala/fis/pm/model/Project.scala Tue Jun 05 15:40:44 2012 +0200
+++ b/src/main/scala/fis/pm/model/Project.scala Tue Jun 05 15:40:45 2012 +0200
@@ -21,10 +21,13 @@
import net.liftweb.common._
import net.liftweb.record.{MetaRecord, Record}
import net.liftweb.record.field._
+import net.liftweb.squerylrecord.RecordTypeMode._
import net.liftweb.util._
import net.tz.lift.model._
import net.tz.lift.model.{FieldLabel => FL}
import org.squeryl.annotations.Column
+import org.squeryl.dsl.QueryDsl
+import org.squeryl.dsl.ast.LogicalBoolean
class Project private() extends Record[Project] with Entity[Project] {
def meta = Project
@@ -41,16 +44,53 @@
with FL
def state = stateFld.item.map { ProjectState(_) }
+
+ @Column("phase")
+ protected[pm] val phaseFld = new CodeListItemField(this, 'project_phase)
+ with FL
+
+ val locationA = new StringField(this, 1024) with FL
+ val locationB = new StringField(this, 1024) with FL
+
+ def delayed = state map {
+ case ProjectState.Closed(_, _) => false
+ case _ => deadline.date.plusDays(1).isBefore(null) // null means now
+ } openOr false
}
object Project extends Project with MetaRecord[Project] with SimpleInjector {
object users extends Inject[Iterable[User]](() => Nil)
}
-case class ProjectState(id: Long, k: String, closed: Boolean)
+abstract sealed class ProjectState {
+ def id: Long
+ def k: String
+}
+
object ProjectState {
- def apply(i: CodeListItem): ProjectState =
- ProjectState(i.id, i.name.get, i.i1.get == 1)
+ def apply(i: CodeListItem): ProjectState = i.s1.get.toLowerCase match {
+ case "new" => New (i.id, i.name.get)
+ case "assigned" => Assigned (i.id, i.name.get)
+ case "closed" => Closed (i.id, i.name.get)
+ case _ => InProgress(i.id, i.name.get)
+ }
+
+ def apply(s: ProjectState): Box[CodeListItem] =
+ Project.stateFld.cl.items find { i => (i.s1.get.toLowerCase, s) match {
+ case ("new", New(_, _)) => true
+ case ("assigned", Assigned(_, _)) => true
+ case ("closed", Closed(_, _)) => true
+ case (_, InProgress(_, _)) => true
+ case (_, _) => false
+ }}
+
+ case class New(id: Long, k: String) extends ProjectState
+ case class Assigned(id: Long, k: String) extends ProjectState
+ case class InProgress(id: Long, k: String) extends ProjectState
+ case class Closed(id: Long, k: String) extends ProjectState
+
+ def unfinishedClause[T](i: CodeListItem)(implicit dsl: QueryDsl):
+ LogicalBoolean = (i.s1 <> "closed")
}
// vim: set ts=2 sw=2 et:
--- a/src/main/scala/fis/pm/model/ProjectCrud.scala Tue Jun 05 15:40:44 2012 +0200
+++ b/src/main/scala/fis/pm/model/ProjectCrud.scala Tue Jun 05 15:40:45 2012 +0200
@@ -16,9 +16,21 @@
package fis.pm.model
import fis.base.model.RecordCrud
+import net.liftweb.common._
-trait ProjectCrud extends RecordCrud[Project] {
+trait ProjectCrud extends RecordCrud[Project] with Loggable {
val table = PmSchema.projectT
+
+ type T = Project
+
+ /** Resets project state to New if project phase has changed. */
+ override protected def beforeUpdate(v: T): Box[T] = {
+ get(v.id) map { orig =>
+ if (orig.phaseFld.get != v.phaseFld.get)
+ ProjectState(ProjectState.New(0, "")) foreach { s => v.stateFld(s.id) }
+ }
+ super.beforeUpdate(v)
+ }
}
object ProjectCrud extends ProjectCrud
--- a/src/main/scala/fis/pm/ui/Delayed.scala Tue Jun 05 15:40:44 2012 +0200
+++ b/src/main/scala/fis/pm/ui/Delayed.scala Tue Jun 05 15:40:45 2012 +0200
@@ -21,13 +21,18 @@
import net.liftweb.util._
import net.liftweb.util.Helpers._
import net.tz.lift.model._
+import scala.xml.Text
object Delayed {
private def decorate(delayed: Boolean, f: ReadableField) =
delayed.box(new ReadOnlyField(f.name, f.displayName,
- addAttributes(f.asHtml, "class" -> "delayed"), Empty)) openOr f
+ addAttributes(f.asHtml match {
+ case Text(v) => <span>{v}</span>
+ case v => v
+ }, "class" -> "delayed"), Empty)) openOr f
def apply(t: Task)(f: ReadableField) = decorate(t.delayed, f)
+ def apply(p: Project)(f: ReadableField) = decorate(p.delayed, f)
}
--- a/src/main/scala/fis/pm/ui/ProjectSnippet.scala Tue Jun 05 15:40:44 2012 +0200
+++ b/src/main/scala/fis/pm/ui/ProjectSnippet.scala Tue Jun 05 15:40:45 2012 +0200
@@ -18,6 +18,7 @@
import fis.aaa.ui.IfLoggedIn
import fis.base.model.ReadOnlyField
import fis.base.ui._
+import fis.cl.model._
import fis.crm.model._
import fis.fs.model._
import fis.fs.ui._
@@ -28,6 +29,7 @@
import net.liftweb.record.field._
import net.liftweb.sitemap._
import net.liftweb.sitemap.Loc._
+import net.liftweb.squerylrecord.RecordTypeMode._
import net.liftweb.util._
import net.liftweb.util.Helpers._
import net.tz.lift.model._
@@ -39,7 +41,7 @@
private val listPre = Menu("project.list", l10n("Projects")) / prefix >>
Title(_ => i18n("Projects")) >>
- locTpl("entity/list") >> Snippet("list", list)
+ locTpl("project/list") >> Snippet("list", list)
private val createPre = Menu("project.create", l10n("Create")) / prefix / ADD >>
Title(_ => i18n("Create project")) >> IfLoggedIn.test >>
@@ -93,7 +95,21 @@
private def cur = viewLoc.currentValue or editLoc.currentValue or
deleteLoc.currentValue
- private def list: CssTr = { _ => ProjectTable(PmSchema.projects) }
+ private def allQp: Boolean = (S param "all") flatMap(asBoolean(_)) openOr false
+ private def allQp(url: String, all: Boolean) =
+ all.box(appendQueryParameters(url, List("all" -> "1"))) openOr url
+
+ private def list: CssTr = {
+ "li" #> (List(false, true) map { b =>
+ val html = <li><a href={allQp(url.list, b)}>{
+ l10n(if (b) "projects.all" else "projects.unfinished")}</a></li>
+ (b == allQp).box(html % ("class" -> "active")) openOr html
+ }) &
+ ".content *" #> ProjectTable(allQp.box(PmSchema.projects) openOr
+ from(PmSchema.projectT, CodeListSchema.cli)((p, i) =>
+ where(p.stateFld === i.id and ProjectState.unfinishedClause(i))
+ select(p) orderBy(p.deadline asc)))
+ }
private def panel: CssTr = "*" #> cur.map { p => ViewPanel(fields(p)) }
@@ -107,9 +123,10 @@
def list: String = listM.loc.calcDefaultHref
}
- private def fields(p: Project) = List(p.name, p.identS, p.stateFld,
- p.createdBy, p.createdAt, p.deadline, p.responsible, p.productLine,
- ProjectCompanyField(p), ProjectLocationsField(p), p.description, p.note)
+ private def fields(p: Project) = List(p.name, p.identS, p.phaseFld,
+ p.stateFld, p.createdBy, p.createdAt, p.deadline, p.responsible,
+ p.productLine, ProjectCompanyField(p), ProjectLocationsField(p),
+ p.description, p.note)
private case class ProjectLink(c: Project) extends EntityLink[Project](c, url.view)
@@ -125,7 +142,9 @@
private case class ProjectLocationsField(p: Project) extends ReadOnlyField(
"locations", l10n("project.locations"), NodeSeq.Empty, Empty) {
override def asHtml = <span>{
- ProjectLocations(p) flatMap(EntityLink(_)) map { l => <div>{l.asHtml}</div> }
+ ((List(p.locationA, p.locationB) filterNot(_.get.isEmpty) map(_.asHtml)) ++
+ (ProjectLocations(p) flatMap(EntityLink(_)) map(_.asHtml))) map(x =>
+ <div>{x}</div>)
}</span>
}
@@ -137,8 +156,9 @@
ProjectCompanyField(project))
private def formFields(p: Project) = {
- List(p.name, p.identS, p.stateFld, p.deadline, p.responsible,
- p.productLine, company.get, p.description, p.note)
+ List(p.name, p.identS, p.phaseFld, p.stateFld, p.deadline, p.responsible,
+ p.productLine, company.get, p.locationA, p.locationB, p.description,
+ p.note)
}
override def screenFields: List[BaseField] = formFields(project)
--- a/src/main/scala/fis/pm/ui/ProjectTable.scala Tue Jun 05 15:40:44 2012 +0200
+++ b/src/main/scala/fis/pm/ui/ProjectTable.scala Tue Jun 05 15:40:45 2012 +0200
@@ -19,8 +19,9 @@
import fis.base.ui._
object ProjectTable extends FieldTable[Project] {
- def fields(p: Project) = EntityLink(p) ++ Seq(p.identS, p.deadline,
- p.responsible, p.description, p.note)
+ def fields(p: Project) = (EntityLink(p) ++ Seq(p.identS, p.phaseFld,
+ p.stateFld, p.deadline, p.responsible, p.description, p.note)) map
+ Delayed(p)
def apply(l: Iterable[Project]) = build(Project, l)
}
--- a/src/main/scala/fis/pm/ui/TaskSnippet.scala Tue Jun 05 15:40:44 2012 +0200
+++ b/src/main/scala/fis/pm/ui/TaskSnippet.scala Tue Jun 05 15:40:45 2012 +0200
@@ -39,20 +39,6 @@
Title(_ => i18n("Tasks")) >>
locTpl("task/list") >> Snippet("list", list)
- private val listUnfinishedM = Menu("task.list-unfinished",
- l10n("tasks.unfinished")) / prefix >>
- Title(_ => i18n("Tasks")) >>
- new TaskReport(() => from(PmSchema.taskT, CodeListSchema.cli)((t, i) =>
- where(t.stateFld === i.id and TaskState.unfinishedClause(i))
- select(t) orderBy(t.deadline asc))) >>
- locTpl("task/list") >> Snippet("list", list) >> Hidden
-
- private val listAllM = Menu("task.list-all",
- l10n("tasks.all")) / prefix / "all" >>
- Title(_ => i18n("All Tasks")) >>
- new TaskReport(() => PmSchema.tasks) >>
- locTpl("task/list") >> Snippet("list", list) >> Hidden
-
private val viewPre = Menu.param[Task]("task.view", l10n("Task"), parse,
encode) / prefix / * >> Title(t => i18n("Task %s", t.linkName)) >>
locTpl("task/view") >> Snippet("panel", panel) >>
@@ -87,26 +73,26 @@
private lazy val deleteLoc = deleteM.toLoc
private lazy val postCommentLoc = postCommentM.toLoc
- val menu = listM submenus(listUnfinishedM, listAllM, viewM, editM, deleteM,
- postCommentM, attachments.menu)
+ val menu = listM submenus(viewM, editM, deleteM, postCommentM,
+ attachments.menu)
private def cur = viewLoc.currentValue or editLoc.currentValue or
deleteLoc.currentValue or postCommentLoc.currentValue
- private def list: CssTr = {
- val curM = Box(for {
- m <- List(listUnfinishedM, listAllM)
- cur <- S.location
- loc <- SiteMap.findAndTestLoc(m.name) if cur.calcDefaultHref == loc.calcDefaultHref
- } yield m) openOr listUnfinishedM
+ private def allQp: Boolean = (S param "all") flatMap(asBoolean(_)) openOr false
+ private def allQp(url: String, all: Boolean) =
+ all.box(appendQueryParameters(url, List("all" -> "1"))) openOr url
- "li" #> (List(listUnfinishedM, listAllM) map { m =>
- val nav = (new SecNavMenu(m)).toHtml
- (m == curM).box(addAttributes(nav, "class" -> "active")) openOr nav }) &
- ".content *" #> curM.params.flatMap {
- case v: TaskReport => _TaskTable(v.tasks)
- case _ => Nil
- }
+ private def list: CssTr = {
+ "li" #> (List(false, true) map { b =>
+ val html = <li><a href={allQp(url.list, b)}>{
+ l10n(if (b) "tasks.all" else "tasks.unfinished")}</a></li>
+ (b == allQp).box(html % ("class" -> "active")) openOr html
+ }) &
+ ".content *" #> _TaskTable(allQp.box(PmSchema.tasks) openOr
+ from(PmSchema.taskT, CodeListSchema.cli)((t, i) =>
+ where(t.stateFld === i.id and TaskState.unfinishedClause(i))
+ select(t) orderBy(t.deadline asc)))
}
private def panel: CssTr = "*" #> cur.map { t => ViewPanel(fields(t)) }
@@ -119,6 +105,7 @@
object url {
def view: Task => Box[String] = (viewLoc.calcHref _) andThen (Box !! _)
+ def list: String = listM.loc.calcDefaultHref
}
private def fields(t: Task) = List(t.ident, t.name, t.project, t.taskType,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/project/list.html Tue Jun 05 15:40:45 2012 +0200
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
+ <title>Entity List</title>
+ </head>
+ <body class="lift:content_id=main">
+ <div id="main" class="lift:surround?with=default;at=content">
+ <div class="row">
+ <div class="span12">
+ <span class="lift:list">
+ <ul class="nav nav-tabs">
+ <li></li>
+ </ul>
+ <div class="content"></div>
+ </span>
+ </div>
+ </div> <!-- /row -->
+ </div>
+ </body>
+</html>
+
+
--- a/src/main/webapp/templates-hidden/_resources.html Tue Jun 05 15:40:44 2012 +0200
+++ b/src/main/webapp/templates-hidden/_resources.html Tue Jun 05 15:40:45 2012 +0200
@@ -213,22 +213,31 @@
<res name="project.name" lang="en" default="true">Name</res>
<res name="project.note" lang="en" default="true">Note</res>
<res name="project.description" lang="en" default="true">Description</res>
+ <res name="project.phaseFld" lang="en" default="true">Phase</res>
<res name="project.identS" lang="en" default="true">Identifier</res>
<res name="project.deadline" lang="en" default="true">Deadline</res>
<res name="project.productLine" lang="en" default="true">Product line</res>
<res name="project.responsible" lang="en" default="true">Responsible</res>
<res name="project.stateFld" lang="en" default="true">State</res>
+ <res name="project.locationA" lang="en" default="true">Location A</res>
+ <res name="project.locationB" lang="en" default="true">Location B</res>
<res name="project.tasks" lang="en" default="true">Tasks</res>
<res name="project.company" lang="en" default="true">Company</res>
<res name="project.locations" lang="en" default="true">Locations</res>
<res name="project.attachments" lang="en" default="true">Attachments</res>
+ <res name="projects.unfinished" lang="en" default="true">Unfinished</res>
+ <res name="projects.all" lang="en" default="true">All</res>
<!-- project states -->
+ <res name="project.state.new" lang="en" default="true">New</res>
<res name="project.state.assigned" lang="en" default="true">Assigned</res>
<res name="project.state.paused" lang="en" default="true">Paused</res>
<res name="project.state.in_preparation" lang="en" default="true">In preparation</res>
<res name="project.state.in_realization" lang="en" default="true">In realization</res>
<res name="project.state.cancelled" lang="en" default="true">Cancelled</res>
<res name="project.state.realized" lang="en" default="true">Realized</res>
+ <!-- project phases -->
+ <res name="project.phase.offer" lang="en" default="true">Offer</res>
+ <res name="project.phase.realization" lang="en" default="true">Realization</res>
<!-- task
--- a/src/main/webapp/templates-hidden/_resources_cs.html Tue Jun 05 15:40:44 2012 +0200
+++ b/src/main/webapp/templates-hidden/_resources_cs.html Tue Jun 05 15:40:45 2012 +0200
@@ -201,22 +201,31 @@
<res name="project.name" lang="cs">Název</res>
<res name="project.note" lang="cs">Poznámka</res>
<res name="project.description" lang="cs">Popis</res>
+ <res name="project.phaseFld" lang="cs">Typ</res>
<res name="project.identS" lang="cs">Identifikátor</res>
<res name="project.deadline" lang="cs">Termín</res>
<res name="project.productLine" lang="cs">Produktová řada</res>
<res name="project.responsible" lang="cs">Odpovědný</res>
<res name="project.stateFld" lang="cs">Stav</res>
+ <res name="project.locationA" lang="cs">Lokalita A</res>
+ <res name="project.locationB" lang="cs">Lokalita B</res>
<res name="project.tasks" lang="cs">Úkoly</res>
<res name="project.company" lang="cs">Společnost</res>
<res name="project.locations" lang="cs">Lokality</res>
<res name="project.attachments" lang="cs">Přílohy</res>
+ <res name="projects.unfinished" lang="cs">Nedokončené</res>
+ <res name="projects.all" lang="cs">Všechny</res>
<!-- project states -->
+ <res name="project.state.new" lang="cs">Nový</res>
<res name="project.state.assigned" lang="cs">Přidělen</res>
<res name="project.state.paused" lang="cs">Pozastaven</res>
<res name="project.state.in_preparation" lang="cs">V přípravě</res>
<res name="project.state.in_realization" lang="cs">V realizaci</res>
<res name="project.state.cancelled" lang="cs">Stornován</res>
<res name="project.state.realized" lang="cs">Realizován</res>
+ <!-- project phases -->
+ <res name="project.phase.offer" lang="cs">Nabídka</res>
+ <res name="project.phase.realization" lang="cs">Realizace</res>
<!-- task -->