122 lines
5.4 KiB
C#
122 lines
5.4 KiB
C#
using System.Text.Json;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Tiku.Application.Commerce;
|
|
using Tiku.Domain.Commerce;
|
|
|
|
namespace Tiku.Infrastructure.Commerce;
|
|
|
|
internal sealed partial class ReconciliationAdministrationService(CommerceAdministrationDependencies dependencies)
|
|
: CommerceAdministrationServiceBase(dependencies), IReconciliationAdministrationService
|
|
{
|
|
public async Task<TenantReconciliationBatchList> GetReconciliationBatchesAsync(
|
|
CommerceAdminActor actor,
|
|
CommerceAdminQuery query,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
await AssertAdminAsync(actor, cancellationToken);
|
|
var batches = commercePersistence.CommerceReconciliationBatches.AsNoTracking()
|
|
.Where(item => item.TenantId == actor.TenantId);
|
|
if (!string.IsNullOrWhiteSpace(query.Provider))
|
|
{
|
|
var provider = NormalizeProvider(query.Provider);
|
|
batches = batches.Where(item => item.Provider == provider);
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(query.Status))
|
|
batches = batches.Where(item => item.Status == ParseReconciliationBatchStatus(query.Status));
|
|
|
|
var items = await batches.OrderByDescending(item => item.CreatedAt)
|
|
.Take(Math.Clamp(query.Limit ?? 50, 1, 200))
|
|
.ToArrayAsync(cancellationToken);
|
|
return new TenantReconciliationBatchList(items);
|
|
}
|
|
|
|
public async Task<CommerceReconciliationBatch> CreateReconciliationBatchAsync(
|
|
CommerceAdminActor actor,
|
|
CreateReconciliationBatchCommand command,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
await AssertAdminAsync(actor, cancellationToken);
|
|
var batch = new CommerceReconciliationBatch
|
|
{
|
|
TenantId = actor.TenantId,
|
|
CreatedBy = actor.UserId,
|
|
Provider = NormalizeProvider(command.Provider),
|
|
BillDate = command.BillDate,
|
|
BillType = command.BillType,
|
|
Source = command.Source,
|
|
SourceName = command.SourceName?.Trim(),
|
|
SourceHash = command.SourceHash.Trim(),
|
|
Status = ReconciliationBatchStatus.Pending,
|
|
Metadata = JsonObjectOrDefault(command.Metadata)
|
|
};
|
|
commercePersistence.CommerceReconciliationBatches.Add(batch);
|
|
await AddAuditAsync(actor, "commerce.reconciliation_batch.created", "commerce_reconciliation_batches", batch.Id,
|
|
new { batch.Provider, batch.BillDate }, cancellationToken);
|
|
await unitOfWork.SaveChangesAsync(cancellationToken);
|
|
return batch;
|
|
}
|
|
|
|
public async Task<TenantReconciliationIssueList> GetReconciliationIssuesAsync(
|
|
CommerceAdminActor actor,
|
|
CommerceAdminQuery query,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
await AssertAdminAsync(actor, cancellationToken);
|
|
var issues = commercePersistence.CommerceReconciliationIssues.AsNoTracking()
|
|
.Where(item => item.TenantId == actor.TenantId);
|
|
if (!string.IsNullOrWhiteSpace(query.Provider))
|
|
{
|
|
var provider = NormalizeProvider(query.Provider);
|
|
issues = issues.Where(item => item.Provider == provider);
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(query.Status))
|
|
issues = issues.Where(item => item.Status == ParseReconciliationIssueStatus(query.Status));
|
|
|
|
var items = await issues.OrderByDescending(item => item.CreatedAt)
|
|
.Take(Math.Clamp(query.Limit ?? 50, 1, 200))
|
|
.ToArrayAsync(cancellationToken);
|
|
return new TenantReconciliationIssueList(items);
|
|
}
|
|
|
|
public async Task<CommerceReconciliationIssue> UpdateReconciliationIssueAsync(
|
|
CommerceAdminActor actor,
|
|
UpdateReconciliationIssueCommand command,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
await AssertAdminAsync(actor, cancellationToken);
|
|
var issue = await commercePersistence.CommerceReconciliationIssues.SingleOrDefaultAsync(
|
|
item => item.TenantId == actor.TenantId && item.Id == command.IssueId,
|
|
cancellationToken) ??
|
|
throw new CommerceException("Reconciliation issue was not found.",
|
|
"reconciliation_issue_not_found");
|
|
var fromStatus = issue.Status;
|
|
issue.Status = command.Status;
|
|
issue.ResolutionType = command.ResolutionType;
|
|
issue.ResolutionNote = command.Note?.Trim();
|
|
issue.AssignedTo = command.AssignedTo ?? issue.AssignedTo;
|
|
if (command.Status is ReconciliationIssueStatus.Resolved or ReconciliationIssueStatus.Ignored)
|
|
{
|
|
issue.ResolvedBy = actor.UserId;
|
|
issue.ResolvedAt = DateTimeOffset.UtcNow;
|
|
}
|
|
|
|
commercePersistence.CommerceReconciliationIssueEvents.Add(new CommerceReconciliationIssueEvent
|
|
{
|
|
TenantId = actor.TenantId,
|
|
IssueId = issue.Id,
|
|
FromStatus = fromStatus,
|
|
ToStatus = command.Status,
|
|
EventType = "status_changed",
|
|
ActorUserId = actor.UserId,
|
|
Note = command.Note,
|
|
Details = JsonSerializer.SerializeToElement(new { command.ResolutionType, command.AssignedTo })
|
|
});
|
|
await AddAuditAsync(actor, "commerce.reconciliation_issue.status_changed", "commerce_reconciliation_issues",
|
|
issue.Id, new { issue.IssueNo, From = fromStatus, To = command.Status }, cancellationToken);
|
|
await unitOfWork.SaveChangesAsync(cancellationToken);
|
|
return issue;
|
|
}
|
|
}
|