From 0229a867af2c98a1ccbb9bec911a7c4842d8de3b Mon Sep 17 00:00:00 2001
From: Eike Kettner <eike.kettner@posteo.de>
Date: Wed, 10 Mar 2021 22:16:04 +0100
Subject: [PATCH] Add a use colum to metadata entities

---
 .../scala/docspell/common/EquipmentUse.scala  | 46 +++++++++++++++++++
 .../main/scala/docspell/common/OrgUse.scala   | 46 +++++++++++++++++++
 .../scala/docspell/common/PersonUse.scala     |  3 ++
 .../docspell/joex/process/FindProposal.scala  | 23 ++++++++--
 .../restserver/conv/Conversions.scala         | 10 ++--
 .../migration/h2/V1.21.1__equip_org_use.sql   | 16 +++++++
 .../mariadb/V1.21.1__equip_org_use.sql        | 16 +++++++
 .../postgresql/V1.21.1__equip_org_use.sql     | 16 +++++++
 .../docspell/store/impl/DoobieMeta.scala      |  6 +++
 .../docspell/store/records/REquipment.scala   | 23 +++++++---
 .../store/records/ROrganization.scala         | 22 ++++++---
 .../webapp/src/main/elm/Comp/PersonForm.elm   |  3 ++
 .../webapp/src/main/elm/Data/PersonUse.elm    | 15 +++++-
 13 files changed, 223 insertions(+), 22 deletions(-)
 create mode 100644 modules/common/src/main/scala/docspell/common/EquipmentUse.scala
 create mode 100644 modules/common/src/main/scala/docspell/common/OrgUse.scala
 create mode 100644 modules/store/src/main/resources/db/migration/h2/V1.21.1__equip_org_use.sql
 create mode 100644 modules/store/src/main/resources/db/migration/mariadb/V1.21.1__equip_org_use.sql
 create mode 100644 modules/store/src/main/resources/db/migration/postgresql/V1.21.1__equip_org_use.sql

diff --git a/modules/common/src/main/scala/docspell/common/EquipmentUse.scala b/modules/common/src/main/scala/docspell/common/EquipmentUse.scala
new file mode 100644
index 00000000..9841da59
--- /dev/null
+++ b/modules/common/src/main/scala/docspell/common/EquipmentUse.scala
@@ -0,0 +1,46 @@
+package docspell.common
+
+import cats.data.NonEmptyList
+
+import io.circe.Decoder
+import io.circe.Encoder
+
+sealed trait EquipmentUse { self: Product =>
+
+  final def name: String =
+    self.productPrefix.toLowerCase()
+}
+
+object EquipmentUse {
+
+  case object Concerning extends EquipmentUse
+  case object Disabled   extends EquipmentUse
+
+  def concerning: EquipmentUse = Concerning
+  def disabled: EquipmentUse   = Disabled
+
+  val all: NonEmptyList[EquipmentUse] =
+    NonEmptyList.of(concerning, disabled)
+
+  val notDisabled: NonEmptyList[EquipmentUse] =
+    NonEmptyList.of(concerning)
+
+  def fromString(str: String): Either[String, EquipmentUse] =
+    str.toLowerCase() match {
+      case "concerning" =>
+        Right(Concerning)
+      case "disabled" =>
+        Right(Disabled)
+      case _ =>
+        Left(s"Unknown equipment-use: $str")
+    }
+
+  def unsafeFromString(str: String): EquipmentUse =
+    fromString(str).fold(sys.error, identity)
+
+  implicit val jsonDecoder: Decoder[EquipmentUse] =
+    Decoder.decodeString.emap(fromString)
+
+  implicit val jsonEncoder: Encoder[EquipmentUse] =
+    Encoder.encodeString.contramap(_.name)
+}
diff --git a/modules/common/src/main/scala/docspell/common/OrgUse.scala b/modules/common/src/main/scala/docspell/common/OrgUse.scala
new file mode 100644
index 00000000..fc2f803e
--- /dev/null
+++ b/modules/common/src/main/scala/docspell/common/OrgUse.scala
@@ -0,0 +1,46 @@
+package docspell.common
+
+import cats.data.NonEmptyList
+
+import io.circe.Decoder
+import io.circe.Encoder
+
+sealed trait OrgUse { self: Product =>
+
+  final def name: String =
+    self.productPrefix.toLowerCase()
+}
+
+object OrgUse {
+
+  case object Correspondent extends OrgUse
+  case object Disabled      extends OrgUse
+
+  def correspondent: OrgUse = Correspondent
+  def disabled: OrgUse      = Disabled
+
+  val all: NonEmptyList[OrgUse] =
+    NonEmptyList.of(correspondent, disabled)
+
+  val notDisabled: NonEmptyList[OrgUse] =
+    NonEmptyList.of(correspondent)
+
+  def fromString(str: String): Either[String, OrgUse] =
+    str.toLowerCase() match {
+      case "correspondent" =>
+        Right(Correspondent)
+      case "disabled" =>
+        Right(Disabled)
+      case _ =>
+        Left(s"Unknown organization-use: $str")
+    }
+
+  def unsafeFromString(str: String): OrgUse =
+    fromString(str).fold(sys.error, identity)
+
+  implicit val jsonDecoder: Decoder[OrgUse] =
+    Decoder.decodeString.emap(fromString)
+
+  implicit val jsonEncoder: Encoder[OrgUse] =
+    Encoder.encodeString.contramap(_.name)
+}
diff --git a/modules/common/src/main/scala/docspell/common/PersonUse.scala b/modules/common/src/main/scala/docspell/common/PersonUse.scala
index 71cd3365..d070425e 100644
--- a/modules/common/src/main/scala/docspell/common/PersonUse.scala
+++ b/modules/common/src/main/scala/docspell/common/PersonUse.scala
@@ -16,6 +16,7 @@ object PersonUse {
   case object Correspondent extends PersonUse
   case object Concerning    extends PersonUse
   case object Both          extends PersonUse
+  case object Disabled      extends PersonUse
 
   def concerning: PersonUse    = Concerning
   def correspondent: PersonUse = Correspondent
@@ -35,6 +36,8 @@ object PersonUse {
         Right(Concerning)
       case "both" =>
         Right(Both)
+      case "disabled" =>
+        Right(Disabled)
       case _ =>
         Left(s"Unknown person-use: $str")
     }
diff --git a/modules/joex/src/main/scala/docspell/joex/process/FindProposal.scala b/modules/joex/src/main/scala/docspell/joex/process/FindProposal.scala
index 42bbb536..a159ad34 100644
--- a/modules/joex/src/main/scala/docspell/joex/process/FindProposal.scala
+++ b/modules/joex/src/main/scala/docspell/joex/process/FindProposal.scala
@@ -47,7 +47,7 @@ object FindProposal {
           ctx.store
             .transact(
               ROrganization
-                .findLike(coll, mp.values.head.ref.name.toLowerCase)
+                .findLike(coll, mp.values.head.ref.name.toLowerCase, OrgUse.notDisabled)
                 .map(_.headOption)
             )
             .flatTap(oref =>
@@ -85,7 +85,11 @@ object FindProposal {
           ctx.store
             .transact(
               REquipment
-                .findLike(coll, mp.values.head.ref.name.toLowerCase)
+                .findLike(
+                  coll,
+                  mp.values.head.ref.name.toLowerCase,
+                  EquipmentUse.notDisabled
+                )
                 .map(_.headOption)
             )
             .flatTap(oref =>
@@ -234,7 +238,10 @@ object FindProposal {
         case NerTag.Organization =>
           ctx.logger.debug(s"Looking for organizations: $value") *>
             ctx.store
-              .transact(ROrganization.findLike(ctx.args.meta.collective, value))
+              .transact(
+                ROrganization
+                  .findLike(ctx.args.meta.collective, value, OrgUse.notDisabled)
+              )
               .map(MetaProposalList.from(MetaProposalType.CorrOrg, nt))
 
         case NerTag.Person =>
@@ -252,7 +259,10 @@ object FindProposal {
             .map(MetaProposalList.from(MetaProposalType.CorrPerson, nt))
           val s3 =
             ctx.store
-              .transact(ROrganization.findLike(ctx.args.meta.collective, value))
+              .transact(
+                ROrganization
+                  .findLike(ctx.args.meta.collective, value, OrgUse.notDisabled)
+              )
               .map(MetaProposalList.from(MetaProposalType.CorrOrg, nt))
           ctx.logger.debug(s"Looking for persons and organizations: $value") *> (for {
             ml0 <- s1
@@ -268,7 +278,10 @@ object FindProposal {
         case NerTag.Misc =>
           ctx.logger.debug(s"Looking for equipments: $value") *>
             ctx.store
-              .transact(REquipment.findLike(ctx.args.meta.collective, value))
+              .transact(
+                REquipment
+                  .findLike(ctx.args.meta.collective, value, EquipmentUse.notDisabled)
+              )
               .map(MetaProposalList.from(MetaProposalType.ConcEquip, nt))
 
         case NerTag.Email =>
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 cede3845..55531fff 100644
--- a/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala
+++ b/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala
@@ -414,7 +414,8 @@ trait Conversions {
         v.notes,
         now,
         now,
-        v.shortName.map(_.trim)
+        v.shortName.map(_.trim),
+        OrgUse.Correspondent
       )
     } yield OOrganization.OrgAndContacts(org, cont)
   }
@@ -439,7 +440,8 @@ trait Conversions {
         v.notes,
         v.created,
         now,
-        v.shortName.map(_.trim)
+        v.shortName.map(_.trim),
+        OrgUse.Correspondent
       )
     } yield OOrganization.OrgAndContacts(org, cont)
   }
@@ -632,13 +634,13 @@ trait Conversions {
 
   def newEquipment[F[_]: Sync](e: Equipment, cid: Ident): F[REquipment] =
     timeId.map({ case (id, now) =>
-      REquipment(id, cid, e.name.trim, now, now, e.notes)
+      REquipment(id, cid, e.name.trim, now, now, e.notes, EquipmentUse.Concerning)
     })
 
   def changeEquipment[F[_]: Sync](e: Equipment, cid: Ident): F[REquipment] =
     Timestamp
       .current[F]
-      .map(now => REquipment(e.id, cid, e.name.trim, e.created, now, e.notes))
+      .map(now => REquipment(e.id, cid, e.name.trim, e.created, now, e.notes, EquipmentUse.Concerning))
 
   // idref
 
diff --git a/modules/store/src/main/resources/db/migration/h2/V1.21.1__equip_org_use.sql b/modules/store/src/main/resources/db/migration/h2/V1.21.1__equip_org_use.sql
new file mode 100644
index 00000000..80069ac3
--- /dev/null
+++ b/modules/store/src/main/resources/db/migration/h2/V1.21.1__equip_org_use.sql
@@ -0,0 +1,16 @@
+ALTER TABLE "equipment"
+ADD COLUMN "equip_use" varchar(254);
+
+UPDATE "equipment" SET "equip_use" = 'concerning';
+
+ALTER TABLE "equipment"
+ALTER COLUMN "equip_use" SET NOT NULL;
+
+
+ALTER TABLE "organization"
+ADD COLUMN "org_use" varchar(254);
+
+UPDATE "organization" SET "org_use" = 'correspondent';
+
+ALTER TABLE "organization"
+ALTER COLUMN "org_use" SET NOT NULL;
diff --git a/modules/store/src/main/resources/db/migration/mariadb/V1.21.1__equip_org_use.sql b/modules/store/src/main/resources/db/migration/mariadb/V1.21.1__equip_org_use.sql
new file mode 100644
index 00000000..8f32937a
--- /dev/null
+++ b/modules/store/src/main/resources/db/migration/mariadb/V1.21.1__equip_org_use.sql
@@ -0,0 +1,16 @@
+ALTER TABLE `equipment`
+ADD COLUMN `equip_use` varchar(254);
+
+UPDATE `equipment` SET `equip_use` = 'concerning';
+
+ALTER TABLE `equipment`
+ALTER COLUMN `equip_use` SET NOT NULL;
+
+
+ALTER TABLE `organization`
+ADD COLUMN `org_use` varchar(254);
+
+UPDATE `organization` SET `org_use` = 'correspondent';
+
+ALTER TABLE `organization`
+ALTER COLUMN `org_use` SET NOT NULL;
diff --git a/modules/store/src/main/resources/db/migration/postgresql/V1.21.1__equip_org_use.sql b/modules/store/src/main/resources/db/migration/postgresql/V1.21.1__equip_org_use.sql
new file mode 100644
index 00000000..80069ac3
--- /dev/null
+++ b/modules/store/src/main/resources/db/migration/postgresql/V1.21.1__equip_org_use.sql
@@ -0,0 +1,16 @@
+ALTER TABLE "equipment"
+ADD COLUMN "equip_use" varchar(254);
+
+UPDATE "equipment" SET "equip_use" = 'concerning';
+
+ALTER TABLE "equipment"
+ALTER COLUMN "equip_use" SET NOT NULL;
+
+
+ALTER TABLE "organization"
+ADD COLUMN "org_use" varchar(254);
+
+UPDATE "organization" SET "org_use" = 'correspondent';
+
+ALTER TABLE "organization"
+ALTER COLUMN "org_use" SET NOT NULL;
diff --git a/modules/store/src/main/scala/docspell/store/impl/DoobieMeta.scala b/modules/store/src/main/scala/docspell/store/impl/DoobieMeta.scala
index e38e8334..e15da7ae 100644
--- a/modules/store/src/main/scala/docspell/store/impl/DoobieMeta.scala
+++ b/modules/store/src/main/scala/docspell/store/impl/DoobieMeta.scala
@@ -106,6 +106,12 @@ trait DoobieMeta extends EmilDoobieMeta {
 
   implicit val metaPersonUse: Meta[PersonUse] =
     Meta[String].timap(PersonUse.unsafeFromString)(_.name)
+
+  implicit val metaEquipUse: Meta[EquipmentUse] =
+    Meta[String].timap(EquipmentUse.unsafeFromString)(_.name)
+
+  implicit val metaOrgUse: Meta[OrgUse] =
+    Meta[String].timap(OrgUse.unsafeFromString)(_.name)
 }
 
 object DoobieMeta extends DoobieMeta {
diff --git a/modules/store/src/main/scala/docspell/store/records/REquipment.scala b/modules/store/src/main/scala/docspell/store/records/REquipment.scala
index 5befc011..6cb31224 100644
--- a/modules/store/src/main/scala/docspell/store/records/REquipment.scala
+++ b/modules/store/src/main/scala/docspell/store/records/REquipment.scala
@@ -15,7 +15,8 @@ case class REquipment(
     name: String,
     created: Timestamp,
     updated: Timestamp,
-    notes: Option[String]
+    notes: Option[String],
+    use: EquipmentUse
 ) {}
 
 object REquipment {
@@ -28,7 +29,8 @@ object REquipment {
     val created = Column[Timestamp]("created", this)
     val updated = Column[Timestamp]("updated", this)
     val notes   = Column[String]("notes", this)
-    val all     = NonEmptyList.of[Column[_]](eid, cid, name, created, updated, notes)
+    val use     = Column[EquipmentUse]("equip_use", this)
+    val all     = NonEmptyList.of[Column[_]](eid, cid, name, created, updated, notes, use)
   }
 
   val T = Table(None)
@@ -41,7 +43,7 @@ object REquipment {
       .insert(
         t,
         t.all,
-        fr"${v.eid},${v.cid},${v.name},${v.created},${v.updated},${v.notes}"
+        fr"${v.eid},${v.cid},${v.name},${v.created},${v.updated},${v.notes},${v.use}"
       )
   }
 
@@ -57,7 +59,8 @@ object REquipment {
             t.cid.setTo(v.cid),
             t.name.setTo(v.name),
             t.updated.setTo(now),
-            t.notes.setTo(v.notes)
+            t.notes.setTo(v.notes),
+            t.use.setTo(v.use)
           )
         )
     } yield n
@@ -90,9 +93,17 @@ object REquipment {
     sql.query[REquipment].to[Vector]
   }
 
-  def findLike(coll: Ident, equipName: String): ConnectionIO[Vector[IdRef]] = {
+  def findLike(
+      coll: Ident,
+      equipName: String,
+      use: NonEmptyList[EquipmentUse]
+  ): ConnectionIO[Vector[IdRef]] = {
     val t = Table(None)
-    run(select(t.eid, t.name), from(t), t.cid === coll && t.name.like(equipName))
+    run(
+      select(t.eid, t.name),
+      from(t),
+      t.cid === coll && t.name.like(equipName) && t.use.in(use)
+    )
       .query[IdRef]
       .to[Vector]
   }
diff --git a/modules/store/src/main/scala/docspell/store/records/ROrganization.scala b/modules/store/src/main/scala/docspell/store/records/ROrganization.scala
index 339c07e3..bbf4e385 100644
--- a/modules/store/src/main/scala/docspell/store/records/ROrganization.scala
+++ b/modules/store/src/main/scala/docspell/store/records/ROrganization.scala
@@ -22,7 +22,8 @@ case class ROrganization(
     notes: Option[String],
     created: Timestamp,
     updated: Timestamp,
-    shortName: Option[String]
+    shortName: Option[String],
+    use: OrgUse
 ) {}
 
 object ROrganization {
@@ -43,6 +44,7 @@ object ROrganization {
     val created   = Column[Timestamp]("created", this)
     val updated   = Column[Timestamp]("updated", this)
     val shortName = Column[String]("short_name", this)
+    val use       = Column[OrgUse]("org_use", this)
     val all =
       NonEmptyList.of[Column[_]](
         oid,
@@ -55,7 +57,8 @@ object ROrganization {
         notes,
         created,
         updated,
-        shortName
+        shortName,
+        use
       )
   }
 
@@ -67,7 +70,7 @@ object ROrganization {
     DML.insert(
       T,
       T.all,
-      fr"${v.oid},${v.cid},${v.name},${v.street},${v.zip},${v.city},${v.country},${v.notes},${v.created},${v.updated},${v.shortName}"
+      fr"${v.oid},${v.cid},${v.name},${v.street},${v.zip},${v.city},${v.country},${v.notes},${v.created},${v.updated},${v.shortName},${v.use}"
     )
 
   def update(v: ROrganization): ConnectionIO[Int] = {
@@ -84,7 +87,8 @@ object ROrganization {
           T.country.setTo(v.country),
           T.notes.setTo(v.notes),
           T.updated.setTo(now),
-          T.shortName.setTo(v.shortName)
+          T.shortName.setTo(v.shortName),
+          T.use.setTo(v.use)
         )
       )
     for {
@@ -109,11 +113,17 @@ object ROrganization {
     sql.query[ROrganization].option
   }
 
-  def findLike(coll: Ident, orgName: String): ConnectionIO[Vector[IdRef]] =
+  def findLike(
+      coll: Ident,
+      orgName: String,
+      use: NonEmptyList[OrgUse]
+  ): ConnectionIO[Vector[IdRef]] =
     run(
       select(T.oid, T.name),
       from(T),
-      T.cid === coll && (T.name.like(orgName) || T.shortName.like(orgName))
+      T.cid === coll && (T.name.like(orgName) || T.shortName.like(orgName)) && T.use.in(
+        use
+      )
     )
       .query[IdRef]
       .to[Vector]
diff --git a/modules/webapp/src/main/elm/Comp/PersonForm.elm b/modules/webapp/src/main/elm/Comp/PersonForm.elm
index 004157db..e9b35c79 100644
--- a/modules/webapp/src/main/elm/Comp/PersonForm.elm
+++ b/modules/webapp/src/main/elm/Comp/PersonForm.elm
@@ -229,6 +229,9 @@ view2 mobile settings model =
 
                     Data.PersonUse.Both ->
                         text "Use as both concerning or correspondent person"
+
+                    Data.PersonUse.Disabled ->
+                        text "Do not use for suggestions."
                 ]
             ]
         , div [ class "mb-4" ]
diff --git a/modules/webapp/src/main/elm/Data/PersonUse.elm b/modules/webapp/src/main/elm/Data/PersonUse.elm
index c00342a4..fc5d2060 100644
--- a/modules/webapp/src/main/elm/Data/PersonUse.elm
+++ b/modules/webapp/src/main/elm/Data/PersonUse.elm
@@ -14,6 +14,7 @@ type PersonUse
     = Correspondent
     | Concerning
     | Both
+    | Disabled
 
 
 fromString : String -> Maybe PersonUse
@@ -28,6 +29,9 @@ fromString str =
         "both" ->
             Just Both
 
+        "disabled" ->
+            Just Disabled
+
         _ ->
             Nothing
 
@@ -44,6 +48,9 @@ asString pu =
         Both ->
             "both"
 
+        Disabled ->
+            "disabled"
+
 
 label : PersonUse -> String
 label pu =
@@ -57,10 +64,13 @@ label pu =
         Both ->
             "Both"
 
+        Disabled ->
+            "Disabled"
+
 
 all : List PersonUse
 all =
-    [ Correspondent, Concerning, Both ]
+    [ Correspondent, Concerning, Both, Disabled ]
 
 
 spanPersonList : List Person -> { concerning : List Person, correspondent : List Person }
@@ -86,5 +96,8 @@ spanPersonList input =
                         | correspondent = p :: res.correspondent
                         , concerning = p :: res.concerning
                     }
+
+                Disabled ->
+                    res
     in
     List.foldl merge init input