--- a/src/main/resources/db/db-schema.sql Thu May 03 22:31:09 2012 +0200
+++ b/src/main/resources/db/db-schema.sql Wed May 09 22:53:03 2012 +0200
@@ -206,6 +206,10 @@
"project" bigint not null,
"company" bigint not null
);
+create table "project_location" (
+ "location" bigint not null,
+ "project" bigint not null
+ );
-- foreign key constraints :
alter table "address" add foreign key ("city_id") references "city"("id");
alter table "city" add foreign key ("country_id") references "country"("id");
@@ -228,9 +232,12 @@
alter table "user_contact" add foreign key ("contact") references "contact"("id") on delete cascade;
alter table "project_company" add foreign key ("project") references "project"("id") on delete cascade;
alter table "project_company" add foreign key ("company") references "company"("id") on delete cascade;
+alter table "project_location" add foreign key ("project") references "project"("id") on delete cascade;
+alter table "project_location" add foreign key ("location") references "location"("id") on delete cascade;
-- composite key indexes :
alter table "company_contact" add unique("entity","contact");
alter table "user_contact" add unique("entity","contact");
alter table "project_company" add unique("project","company");
+alter table "project_location" add unique("project","location");
-- column group indexes :
create index "user_deleted_active_idx" on "user" ("deleted","active");
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/resources/db/schema-changes-0.2-0.3.sql Wed May 09 22:53:03 2012 +0200
@@ -0,0 +1,10 @@
+-- DATABASE SCHEMA CHANGES BETWEEN 0.2 -> 0.3 VERSION
+
+-- project_location
+create table "project_location" (
+ "location" bigint not null,
+ "project" bigint not null
+ );
+alter table "project_location" add foreign key ("project") references "project"("id") on delete cascade;
+alter table "project_location" add foreign key ("location") references "location"("id") on delete cascade;
+alter table "project_location" add unique("project","location");
--- a/src/main/scala/fis/geo/model/GeoSchema.scala Thu May 03 22:31:09 2012 +0200
+++ b/src/main/scala/fis/geo/model/GeoSchema.scala Wed May 09 22:53:03 2012 +0200
@@ -38,6 +38,10 @@
Country.countries.default.set { () => from(countryT)( c =>
select(c) orderBy(c.name asc)) }
+
+ Location.locations.default.set { () => from(locationT, addressT, cityT)(
+ (l, a, c) => where(l.address === a.id and a.city === c.id)
+ select(l) orderBy(c.name asc, l.name asc)) }
}
object GeoSchema extends GeoSchema
--- a/src/main/scala/fis/geo/model/Location.scala Thu May 03 22:31:09 2012 +0200
+++ b/src/main/scala/fis/geo/model/Location.scala Wed May 09 22:53:03 2012 +0200
@@ -19,6 +19,7 @@
import net.liftweb.record.{MetaRecord, Record}
import net.liftweb.record.field._
import net.liftweb.squerylrecord.KeyedRecord
+import net.liftweb.util.SimpleInjector
import net.tz.lift.model.{FieldLabel => FL}
import org.squeryl.annotations.Column
@@ -32,6 +33,9 @@
}
-object Location extends Location with MetaRecord[Location]
+object Location extends Location with MetaRecord[Location] with
+ SimpleInjector {
+ object locations extends Inject[Iterable[Location]](() => Nil)
+}
// vim: set ts=2 sw=2 et:
--- a/src/main/scala/fis/geo/ui/LocationSnippet.scala Thu May 03 22:31:09 2012 +0200
+++ b/src/main/scala/fis/geo/ui/LocationSnippet.scala Wed May 09 22:53:03 2012 +0200
@@ -18,6 +18,8 @@
import fis.aaa.ui.IfLoggedIn
import fis.base.ui._
import fis.geo.model._
+import fis.pm.model.LocationProjects
+import fis.pm.ui.ProjectTable
import net.liftweb.common._
import net.liftweb.http._
import net.liftweb.sitemap._
@@ -37,7 +39,8 @@
private val viewPre = Menu.param[Location]("location.view", l10n("Location"), parse,
encode) / prefix / * >> Title(l => i18n("Location %s", l.linkName)) >>
- locTpl("entity/view") >> Snippet("panel", panel) >> Hidden
+ locTpl("location/view") >> Snippet("panel", panel) >>
+ Snippet("projects", projects) >> Hidden
private val editPre = Menu.param[Location]("location.edit", l10n("Edit"), parse,
encode) / prefix / * / EDIT >>
@@ -64,6 +67,9 @@
private def panel: CssTr = "*" #> cur.map(LocationPanel(_))
+ private def projects: CssTr = "*" #> cur.map { l =>
+ ProjectTable(LocationProjects(l)) }
+
object url {
def view: Location => Box[String] = (viewLoc.calcHref _) andThen (Box !! _)
}
--- a/src/main/scala/fis/pm/model/PmSchema.scala Thu May 03 22:31:09 2012 +0200
+++ b/src/main/scala/fis/pm/model/PmSchema.scala Wed May 09 22:53:03 2012 +0200
@@ -72,6 +72,15 @@
))
projectCompany.leftForeignKeyDeclaration.constrainReference(onDelete cascade)
projectCompany.rightForeignKeyDeclaration.constrainReference(onDelete cascade)
+
+ /* project - location */
+ val projectLocation = manyToManyRelation(projectT, locationT).
+ via[ProjectLocation]((p, l, pl) => (
+ p.id === pl.project,
+ l.id === pl.location
+ ))
+ projectLocation.leftForeignKeyDeclaration.constrainReference(onDelete cascade)
+ projectLocation.rightForeignKeyDeclaration.constrainReference(onDelete cascade)
}
object PmSchema extends PmSchema
@@ -104,6 +113,21 @@
select(p) orderBy(p.name asc))
}
+object ProjectLocations {
+ import fis.geo.model._
+ def apply(p: Project): Iterable[Location] =
+ from(PmSchema.projectLocation.left(p), GeoSchema.addressT,
+ GeoSchema.cityT)((l, a, c) =>
+ where(l.address === a.id and a.city === c.id)
+ select(l) orderBy(c.name asc, l.name asc))
+}
+
+object LocationProjects {
+ import fis.geo.model._
+ def apply(l: Location): Iterable[Project] =
+ from(PmSchema.projectLocation.right(l))(p => select(p) orderBy(p.name asc))
+}
+
/* Many-to-many relations */
import org.squeryl.KeyedEntity
@@ -114,4 +138,9 @@
def id = CompositeKey2(project, company)
}
+case class ProjectLocation(val project: Long, val location: Long)
+ extends KeyedEntity[CompositeKey2[Long, Long]] {
+ def id = CompositeKey2(project, location)
+}
+
// vim: set ts=2 sw=2 et:
--- a/src/main/scala/fis/pm/ui/ProjectSnippet.scala Thu May 03 22:31:09 2012 +0200
+++ b/src/main/scala/fis/pm/ui/ProjectSnippet.scala Wed May 09 22:53:03 2012 +0200
@@ -16,8 +16,10 @@
package fis.pm.ui
import fis.aaa.ui.IfLoggedIn
+import fis.base.model.ReadOnlyField
import fis.base.ui._
import fis.crm.model._
+import fis.geo.model._
import fis.pm.model._
import net.liftweb.common._
import net.liftweb.http._
@@ -62,19 +64,27 @@
IfLoggedIn.testVal >>
locTpl("entity/form") >> Snippet("form", taskF) >> Hidden
+ private val locationsPre = Menu.param[Project]("project.locations",
+ l10n("Edit locations"), parse, encode) / prefix / * / "locations" >>
+ Title(p => i18n("Edit locations of project %s", p.linkName)) >>
+ IfLoggedIn.testVal >>
+ locTpl("entity/form") >> Snippet("form", locationsF) >> Hidden
+
private val listM = listPre >> SecNav(createPre).build
private val createM = createPre >> SecNav(listPre).build
private val viewM = viewPre >> (SecNav(editPre) + deletePre +
- createTaskPre).build
+ locationsPre + createTaskPre).build
private val editM = editPre >> SecNav(viewPre).build
private val deleteM = deletePre >> SecNav(viewPre).build
private val createTaskM = createTaskPre >> SecNav(viewPre).build
+ private val locationsM = locationsPre >> SecNav(viewPre).build
private lazy val viewLoc = viewM.toLoc
private lazy val editLoc = editM.toLoc
private lazy val deleteLoc = deleteM.toLoc
- val menu = listM submenus(viewM, editM, createM, deleteM, createTaskM)
+ val menu = listM submenus(viewM, editM, createM, deleteM, createTaskM,
+ locationsM)
private def cur = viewLoc.currentValue or editLoc.currentValue or
deleteLoc.currentValue
@@ -92,7 +102,7 @@
private def fields(p: Project) = List(p.name, p.identS, p.stateFld,
p.createdBy, p.createdAt, p.deadline, p.responsible, p.productLine,
- ProjectCompanyField(p), p.description, p.note)
+ ProjectCompanyField(p), ProjectLocationsField(p), p.description, p.note)
private case class ProjectLink(c: Project) extends EntityLink[Project](c, url.view)
@@ -105,6 +115,13 @@
PmSchema.projectCompany.left(p).headOption.map(_.id)
}
+ 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> }
+ }</span>
+ }
+
private object form extends HorizontalScreen with CancelButton with
SaveButton {
@@ -167,6 +184,52 @@
}
}
+ private object locationsF extends HorizontalScreen with CancelButton with
+ SaveButton {
+
+ private class V(iv: Boolean, val l: Location) { var v: Boolean = iv }
+
+ private object locs extends ScreenVar[Iterable[V]](Nil)
+
+ def v2r(v: V): Box[List[NodeSeq]] = for {
+ ll <- EntityLink(v.l)
+ a <- v.l.address.vend
+ c <- a.city.vend
+ cl <- EntityLink(c)
+ } yield {
+ List(SHtml.checkbox(v.v, v.v = _), ll.asHtml, v.l.address.asHtml,
+ cl.asHtml)
+ }
+
+ def locsTbl =
+ (new DataTable(List(l10n("Check"), l10n("Location"),
+ Location.address.displayName, l10n("City")),
+ locs flatMap { v2r _ } toList))(NodeSeq.Empty)
+
+ addFields(() => new ReadOnlyField("locations", l10n("project.locations"),
+ NodeSeq.Empty, Full(locsTbl)))
+
+ override def localSetup() {
+ locationsM.toLoc.currentValue.foreach { p =>
+ val ids = ProjectLocations(p) map(_.id) toSet
+ val ls = Location.locations() map { l => new V(ids contains l.id, l) }
+ locs(ls)
+ }
+ }
+
+ def finish() { locationsM.toLoc.currentValue.foreach { p =>
+ val fk = PmSchema.projectLocation.left(p)
+ val curIds = fk map(_.id) toSet
+ val newLocs = locs filter(_.v) map(_.l)
+ val newIds = newLocs map(_.id) toSet
+
+ fk filterNot(newIds contains _.id) foreach { l => fk.dissociate(l) }
+ newLocs filterNot(curIds contains _.id) foreach { l => fk.associate(l) }
+ S notice l10n("Locations saved.")
+ S redirectTo viewLoc.calcHref(p)
+ }}
+ }
+
}
// vim: set ts=2 sw=2 et:
--- a/src/main/webapp/css/base.css Thu May 03 22:31:09 2012 +0200
+++ b/src/main/webapp/css/base.css Wed May 09 22:53:03 2012 +0200
@@ -28,6 +28,7 @@
font-weight: bold;
width: 150px;
text-align: justify;
+ vertical-align: top;
}
.attr-name-wide {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/location/view.html Wed May 09 22:53:03 2012 +0200
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
+ <title>Entity View</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:panel"></span>
+ </div>
+ </div> <!-- /row -->
+ <div class="row section">
+ <div class="span12">
+ <h3><span class="lift:loc?locid=location.projects"></span></h3>
+ <span class="lift:projects"></span>
+ </div>
+ </div> <!-- /row -->
+ </div>
+ </body>
+</html>
+
+
--- a/src/main/webapp/templates-hidden/_resources.html Thu May 03 22:31:09 2012 +0200
+++ b/src/main/webapp/templates-hidden/_resources.html Wed May 09 22:53:03 2012 +0200
@@ -16,6 +16,7 @@
<!--
Remove
Entity type
+ Check
-->
<!-- authn -->
@@ -183,6 +184,7 @@
Location %s deleted.
Location %s saved.
-->
+ <res name="location.projects" lang="en" default="true">Used in projects</res>
<!-- location fields -->
<res name="location.name" lang="en" default="true">Name</res>
<res name="location.note" lang="en" default="true">Note</res>
@@ -200,6 +202,9 @@
Project %s saved.
Really delete this project?
Project %s deleted.
+ Edit locations
+ Edit locations of project %s
+ Locations saved.
-->
<res name="Projects" lang="en" default="true">Projects</res>
<!-- project fields -->
@@ -213,6 +218,7 @@
<res name="project.stateFld" lang="en" default="true">State</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>
<!-- project states -->
<res name="project.state.assigned" lang="en" default="true">Assigned</res>
<res name="project.state.paused" lang="en" default="true">Paused</res>
--- a/src/main/webapp/templates-hidden/_resources_cs.html Thu May 03 22:31:09 2012 +0200
+++ b/src/main/webapp/templates-hidden/_resources_cs.html Wed May 09 22:53:03 2012 +0200
@@ -15,6 +15,7 @@
<res name="createdBy" lang="cs">Vytvořil</res>
<res name="updatedAt" lang="cs">Aktualizováno</res>
<res name="updatedBy" lang="cs">Aktualizoval</res>
+ <res name="Check" lang="cs">Označit</res>
<!-- authn -->
@@ -173,6 +174,7 @@
<res name="Really delete this location?" lang="cs">Skutečně smazat lokalitu?</res>
<res name="Location %s deleted." lang="cs">Lokalita %s smazána.</res>
<res name="Location %s saved." lang="cs">Lokalita %s uložena.</res>
+ <res name="location.projects" lang="cs">Použito v projektech</res>
<!-- location fields -->
<res name="location.name" lang="cs">Název</res>
@@ -190,6 +192,9 @@
<res name="Project %s saved." lang="cs">Projekt %s uložen.</res>
<res name="Really delete this project?" lang="cs">Skutečně smazat projekt?</res>
<res name="Project %s deleted." lang="cs">Projekt %s smazán.</res>
+ <res name="Edit locations" lang="cs">Upravit lokality</res>
+ <res name="Edit locations of project %s" lang="cs">Upravit lokality pro projekt %s</res>
+ <res name="Locations saved." lang="cs">Lokality uloženy.</res>
<!-- project fields -->
<res name="project.name" lang="cs">Název</res>
<res name="project.note" lang="cs">Poznámka</res>
@@ -201,6 +206,7 @@
<res name="project.stateFld" lang="cs">Stav</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>
<!-- project states -->
<res name="project.state.assigned" lang="cs">Přidělen</res>
<res name="project.state.paused" lang="cs">Pozastaven</res>