116 lines
4.5 KiB
C#
116 lines
4.5 KiB
C#
|
|
using System;
|
||
|
|
using System.Globalization;
|
||
|
|
using System.Text.Json;
|
||
|
|
using System.Text.Json.Nodes;
|
||
|
|
using System.Threading.Tasks;
|
||
|
|
using Microsoft.Extensions.Caching.Distributed;
|
||
|
|
using Microsoft.Extensions.Localization;
|
||
|
|
using Microsoft.Extensions.Logging;
|
||
|
|
using Sozsoft.Platform.Entities;
|
||
|
|
using Sozsoft.Platform.Localization;
|
||
|
|
using Volo.Abp.Caching;
|
||
|
|
using Volo.Abp.DependencyInjection;
|
||
|
|
using Volo.Abp.Domain.Repositories;
|
||
|
|
using Volo.Abp.Guids;
|
||
|
|
using Volo.Abp.Localization;
|
||
|
|
using Volo.Abp.Timing;
|
||
|
|
using Volo.Abp.Users;
|
||
|
|
|
||
|
|
namespace Sozsoft.Mcp;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Gelistirme talebinin Timesheets listesini MCP islemlerinden doldurur. Talep
|
||
|
|
/// <c>get_dev_request</c> / <c>start_dev_request</c> ile acildiginda kullanicinin etkin talebi
|
||
|
|
/// olur; ardindan yapilan her yazma islemi o talebe bir satir ekler. Satir modelin dikkatine
|
||
|
|
/// birakilmaz: aciklama dil anahtarindan Turkce uretilir, sure son etkinlikten bu yana olculur.
|
||
|
|
/// </summary>
|
||
|
|
public interface IMcpDevRequestTimesheetRecorder
|
||
|
|
{
|
||
|
|
/// <summary>Talebi kullanicinin etkin talebi yapar ve olcumu baslatir.</summary>
|
||
|
|
Task BeginAsync(Guid devRequestId);
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Etkin talebe bir satir yazar; etkin talep yoksa (islem bir talep dongusunun parcasi
|
||
|
|
/// degilse) hicbir sey yapmaz.
|
||
|
|
/// </summary>
|
||
|
|
Task RecordAsync(string descriptionKey, params object[] args);
|
||
|
|
}
|
||
|
|
|
||
|
|
public class McpDevRequestTimesheetRecorder(
|
||
|
|
IDistributedCache<McpDevRequestActivityCacheItem> activityCache,
|
||
|
|
IRepository<DevRequest, Guid> devRequestRepository,
|
||
|
|
IStringLocalizer<PlatformResource> localizer,
|
||
|
|
ICurrentUser currentUser,
|
||
|
|
IClock clock,
|
||
|
|
IGuidGenerator guidGenerator,
|
||
|
|
ILogger<McpDevRequestTimesheetRecorder> logger) : IMcpDevRequestTimesheetRecorder, ITransientDependency
|
||
|
|
{
|
||
|
|
/// <summary>Timesheet satirlarinin dili; talep ekrani Turkce kullanilir.</summary>
|
||
|
|
private const string TimesheetCulture = "tr";
|
||
|
|
|
||
|
|
/// <summary>Talep ekranindaki tarih kutusunun okudugu bicim.</summary>
|
||
|
|
private const string DateFormat = "yyyy-MM-dd";
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Etkin talebin omru bir calisma oturumunu kapsar; suresi dolan kayit, gunler once acilmis
|
||
|
|
/// bir talebe baska bir isin yazilmasini ve sahte uzun sureyi engeller.
|
||
|
|
/// </summary>
|
||
|
|
private static readonly TimeSpan ActivityLifetime = TimeSpan.FromHours(12);
|
||
|
|
|
||
|
|
public Task BeginAsync(Guid devRequestId) => StampAsync(devRequestId, clock.Now);
|
||
|
|
|
||
|
|
public async Task RecordAsync(string descriptionKey, params object[] args)
|
||
|
|
{
|
||
|
|
var activity = await activityCache.GetAsync(ActivityKey());
|
||
|
|
if (activity is null)
|
||
|
|
return;
|
||
|
|
|
||
|
|
var devRequest = await devRequestRepository.FindAsync(activity.RequestId);
|
||
|
|
if (devRequest is null)
|
||
|
|
return;
|
||
|
|
|
||
|
|
JsonArray timesheets;
|
||
|
|
try
|
||
|
|
{
|
||
|
|
timesheets = string.IsNullOrWhiteSpace(devRequest.Timesheets)
|
||
|
|
? []
|
||
|
|
: JsonNode.Parse(devRequest.Timesheets) as JsonArray ?? throw new JsonException();
|
||
|
|
}
|
||
|
|
catch (JsonException)
|
||
|
|
{
|
||
|
|
// Yapilan is (deploy, tablo) zaten tamamlandi; bozuk bir timesheet onu geri almamali.
|
||
|
|
logger.LogWarning("Timesheets of development request {RequestId} could not be read; entry skipped.", devRequest.Id);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
string description;
|
||
|
|
using (CultureHelper.Use(TimesheetCulture))
|
||
|
|
{
|
||
|
|
description = localizer[descriptionKey, args];
|
||
|
|
}
|
||
|
|
|
||
|
|
var now = clock.Now;
|
||
|
|
var minutes = Math.Max(1, (int)Math.Round((now - activity.At).TotalMinutes));
|
||
|
|
|
||
|
|
timesheets.Add(new JsonObject
|
||
|
|
{
|
||
|
|
["id"] = guidGenerator.Create().ToString(),
|
||
|
|
["date"] = now.ToString(DateFormat, CultureInfo.InvariantCulture),
|
||
|
|
["userName"] = currentUser.UserName ?? string.Empty,
|
||
|
|
["description"] = description,
|
||
|
|
// Ekranin sure kutusu saati ondalik sayi olarak okur.
|
||
|
|
["duration"] = Math.Round(minutes / 60d, 4)
|
||
|
|
});
|
||
|
|
|
||
|
|
devRequest.Timesheets = timesheets.ToJsonString();
|
||
|
|
await devRequestRepository.UpdateAsync(devRequest, autoSave: true);
|
||
|
|
await StampAsync(devRequest.Id, now);
|
||
|
|
}
|
||
|
|
|
||
|
|
private string ActivityKey() => currentUser.Id?.ToString() ?? string.Empty;
|
||
|
|
|
||
|
|
private Task StampAsync(Guid devRequestId, DateTime at) =>
|
||
|
|
activityCache.SetAsync(ActivityKey(), new McpDevRequestActivityCacheItem { RequestId = devRequestId, At = at },
|
||
|
|
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = ActivityLifetime });
|
||
|
|
}
|