diff --git a/backend/Tnsu.ApprovalRoute.Application/Reminders/ApprovalRow.cs b/backend/Tnsu.ApprovalRoute.Application/Reminders/ApprovalRow.cs
index 3fcf63f..dacb884 100644
--- a/backend/Tnsu.ApprovalRoute.Application/Reminders/ApprovalRow.cs
+++ b/backend/Tnsu.ApprovalRoute.Application/Reminders/ApprovalRow.cs
@@ -1,3 +1,4 @@
+using System.Globalization;
using System.Text.Json;
namespace Tnsu.ApprovalRoute.Application.Reminders;
@@ -44,19 +45,53 @@ internal sealed record ApprovalRow(
private static string? GetString(JsonElement e, string name) =>
e.TryGetProperty(name, out var v) && v.ValueKind == JsonValueKind.String ? v.GetString() : null;
+ ///
+ /// Целое из JSON — с поправкой на то, КАК SharePoint отдаёт Number-колонки.
+ ///
+ /// Одного TryGetInt32 здесь мало: у колонки типа Number значение приходит
+ /// дробным («1.0», а не «1»), а TryGetInt32 на числе с десятичной точкой
+ /// возвращает false — и поле молча становилось null. На фронтенде этой
+ /// проблемы нет вовсе: в JavaScript 1.0 и 1 — одно и то же число, поэтому
+ /// там те же данные читались верно, а на бэкенде терялись.
+ ///
+ /// Строку тоже принимаем: с odata=verbose (и у части полей) SharePoint
+ /// отдаёт числа строками.
+ ///
private static bool TryGetInt(JsonElement e, string name, out int value)
{
value = 0;
- return e.TryGetProperty(name, out var v) && v.ValueKind == JsonValueKind.Number && v.TryGetInt32(out value);
+ if (!e.TryGetProperty(name, out var v)) return false;
+
+ if (v.ValueKind == JsonValueKind.Number)
+ {
+ if (v.TryGetInt32(out value)) return true;
+ if (!v.TryGetDouble(out var d) || d is < int.MinValue or > int.MaxValue) return false;
+ value = (int)Math.Round(d);
+ return true;
+ }
+
+ if (v.ValueKind != JsonValueKind.String) return false;
+ if (!double.TryParse(v.GetString(), NumberStyles.Float, CultureInfo.InvariantCulture, out var parsed))
+ return false;
+ if (parsed is < int.MinValue or > int.MaxValue) return false;
+
+ value = (int)Math.Round(parsed);
+ return true;
}
private static bool TryGetBool(JsonElement e, string name, out bool value)
{
value = false;
if (!e.TryGetProperty(name, out var v)) return false;
- if (v.ValueKind is not (JsonValueKind.True or JsonValueKind.False)) return false;
- value = v.GetBoolean();
- return true;
+
+ if (v.ValueKind is JsonValueKind.True or JsonValueKind.False)
+ {
+ value = v.GetBoolean();
+ return true;
+ }
+
+ // Да/Нет-колонка тоже может прийти строкой («true»/«false») — см. TryGetInt.
+ return v.ValueKind == JsonValueKind.String && bool.TryParse(v.GetString(), out value);
}
private static DateTimeOffset? TryGetDate(JsonElement e, string name) =>
diff --git a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/ReminderServiceTests.cs b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/ReminderServiceTests.cs
index 0d32178..3467c06 100644
--- a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/ReminderServiceTests.cs
+++ b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/ReminderServiceTests.cs
@@ -54,7 +54,7 @@ public sealed class ReminderServiceTests : IClassFixture
+ string? date = null, string email = "approver@tnsu.kz", object? slaHours = null, bool? ask = null) =>
new
{
Id = id,
@@ -207,6 +207,27 @@ public sealed class ReminderServiceTests : IClassFixture