diff --git a/RMuseum/RMuseum.xml b/RMuseum/RMuseum.xml
index 622ea45a..24710fa7 100644
--- a/RMuseum/RMuseum.xml
+++ b/RMuseum/RMuseum.xml
@@ -2816,6 +2816,35 @@
+
+
+ related people (family tree / person tagging) - read-only for now, see IGanjoorRelatedPersonService
+
+
+
+
+ get all people
+
+
+
+
+
+ get person by id
+
+
+
+
+
+
+ person service
+
+
+
+
+ constructor
+
+
+
get all locations
@@ -14135,6 +14164,84 @@
model (not an EF entity), so no [NotMapped] or migration concerns here.
+
+
+ deserialized shape of GanjoorPoemGeoDateTagCorrection.SuggestedPersonGraphJson - a brand new
+ person (optionally linked to other people, new or existing) suggested in one go alongside a
+ poem's geo/date/person tag. See that field's doc comment for the full JSON shape.
+
+
+
+
+ the node that ends up assigned to the tag's PersonId once approved
+
+
+
+
+ other people this submission also introduces or links to, referenced from Relations by
+ their LocalKey
+
+
+
+
+ kinship and/or non-family edges between any of Person/RelatedPeople's local keys
+
+
+
+
+ one person node in a PersonGraphSuggestion - either a link to an existing, already approved
+ GanjoorRelatedPerson (ExistingPersonId set, the rest ignored) or a brand new person to create
+ (ExistingPersonId null, the rest describing them)
+
+
+
+
+ key used only to resolve Relations entries within this same submission - never stored
+ beyond approval time
+
+
+
+
+ set to link to an existing, already approved person instead of creating a new one - when
+ set, every other field on this node is ignored
+
+
+
+
+ see GanjoorRelatedPerson.FamilyTreeCaption - only meaningful for whichever node is meant
+ to be a tree's named root
+
+
+
+
+ one edge in a PersonGraphSuggestion, between two nodes referenced by their LocalKey (Person1/
+ Person2 - matching PersonGraphNode.LocalKey, not a real database id yet). Kind picks which
+ enum RelationType/AffiliationType is parsed against and which live table the edge is
+ materialized into on approval.
+
+
+
+
+ "family" (default, materialized as GanjoorPersonRelation - RelationType against
+ PersonRelationType) or "affiliation" (materialized as GanjoorPersonAffiliation -
+ AffiliationType against PersonAffiliationType)
+
+
+
+
+ parsed against PersonRelationType when Kind is "family"
+
+
+
+
+ parsed against PersonAffiliationType when Kind is "affiliation"
+
+
+
+
+ only meaningful for a family relation of type Ancestor - see GanjoorPersonRelation.DegreeHint
+
+
Ganjoor Poem related music track model
@@ -17460,6 +17567,26 @@
+
+
+ related people (family tree / person tagging) service - read-only for now: the only way a
+ new person is created is via the geo/date/person tag correction's SuggestedPersonGraphJson,
+ materialized on moderator approval (see GanjoorService-ModeratePoemCorrection.cs)
+
+
+
+
+ get all people (for the search-as-you-type person picker)
+
+
+
+
+
+ get person by id
+
+
+
+
Ganjoor Poems Content Privider Service
@@ -20891,6 +21018,35 @@
+
+
+ related people service implementation
+
+
+
+
+ get all people
+
+
+
+
+
+ get person by id
+
+
+
+
+
+
+ Database Context
+
+
+
+
+ constructor
+
+
+
IGanjoorService implementation
@@ -21438,6 +21594,16 @@
+
+
+ materializes a GanjoorPoemGeoDateTagCorrection.SuggestedPersonGraphJson payload: creates
+ any brand new person nodes it describes (or reuses an existing one when a node names
+ ExistingPersonId), then creates the kinship (GanjoorPersonRelation) and/or non-family
+ (GanjoorPersonAffiliation) edges between them. Returns the resolved id of the "person"
+ node (the one that ends up assigned to the tag's PersonId), or an error message.
+
+ SuggestedPersonGraphJson value - see its doc comment for the expected shape
+
moderate poem section correction
diff --git a/RMuseum/Services/Implementation/GanjoorService.cs b/RMuseum/Services/Implementation/GanjoorService.cs
index 71614668..d21ece68 100644
--- a/RMuseum/Services/Implementation/GanjoorService.cs
+++ b/RMuseum/Services/Implementation/GanjoorService.cs
@@ -2129,9 +2129,10 @@ namespace RMuseum.Services.Implementation
bool hasLocation = geoDateTag.LocationId != null ||
(!string.IsNullOrWhiteSpace(geoDateTag.SuggestedLocationName) && geoDateTag.SuggestedLatitude != null && geoDateTag.SuggestedLongitude != null);
bool hasDate = geoDateTag.LunarYear != null;
- if (!hasLocation && !hasDate)
+ bool hasPerson = geoDateTag.PersonId != null || !string.IsNullOrWhiteSpace(geoDateTag.SuggestedPersonGraphJson);
+ if (!hasLocation && !hasDate && !hasPerson)
{
- return new RServiceResult(null, "برچسب جغرافیایی/تاریخی باید حداقل شامل مکان یا تاریخ باشد.");
+ return new RServiceResult(null, "برچسب جغرافیایی/تاریخی/فردی باید حداقل شامل مکان، تاریخ یا فرد باشد.");
}
if (geoDateTag.LocationId == null && !string.IsNullOrWhiteSpace(geoDateTag.SuggestedLocationName)