# HG changeset patch # User Tomas Zeman # Date 1338903645 -7200 # Node ID ef29ecada49d5d1572d44e607aeccae7872f4644 # Parent 6a2a19785cd8df2a0392f623728b63eefdd6af2e d6925c97404faf15 Project extensions diff -r 6a2a19785cd8 -r ef29ecada49d src/main/resources/db/db-data.sql --- 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, diff -r 6a2a19785cd8 -r ef29ecada49d src/main/resources/db/db-schema.sql --- 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"); diff -r 6a2a19785cd8 -r ef29ecada49d src/main/resources/db/schema-changes-0.2-0.3.sql --- 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 ; + diff -r 6a2a19785cd8 -r ef29ecada49d src/main/scala/fis/pm/model/PmSchema.scala --- 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 => diff -r 6a2a19785cd8 -r ef29ecada49d src/main/scala/fis/pm/model/Project.scala --- 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: diff -r 6a2a19785cd8 -r ef29ecada49d src/main/scala/fis/pm/model/ProjectCrud.scala --- 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 diff -r 6a2a19785cd8 -r ef29ecada49d src/main/scala/fis/pm/ui/Delayed.scala --- 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) => {v} + 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) } diff -r 6a2a19785cd8 -r ef29ecada49d src/main/scala/fis/pm/ui/ProjectSnippet.scala --- 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 =
  • { + l10n(if (b) "projects.all" else "projects.unfinished")}
  • + (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 = { - ProjectLocations(p) flatMap(EntityLink(_)) map { l =>
    {l.asHtml}
    } + ((List(p.locationA, p.locationB) filterNot(_.get.isEmpty) map(_.asHtml)) ++ + (ProjectLocations(p) flatMap(EntityLink(_)) map(_.asHtml))) map(x => +
    {x}
    ) }
    } @@ -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) diff -r 6a2a19785cd8 -r ef29ecada49d src/main/scala/fis/pm/ui/ProjectTable.scala --- 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) } diff -r 6a2a19785cd8 -r ef29ecada49d src/main/scala/fis/pm/ui/TaskSnippet.scala --- 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 =
  • { + l10n(if (b) "tasks.all" else "tasks.unfinished")}
  • + (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, diff -r 6a2a19785cd8 -r ef29ecada49d src/main/webapp/project/list.html --- /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 @@ + + + + + Entity List + + +
    +
    +
    + + +
    +
    +
    +
    +
    + + + + diff -r 6a2a19785cd8 -r ef29ecada49d src/main/webapp/templates-hidden/_resources.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 @@ Name Note Description + Phase Identifier Deadline Product line Responsible State + Location A + Location B Tasks Company Locations Attachments + Unfinished + All + New Assigned Paused In preparation In realization Cancelled Realized + + Offer + Realization + Nový Přidělen Pozastaven V přípravě V realizaci Stornován Realizován + + Nabídka + Realizace