people tags #387

This commit is contained in:
Hamid Reza Mohammadi 2026-09-30 20:44:35 +03:30
parent 800857aadc
commit ddbef76d94
13 changed files with 7276 additions and 258 deletions

View File

@ -19,6 +19,24 @@
}
}
string AffiliationTypeName(PersonAffiliationType t)
{
switch (t)
{
case PersonAffiliationType.Minister: return "وزیر/کارگزار";
case PersonAffiliationType.Advisor: return "مشاور";
case PersonAffiliationType.Courtier: return "درباری/همراه";
case PersonAffiliationType.Patron: return "حامی";
case PersonAffiliationType.Ally: return "متحد";
case PersonAffiliationType.Rival: return "رقیب";
case PersonAffiliationType.Servant: return "خدمتگزار";
case PersonAffiliationType.Companion: return "همدم";
case PersonAffiliationType.Successor: return "جانشین";
case PersonAffiliationType.Other: return "نوع دیگر";
default: return t.ToString();
}
}
string ActionName(PersonRelationSuggestionAction a)
{
switch (a)
@ -49,6 +67,7 @@ else
<p>
<span class="up-badge">@ActionName(s.Action)</span>
<span class="up-badge">@(s.Kind == PersonRelationSuggestionKind.Affiliation ? "وابستگی غیرخویشاوندی" : "نسبت خویشاوندی")</span>
</p>
<table>
@ -111,25 +130,45 @@ else
</td>
}
</tr>
<tr>
<td>نوع نسبت</td>
<td>@(s.Action == PersonRelationSuggestionAction.Modify && s.ExistingRelation != null ? RelationTypeName(s.ExistingRelation.RelationType) : RelationTypeName(s.SuggestedRelationType))</td>
@if (s.Action == PersonRelationSuggestionAction.Modify)
{
<td>@RelationTypeName(s.SuggestedRelationType)</td>
}
</tr>
<tr>
<td>درجه (در نیا/نواده)</td>
<td>@((s.Action == PersonRelationSuggestionAction.Modify && s.ExistingRelation != null ? s.ExistingRelation.DegreeHint : s.SuggestedDegreeHint)?.ToString().ToPersianNumbers())</td>
@if (s.Action == PersonRelationSuggestionAction.Modify)
{
<td>@s.SuggestedDegreeHint?.ToString().ToPersianNumbers()</td>
}
</tr>
@if (s.Kind == PersonRelationSuggestionKind.Affiliation)
{
<tr>
<td>نوع وابستگی</td>
<td>@(s.Action == PersonRelationSuggestionAction.Modify && s.ExistingAffiliation != null ? AffiliationTypeName(s.ExistingAffiliation.AffiliationType) : AffiliationTypeName(s.SuggestedAffiliationType ?? PersonAffiliationType.Other))</td>
@if (s.Action == PersonRelationSuggestionAction.Modify)
{
<td>@AffiliationTypeName(s.SuggestedAffiliationType ?? PersonAffiliationType.Other)</td>
}
</tr>
}
else
{
<tr>
<td>نوع نسبت</td>
<td>@(s.Action == PersonRelationSuggestionAction.Modify && s.ExistingRelation != null ? RelationTypeName(s.ExistingRelation.RelationType) : RelationTypeName(s.SuggestedRelationType))</td>
@if (s.Action == PersonRelationSuggestionAction.Modify)
{
<td>@RelationTypeName(s.SuggestedRelationType)</td>
}
</tr>
<tr>
<td>درجه (در نیا/نواده)</td>
<td>@((s.Action == PersonRelationSuggestionAction.Modify && s.ExistingRelation != null ? s.ExistingRelation.DegreeHint : s.SuggestedDegreeHint)?.ToString().ToPersianNumbers())</td>
@if (s.Action == PersonRelationSuggestionAction.Modify)
{
<td>@s.SuggestedDegreeHint?.ToString().ToPersianNumbers()</td>
}
</tr>
}
<tr>
<td>یادداشت</td>
<td><small>@(s.Action == PersonRelationSuggestionAction.Modify && s.ExistingRelation != null ? s.ExistingRelation.Note : s.SuggestedNote)</small></td>
<td>
<small>
@(s.Action == PersonRelationSuggestionAction.Modify
? (s.Kind == PersonRelationSuggestionKind.Affiliation ? s.ExistingAffiliation?.Note : s.ExistingRelation?.Note)
: s.SuggestedNote)
</small>
</td>
@if (s.Action == PersonRelationSuggestionAction.Modify)
{
<td><small>@s.SuggestedNote</small></td>

View File

@ -214,29 +214,6 @@
document.getElementById('geotag-new-person-name').value = typedText;
}
);
// birth/death location pickers inside the "new person" panel - these only ever pick an
// existing location (a brand-new-location-within-a-brand-new-person is one indirection too
// many for this form), so an unmatched search has nothing to offer
setupLocationAutocomplete(
document.getElementById('geotag-new-person-birthlocation-search'),
document.getElementById('geotag-new-person-birthlocation-id'),
document.getElementById('geotag-new-person-birthlocation-results'),
allGeoLocations,
null,
function (typedText) {
upToast('این مکان در فهرست نیست. ابتدا آن را جدا (در بخش «مکان» بالا) به‌عنوان مکان جدید پیشنهاد دهید.', 'error');
}
);
setupLocationAutocomplete(
document.getElementById('geotag-new-person-deathlocation-search'),
document.getElementById('geotag-new-person-deathlocation-id'),
document.getElementById('geotag-new-person-deathlocation-results'),
allGeoLocations,
null,
function (typedText) {
upToast('این مکان در فهرست نیست. ابتدا آن را جدا (در بخش «مکان» بالا) به‌عنوان مکان جدید پیشنهاد دهید.', 'error');
}
);
});
var geoDateTagMap = null;
var geoDateTagMarker = null;
@ -488,21 +465,22 @@
return null;
}
var birthLocationId = $('#geotag-new-person-birthlocation-id').val();
var deathLocationId = $('#geotag-new-person-deathlocation-id').val();
// birth/death year, "date is documented" and birth/death location are left out of this
// form for now (no real usage of them yet elsewhere) - the shape below still carries all
// of PersonGraphNode's fields, just with those ones always null/false, same as any other
// field this form doesn't offer a control for.
var person = {
localKey: 'new',
existingPersonId: null,
name: name,
description: $('#geotag-new-person-description').val() || null,
wikiUrl: $('#geotag-new-person-wikiurl').val() || null,
birthYearInLHijri: $('#geotag-new-person-birthyear').val() ? parseInt($('#geotag-new-person-birthyear').val()) : null,
deathYearInLHijri: $('#geotag-new-person-deathyear').val() ? parseInt($('#geotag-new-person-deathyear').val()) : null,
validBirthDate: $('#geotag-new-person-validbirth').is(':checked'),
validDeathDate: $('#geotag-new-person-validdeath').is(':checked'),
birthLocationId: birthLocationId ? parseInt(birthLocationId) : null,
deathLocationId: deathLocationId ? parseInt(deathLocationId) : null,
birthYearInLHijri: null,
deathYearInLHijri: null,
validBirthDate: false,
validDeathDate: false,
birthLocationId: null,
deathLocationId: null,
familyTreeCaption: $('#geotag-new-person-familytreecaption').val() || null
};
@ -794,16 +772,6 @@
$('#geotag-new-person-name').val('');
$('#geotag-new-person-description').val('');
$('#geotag-new-person-wikiurl').val('');
$('#geotag-new-person-birthyear').val('');
$('#geotag-new-person-deathyear').val('');
$('#geotag-new-person-validbirth').prop('checked', false);
$('#geotag-new-person-validdeath').prop('checked', false);
$('#geotag-new-person-birthlocation-id').val('');
$('#geotag-new-person-birthlocation-search').val('');
$('#geotag-new-person-birthlocation-results').hide().empty();
$('#geotag-new-person-deathlocation-id').val('');
$('#geotag-new-person-deathlocation-search').val('');
$('#geotag-new-person-deathlocation-results').hide().empty();
$('#geotag-new-person-familytreecaption').val('');
$('#geotag-person-relatives-list').empty();
if (document.getElementById('new-person-widget').style.display !== 'none') {
@ -2617,36 +2585,6 @@ else
<label for="geotag-new-person-wikiurl">لینک ویکی‌پدیا (اختیاری)</label>
<input type="text" id="geotag-new-person-wikiurl" />
</div>
<div class="up-field-row">
<div class="up-field">
<label for="geotag-new-person-birthyear">سال تولد (قمری)</label>
<input type="text" id="geotag-new-person-birthyear" />
<span class="up-check-row"><input type="checkbox" id="geotag-new-person-validbirth" /><label for="geotag-new-person-validbirth">تاریخ تولد قطعی است</label></span>
</div>
<div class="up-field">
<label for="geotag-new-person-deathyear">سال وفات (قمری)</label>
<input type="text" id="geotag-new-person-deathyear" />
<span class="up-check-row"><input type="checkbox" id="geotag-new-person-validdeath" /><label for="geotag-new-person-validdeath">تاریخ وفات قطعی است</label></span>
</div>
</div>
<div class="up-field-row">
<div class="up-field">
<label for="geotag-new-person-birthlocation-search">محل تولد (اختیاری، فقط از مکان‌های موجود در فهرست)</label>
<div class="up-autocomplete-wrap">
<input type="text" id="geotag-new-person-birthlocation-search" autocomplete="off" placeholder="نام مکان را تایپ کنید..." />
<input type="hidden" id="geotag-new-person-birthlocation-id" />
<div id="geotag-new-person-birthlocation-results" class="up-autocomplete-results" style="display:none;"></div>
</div>
</div>
<div class="up-field">
<label for="geotag-new-person-deathlocation-search">محل وفات (اختیاری، فقط از مکان‌های موجود در فهرست)</label>
<div class="up-autocomplete-wrap">
<input type="text" id="geotag-new-person-deathlocation-search" autocomplete="off" placeholder="نام مکان را تایپ کنید..." />
<input type="hidden" id="geotag-new-person-deathlocation-id" />
<div id="geotag-new-person-deathlocation-results" class="up-autocomplete-results" style="display:none;"></div>
</div>
</div>
</div>
<div class="up-field">
<label for="geotag-new-person-familytreecaption">عنوان شجره‌نامه (اختیاری، فقط اگر این شخصیت ریشهٔ یک خانواده/سلسله شناخته‌شده است - مثلاً «ساسانیان»)</label>
<input type="text" id="geotag-new-person-familytreecaption" />

View File

@ -3,7 +3,7 @@
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = "_UserPanelLayout";
ViewData["Title"] = "پیشنهاد خویشاوند جدید";
ViewData["Title"] = "پیشنهاد خویشاوند یا وابستگی جدید";
await GanjooRazor.Utils.GanjoorSessionChecker.ApplyPermissionsToViewData(Request, Response, ViewData);
}
@if (!string.IsNullOrEmpty(Model.LastError))
@ -13,11 +13,11 @@
else
{
<div class="up-page-header">
<h1>پیشنهاد خویشاوند جدید برای: @Model.Person.Name</h1>
<h1>پیشنهاد خویشاوند یا وابستگی جدید برای: @Model.Person.Name</h1>
</div>
<p class="up-hint">
این پیشنهاد مستقیماً اعمال نمی‌شود؛ پس از بررسی و تأیید توسط مدیران، به شجره‌نامهٔ
این پیشنهاد مستقیماً اعمال نمی‌شود؛ پس از بررسی و تأیید توسط مدیران، به شجره‌نامه/شبکهٔ وابستگی‌های
<a href="javascript:void(0)" onclick="PersonWindow.open(@Model.Person.Id)">@Model.Person.Name</a> افزوده خواهد شد.
</p>
@ -32,7 +32,7 @@ else
<table>
<tr>
<td>خویشاوند:</td>
<td>خویشاوند یا شخصیت دیگر:</td>
<td>
<select asp-for="OtherPersonId">
<option value="0">— انتخاب کنید —</option>
@ -43,6 +43,18 @@ else
</select>
</td>
</tr>
<tr>
<td>نوع پیوند:</td>
<td>
<select id="RelationKindGroup" asp-for="RelationKindGroup">
<option value="family">نسبت خویشاوندی (پدر/مادر، فرزند، همسر، نیا/نواده...)</option>
<option value="affiliation">وابستگی غیرخویشاوندی (وزیر، مشاور، حامی، متحد، رقیب...)</option>
</select>
</td>
</tr>
</table>
<table id="family-relation-fields">
<tr>
<td>نسبت با @Model.Person.Name:</td>
<td>
@ -63,6 +75,35 @@ else
</td>
<td><input asp-for="DegreeHint" /></td>
</tr>
</table>
<table id="affiliation-fields" style="display:none">
<tr>
<td>وابستگی با @Model.Person.Name:</td>
<td>
<select asp-for="AffiliationKind">
<option value="Minister_Other">او وزیر یا کارگزار این شخصیت بود</option>
<option value="Minister_Subject">این شخصیت وزیر یا کارگزار او بود</option>
<option value="Advisor_Other">او مشاور این شخصیت بود</option>
<option value="Advisor_Subject">این شخصیت مشاور او بود</option>
<option value="Courtier_Other">او از درباریان/همراهان این شخصیت بود</option>
<option value="Courtier_Subject">این شخصیت از درباریان/همراهان او بود</option>
<option value="Servant_Other">او خدمتگزار این شخصیت بود</option>
<option value="Servant_Subject">این شخصیت خدمتگزار او بود</option>
<option value="Patron_Other">او از این شخصیت حمایت می‌کرد (حامی او بود)</option>
<option value="Patron_Subject">این شخصیت از او حمایت می‌کرد (حامی او بود)</option>
<option value="Successor_Other">او جانشین این شخصیت شد (پس از او آمد)</option>
<option value="Successor_Subject">این شخصیت جانشین او شد (پس از او آمد)</option>
<option value="Ally">با او متحد/هم‌پیمان بود</option>
<option value="Rival">با او رقیب/دشمن بود</option>
<option value="Companion">با او همراه/همدم بود</option>
<option value="Other">نوع دیگر (در یادداشت توضیح دهید)</option>
</select>
</td>
</tr>
</table>
<table>
<tr>
<td>یادداشت (مستندسازی):</td>
<td><textarea asp-for="Note" rows="3" cols="60"></textarea></td>
@ -77,4 +118,21 @@ else
<a role="button" href="javascript:void(0)" onclick="history.back()" class="up-btn up-btn--ghost">انصراف</a>
</form>
</div>
<script>
(function () {
var groupSelect = document.getElementById('RelationKindGroup');
var familyFields = document.getElementById('family-relation-fields');
var affiliationFields = document.getElementById('affiliation-fields');
function applyKindGroup() {
var isAffiliation = groupSelect.value === 'affiliation';
familyFields.style.display = isAffiliation ? 'none' : '';
affiliationFields.style.display = isAffiliation ? '' : 'none';
}
groupSelect.addEventListener('change', applyKindGroup);
applyKindGroup();
})();
</script>
}

View File

@ -14,14 +14,17 @@ using RMuseum.Models.Ganjoor;
namespace GanjooRazor.Areas.User.Pages
{
/// <summary>
/// contributor-facing form for suggesting a brand new kinship edge between the subject person
/// and another already-approved person - the "Add" counterpart of SuggestPersonRelationEdit.cshtml
/// (which only handles Modify/Remove of an already-existing edge). Presents a direction-aware
/// "RelationKind" choice (e.g. "فرزند" vs "پدر یا مادر") from the subject's point of view, and
/// translates it into the correct Person1Id/Person2Id/RelationType triple before submitting -
/// GanjoorPersonRelation itself has no notion of "from the subject's point of view", only
/// Person1/Person2, so that translation has to happen somewhere, and doing it here keeps the
/// underlying suggestion/moderation code simple and symmetric.
/// contributor-facing form for suggesting a brand new edge between the subject person and
/// another already-approved person - the "Add" counterpart of SuggestPersonRelationEdit.cshtml
/// (which only handles Modify/Remove of an already-existing edge). Covers both kinds of edge:
/// a family/kinship tie (GanjoorPersonRelation, via RelationKind) and a non-family affiliation
/// tie (GanjoorPersonAffiliation, via AffiliationKind) - RelationKindGroup picks which. Both
/// present a direction-aware choice from the subject's point of view (e.g. "فرزند" vs "پدر یا
/// مادر", or "او وزیر این شخصیت بود" vs "این شخصیت وزیر او بود") and translate it into the
/// symmetric Person1Id/Person2Id/type triple the underlying entity actually stores - neither
/// GanjoorPersonRelation nor GanjoorPersonAffiliation has any notion of "from the subject's own
/// point of view", only Person1/Person2, so that translation has to happen somewhere, and doing
/// it here keeps the underlying suggestion/moderation code simple and symmetric.
/// </summary>
public class SuggestNewPersonRelationModel : LoginPartialEnabledPageModel
{
@ -40,9 +43,23 @@ namespace GanjooRazor.Areas.User.Pages
[BindProperty]
public int OtherPersonId { get; set; }
/// <summary>
/// "family" (default) or "affiliation" - which of RelationKind/AffiliationKind below applies
/// </summary>
[BindProperty]
public string RelationKindGroup { get; set; }
[BindProperty]
public string RelationKind { get; set; }
/// <summary>
/// combined type+direction key for a non-family tie, e.g. "Minister_Subject" meaning
/// "this person served as minister to the other person" - see the switch in
/// OnPostSuggestAsync for the full list and their direction handling
/// </summary>
[BindProperty]
public string AffiliationKind { get; set; }
[BindProperty]
public int? DegreeHint { get; set; }
@ -112,12 +129,10 @@ namespace GanjooRazor.Areas.User.Pages
if (OtherPersonId == 0 || OtherPersonId == personId)
{
LastResult = "لطفاً خویشاوند مورد نظر را انتخاب کنید.";
LastResult = "لطفاً خویشاوند یا شخصیت مورد نظر را انتخاب کنید.";
return Page();
}
// translate the direction-aware choice (from Person's own point of view) into the
// symmetric Person1Id/Person2Id/RelationType triple GanjoorPersonRelation actually stores
var suggestion = new GanjoorPersonRelationEditSuggestion()
{
Action = PersonRelationSuggestionAction.Add,
@ -126,41 +141,142 @@ namespace GanjooRazor.Areas.User.Pages
SuggestionNote = string.IsNullOrWhiteSpace(SuggestionNote) ? null : SuggestionNote.Trim(),
};
switch (RelationKind)
if (RelationKindGroup == "affiliation")
{
case "Child": // OtherPerson is a child of Person
suggestion.Person1Id = personId;
suggestion.Person2Id = OtherPersonId;
suggestion.SuggestedRelationType = PersonRelationType.Parent;
break;
case "Parent": // OtherPerson is a parent of Person
suggestion.Person1Id = OtherPersonId;
suggestion.Person2Id = personId;
suggestion.SuggestedRelationType = PersonRelationType.Parent;
break;
case "Sibling":
suggestion.Person1Id = personId;
suggestion.Person2Id = OtherPersonId;
suggestion.SuggestedRelationType = PersonRelationType.Sibling;
break;
case "Spouse":
suggestion.Person1Id = personId;
suggestion.Person2Id = OtherPersonId;
suggestion.SuggestedRelationType = PersonRelationType.Spouse;
break;
case "DistantAncestor": // OtherPerson is a distant ancestor of Person
suggestion.Person1Id = OtherPersonId;
suggestion.Person2Id = personId;
suggestion.SuggestedRelationType = PersonRelationType.Ancestor;
break;
case "DistantDescendant": // OtherPerson is a distant descendant of Person
suggestion.Person1Id = personId;
suggestion.Person2Id = OtherPersonId;
suggestion.SuggestedRelationType = PersonRelationType.Ancestor;
break;
default:
LastResult = "نوع نسبت نامعتبر است.";
return Page();
suggestion.Kind = PersonRelationSuggestionKind.Affiliation;
// OtherIsPerson1/SubjectIsPerson1 pairs translate the direction-aware choice into the
// symmetric Person1Id/Person2Id pair, following the direction each PersonAffiliationType
// value's own doc comment defines (Minister/Advisor/Courtier/Servant: Person1 is the
// subordinate one; Patron/Successor: Person1 is the patron/later one; Ally/Rival/
// Companion/Other: symmetric, direction doesn't matter)
switch (AffiliationKind)
{
case "Minister_Other": // the other person served as minister to this one
suggestion.Person1Id = OtherPersonId;
suggestion.Person2Id = personId;
suggestion.SuggestedAffiliationType = PersonAffiliationType.Minister;
break;
case "Minister_Subject": // this person served as minister to the other one
suggestion.Person1Id = personId;
suggestion.Person2Id = OtherPersonId;
suggestion.SuggestedAffiliationType = PersonAffiliationType.Minister;
break;
case "Advisor_Other":
suggestion.Person1Id = OtherPersonId;
suggestion.Person2Id = personId;
suggestion.SuggestedAffiliationType = PersonAffiliationType.Advisor;
break;
case "Advisor_Subject":
suggestion.Person1Id = personId;
suggestion.Person2Id = OtherPersonId;
suggestion.SuggestedAffiliationType = PersonAffiliationType.Advisor;
break;
case "Courtier_Other":
suggestion.Person1Id = OtherPersonId;
suggestion.Person2Id = personId;
suggestion.SuggestedAffiliationType = PersonAffiliationType.Courtier;
break;
case "Courtier_Subject":
suggestion.Person1Id = personId;
suggestion.Person2Id = OtherPersonId;
suggestion.SuggestedAffiliationType = PersonAffiliationType.Courtier;
break;
case "Servant_Other":
suggestion.Person1Id = OtherPersonId;
suggestion.Person2Id = personId;
suggestion.SuggestedAffiliationType = PersonAffiliationType.Servant;
break;
case "Servant_Subject":
suggestion.Person1Id = personId;
suggestion.Person2Id = OtherPersonId;
suggestion.SuggestedAffiliationType = PersonAffiliationType.Servant;
break;
case "Patron_Other": // the other person was patron of this one
suggestion.Person1Id = OtherPersonId;
suggestion.Person2Id = personId;
suggestion.SuggestedAffiliationType = PersonAffiliationType.Patron;
break;
case "Patron_Subject": // this person was patron of the other one
suggestion.Person1Id = personId;
suggestion.Person2Id = OtherPersonId;
suggestion.SuggestedAffiliationType = PersonAffiliationType.Patron;
break;
case "Successor_Other": // the other person succeeded this one
suggestion.Person1Id = OtherPersonId;
suggestion.Person2Id = personId;
suggestion.SuggestedAffiliationType = PersonAffiliationType.Successor;
break;
case "Successor_Subject": // this person succeeded the other one
suggestion.Person1Id = personId;
suggestion.Person2Id = OtherPersonId;
suggestion.SuggestedAffiliationType = PersonAffiliationType.Successor;
break;
case "Ally":
suggestion.Person1Id = personId;
suggestion.Person2Id = OtherPersonId;
suggestion.SuggestedAffiliationType = PersonAffiliationType.Ally;
break;
case "Rival":
suggestion.Person1Id = personId;
suggestion.Person2Id = OtherPersonId;
suggestion.SuggestedAffiliationType = PersonAffiliationType.Rival;
break;
case "Companion":
suggestion.Person1Id = personId;
suggestion.Person2Id = OtherPersonId;
suggestion.SuggestedAffiliationType = PersonAffiliationType.Companion;
break;
case "Other":
suggestion.Person1Id = personId;
suggestion.Person2Id = OtherPersonId;
suggestion.SuggestedAffiliationType = PersonAffiliationType.Other;
break;
default:
LastResult = "نوع وابستگی نامعتبر است.";
return Page();
}
}
else
{
suggestion.Kind = PersonRelationSuggestionKind.Family;
switch (RelationKind)
{
case "Child": // OtherPerson is a child of Person
suggestion.Person1Id = personId;
suggestion.Person2Id = OtherPersonId;
suggestion.SuggestedRelationType = PersonRelationType.Parent;
break;
case "Parent": // OtherPerson is a parent of Person
suggestion.Person1Id = OtherPersonId;
suggestion.Person2Id = personId;
suggestion.SuggestedRelationType = PersonRelationType.Parent;
break;
case "Sibling":
suggestion.Person1Id = personId;
suggestion.Person2Id = OtherPersonId;
suggestion.SuggestedRelationType = PersonRelationType.Sibling;
break;
case "Spouse":
suggestion.Person1Id = personId;
suggestion.Person2Id = OtherPersonId;
suggestion.SuggestedRelationType = PersonRelationType.Spouse;
break;
case "DistantAncestor": // OtherPerson is a distant ancestor of Person
suggestion.Person1Id = OtherPersonId;
suggestion.Person2Id = personId;
suggestion.SuggestedRelationType = PersonRelationType.Ancestor;
break;
case "DistantDescendant": // OtherPerson is a distant descendant of Person
suggestion.Person1Id = personId;
suggestion.Person2Id = OtherPersonId;
suggestion.SuggestedRelationType = PersonRelationType.Ancestor;
break;
default:
LastResult = "نوع نسبت نامعتبر است.";
return Page();
}
}
using (HttpClient secureClient = new HttpClient(new GanjoorReloginHandler(Request, Response)))

View File

@ -30,6 +30,17 @@ else
<form method="post" action="/User/SuggestPersonEdit?id=@Model.Person.Id&amp;Handler=SuggestEdit">
@Html.AntiForgeryToken()
<input type="hidden" asp-for="Suggestion.PersonId" />
@* birth/death year, "date is documented" and birth/death location have no UI here for now
(no real usage of them elsewhere yet) - carried through as hidden fields instead of
dropped outright, so submitting this form never blanks out whatever the person record
already has for these on approval (GanjoorRelatedPersonService applies every Suggested*
field unconditionally, with no "did they actually change this" check). *@
<input type="hidden" asp-for="Suggestion.SuggestedBirthYearInLHijri" />
<input type="hidden" asp-for="Suggestion.SuggestedValidBirthDate" />
<input type="hidden" asp-for="Suggestion.SuggestedBirthLocationId" />
<input type="hidden" asp-for="Suggestion.SuggestedDeathYearInLHijri" />
<input type="hidden" asp-for="Suggestion.SuggestedValidDeathDate" />
<input type="hidden" asp-for="Suggestion.SuggestedDeathLocationId" />
<table>
<tr>
@ -47,44 +58,6 @@ else
<td>نشانی ویکی‌پدیا:</td>
<td><input style="text-align:left;direction:ltr;" asp-for="Suggestion.SuggestedWikiUrl" size="60" /></td>
</tr>
<tr>
<td>زاده:</td>
<td><input asp-for="Suggestion.SuggestedBirthYearInLHijri" /> (قمری)</td>
</tr>
<tr>
<td>تاریخ تولد مستند است:</td>
<td><input asp-for="Suggestion.SuggestedValidBirthDate" /></td>
</tr>
<tr>
<td>زادگاه:</td>
<td>
<select asp-for="Suggestion.SuggestedBirthLocationId">
@foreach (var location in Model.Locations)
{
<option value="@(location.Id == 0 ? "" : location.Id.ToString())">@location.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>درگذشته:</td>
<td><input asp-for="Suggestion.SuggestedDeathYearInLHijri" /> (قمری)</td>
</tr>
<tr>
<td>تاریخ وفات مستند است:</td>
<td><input asp-for="Suggestion.SuggestedValidDeathDate" /></td>
</tr>
<tr>
<td>آرامگاه:</td>
<td>
<select asp-for="Suggestion.SuggestedDeathLocationId">
@foreach (var location in Model.Locations)
{
<option value="@(location.Id == 0 ? "" : location.Id.ToString())">@location.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
عنوان شجره‌نامه:

View File

@ -177,6 +177,14 @@ namespace RMuseum.DbContext
.HasForeignKey(s => s.ExistingRelationId)
.OnDelete(DeleteBehavior.Restrict);
// same Restrict treatment as ExistingRelationId above, but for Kind == Affiliation
// suggestions targeting a GanjoorPersonAffiliation instead
builder.Entity<GanjoorPersonRelationEditSuggestion>()
.HasOne(s => s.ExistingAffiliation)
.WithMany()
.HasForeignKey(s => s.ExistingAffiliationId)
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<GanjoorUserBookmark>()
.HasIndex(b => new { b.UserId, b.PoemId, b.CoupletIndex });

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,70 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace RMuseum.Migrations
{
/// <inheritdoc />
public partial class AddPersonRelationSuggestionAffiliationSupport : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "ExistingAffiliationId",
table: "GanjoorPersonRelationEditSuggestions",
type: "int",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "Kind",
table: "GanjoorPersonRelationEditSuggestions",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "SuggestedAffiliationType",
table: "GanjoorPersonRelationEditSuggestions",
type: "int",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_GanjoorPersonRelationEditSuggestions_ExistingAffiliationId",
table: "GanjoorPersonRelationEditSuggestions",
column: "ExistingAffiliationId");
migrationBuilder.AddForeignKey(
name: "FK_GanjoorPersonRelationEditSuggestions_GanjoorPersonAffiliations_ExistingAffiliationId",
table: "GanjoorPersonRelationEditSuggestions",
column: "ExistingAffiliationId",
principalTable: "GanjoorPersonAffiliations",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_GanjoorPersonRelationEditSuggestions_GanjoorPersonAffiliations_ExistingAffiliationId",
table: "GanjoorPersonRelationEditSuggestions");
migrationBuilder.DropIndex(
name: "IX_GanjoorPersonRelationEditSuggestions_ExistingAffiliationId",
table: "GanjoorPersonRelationEditSuggestions");
migrationBuilder.DropColumn(
name: "ExistingAffiliationId",
table: "GanjoorPersonRelationEditSuggestions");
migrationBuilder.DropColumn(
name: "Kind",
table: "GanjoorPersonRelationEditSuggestions");
migrationBuilder.DropColumn(
name: "SuggestedAffiliationType",
table: "GanjoorPersonRelationEditSuggestions");
}
}
}

View File

@ -1557,9 +1557,15 @@ namespace RMuseum.Migrations
b.Property<DateTime>("Date")
.HasColumnType("datetime2");
b.Property<int?>("ExistingAffiliationId")
.HasColumnType("int");
b.Property<int?>("ExistingRelationId")
.HasColumnType("int");
b.Property<int>("Kind")
.HasColumnType("int");
b.Property<int>("Person1Id")
.HasColumnType("int");
@ -1581,6 +1587,9 @@ namespace RMuseum.Migrations
b.Property<Guid?>("ReviewerUserId")
.HasColumnType("uniqueidentifier");
b.Property<int?>("SuggestedAffiliationType")
.HasColumnType("int");
b.Property<int?>("SuggestedDegreeHint")
.HasColumnType("int");
@ -1598,6 +1607,8 @@ namespace RMuseum.Migrations
b.HasKey("Id");
b.HasIndex("ExistingAffiliationId");
b.HasIndex("ExistingRelationId");
b.HasIndex("Person1Id");
@ -5514,6 +5525,11 @@ namespace RMuseum.Migrations
modelBuilder.Entity("RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion", b =>
{
b.HasOne("RMuseum.Models.Ganjoor.GanjoorPersonAffiliation", "ExistingAffiliation")
.WithMany()
.HasForeignKey("ExistingAffiliationId")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("RMuseum.Models.Ganjoor.GanjoorPersonRelation", "ExistingRelation")
.WithMany()
.HasForeignKey("ExistingRelationId")
@ -5541,6 +5557,8 @@ namespace RMuseum.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ExistingAffiliation");
b.Navigation("ExistingRelation");
b.Navigation("Person1");

View File

@ -23,17 +23,39 @@ namespace RMuseum.Models.Ganjoor
public PersonRelationSuggestionAction Action { get; set; }
/// <summary>
/// for Modify/Remove: the existing GanjoorPersonRelation row this suggestion targets. Null
/// for Add, where there's nothing existing yet.
/// whether this suggestion is about a kinship edge (Family, using SuggestedRelationType /
/// ExistingRelationId / ExistingRelation) or a non-family affiliation edge (Affiliation, using
/// SuggestedAffiliationType / ExistingAffiliationId / ExistingAffiliation instead). Defaults
/// to Family so rows created before this field existed keep working unchanged.
/// </summary>
public PersonRelationSuggestionKind Kind { get; set; }
/// <summary>
/// for Modify/Remove when Kind is Family: the existing GanjoorPersonRelation row this
/// suggestion targets. Null for Add, where there's nothing existing yet, and for any
/// Kind == Affiliation suggestion (see ExistingAffiliationId instead).
/// </summary>
public int? ExistingRelationId { get; set; }
/// <summary>
/// the existing relation being modified/removed (for showing a before/after diff to the
/// moderator) - null for Add
/// moderator) - null for Add, and for Kind == Affiliation
/// </summary>
public virtual GanjoorPersonRelation ExistingRelation { get; set; }
/// <summary>
/// for Modify/Remove when Kind is Affiliation: the existing GanjoorPersonAffiliation row this
/// suggestion targets. Null for Add, and for any Kind == Family suggestion (see
/// ExistingRelationId instead).
/// </summary>
public int? ExistingAffiliationId { get; set; }
/// <summary>
/// the existing affiliation being modified/removed (for showing a before/after diff to the
/// moderator) - null unless Kind is Affiliation and Action is Modify/Remove
/// </summary>
public virtual GanjoorPersonAffiliation ExistingAffiliation { get; set; }
/// <summary>
/// the pair this suggestion is about. For Add, this and Person2Id define the new relation.
/// For Modify/Remove, these are copied from ExistingRelation at submission time, so the
@ -58,11 +80,19 @@ namespace RMuseum.Models.Ganjoor
public virtual GanjoorRelatedPerson Person2 { get; set; }
/// <summary>
/// for Add/Modify: the relation type to create/change to. For Remove: a snapshot of the
/// existing relation's type at submission time, purely for display (removing doesn't use it).
/// for Add/Modify when Kind is Family: the relation type to create/change to. For Remove: a
/// snapshot of the existing relation's type at submission time, purely for display (removing
/// doesn't use it). Ignored when Kind is Affiliation - see SuggestedAffiliationType instead.
/// </summary>
public PersonRelationType SuggestedRelationType { get; set; }
/// <summary>
/// for Add/Modify when Kind is Affiliation: the affiliation type to create/change to. For
/// Remove: a snapshot of the existing affiliation's type at submission time, purely for
/// display. Null/ignored when Kind is Family - see SuggestedRelationType instead.
/// </summary>
public PersonAffiliationType? SuggestedAffiliationType { get; set; }
/// <summary>
/// see GanjoorPersonRelation.DegreeHint - same for/Add/Modify/Remove-display purpose as
/// SuggestedRelationType above

View File

@ -0,0 +1,16 @@
namespace RMuseum.Models.Ganjoor
{
/// <summary>
/// which kind of edge a GanjoorPersonRelationEditSuggestion is about: a kinship tie
/// (GanjoorPersonRelation, via SuggestedRelationType/ExistingRelationId) or a non-family
/// affiliation tie (GanjoorPersonAffiliation, via SuggestedAffiliationType/ExistingAffiliationId).
/// Defaults to Family (0), so every suggestion row created before this field existed keeps being
/// read exactly as it was before - a plain kinship suggestion.
/// </summary>
public enum PersonRelationSuggestionKind
{
Family = 0,
Affiliation = 1,
}
}

View File

@ -4955,6 +4955,18 @@
<member name="M:RMuseum.Migrations.AddGanjoorPersonRelationEditSuggestion.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
<inheritdoc />
</member>
<member name="T:RMuseum.Migrations.AddPersonRelationSuggestionAffiliationSupport">
<inheritdoc />
</member>
<member name="M:RMuseum.Migrations.AddPersonRelationSuggestionAffiliationSupport.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
<inheritdoc />
</member>
<member name="M:RMuseum.Migrations.AddPersonRelationSuggestionAffiliationSupport.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
<inheritdoc />
</member>
<member name="M:RMuseum.Migrations.AddPersonRelationSuggestionAffiliationSupport.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
<inheritdoc />
</member>
<member name="T:RMuseum.Models.Accounting.DonationExpenditure">
<summary>
donation expenditures
@ -9590,16 +9602,38 @@
what this suggestion proposes doing
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.Kind">
<summary>
whether this suggestion is about a kinship edge (Family, using SuggestedRelationType /
ExistingRelationId / ExistingRelation) or a non-family affiliation edge (Affiliation, using
SuggestedAffiliationType / ExistingAffiliationId / ExistingAffiliation instead). Defaults
to Family so rows created before this field existed keep working unchanged.
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.ExistingRelationId">
<summary>
for Modify/Remove: the existing GanjoorPersonRelation row this suggestion targets. Null
for Add, where there's nothing existing yet.
for Modify/Remove when Kind is Family: the existing GanjoorPersonRelation row this
suggestion targets. Null for Add, where there's nothing existing yet, and for any
Kind == Affiliation suggestion (see ExistingAffiliationId instead).
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.ExistingRelation">
<summary>
the existing relation being modified/removed (for showing a before/after diff to the
moderator) - null for Add
moderator) - null for Add, and for Kind == Affiliation
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.ExistingAffiliationId">
<summary>
for Modify/Remove when Kind is Affiliation: the existing GanjoorPersonAffiliation row this
suggestion targets. Null for Add, and for any Kind == Family suggestion (see
ExistingRelationId instead).
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.ExistingAffiliation">
<summary>
the existing affiliation being modified/removed (for showing a before/after diff to the
moderator) - null unless Kind is Affiliation and Action is Modify/Remove
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.Person1Id">
@ -9627,8 +9661,16 @@
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.SuggestedRelationType">
<summary>
for Add/Modify: the relation type to create/change to. For Remove: a snapshot of the
existing relation's type at submission time, purely for display (removing doesn't use it).
for Add/Modify when Kind is Family: the relation type to create/change to. For Remove: a
snapshot of the existing relation's type at submission time, purely for display (removing
doesn't use it). Ignored when Kind is Affiliation - see SuggestedAffiliationType instead.
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.SuggestedAffiliationType">
<summary>
for Add/Modify when Kind is Affiliation: the affiliation type to create/change to. For
Remove: a snapshot of the existing affiliation's type at submission time, purely for
display. Null/ignored when Kind is Family - see SuggestedRelationType instead.
</summary>
</member>
<member name="P:RMuseum.Models.Ganjoor.GanjoorPersonRelationEditSuggestion.SuggestedDegreeHint">
@ -11819,6 +11861,15 @@
remove the existing relation ExistingRelationId points to outright
</summary>
</member>
<member name="T:RMuseum.Models.Ganjoor.PersonRelationSuggestionKind">
<summary>
which kind of edge a GanjoorPersonRelationEditSuggestion is about: a kinship tie
(GanjoorPersonRelation, via SuggestedRelationType/ExistingRelationId) or a non-family
affiliation tie (GanjoorPersonAffiliation, via SuggestedAffiliationType/ExistingAffiliationId).
Defaults to Family (0), so every suggestion row created before this field existed keeps being
read exactly as it was before - a plain kinship suggestion.
</summary>
</member>
<member name="T:RMuseum.Models.Ganjoor.PersonRelationType">
<summary>
the kind of kinship edge a GanjoorPersonRelation represents between two GanjoorRelatedPerson

View File

@ -465,16 +465,18 @@ namespace RMuseum.Services.Implementation
var affiliations = await _context.GanjoorPersonAffiliations
.Where(a => a.Person1Id == person.Id || a.Person2Id == person.Id)
.ToListAsync();
var affiliationIds = affiliations.Select(a => a.Id).ToList();
// any other still-pending relation-edit suggestion that touches this person directly, or
// targets one of the relations we're about to remove, would otherwise dangle or violate
// the Restrict FK above - auto-reject those with an explanatory note rather than letting
// the delete fail or silently drop them
// targets one of the relations/affiliations we're about to remove, would otherwise dangle
// or violate the Restrict FKs above - auto-reject those with an explanatory note rather
// than letting the delete fail or silently drop them
var conflictingRelationSuggestions = await _context.GanjoorPersonRelationEditSuggestions
.Where(s => !s.Reviewed && (
s.Person1Id == person.Id ||
s.Person2Id == person.Id ||
(s.ExistingRelationId != null && relationIds.Contains(s.ExistingRelationId.Value))
(s.ExistingRelationId != null && relationIds.Contains(s.ExistingRelationId.Value)) ||
(s.ExistingAffiliationId != null && affiliationIds.Contains(s.ExistingAffiliationId.Value))
))
.ToListAsync();
foreach (var conflicting in conflictingRelationSuggestions)
@ -553,34 +555,65 @@ namespace RMuseum.Services.Implementation
}
GanjoorPersonRelation existingRelation = null;
GanjoorPersonAffiliation existingAffiliation = null;
if (suggestion.Action == PersonRelationSuggestionAction.Modify || suggestion.Action == PersonRelationSuggestionAction.Remove)
{
if (suggestion.ExistingRelationId == null)
if (suggestion.Kind == PersonRelationSuggestionKind.Affiliation)
{
return new RServiceResult<GanjoorPersonRelationEditSuggestion>(null, "نسبت مورد نظر برای ویرایش یا حذف مشخص نشده است.");
if (suggestion.ExistingAffiliationId == null)
{
return new RServiceResult<GanjoorPersonRelationEditSuggestion>(null, "وابستگی مورد نظر برای ویرایش یا حذف مشخص نشده است.");
}
existingAffiliation = await _context.GanjoorPersonAffiliations
.Include(a => a.Person1)
.Include(a => a.Person2)
.Where(a => a.Id == suggestion.ExistingAffiliationId.Value)
.SingleOrDefaultAsync();
if (existingAffiliation == null)
{
return new RServiceResult<GanjoorPersonRelationEditSuggestion>(null, "وابستگی مورد نظر پیدا نشد.");
}
// same trust-the-existing-row convention as the Family branch below
suggestion.Person1Id = existingAffiliation.Person1Id;
suggestion.Person2Id = existingAffiliation.Person2Id;
if (suggestion.Action == PersonRelationSuggestionAction.Remove)
{
suggestion.SuggestedAffiliationType = existingAffiliation.AffiliationType;
suggestion.SuggestedNote = existingAffiliation.Note;
}
}
existingRelation = await _context.GanjoorPersonRelations
.Include(r => r.Person1)
.Include(r => r.Person2)
.Where(r => r.Id == suggestion.ExistingRelationId.Value)
.SingleOrDefaultAsync();
if (existingRelation == null)
else
{
return new RServiceResult<GanjoorPersonRelationEditSuggestion>(null, "نسبت مورد نظر پیدا نشد.");
}
if (suggestion.ExistingRelationId == null)
{
return new RServiceResult<GanjoorPersonRelationEditSuggestion>(null, "نسبت مورد نظر برای ویرایش یا حذف مشخص نشده است.");
}
// always trust the existing relation's own Person1Id/Person2Id/type/etc over
// whatever the client sent, so the suggestion is guaranteed self-consistent with
// what it actually targets, even for a Remove-display
suggestion.Person1Id = existingRelation.Person1Id;
suggestion.Person2Id = existingRelation.Person2Id;
if (suggestion.Action == PersonRelationSuggestionAction.Remove)
{
suggestion.SuggestedRelationType = existingRelation.RelationType;
suggestion.SuggestedDegreeHint = existingRelation.DegreeHint;
suggestion.SuggestedNote = existingRelation.Note;
existingRelation = await _context.GanjoorPersonRelations
.Include(r => r.Person1)
.Include(r => r.Person2)
.Where(r => r.Id == suggestion.ExistingRelationId.Value)
.SingleOrDefaultAsync();
if (existingRelation == null)
{
return new RServiceResult<GanjoorPersonRelationEditSuggestion>(null, "نسبت مورد نظر پیدا نشد.");
}
// always trust the existing relation's own Person1Id/Person2Id/type/etc over
// whatever the client sent, so the suggestion is guaranteed self-consistent with
// what it actually targets, even for a Remove-display
suggestion.Person1Id = existingRelation.Person1Id;
suggestion.Person2Id = existingRelation.Person2Id;
if (suggestion.Action == PersonRelationSuggestionAction.Remove)
{
suggestion.SuggestedRelationType = existingRelation.RelationType;
suggestion.SuggestedDegreeHint = existingRelation.DegreeHint;
suggestion.SuggestedNote = existingRelation.Note;
}
}
}
else // Add
@ -600,6 +633,7 @@ namespace RMuseum.Services.Implementation
suggestion.Id = 0;
suggestion.ExistingRelation = null;
suggestion.ExistingAffiliation = null;
suggestion.Person1 = null;
suggestion.Person2 = null;
suggestion.Date = DateTime.Now;
@ -613,17 +647,19 @@ namespace RMuseum.Services.Implementation
_context.GanjoorPersonRelationEditSuggestions.Add(suggestion);
await _context.SaveChangesAsync();
bool isAffiliation = suggestion.Kind == PersonRelationSuggestionKind.Affiliation;
string actionTitle = suggestion.Action switch
{
PersonRelationSuggestionAction.Add => "پیشنهاد نسبت خویشاوندی جدید",
PersonRelationSuggestionAction.Modify => "پیشنهاد ویرایش نسبت خویشاوندی",
_ => "پیشنهاد حذف نسبت خویشاوندی",
PersonRelationSuggestionAction.Add => isAffiliation ? "پیشنهاد وابستگی جدید" : "پیشنهاد نسبت خویشاوندی جدید",
PersonRelationSuggestionAction.Modify => isAffiliation ? "پیشنهاد ویرایش وابستگی" : "پیشنهاد ویرایش نسبت خویشاوندی",
_ => isAffiliation ? "پیشنهاد حذف وابستگی" : "پیشنهاد حذف نسبت خویشاوندی",
};
string edgeLabel = isAffiliation ? "وابستگی" : "نسبت خویشاوندی";
string actionText = suggestion.Action switch
{
PersonRelationSuggestionAction.Add => $"کاربری پیشنهاد افزودن نسبت خویشاوندی جدید بین «{person1.Name}» و «{person2.Name}» را داده است.",
PersonRelationSuggestionAction.Modify => $"کاربری پیشنهاد ویرایش نسبت خویشاوندی بین «{person1.Name}» و «{person2.Name}» را داده است.",
_ => $"کاربری پیشنهاد حذف نسبت خویشاوندی بین «{person1.Name}» و «{person2.Name}» را داده است.",
PersonRelationSuggestionAction.Add => $"کاربری پیشنهاد افزودن {edgeLabel} جدید بین «{person1.Name}» و «{person2.Name}» را داده است.",
PersonRelationSuggestionAction.Modify => $"کاربری پیشنهاد ویرایش {edgeLabel} بین «{person1.Name}» و «{person2.Name}» را داده است.",
_ => $"کاربری پیشنهاد حذف {edgeLabel} بین «{person1.Name}» و «{person2.Name}» را داده است.",
};
await NotifyModeratorsOfPendingSuggestionAsync(
@ -652,6 +688,7 @@ namespace RMuseum.Services.Implementation
.Include(s => s.Person1)
.Include(s => s.Person2)
.Include(s => s.ExistingRelation)
.Include(s => s.ExistingAffiliation)
.Include(s => s.User)
.Where(s => s.Reviewed == false)
.OrderBy(s => s.Id)
@ -715,51 +752,101 @@ namespace RMuseum.Services.Implementation
if (result == CorrectionReviewResult.Approved)
{
switch (suggestion.Action)
if (suggestion.Kind == PersonRelationSuggestionKind.Affiliation)
{
case PersonRelationSuggestionAction.Add:
_context.GanjoorPersonRelations.Add(new GanjoorPersonRelation()
{
Person1Id = suggestion.Person1Id,
Person2Id = suggestion.Person2Id,
RelationType = suggestion.SuggestedRelationType,
DegreeHint = suggestion.SuggestedDegreeHint,
Note = suggestion.SuggestedNote,
});
break;
case PersonRelationSuggestionAction.Modify:
{
var existing = await _context.GanjoorPersonRelations.Where(r => r.Id == suggestion.ExistingRelationId.Value).SingleOrDefaultAsync();
if (existing == null)
switch (suggestion.Action)
{
case PersonRelationSuggestionAction.Add:
_context.GanjoorPersonAffiliations.Add(new GanjoorPersonAffiliation()
{
return new RServiceResult<GanjoorPersonRelationEditSuggestion>(null, "نسبت مورد نظر دیگر وجود ندارد.");
}
existing.RelationType = suggestion.SuggestedRelationType;
existing.DegreeHint = suggestion.SuggestedDegreeHint;
existing.Note = suggestion.SuggestedNote;
Person1Id = suggestion.Person1Id,
Person2Id = suggestion.Person2Id,
AffiliationType = suggestion.SuggestedAffiliationType ?? PersonAffiliationType.Other,
Note = suggestion.SuggestedNote,
});
break;
}
case PersonRelationSuggestionAction.Remove:
{
var existing = await _context.GanjoorPersonRelations.Where(r => r.Id == suggestion.ExistingRelationId.Value).SingleOrDefaultAsync();
if (existing != null)
case PersonRelationSuggestionAction.Modify:
{
// any other still-pending suggestion targeting this same relation
// would otherwise dangle once it's gone - auto-reject those too
var conflicting = await _context.GanjoorPersonRelationEditSuggestions
.Where(s => !s.Reviewed && s.Id != suggestion.Id && s.ExistingRelationId == existing.Id)
.ToListAsync();
foreach (var c in conflicting)
var existing = await _context.GanjoorPersonAffiliations.Where(a => a.Id == suggestion.ExistingAffiliationId.Value).SingleOrDefaultAsync();
if (existing == null)
{
c.Reviewed = true;
c.Result = CorrectionReviewResult.RejectedBecauseUnnecessaryChange;
c.ReviewNote = "این نسبت پیش‌تر حذف شد.";
c.ReviewDate = DateTime.Now;
return new RServiceResult<GanjoorPersonRelationEditSuggestion>(null, "وابستگی مورد نظر دیگر وجود ندارد.");
}
_context.GanjoorPersonRelations.Remove(existing);
existing.AffiliationType = suggestion.SuggestedAffiliationType ?? existing.AffiliationType;
existing.Note = suggestion.SuggestedNote;
break;
}
case PersonRelationSuggestionAction.Remove:
{
var existing = await _context.GanjoorPersonAffiliations.Where(a => a.Id == suggestion.ExistingAffiliationId.Value).SingleOrDefaultAsync();
if (existing != null)
{
// any other still-pending suggestion targeting this same affiliation
// would otherwise dangle once it's gone - auto-reject those too
var conflicting = await _context.GanjoorPersonRelationEditSuggestions
.Where(s => !s.Reviewed && s.Id != suggestion.Id && s.ExistingAffiliationId == existing.Id)
.ToListAsync();
foreach (var c in conflicting)
{
c.Reviewed = true;
c.Result = CorrectionReviewResult.RejectedBecauseUnnecessaryChange;
c.ReviewNote = "این وابستگی پیش‌تر حذف شد.";
c.ReviewDate = DateTime.Now;
}
_context.GanjoorPersonAffiliations.Remove(existing);
}
break;
}
}
}
else
{
switch (suggestion.Action)
{
case PersonRelationSuggestionAction.Add:
_context.GanjoorPersonRelations.Add(new GanjoorPersonRelation()
{
Person1Id = suggestion.Person1Id,
Person2Id = suggestion.Person2Id,
RelationType = suggestion.SuggestedRelationType,
DegreeHint = suggestion.SuggestedDegreeHint,
Note = suggestion.SuggestedNote,
});
break;
}
case PersonRelationSuggestionAction.Modify:
{
var existing = await _context.GanjoorPersonRelations.Where(r => r.Id == suggestion.ExistingRelationId.Value).SingleOrDefaultAsync();
if (existing == null)
{
return new RServiceResult<GanjoorPersonRelationEditSuggestion>(null, "نسبت مورد نظر دیگر وجود ندارد.");
}
existing.RelationType = suggestion.SuggestedRelationType;
existing.DegreeHint = suggestion.SuggestedDegreeHint;
existing.Note = suggestion.SuggestedNote;
break;
}
case PersonRelationSuggestionAction.Remove:
{
var existing = await _context.GanjoorPersonRelations.Where(r => r.Id == suggestion.ExistingRelationId.Value).SingleOrDefaultAsync();
if (existing != null)
{
// any other still-pending suggestion targeting this same relation
// would otherwise dangle once it's gone - auto-reject those too
var conflicting = await _context.GanjoorPersonRelationEditSuggestions
.Where(s => !s.Reviewed && s.Id != suggestion.Id && s.ExistingRelationId == existing.Id)
.ToListAsync();
foreach (var c in conflicting)
{
c.Reviewed = true;
c.Result = CorrectionReviewResult.RejectedBecauseUnnecessaryChange;
c.ReviewNote = "این نسبت پیش‌تر حذف شد.";
c.ReviewDate = DateTime.Now;
}
_context.GanjoorPersonRelations.Remove(existing);
}
break;
}
}
}
}
@ -771,11 +858,12 @@ namespace RMuseum.Services.Implementation
await _context.SaveChangesAsync();
string edgeLabel = suggestion.Kind == PersonRelationSuggestionKind.Affiliation ? "وابستگی" : "نسبت خویشاوندی";
string actionLabel = suggestion.Action switch
{
PersonRelationSuggestionAction.Add => "افزودن نسبت خویشاوندی",
PersonRelationSuggestionAction.Modify => "ویرایش نسبت خویشاوندی",
_ => "حذف نسبت خویشاوندی",
PersonRelationSuggestionAction.Add => $"افزودن {edgeLabel}",
PersonRelationSuggestionAction.Modify => $"ویرایش {edgeLabel}",
_ => $"حذف {edgeLabel}",
};
if (result == CorrectionReviewResult.Approved)