User contacts
authorTomas Zeman <tzeman@volny.cz>
Fri, 20 Apr 2012 09:45:51 +0200
changeset 73 4bcb7deedd3f
parent 72 077b875a2a0c
child 74 3ce7ecb6bc70
User contacts
db/db-schema.sql
src/main/scala/fis/aaa/model/AaaSchema.scala
src/main/scala/fis/aaa/ui/UserSnippet.scala
src/main/webapp/entity/add-contact.html
src/main/webapp/user/view.html
--- a/db/db-schema.sql	Fri Apr 20 08:44:36 2012 +0200
+++ b/db/db-schema.sql	Fri Apr 20 09:45:51 2012 +0200
@@ -34,22 +34,6 @@
 create sequence "code_list_item_id_seq";
 -- indexes on code_list_item
 create index "code_list_item_code_list_idx" on "code_list_item" ("code_list");
-create table "user" (
-    "name" varchar(100) not null,
-    "updated_at" timestamp not null,
-    "id" bigint primary key not null,
-    "note" varchar(10240),
-    "created_at" timestamp not null,
-    "created_by" bigint,
-    "login" varchar(40) not null,
-    "updated_by" bigint,
-    "deleted" boolean not null,
-    "active" boolean not null,
-    "password" varchar(128) not null
-  );
-create sequence "user_id_seq";
--- indexes on user
-create index "user_login_idx" on "user" ("login");
 create table "city" (
     "name" varchar(100) not null,
     "updated_at" timestamp not null,
@@ -142,6 +126,26 @@
     "contact" bigint not null,
     "entity" bigint not null
   );
+create table "user" (
+    "name" varchar(100) not null,
+    "updated_at" timestamp not null,
+    "id" bigint primary key not null,
+    "note" varchar(10240),
+    "created_at" timestamp not null,
+    "created_by" bigint,
+    "login" varchar(40) not null,
+    "updated_by" bigint,
+    "deleted" boolean not null,
+    "active" boolean not null,
+    "password" varchar(128) not null
+  );
+create sequence "user_id_seq";
+-- indexes on user
+create index "user_login_idx" on "user" ("login");
+create table "user_contact" (
+    "contact" bigint not null,
+    "entity" bigint not null
+  );
 -- foreign key constraints :
 alter table "address" add constraint "addressFK1" foreign key ("city_id") references "city"("id");
 alter table "city" add constraint "cityFK2" foreign key ("country_id") references "country"("id");
@@ -150,7 +154,10 @@
 alter table "bank_account" add constraint "bank_accountFK5" foreign key ("company_id") references "company"("id") on delete cascade;
 alter table "company_contact" add constraint "company_contactFK6" foreign key ("entity") references "company"("id") on delete cascade;
 alter table "company_contact" add constraint "company_contactFK7" foreign key ("contact") references "contact"("id") on delete cascade;
+alter table "user_contact" add constraint "user_contactFK8" foreign key ("entity") references "user"("id") on delete cascade;
+alter table "user_contact" add constraint "user_contactFK9" foreign key ("contact") references "contact"("id") on delete cascade;
 -- composite key indexes :
 alter table "company_contact" add constraint "company_contactCPK" unique("entity","contact");
+alter table "user_contact" add constraint "user_contactCPK" unique("entity","contact");
 -- column group indexes :
 create index "user_deleted_active_idx" on "user" ("deleted","active");
--- a/src/main/scala/fis/aaa/model/AaaSchema.scala	Fri Apr 20 08:44:36 2012 +0200
+++ b/src/main/scala/fis/aaa/model/AaaSchema.scala	Fri Apr 20 09:45:51 2012 +0200
@@ -16,12 +16,13 @@
 package fis.aaa.model
 
 import fis.base.model.BaseSchema
+import fis.crm.model.{Contact, CrmSchema, EntityContact}
 import net.liftweb.squerylrecord.RecordTypeMode._
 
 /**
  * Database schema for users, ...
  */
-trait AaaSchema extends BaseSchema {
+trait AaaSchema extends BaseSchema with CrmSchema {
   val userT = tableWithSeq[User]
   on(userT)(t => declare(
     t.login   defineAs(indexed("user_login_idx")),
@@ -38,8 +39,28 @@
   val activeUsersF = () => from(userT)(u =>
     where(u.deleted === false and u.active === true) select(u)
     orderBy(u.name asc))
+
+  val userContacts = manyToManyRelation(userT, contactT).
+    via[UserContact]((u, c, uc) => (
+      u.id === uc.entity,
+      c.id === uc.contact
+    ))
+  userContacts.leftForeignKeyDeclaration.constrainReference(onDelete cascade)
+  userContacts.rightForeignKeyDeclaration.constrainReference(onDelete cascade)
+
 }
 
 object AaaSchema extends AaaSchema
 
+case class UserContact(entity: Long, contact: Long) extends
+  EntityContact[User]
+
+object UserContacts {
+  def apply(u: User): Iterable[Contact] =
+    from(AaaSchema.userContacts.left(u)) (c =>
+      select(c) orderBy(c.lastName asc, c.firstName asc)
+    )
+}
+
+
 // vim: set ts=2 sw=2 et:
--- a/src/main/scala/fis/aaa/ui/UserSnippet.scala	Fri Apr 20 08:44:36 2012 +0200
+++ b/src/main/scala/fis/aaa/ui/UserSnippet.scala	Fri Apr 20 09:45:51 2012 +0200
@@ -18,8 +18,11 @@
 import fis.base.model._
 import fis.base.ui._
 import fis.aaa.model._
+import fis.crm.model.{Contact, ContactCrud, CrmSchema}
+import fis.crm.ui.ContactTable
 import net.liftweb.common._
 import net.liftweb.http._
+import net.liftweb.http.js.JsCmds.RedirectTo
 import net.liftweb.sitemap._
 import net.liftweb.sitemap.Loc._
 import net.liftweb.util._
@@ -41,7 +44,8 @@
 
   private val viewPre = Menu.param[User]("user.view", l10n("User"), parse,
     encode) / prefix / * >> Title(c => i18n("User %s", c.linkName)) >>
-    locTpl("entity/view") >> Snippet("panel", panel) >> Hidden
+    locTpl("user/view") >> Snippet("panel", panel) >>
+    Snippet("contacts", contacts.contacts) >> Hidden
 
   private val editPre = Menu.param[User]("user.edit", l10n("Edit"), parse,
     encode) / prefix / * / EDIT >>
@@ -55,7 +59,8 @@
 
   private val listM = listPre >> SecNav(createPre).build
   private val createM = createPre >> SecNav(listPre).build
-  private val viewM = viewPre >> (SecNav(editPre) + deletePre).build
+  private val viewM = viewPre >>
+    (SecNav(editPre) + deletePre + contacts.chooseM).build
   private val editM = editPre >> SecNav(viewPre).build
   private val deleteM = deletePre >> SecNav(viewPre).build
 
@@ -63,7 +68,8 @@
   private lazy val editLoc = editM.toLoc
   private lazy val deleteLoc = deleteM.toLoc
 
-  val menu = listM submenus(viewM, editM, createM, deleteM)
+  val menu = listM submenus(viewM, editM, createM, deleteM,
+    contacts.chooseM, contacts.confirmM)
 
   private def cur = viewLoc.currentValue or editLoc.currentValue or
     deleteLoc.currentValue
@@ -135,6 +141,79 @@
     }
   }
 
+  /* Contacts view + add contact op. */
+  private object contacts {
+
+    private val choosePreM = Menu.param[User]("user.addContact",
+      l10n("Add contact"), parse, encode) / prefix / * / "add-contact" >>
+      Title(c => i18n("Add contact to %s", c.linkName)) >>
+      locTpl("entity/add-contact") >> Snippet("panel", chooseContact) >> Hidden
+
+    val confirmM = Menu.params[(User, Contact)](
+      "user.addContactId", l10n("Add contact"), { _ match {
+        case AsLong(uId) :: AsLong(cntId) :: Nil => for {
+            u <- get(uId)
+            cnt <- ContactCrud.get(cntId)
+          } yield (u, cnt)
+        case _ => Empty
+      }},
+      { p => List(p._1, p._2) map(_.id.toString) }) / prefix /
+        * / "add-contact" / * >>
+      Title(p => i18n("Add contact %s to %s", p._2.linkName, p._1.linkName)) >>
+      locTpl("entity/form") >> Snippet("form", addContact) >> Hidden
+
+    val chooseM = choosePreM >> SecNav(viewPre).build
+
+    private case class RemoveContactLink(u: User, cnt: Contact) extends
+      ReadOnlyField("actions", "", ConfirmationLink(i18n("Remove"),
+        l10n("Really remove contact?"), {
+          AaaSchema.userContacts.left(u).dissociate(cnt)
+          RedirectTo(viewLoc.calcHref(u))
+        }), Empty)
+
+    def contacts: CssTr = "*" #> cur.map { u =>
+      FieldTable[Contact]({ c => ContactTable.fields(c).toSeq :+
+        RemoveContactLink(u, c) }, Contact)(UserContacts(u)) }
+
+    private case class AddContactLink(u: User, c: Contact) extends
+      EntityLink[Contact](c,
+        { _ => Full(confirmM.toLoc.calcHref((u, c))) })
+
+    private def chooseContact: CssTr = "*" #> chooseM.toLoc.currentValue.map {
+      u =>
+        FieldTable[Contact]({ c =>
+          List(AddContactLink(u, c)) }, Contact)(CrmSchema.allContacts)
+    }
+
+    private object addContact extends HorizontalScreen with CancelButton
+      with SaveButton {
+
+      override def screenFields: List[BaseField] = (for {
+        (u, c) <- confirmM.toLoc.currentValue
+        ul <- EntityLink(u)
+        cl <- EntityLink(c)
+      } yield {
+        List(ReadOnlyField(l10n("User"), ul),
+          ReadOnlyField(l10n("Contact"), cl)) flatMap(_.allFields)
+      }) openOr Nil
+
+      def finish() { for {
+        (u, c) <- confirmM.toLoc.currentValue
+      } {
+        val fk = AaaSchema.userContacts.left(u)
+        fk.exists(_.id == c.id) match {
+          case true =>
+            S notice l10n("Contact %s is already associated with %s",
+              c.linkName, u.linkName)
+          case false =>
+            fk.associate(c)
+            S notice l10n("Added contact %s", c.linkName)
+        }
+        S redirectTo viewLoc.calcHref(u)
+      }}
+    }
+  }
+
 }
 
 // vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/entity/add-contact.html	Fri Apr 20 09:45:51 2012 +0200
@@ -0,0 +1,18 @@
+<!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="span5">
+          <span class="lift:panel"></span>
+        </div>
+      </div> <!-- /row -->
+    </div>
+  </body>
+</html>
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/user/view.html	Fri Apr 20 09:45:51 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=Contacts"></span></h3>
+          <span class="lift:contacts"></span>
+        </div>
+      </div> <!-- /row -->
+    </div>
+  </body>
+</html>
+
+