From fc2668feee3d5c36747002196b441922fbc8f960 Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Mon, 30 Nov 2020 21:04:29 +0100 Subject: [PATCH] Allow to connect a person to an organization --- .../docspell/backend/ops/OOrganization.scala | 12 ++++-- .../src/main/resources/docspell-openapi.yml | 2 + .../restserver/conv/Conversions.scala | 29 +++++++------ .../db/migration/h2/V1.14.0__person_org.sql | 7 ++++ .../migration/mariadb/V1.14.0__person_org.sql | 7 ++++ .../postgresql/V1.14.0__person_org.sql | 7 ++++ .../store/queries/QOrganization.scala | 42 ++++++++++++------- .../docspell/store/records/RPerson.scala | 10 +++-- 8 files changed, 81 insertions(+), 35 deletions(-) create mode 100644 modules/store/src/main/resources/db/migration/h2/V1.14.0__person_org.sql create mode 100644 modules/store/src/main/resources/db/migration/mariadb/V1.14.0__person_org.sql create mode 100644 modules/store/src/main/resources/db/migration/postgresql/V1.14.0__person_org.sql diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OOrganization.scala b/modules/backend/src/main/scala/docspell/backend/ops/OOrganization.scala index e007b1af..eba07e84 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OOrganization.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OOrganization.scala @@ -28,8 +28,10 @@ trait OOrganization[F[_]] { 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] + /** Update a person with their contacts. The additional organization is ignored. */ def updatePerson(s: PersonAndContacts): 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 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]] = Resource.pure[F, OOrganization[F]](new OOrganization[F] { @@ -79,14 +85,14 @@ object OOrganization { ): F[Vector[PersonAndContacts]] = store .transact(QOrganization.findPersonAndContact(account.collective, query, _.name)) - .map({ case (person, cont) => PersonAndContacts(person, cont) }) + .map({ case (person, org, cont) => PersonAndContacts(person, org, cont) }) .compile .toVector def findPerson(account: AccountId, persId: Ident): F[Option[PersonAndContacts]] = store .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( account: AccountId, diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index db8a440d..70d7c979 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -4833,6 +4833,8 @@ components: format: ident name: type: string + organization: + $ref: "#/components/schemas/IdName" address: $ref: "#/components/schemas/Address" contacts: diff --git a/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala b/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala index b17ca5f7..bdba7bf6 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala @@ -414,15 +414,16 @@ trait Conversions { } def mkPerson(v: OOrganization.PersonAndContacts): Person = { - val ro = v.person + val rp = v.person Person( - ro.pid, - ro.name, - Address(ro.street, ro.zip, ro.city, ro.country), + rp.pid, + rp.name, + v.org.map(o => IdName(o.oid, o.name)), + Address(rp.street, rp.zip, rp.city, rp.country), v.contacts.map(mkContact).toList, - ro.notes, - ro.concerning, - ro.created + rp.notes, + rp.concerning, + rp.created ) } @@ -433,7 +434,7 @@ trait Conversions { now <- Timestamp.current[F] pid <- Ident.randomId[F] cont <- contacts(pid) - org = RPerson( + pers = RPerson( pid, cid, v.name, @@ -444,9 +445,10 @@ trait Conversions { v.notes, v.concerning, now, - now + now, + v.organization.map(_.id) ) - } yield OOrganization.PersonAndContacts(org, cont) + } yield OOrganization.PersonAndContacts(pers, None, cont) } def changePerson[F[_]: Sync]( @@ -458,7 +460,7 @@ trait Conversions { for { now <- Timestamp.current[F] cont <- contacts(v.id) - org = RPerson( + pers = RPerson( v.id, cid, v.name, @@ -469,9 +471,10 @@ trait Conversions { v.notes, v.concerning, v.created, - now + now, + v.organization.map(_.id) ) - } yield OOrganization.PersonAndContacts(org, cont) + } yield OOrganization.PersonAndContacts(pers, None, cont) } // contact diff --git a/modules/store/src/main/resources/db/migration/h2/V1.14.0__person_org.sql b/modules/store/src/main/resources/db/migration/h2/V1.14.0__person_org.sql new file mode 100644 index 00000000..82832cc8 --- /dev/null +++ b/modules/store/src/main/resources/db/migration/h2/V1.14.0__person_org.sql @@ -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"); diff --git a/modules/store/src/main/resources/db/migration/mariadb/V1.14.0__person_org.sql b/modules/store/src/main/resources/db/migration/mariadb/V1.14.0__person_org.sql new file mode 100644 index 00000000..86b867ad --- /dev/null +++ b/modules/store/src/main/resources/db/migration/mariadb/V1.14.0__person_org.sql @@ -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`); diff --git a/modules/store/src/main/resources/db/migration/postgresql/V1.14.0__person_org.sql b/modules/store/src/main/resources/db/migration/postgresql/V1.14.0__person_org.sql new file mode 100644 index 00000000..82832cc8 --- /dev/null +++ b/modules/store/src/main/resources/db/migration/postgresql/V1.14.0__person_org.sql @@ -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"); diff --git a/modules/store/src/main/scala/docspell/store/queries/QOrganization.scala b/modules/store/src/main/scala/docspell/store/queries/QOrganization.scala index 50c0d2f2..d0234973 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QOrganization.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QOrganization.scala @@ -41,7 +41,7 @@ object QOrganization { 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])] .stream .groupAdjacentBy(_._1) @@ -82,17 +82,21 @@ object QOrganization { coll: Ident, query: Option[String], order: PC.type => Column - ): Stream[ConnectionIO, (RPerson, Vector[RContact])] = { + ): Stream[ConnectionIO, (RPerson, Option[ROrganization], Vector[RContact])] = { val pColl = PC.cid.prefix("p") val pName = RPerson.Columns.name.prefix("p") val pNotes = RPerson.Columns.notes.prefix("p") val pId = RPerson.Columns.pid.prefix("p") val cPers = RContact.Columns.personId.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 - .map(_.prefix("c")) + val cols = RPerson.Columns.all.map(_.prefix("p")) ++ + ROrganization.Columns.all.map(_.prefix("o")) ++ + RContact.Columns.all.map(_.prefix("c")) 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) val q = Seq(pColl.is(coll)) ++ (query match { @@ -103,38 +107,44 @@ object QOrganization { Seq.empty }) - (selectSimple(cols, from, and(q)) ++ orderBy(order(PC).f)) - .query[(RPerson, Option[RContact])] + (selectSimple(cols, from, and(q)) ++ orderBy(order(PC).prefix("p").f)) + .query[(RPerson, Option[ROrganization], Option[RContact])] .stream .groupAdjacentBy(_._1) - .map({ case (ro, chunk) => - val cs = chunk.toVector.flatMap(_._2) - (ro, cs) + .map({ case (rp, chunk) => + val cs = chunk.toVector.flatMap(_._3) + val ro = chunk.map(_._2).head.flatten + (rp, ro, cs) }) } def getPersonAndContact( coll: Ident, persId: Ident - ): ConnectionIO[Option[(RPerson, Vector[RContact])]] = { + ): ConnectionIO[Option[(RPerson, Option[ROrganization], Vector[RContact])]] = { val pColl = PC.cid.prefix("p") val pId = RPerson.Columns.pid.prefix("p") 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 - .map(_.prefix("c")) + val cols = RPerson.Columns.all.map(_.prefix("p")) ++ + ROrganization.Columns.all.map(_.prefix("o")) ++ + RContact.Columns.all.map(_.prefix("c")) 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) val q = and(pColl.is(coll), pId.is(persId)) selectSimple(cols, from, q) - .query[(RPerson, Option[RContact])] + .query[(RPerson, Option[ROrganization], Option[RContact])] .stream .groupAdjacentBy(_._1) - .map({ case (ro, chunk) => - val cs = chunk.toVector.flatMap(_._2) - (ro, cs) + .map({ case (rp, chunk) => + val cs = chunk.toVector.flatMap(_._3) + val ro = chunk.map(_._2).head.flatten + (rp, ro, cs) }) .compile .last diff --git a/modules/store/src/main/scala/docspell/store/records/RPerson.scala b/modules/store/src/main/scala/docspell/store/records/RPerson.scala index 87abb00c..b59d4cf4 100644 --- a/modules/store/src/main/scala/docspell/store/records/RPerson.scala +++ b/modules/store/src/main/scala/docspell/store/records/RPerson.scala @@ -21,7 +21,8 @@ case class RPerson( notes: Option[String], concerning: Boolean, created: Timestamp, - updated: Timestamp + updated: Timestamp, + oid: Option[Ident] ) {} object RPerson { @@ -42,6 +43,7 @@ object RPerson { val concerning = Column("concerning") val created = Column("created") val updated = Column("updated") + val oid = Column("oid") val all = List( pid, cid, @@ -53,7 +55,8 @@ object RPerson { notes, concerning, created, - updated + updated, + oid ) } @@ -63,7 +66,7 @@ object RPerson { val sql = insertRow( table, 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 } @@ -82,6 +85,7 @@ object RPerson { country.setTo(v.country), concerning.setTo(v.concerning), notes.setTo(v.notes), + oid.setTo(v.oid), updated.setTo(now) ) )