Files
tnsu-approval-route/backend/Tnsu.ApprovalRoute.Application/Reminders/ApprovalRow.cs
T
gorbenko.pavel d076f4e98d
Tests / backend-test (push) Successful in 1m50s
Tests / frontend-test (push) Successful in 49s
Sla (#11)
Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/11
2026-08-13 04:44:08 +00:00

116 lines
4.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Globalization;
using System.Text.Json;
namespace Tnsu.ApprovalRoute.Application.Reminders;
/// <summary>
/// Строка списка «… — согласование», разобранная из ответа SharePoint.
/// Имена колонок — как в request-portal (FIELDS в approvalList.service.ts):
/// именно он эти списки и создаёт.
/// </summary>
internal sealed record ApprovalRow(
int Id,
int TicketId,
int StepOrder,
string Status,
string Phase,
DateTimeOffset? Date,
string? ApproverEmail,
int? SlaHours,
bool? AskOverdueReason)
{
public static ApprovalRow? TryParse(JsonElement item)
{
// TicketId — Lookup на заявку; без него строку не с чем связать.
if (!TryGetInt(item, "TicketId", out var ticketId)) return null;
var approver = item.TryGetProperty("TansuSiteApprover", out var a) && a.ValueKind == JsonValueKind.Object
? GetString(a, "EMail")
: null;
return new ApprovalRow(
TryGetInt(item, "Id", out var id) ? id : 0,
ticketId,
TryGetInt(item, "StepOrder", out var order) ? order : 0,
GetString(item, "ApprovalStatus") ?? string.Empty,
// Пустая фаза — старые строки, заведённые до появления колонки;
// трактуем как «Согласование» (так же поступает request-portal).
GetString(item, "Phase") is { Length: > 0 } phase ? phase : ApprovalPhases.Approval,
TryGetDate(item, "Date"),
approver,
TryGetInt(item, "SLAhours", out var sla) ? sla : null,
TryGetBool(item, "AskOverdueReason", out var ask) ? ask : null);
}
private static string? GetString(JsonElement e, string name) =>
e.TryGetProperty(name, out var v) && v.ValueKind == JsonValueKind.String ? v.GetString() : null;
/// <summary>
/// Целое из JSON — с поправкой на то, КАК SharePoint отдаёт Number-колонки.
///
/// Одного TryGetInt32 здесь мало: у колонки типа Number значение приходит
/// дробным («1.0», а не «1»), а TryGetInt32 на числе с десятичной точкой
/// возвращает false — и поле молча становилось null. На фронтенде этой
/// проблемы нет вовсе: в JavaScript 1.0 и 1 — одно и то же число, поэтому
/// там те же данные читались верно, а на бэкенде терялись.
///
/// Строку тоже принимаем: с odata=verbose (и у части полей) SharePoint
/// отдаёт числа строками.
/// </summary>
private static bool TryGetInt(JsonElement e, string name, out int value)
{
value = 0;
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 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) =>
e.TryGetProperty(name, out var v) &&
v.ValueKind == JsonValueKind.String &&
DateTimeOffset.TryParse(v.GetString(), out var parsed)
? parsed
: null;
}
internal static class ApprovalPhases
{
public const string Approval = "Согласование";
public const string Execution = "Исполнение";
}
internal static class ApprovalStatuses
{
public const string OnApproval = "На согласовании";
public const string OnExecution = "На исполнении";
}