216 lines
7.3 KiB
C#
216 lines
7.3 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using TakeoutSaaS.Domain.Tenants.Entities;
|
|
using TakeoutSaaS.Domain.Tenants.Enums;
|
|
using TakeoutSaaS.Domain.Tenants.Repositories;
|
|
using TakeoutSaaS.Infrastructure.App.Persistence;
|
|
|
|
namespace TakeoutSaaS.Infrastructure.App.Repositories;
|
|
|
|
/// <summary>
|
|
/// 租户发票仓储 EF Core 实现。
|
|
/// </summary>
|
|
public sealed class EfTenantInvoiceRepository(TakeoutAppDbContext context) : ITenantInvoiceRepository
|
|
{
|
|
/// <inheritdoc />
|
|
public Task<TenantInvoiceSetting?> GetSettingAsync(long tenantId, CancellationToken cancellationToken = default)
|
|
{
|
|
return context.TenantInvoiceSettings
|
|
.Where(item => item.TenantId == tenantId)
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task AddSettingAsync(TenantInvoiceSetting entity, CancellationToken cancellationToken = default)
|
|
{
|
|
return context.TenantInvoiceSettings.AddAsync(entity, cancellationToken).AsTask();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task UpdateSettingAsync(TenantInvoiceSetting entity, CancellationToken cancellationToken = default)
|
|
{
|
|
context.TenantInvoiceSettings.Update(entity);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<(IReadOnlyList<TenantInvoiceRecord> Items, int TotalCount)> SearchRecordsAsync(
|
|
long tenantId,
|
|
DateTime? startUtc,
|
|
DateTime? endUtc,
|
|
TenantInvoiceStatus? status,
|
|
TenantInvoiceType? invoiceType,
|
|
string? keyword,
|
|
int page,
|
|
int pageSize,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var normalizedPage = Math.Max(1, page);
|
|
var normalizedPageSize = Math.Clamp(pageSize, 1, 500);
|
|
|
|
var query = BuildRecordQuery(tenantId, startUtc, endUtc, status, invoiceType, keyword);
|
|
|
|
var totalCount = await query.CountAsync(cancellationToken);
|
|
if (totalCount == 0)
|
|
{
|
|
return ([], 0);
|
|
}
|
|
|
|
var items = await query
|
|
.OrderByDescending(item => item.AppliedAt)
|
|
.ThenByDescending(item => item.Id)
|
|
.Skip((normalizedPage - 1) * normalizedPageSize)
|
|
.Take(normalizedPageSize)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
return (items, totalCount);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<TenantInvoiceRecordStatsSnapshot> GetStatsAsync(
|
|
long tenantId,
|
|
DateTime nowUtc,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var utcNow = NormalizeUtc(nowUtc);
|
|
var monthStart = new DateTime(utcNow.Year, utcNow.Month, 1, 0, 0, 0, DateTimeKind.Utc);
|
|
|
|
var summary = await context.TenantInvoiceRecords
|
|
.AsNoTracking()
|
|
.Where(item => item.TenantId == tenantId)
|
|
.GroupBy(_ => 1)
|
|
.Select(group => new
|
|
{
|
|
CurrentMonthIssuedAmount = group
|
|
.Where(item =>
|
|
item.Status == TenantInvoiceStatus.Issued &&
|
|
item.IssuedAt.HasValue &&
|
|
item.IssuedAt.Value >= monthStart &&
|
|
item.IssuedAt.Value <= utcNow)
|
|
.Sum(item => item.Amount),
|
|
CurrentMonthIssuedCount = group
|
|
.Count(item =>
|
|
item.Status == TenantInvoiceStatus.Issued &&
|
|
item.IssuedAt.HasValue &&
|
|
item.IssuedAt.Value >= monthStart &&
|
|
item.IssuedAt.Value <= utcNow),
|
|
PendingCount = group.Count(item => item.Status == TenantInvoiceStatus.Pending),
|
|
VoidedCount = group.Count(item => item.Status == TenantInvoiceStatus.Voided)
|
|
})
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
|
|
if (summary is null)
|
|
{
|
|
return new TenantInvoiceRecordStatsSnapshot();
|
|
}
|
|
|
|
return new TenantInvoiceRecordStatsSnapshot
|
|
{
|
|
CurrentMonthIssuedAmount = summary.CurrentMonthIssuedAmount,
|
|
CurrentMonthIssuedCount = summary.CurrentMonthIssuedCount,
|
|
PendingCount = summary.PendingCount,
|
|
VoidedCount = summary.VoidedCount
|
|
};
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task<TenantInvoiceRecord?> FindRecordByIdAsync(
|
|
long tenantId,
|
|
long recordId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return context.TenantInvoiceRecords
|
|
.Where(item => item.TenantId == tenantId && item.Id == recordId)
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task<bool> ExistsInvoiceNoAsync(
|
|
long tenantId,
|
|
string invoiceNo,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return context.TenantInvoiceRecords
|
|
.AsNoTracking()
|
|
.AnyAsync(
|
|
item => item.TenantId == tenantId && item.InvoiceNo == invoiceNo,
|
|
cancellationToken);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task AddRecordAsync(TenantInvoiceRecord entity, CancellationToken cancellationToken = default)
|
|
{
|
|
return context.TenantInvoiceRecords.AddAsync(entity, cancellationToken).AsTask();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task UpdateRecordAsync(TenantInvoiceRecord entity, CancellationToken cancellationToken = default)
|
|
{
|
|
context.TenantInvoiceRecords.Update(entity);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
return context.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
private IQueryable<TenantInvoiceRecord> BuildRecordQuery(
|
|
long tenantId,
|
|
DateTime? startUtc,
|
|
DateTime? endUtc,
|
|
TenantInvoiceStatus? status,
|
|
TenantInvoiceType? invoiceType,
|
|
string? keyword)
|
|
{
|
|
var query = context.TenantInvoiceRecords
|
|
.AsNoTracking()
|
|
.Where(item => item.TenantId == tenantId);
|
|
|
|
if (startUtc.HasValue)
|
|
{
|
|
var normalizedStart = NormalizeUtc(startUtc.Value);
|
|
query = query.Where(item => item.AppliedAt >= normalizedStart);
|
|
}
|
|
|
|
if (endUtc.HasValue)
|
|
{
|
|
var normalizedEnd = NormalizeUtc(endUtc.Value);
|
|
query = query.Where(item => item.AppliedAt <= normalizedEnd);
|
|
}
|
|
|
|
if (status.HasValue)
|
|
{
|
|
query = query.Where(item => item.Status == status.Value);
|
|
}
|
|
|
|
if (invoiceType.HasValue)
|
|
{
|
|
query = query.Where(item => item.InvoiceType == invoiceType.Value);
|
|
}
|
|
|
|
var normalizedKeyword = (keyword ?? string.Empty).Trim();
|
|
if (!string.IsNullOrWhiteSpace(normalizedKeyword))
|
|
{
|
|
var like = $"%{normalizedKeyword}%";
|
|
query = query.Where(item =>
|
|
EF.Functions.ILike(item.InvoiceNo, like) ||
|
|
EF.Functions.ILike(item.CompanyName, like) ||
|
|
EF.Functions.ILike(item.ApplicantName, like) ||
|
|
EF.Functions.ILike(item.OrderNo, like));
|
|
}
|
|
|
|
return query;
|
|
}
|
|
|
|
private static DateTime NormalizeUtc(DateTime value)
|
|
{
|
|
return value.Kind switch
|
|
{
|
|
DateTimeKind.Utc => value,
|
|
DateTimeKind.Local => value.ToUniversalTime(),
|
|
_ => DateTime.SpecifyKind(value, DateTimeKind.Utc)
|
|
};
|
|
}
|
|
}
|