# HG changeset patch # User Tomas Zeman # Date 1334904276 -7200 # Node ID 077b875a2a0ccba6f5432da04f84bf5eeeb61d24 # Parent b177060246a980359049d7ecb019e7e1c8ffb6a0 Company contacts diff -r b177060246a9 -r 077b875a2a0c db/db-schema.sql --- a/db/db-schema.sql Fri Apr 20 08:44:28 2012 +0200 +++ b/db/db-schema.sql Fri Apr 20 08:44:36 2012 +0200 @@ -138,11 +138,19 @@ "updated_by" bigint ); create sequence "bank_account_id_seq"; +create table "company_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"); alter table "company" add constraint "companyFK3" foreign key ("address_id") references "address"("id"); alter table "company" add constraint "companyFK4" foreign key ("post_adress_id") references "address"("id") on delete set null; 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; +-- composite key indexes : +alter table "company_contact" add constraint "company_contactCPK" unique("entity","contact"); -- column group indexes : create index "user_deleted_active_idx" on "user" ("deleted","active"); diff -r b177060246a9 -r 077b875a2a0c src/main/scala/fis/aaa/ui/UserSnippet.scala --- a/src/main/scala/fis/aaa/ui/UserSnippet.scala Fri Apr 20 08:44:28 2012 +0200 +++ b/src/main/scala/fis/aaa/ui/UserSnippet.scala Fri Apr 20 08:44:36 2012 +0200 @@ -15,6 +15,7 @@ */ package fis.aaa.ui +import fis.base.model._ import fis.base.ui._ import fis.aaa.model._ import net.liftweb.common._ @@ -92,19 +93,10 @@ private def validatePass: List[FieldError] = (pass1.get != pass2.get).box { FieldError(pass2, i18n("Passwords do not match.")) } toList - private def roText(f: BaseField) = new Field { - type ValueType = String - def default = "" - override def name = f.name - override def displayName = f.displayName - override def toForm = Full({f.asHtml}) - override implicit def manifest = buildIt[String] - } - private def fields(u: User): List[FieldContainer] = { val n_l = List(u.name, u.login) - val n_l_f = (u.id != u.idField.defaultValue).box(n_l map { roText _ }). - openOr(n_l) + val n_l_f = (u.id != u.idField.defaultValue).box( + n_l map(ReadOnlyField(_))).openOr(n_l) n_l_f ++ List[FieldContainer](pass1, pass2, u.active, u.note) } diff -r b177060246a9 -r 077b875a2a0c src/main/scala/fis/base/ui/FieldTable.scala --- a/src/main/scala/fis/base/ui/FieldTable.scala Fri Apr 20 08:44:28 2012 +0200 +++ b/src/main/scala/fis/base/ui/FieldTable.scala Fri Apr 20 08:44:36 2012 +0200 @@ -40,4 +40,11 @@ (cols(fields(header)) & cells(rows))(load) } +object FieldTable { + def apply[T](fieldsF: T => Iterable[ReadableField], header: T)(rows: Iterable[T]): + NodeSeq = (new FieldTable[T] { + def fields(v: T) = fieldsF(v) + }).build(header, rows) +} + // vim: set ts=2 sw=2 et: diff -r b177060246a9 -r 077b875a2a0c src/main/scala/fis/crm/model/CrmSchema.scala --- a/src/main/scala/fis/crm/model/CrmSchema.scala Fri Apr 20 08:44:28 2012 +0200 +++ b/src/main/scala/fis/crm/model/CrmSchema.scala Fri Apr 20 08:44:36 2012 +0200 @@ -55,6 +55,14 @@ via((c, a) => c.id === a.company) companyBankAccounts.foreignKeyDeclaration.constrainReference(onDelete cascade) + val companyContacts = manyToManyRelation(companyT, contactT). + via[CompanyContact]((comp, cnt, cc) => ( + comp.id === cc.entity, + cnt.id === cc.contact + )) + companyContacts.leftForeignKeyDeclaration.constrainReference(onDelete cascade) + companyContacts.rightForeignKeyDeclaration.constrainReference(onDelete cascade) + def allCompanies: Iterable[Company] = from(companyT) (c => select(c) orderBy(c.name asc)) @@ -62,14 +70,24 @@ object CrmSchema extends CrmSchema +object CompanyContacts { + def apply(company: Company): Iterable[Contact] = + from(CrmSchema.companyContacts.left(company)) (c => + select(c) orderBy(c.lastName asc, c.firstName asc) + ) +} + import org.squeryl.KeyedEntity import org.squeryl.dsl._ -case class EntityContact[T](val entityId: Long, val contactId: Long) extends - KeyedEntity[CompositeKey2[Long, Long]] { +trait EntityContact[T] extends KeyedEntity[CompositeKey2[Long, Long]] { + def entity: Long + def contact: Long + def id = CompositeKey2(entity, contact) +} - def id = CompositeKey2(entityId, contactId) -} +case class CompanyContact(entity: Long, contact: Long) extends + EntityContact[Company] case class EntityContactWithType[T](val entityId: Long, val contactId: Long, val typeId: Long) extends KeyedEntity[CompositeKey3[Long, Long, Long]] { diff -r b177060246a9 -r 077b875a2a0c src/main/scala/fis/crm/ui/CompanySnippet.scala --- a/src/main/scala/fis/crm/ui/CompanySnippet.scala Fri Apr 20 08:44:28 2012 +0200 +++ b/src/main/scala/fis/crm/ui/CompanySnippet.scala Fri Apr 20 08:44:36 2012 +0200 @@ -15,11 +15,13 @@ */ package fis.crm.ui +import fis.base.model._ import fis.base.ui._ import fis.crm.model._ import fis.geo.model._ 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 +43,8 @@ private val viewPre = Menu.param[Company]("company.view", l10n("Company"), parse, encode) / prefix / * >> Title(c => i18n("Company %s", c.linkName)) >> - locTpl("entity/view") >> Snippet("panel", panel) >> Hidden + locTpl("company/view") >> Snippet("panel", panel) >> + Snippet("contacts", contacts.contacts) >> Hidden private val editPre = Menu.param[Company]("company.edit", l10n("Edit"), parse, encode) / prefix / * / EDIT >> @@ -55,7 +58,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 +67,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 @@ -94,17 +99,9 @@ override def screenFields: List[BaseField] = fields(company) flatMap(_.allFields) - private def labelField(f: BaseField) = new Field { - type ValueType = String - def default = "" - override def name = f.name - override def displayName = f.displayName - override def toForm = Empty - override implicit def manifest = buildIt[String] - } - private def fields(c: Company): List[FieldContainer] = { - List[FieldContainer](c.name, c.ico, c.dic, labelField(c.address)) ++ + List[FieldContainer](c.name, c.ico, c.dic, + ReadOnlyField.labelOnly(c.address)) ++ address.formFields ++ List[FieldContainer](hasPostAddr) ++ postAddress.formFields.map { f => field(f, @@ -167,6 +164,79 @@ } } + /* Contacts view + add contact op. */ + private object contacts { + + private val choosePreM = Menu.param[Company]("company.addContact", + l10n("Add contact"), parse, encode) / prefix / * / "add-contact" >> + Title(c => i18n("Add contact to %s", c.linkName)) >> + locTpl("company/add-contact") >> Snippet("panel", chooseContact) >> Hidden + + val confirmM = Menu.params[(Company, Contact)]( + "company.addContactId", l10n("Add contact"), { _ match { + case AsLong(cmpId) :: AsLong(cntId) :: Nil => for { + comp <- get(cmpId) + cnt <- ContactCrud.get(cntId) + } yield (comp, 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(comp: Company, cnt: Contact) extends + ReadOnlyField("actions", "", ConfirmationLink(i18n("Remove"), + l10n("Really remove contact?"), { + CrmSchema.companyContacts.left(comp).dissociate(cnt) + RedirectTo(viewLoc.calcHref(comp)) + }), Empty) + + def contacts: CssTr = "*" #> cur.map { c => + FieldTable[Contact]({ cnt => ContactTable.fields(cnt).toSeq :+ + RemoveContactLink(c, cnt) }, Contact)(CompanyContacts(c)) } + + private case class AddContactLink(comp: Company, cnt: Contact) extends + EntityLink[Contact](cnt, + { c => Full(confirmM.toLoc.calcHref((comp, cnt))) }) + + private def chooseContact: CssTr = "*" #> chooseM.toLoc.currentValue.map { + comp => + FieldTable[Contact]({ c => + List(AddContactLink(comp, c)) }, Contact)(CrmSchema.allContacts) + } + + private object addContact extends HorizontalScreen with CancelButton + with SaveButton { + + override def screenFields: List[BaseField] = (for { + (comp, cnt) <- confirmM.toLoc.currentValue + cl <- EntityLink(comp) + cntl <- EntityLink(cnt) + } yield { + List(ReadOnlyField(l10n("Company"), cl), + ReadOnlyField(l10n("Contact"), cntl)) flatMap(_.allFields) + }) openOr Nil + + def finish() { for { + (comp, cnt) <- confirmM.toLoc.currentValue + } { + val fk = CrmSchema.companyContacts.left(comp) + fk.exists(_.id == cnt.id) match { + case true => + S notice l10n("Contact %s is already associated with %s", + cnt.linkName, comp.linkName) + case false => + fk.associate(cnt) + S notice l10n("Added contact %s", cnt.linkName) + } + S redirectTo viewLoc.calcHref(comp) + }} + } + } + } // vim: set ts=2 sw=2 et: diff -r b177060246a9 -r 077b875a2a0c src/main/webapp/company/add-contact.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/webapp/company/add-contact.html Fri Apr 20 08:44:36 2012 +0200 @@ -0,0 +1,18 @@ + + + + + Entity View + + +
+
+
+ +
+
+
+ + + + diff -r b177060246a9 -r 077b875a2a0c src/main/webapp/company/view.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/webapp/company/view.html Fri Apr 20 08:44:36 2012 +0200 @@ -0,0 +1,24 @@ + + + + + Entity View + + +
+
+
+ +
+
+
+
+

+ +
+
+
+ + + + diff -r b177060246a9 -r 077b875a2a0c src/main/webapp/css/base.css --- a/src/main/webapp/css/base.css Fri Apr 20 08:44:28 2012 +0200 +++ b/src/main/webapp/css/base.css Fri Apr 20 08:44:36 2012 +0200 @@ -77,3 +77,7 @@ #user-label { font-size: 12px; } + +.section { + margin-top: 1em; +} diff -r b177060246a9 -r 077b875a2a0c src/main/webapp/templates-hidden/_resources.html --- a/src/main/webapp/templates-hidden/_resources.html Fri Apr 20 08:44:28 2012 +0200 +++ b/src/main/webapp/templates-hidden/_resources.html Fri Apr 20 08:44:36 2012 +0200 @@ -8,6 +8,9 @@ Edit Add Name + Log In @@ -23,7 +26,15 @@ Invalid password. - <-- contact --> + <-- contact + default strings: + Add contact + Add contact to %s + Add contact %s to %s + Really remove contact? + Contact %s is already associated with %s + Added contact %s + --> Contact Contact %s Contacts diff -r b177060246a9 -r 077b875a2a0c src/main/webapp/templates-hidden/_resources_cs.html --- a/src/main/webapp/templates-hidden/_resources_cs.html Fri Apr 20 08:44:28 2012 +0200 +++ b/src/main/webapp/templates-hidden/_resources_cs.html Fri Apr 20 08:44:36 2012 +0200 @@ -8,6 +8,7 @@ Upravit Přidat Název + Odebrat @@ -34,6 +35,12 @@ Skutečně smazat kontakt? Kontakt %s smazán. Nesprávná emailová adresa. + Přidat kontakt + Přidat kontakt k %s + Přidat kontakt %s k %s + Skutečně odebrat kontakt? + Kontakt %s je již asociován s %s. + Přidán kontakt %s. Jméno