Allow to connect a person to an organization

This commit is contained in:
Eike Kettner 2020-11-30 21:04:29 +01:00
parent b05bd43d4e
commit fc2668feee
8 changed files with 81 additions and 35 deletions

View File

@ -28,8 +28,10 @@ trait OOrganization[F[_]] {
def findAllPersonRefs(account: AccountId, nameQuery: Option[String]): F[Vector[IdRef]] def findAllPersonRefs(account: AccountId, nameQuery: Option[String]): F[Vector[IdRef]]
/** Add a new person with their contacts. The additional organization is ignored. */
def addPerson(s: PersonAndContacts): F[AddResult] def addPerson(s: PersonAndContacts): F[AddResult]
/** Update a person with their contacts. The additional organization is ignored. */
def updatePerson(s: PersonAndContacts): F[AddResult] def updatePerson(s: PersonAndContacts): F[AddResult]
def deleteOrg(orgId: Ident, collective: Ident): F[AddResult] def deleteOrg(orgId: Ident, collective: Ident): F[AddResult]
@ -41,7 +43,11 @@ object OOrganization {
case class OrgAndContacts(org: ROrganization, contacts: Seq[RContact]) case class OrgAndContacts(org: ROrganization, contacts: Seq[RContact])
case class PersonAndContacts(person: RPerson, contacts: Seq[RContact]) case class PersonAndContacts(
person: RPerson,
org: Option[ROrganization],
contacts: Seq[RContact]
)
def apply[F[_]: Effect](store: Store[F]): Resource[F, OOrganization[F]] = def apply[F[_]: Effect](store: Store[F]): Resource[F, OOrganization[F]] =
Resource.pure[F, OOrganization[F]](new OOrganization[F] { Resource.pure[F, OOrganization[F]](new OOrganization[F] {
@ -79,14 +85,14 @@ object OOrganization {
): F[Vector[PersonAndContacts]] = ): F[Vector[PersonAndContacts]] =
store store
.transact(QOrganization.findPersonAndContact(account.collective, query, _.name)) .transact(QOrganization.findPersonAndContact(account.collective, query, _.name))
.map({ case (person, cont) => PersonAndContacts(person, cont) }) .map({ case (person, org, cont) => PersonAndContacts(person, org, cont) })
.compile .compile
.toVector .toVector
def findPerson(account: AccountId, persId: Ident): F[Option[PersonAndContacts]] = def findPerson(account: AccountId, persId: Ident): F[Option[PersonAndContacts]] =
store store
.transact(QOrganization.getPersonAndContact(account.collective, persId)) .transact(QOrganization.getPersonAndContact(account.collective, persId))
.map(_.map({ case (org, cont) => PersonAndContacts(org, cont) })) .map(_.map({ case (pers, org, cont) => PersonAndContacts(pers, org, cont) }))
def findAllPersonRefs( def findAllPersonRefs(
account: AccountId, account: AccountId,

View File

@ -4833,6 +4833,8 @@ components:
format: ident format: ident
name: name:
type: string type: string
organization:
$ref: "#/components/schemas/IdName"
address: address:
$ref: "#/components/schemas/Address" $ref: "#/components/schemas/Address"
contacts: contacts:

View File

@ -414,15 +414,16 @@ trait Conversions {
} }
def mkPerson(v: OOrganization.PersonAndContacts): Person = { def mkPerson(v: OOrganization.PersonAndContacts): Person = {
val ro = v.person val rp = v.person
Person( Person(
ro.pid, rp.pid,
ro.name, rp.name,
Address(ro.street, ro.zip, ro.city, ro.country), v.org.map(o => IdName(o.oid, o.name)),
Address(rp.street, rp.zip, rp.city, rp.country),
v.contacts.map(mkContact).toList, v.contacts.map(mkContact).toList,
ro.notes, rp.notes,
ro.concerning, rp.concerning,
ro.created rp.created
) )
} }
@ -433,7 +434,7 @@ trait Conversions {
now <- Timestamp.current[F] now <- Timestamp.current[F]
pid <- Ident.randomId[F] pid <- Ident.randomId[F]
cont <- contacts(pid) cont <- contacts(pid)
org = RPerson( pers = RPerson(
pid, pid,
cid, cid,
v.name, v.name,
@ -444,9 +445,10 @@ trait Conversions {
v.notes, v.notes,
v.concerning, v.concerning,
now, now,
now now,
v.organization.map(_.id)
) )
} yield OOrganization.PersonAndContacts(org, cont) } yield OOrganization.PersonAndContacts(pers, None, cont)
} }
def changePerson[F[_]: Sync]( def changePerson[F[_]: Sync](
@ -458,7 +460,7 @@ trait Conversions {
for { for {
now <- Timestamp.current[F] now <- Timestamp.current[F]
cont <- contacts(v.id) cont <- contacts(v.id)
org = RPerson( pers = RPerson(
v.id, v.id,
cid, cid,
v.name, v.name,
@ -469,9 +471,10 @@ trait Conversions {
v.notes, v.notes,
v.concerning, v.concerning,
v.created, v.created,
now now,
v.organization.map(_.id)
) )
} yield OOrganization.PersonAndContacts(org, cont) } yield OOrganization.PersonAndContacts(pers, None, cont)
} }
// contact // contact

View File

@ -0,0 +1,7 @@
ALTER TABLE "person"
ADD COLUMN "oid" varchar(254);
ALTER TABLE "person"
ADD CONSTRAINT fk_person_organization
FOREIGN KEY ("oid")
REFERENCES "organization"("oid");

View File

@ -0,0 +1,7 @@
ALTER TABLE `person`
ADD COLUMN `oid` varchar(254);
ALTER TABLE `person`
ADD CONSTRAINT fk_person_organization
FOREIGN KEY (`oid`)
REFERENCES `organization`(`oid`);

View File

@ -0,0 +1,7 @@
ALTER TABLE "person"
ADD COLUMN "oid" varchar(254);
ALTER TABLE "person"
ADD CONSTRAINT fk_person_organization
FOREIGN KEY ("oid")
REFERENCES "organization"("oid");

View File

@ -41,7 +41,7 @@ object QOrganization {
Seq.empty Seq.empty
}) })
(selectSimple(cols, from, and(q)) ++ orderBy(order(OC).f)) (selectSimple(cols, from, and(q)) ++ orderBy(order(OC).prefix("o").f))
.query[(ROrganization, Option[RContact])] .query[(ROrganization, Option[RContact])]
.stream .stream
.groupAdjacentBy(_._1) .groupAdjacentBy(_._1)
@ -82,17 +82,21 @@ object QOrganization {
coll: Ident, coll: Ident,
query: Option[String], query: Option[String],
order: PC.type => Column order: PC.type => Column
): Stream[ConnectionIO, (RPerson, Vector[RContact])] = { ): Stream[ConnectionIO, (RPerson, Option[ROrganization], Vector[RContact])] = {
val pColl = PC.cid.prefix("p") val pColl = PC.cid.prefix("p")
val pName = RPerson.Columns.name.prefix("p") val pName = RPerson.Columns.name.prefix("p")
val pNotes = RPerson.Columns.notes.prefix("p") val pNotes = RPerson.Columns.notes.prefix("p")
val pId = RPerson.Columns.pid.prefix("p") val pId = RPerson.Columns.pid.prefix("p")
val cPers = RContact.Columns.personId.prefix("c") val cPers = RContact.Columns.personId.prefix("c")
val cVal = RContact.Columns.value.prefix("c") val cVal = RContact.Columns.value.prefix("c")
val oId = ROrganization.Columns.oid.prefix("o")
val pOid = RPerson.Columns.oid.prefix("p")
val cols = RPerson.Columns.all.map(_.prefix("p")) ++ RContact.Columns.all val cols = RPerson.Columns.all.map(_.prefix("p")) ++
.map(_.prefix("c")) ROrganization.Columns.all.map(_.prefix("o")) ++
RContact.Columns.all.map(_.prefix("c"))
val from = RPerson.table ++ fr"p LEFT JOIN" ++ val from = RPerson.table ++ fr"p LEFT JOIN" ++
ROrganization.table ++ fr"o ON" ++ pOid.is(oId) ++ fr"LEFT JOIN" ++
RContact.table ++ fr"c ON" ++ cPers.is(pId) RContact.table ++ fr"c ON" ++ cPers.is(pId)
val q = Seq(pColl.is(coll)) ++ (query match { val q = Seq(pColl.is(coll)) ++ (query match {
@ -103,38 +107,44 @@ object QOrganization {
Seq.empty Seq.empty
}) })
(selectSimple(cols, from, and(q)) ++ orderBy(order(PC).f)) (selectSimple(cols, from, and(q)) ++ orderBy(order(PC).prefix("p").f))
.query[(RPerson, Option[RContact])] .query[(RPerson, Option[ROrganization], Option[RContact])]
.stream .stream
.groupAdjacentBy(_._1) .groupAdjacentBy(_._1)
.map({ case (ro, chunk) => .map({ case (rp, chunk) =>
val cs = chunk.toVector.flatMap(_._2) val cs = chunk.toVector.flatMap(_._3)
(ro, cs) val ro = chunk.map(_._2).head.flatten
(rp, ro, cs)
}) })
} }
def getPersonAndContact( def getPersonAndContact(
coll: Ident, coll: Ident,
persId: Ident persId: Ident
): ConnectionIO[Option[(RPerson, Vector[RContact])]] = { ): ConnectionIO[Option[(RPerson, Option[ROrganization], Vector[RContact])]] = {
val pColl = PC.cid.prefix("p") val pColl = PC.cid.prefix("p")
val pId = RPerson.Columns.pid.prefix("p") val pId = RPerson.Columns.pid.prefix("p")
val cPers = RContact.Columns.personId.prefix("c") val cPers = RContact.Columns.personId.prefix("c")
val oId = ROrganization.Columns.oid.prefix("o")
val pOid = RPerson.Columns.oid.prefix("p")
val cols = RPerson.Columns.all.map(_.prefix("p")) ++ RContact.Columns.all val cols = RPerson.Columns.all.map(_.prefix("p")) ++
.map(_.prefix("c")) ROrganization.Columns.all.map(_.prefix("o")) ++
RContact.Columns.all.map(_.prefix("c"))
val from = RPerson.table ++ fr"p LEFT JOIN" ++ val from = RPerson.table ++ fr"p LEFT JOIN" ++
ROrganization.table ++ fr"o ON" ++ pOid.is(oId) ++ fr"LEFT JOIN" ++
RContact.table ++ fr"c ON" ++ cPers.is(pId) RContact.table ++ fr"c ON" ++ cPers.is(pId)
val q = and(pColl.is(coll), pId.is(persId)) val q = and(pColl.is(coll), pId.is(persId))
selectSimple(cols, from, q) selectSimple(cols, from, q)
.query[(RPerson, Option[RContact])] .query[(RPerson, Option[ROrganization], Option[RContact])]
.stream .stream
.groupAdjacentBy(_._1) .groupAdjacentBy(_._1)
.map({ case (ro, chunk) => .map({ case (rp, chunk) =>
val cs = chunk.toVector.flatMap(_._2) val cs = chunk.toVector.flatMap(_._3)
(ro, cs) val ro = chunk.map(_._2).head.flatten
(rp, ro, cs)
}) })
.compile .compile
.last .last

View File

@ -21,7 +21,8 @@ case class RPerson(
notes: Option[String], notes: Option[String],
concerning: Boolean, concerning: Boolean,
created: Timestamp, created: Timestamp,
updated: Timestamp updated: Timestamp,
oid: Option[Ident]
) {} ) {}
object RPerson { object RPerson {
@ -42,6 +43,7 @@ object RPerson {
val concerning = Column("concerning") val concerning = Column("concerning")
val created = Column("created") val created = Column("created")
val updated = Column("updated") val updated = Column("updated")
val oid = Column("oid")
val all = List( val all = List(
pid, pid,
cid, cid,
@ -53,7 +55,8 @@ object RPerson {
notes, notes,
concerning, concerning,
created, created,
updated updated,
oid
) )
} }
@ -63,7 +66,7 @@ object RPerson {
val sql = insertRow( val sql = insertRow(
table, table,
all, all,
fr"${v.pid},${v.cid},${v.name},${v.street},${v.zip},${v.city},${v.country},${v.notes},${v.concerning},${v.created},${v.updated}" fr"${v.pid},${v.cid},${v.name},${v.street},${v.zip},${v.city},${v.country},${v.notes},${v.concerning},${v.created},${v.updated},${v.oid}"
) )
sql.update.run sql.update.run
} }
@ -82,6 +85,7 @@ object RPerson {
country.setTo(v.country), country.setTo(v.country),
concerning.setTo(v.concerning), concerning.setTo(v.concerning),
notes.setTo(v.notes), notes.setTo(v.notes),
oid.setTo(v.oid),
updated.setTo(now) updated.setTo(now)
) )
) )