d6925c97404faf15 Project extensions
authorTomas Zeman <tzeman@volny.cz>
Tue, 05 Jun 2012 15:40:45 +0200
changeset 104 ef29ecada49d
parent 103 6a2a19785cd8
child 105 66aeb6b3a3a7
d6925c97404faf15 Project extensions
src/main/resources/db/db-data.sql
src/main/resources/db/db-schema.sql
src/main/resources/db/schema-changes-0.2-0.3.sql
src/main/scala/fis/pm/model/PmSchema.scala
src/main/scala/fis/pm/model/Project.scala
src/main/scala/fis/pm/model/ProjectCrud.scala
src/main/scala/fis/pm/ui/Delayed.scala
src/main/scala/fis/pm/ui/ProjectSnippet.scala
src/main/scala/fis/pm/ui/ProjectTable.scala
src/main/scala/fis/pm/ui/TaskSnippet.scala
src/main/webapp/project/list.html
src/main/webapp/templates-hidden/_resources.html
src/main/webapp/templates-hidden/_resources_cs.html
--- 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 -->