feat: 租户账单公告通知接口
This commit is contained in:
@@ -39,6 +39,10 @@ public static class AppServiceCollectionExtensions
|
||||
services.AddScoped<IPaymentRepository, EfPaymentRepository>();
|
||||
services.AddScoped<IDeliveryRepository, EfDeliveryRepository>();
|
||||
services.AddScoped<ITenantRepository, EfTenantRepository>();
|
||||
services.AddScoped<ITenantBillingRepository, EfTenantBillingRepository>();
|
||||
services.AddScoped<ITenantAnnouncementRepository, EfTenantAnnouncementRepository>();
|
||||
services.AddScoped<ITenantAnnouncementReadRepository, EfTenantAnnouncementReadRepository>();
|
||||
services.AddScoped<ITenantNotificationRepository, EfTenantNotificationRepository>();
|
||||
services.AddScoped<ITenantPackageRepository, EfTenantPackageRepository>();
|
||||
services.AddScoped<ITenantQuotaUsageRepository, EfTenantQuotaUsageRepository>();
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ public sealed class TakeoutAppDbContext(
|
||||
public DbSet<TenantQuotaUsage> TenantQuotaUsages => Set<TenantQuotaUsage>();
|
||||
public DbSet<TenantBillingStatement> TenantBillingStatements => Set<TenantBillingStatement>();
|
||||
public DbSet<TenantNotification> TenantNotifications => Set<TenantNotification>();
|
||||
public DbSet<TenantAnnouncement> TenantAnnouncements => Set<TenantAnnouncement>();
|
||||
public DbSet<TenantAnnouncementRead> TenantAnnouncementReads => Set<TenantAnnouncementRead>();
|
||||
public DbSet<TenantVerificationProfile> TenantVerificationProfiles => Set<TenantVerificationProfile>();
|
||||
public DbSet<TenantAuditLog> TenantAuditLogs => Set<TenantAuditLog>();
|
||||
|
||||
@@ -141,6 +143,8 @@ public sealed class TakeoutAppDbContext(
|
||||
ConfigureTenantQuotaUsage(modelBuilder.Entity<TenantQuotaUsage>());
|
||||
ConfigureTenantBilling(modelBuilder.Entity<TenantBillingStatement>());
|
||||
ConfigureTenantNotification(modelBuilder.Entity<TenantNotification>());
|
||||
ConfigureTenantAnnouncement(modelBuilder.Entity<TenantAnnouncement>());
|
||||
ConfigureTenantAnnouncementRead(modelBuilder.Entity<TenantAnnouncementRead>());
|
||||
ConfigureTenantVerificationProfile(modelBuilder.Entity<TenantVerificationProfile>());
|
||||
ConfigureTenantAuditLog(modelBuilder.Entity<TenantAuditLog>());
|
||||
ConfigureMerchantDocument(modelBuilder.Entity<MerchantDocument>());
|
||||
@@ -465,6 +469,35 @@ public sealed class TakeoutAppDbContext(
|
||||
builder.HasIndex(x => new { x.TenantId, x.Channel, x.SentAt });
|
||||
}
|
||||
|
||||
private static void ConfigureTenantAnnouncement(EntityTypeBuilder<TenantAnnouncement> builder)
|
||||
{
|
||||
builder.ToTable("tenant_announcements");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.TenantId).IsRequired();
|
||||
builder.Property(x => x.Title).HasMaxLength(128).IsRequired();
|
||||
builder.Property(x => x.Content).HasColumnType("text").IsRequired();
|
||||
builder.Property(x => x.AnnouncementType).HasConversion<int>();
|
||||
builder.Property(x => x.Priority).IsRequired();
|
||||
builder.Property(x => x.IsActive).IsRequired();
|
||||
ConfigureAuditableEntity(builder);
|
||||
ConfigureSoftDeleteEntity(builder);
|
||||
builder.HasIndex(x => new { x.TenantId, x.AnnouncementType, x.IsActive });
|
||||
builder.HasIndex(x => new { x.TenantId, x.EffectiveFrom, x.EffectiveTo });
|
||||
}
|
||||
|
||||
private static void ConfigureTenantAnnouncementRead(EntityTypeBuilder<TenantAnnouncementRead> builder)
|
||||
{
|
||||
builder.ToTable("tenant_announcement_reads");
|
||||
builder.HasKey(x => x.Id);
|
||||
builder.Property(x => x.TenantId).IsRequired();
|
||||
builder.Property(x => x.AnnouncementId).IsRequired();
|
||||
builder.Property(x => x.UserId);
|
||||
builder.Property(x => x.ReadAt).IsRequired();
|
||||
ConfigureAuditableEntity(builder);
|
||||
ConfigureSoftDeleteEntity(builder);
|
||||
builder.HasIndex(x => new { x.TenantId, x.AnnouncementId, x.UserId }).IsUnique();
|
||||
}
|
||||
|
||||
private static void ConfigureMerchantDocument(EntityTypeBuilder<MerchantDocument> builder)
|
||||
{
|
||||
builder.ToTable("merchant_documents");
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TakeoutSaaS.Domain.Tenants.Entities;
|
||||
using TakeoutSaaS.Domain.Tenants.Repositories;
|
||||
using TakeoutSaaS.Infrastructure.App.Persistence;
|
||||
|
||||
namespace TakeoutSaaS.Infrastructure.App.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// EF 公告已读仓储。
|
||||
/// </summary>
|
||||
public sealed class EfTenantAnnouncementReadRepository(TakeoutAppDbContext context) : ITenantAnnouncementReadRepository
|
||||
{
|
||||
public Task<IReadOnlyList<TenantAnnouncementRead>> GetByAnnouncementAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return context.TenantAnnouncementReads.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId && x.AnnouncementId == announcementId)
|
||||
.OrderBy(x => x.ReadAt)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ContinueWith(t => (IReadOnlyList<TenantAnnouncementRead>)t.Result, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<TenantAnnouncementRead?> FindAsync(long tenantId, long announcementId, long? userId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return context.TenantAnnouncementReads
|
||||
.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.AnnouncementId == announcementId && x.UserId == userId, cancellationToken);
|
||||
}
|
||||
|
||||
public Task AddAsync(TenantAnnouncementRead record, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return context.TenantAnnouncementReads.AddAsync(record, cancellationToken).AsTask();
|
||||
}
|
||||
|
||||
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using System.Linq;
|
||||
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 租户公告仓储。
|
||||
/// </summary>
|
||||
public sealed class EfTenantAnnouncementRepository(TakeoutAppDbContext context) : ITenantAnnouncementRepository
|
||||
{
|
||||
public Task<IReadOnlyList<TenantAnnouncement>> SearchAsync(
|
||||
long tenantId,
|
||||
TenantAnnouncementType? type,
|
||||
bool? isActive,
|
||||
DateTime? effectiveAt,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = context.TenantAnnouncements.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId);
|
||||
|
||||
if (type.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.AnnouncementType == type.Value);
|
||||
}
|
||||
|
||||
if (isActive.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.IsActive == isActive.Value);
|
||||
}
|
||||
|
||||
if (effectiveAt.HasValue)
|
||||
{
|
||||
var at = effectiveAt.Value;
|
||||
query = query.Where(x => x.EffectiveFrom <= at && (x.EffectiveTo == null || x.EffectiveTo >= at));
|
||||
}
|
||||
|
||||
return query
|
||||
.OrderByDescending(x => x.Priority)
|
||||
.ThenByDescending(x => x.CreatedAt)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ContinueWith(t => (IReadOnlyList<TenantAnnouncement>)t.Result, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<TenantAnnouncement?> FindByIdAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return context.TenantAnnouncements.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Id == announcementId, cancellationToken);
|
||||
}
|
||||
|
||||
public Task AddAsync(TenantAnnouncement announcement, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return context.TenantAnnouncements.AddAsync(announcement, cancellationToken).AsTask();
|
||||
}
|
||||
|
||||
public Task UpdateAsync(TenantAnnouncement announcement, CancellationToken cancellationToken = default)
|
||||
{
|
||||
context.TenantAnnouncements.Update(announcement);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(long tenantId, long announcementId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var entity = await context.TenantAnnouncements.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Id == announcementId, cancellationToken);
|
||||
if (entity != null)
|
||||
{
|
||||
context.TenantAnnouncements.Remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Linq;
|
||||
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 租户账单仓储。
|
||||
/// </summary>
|
||||
public sealed class EfTenantBillingRepository(TakeoutAppDbContext context) : ITenantBillingRepository
|
||||
{
|
||||
public Task<IReadOnlyList<TenantBillingStatement>> SearchAsync(
|
||||
long tenantId,
|
||||
TenantBillingStatus? status,
|
||||
DateTime? from,
|
||||
DateTime? to,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = context.TenantBillingStatements.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId);
|
||||
|
||||
if (status.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.Status == status.Value);
|
||||
}
|
||||
|
||||
if (from.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.PeriodStart >= from.Value);
|
||||
}
|
||||
|
||||
if (to.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.PeriodEnd <= to.Value);
|
||||
}
|
||||
|
||||
return query
|
||||
.OrderByDescending(x => x.PeriodEnd)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ContinueWith(t => (IReadOnlyList<TenantBillingStatement>)t.Result, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<TenantBillingStatement?> FindByIdAsync(long tenantId, long billingId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return context.TenantBillingStatements.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Id == billingId, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<TenantBillingStatement?> FindByStatementNoAsync(long tenantId, string statementNo, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return context.TenantBillingStatements.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.StatementNo == statementNo, cancellationToken);
|
||||
}
|
||||
|
||||
public Task AddAsync(TenantBillingStatement bill, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return context.TenantBillingStatements.AddAsync(bill, cancellationToken).AsTask();
|
||||
}
|
||||
|
||||
public Task UpdateAsync(TenantBillingStatement bill, CancellationToken cancellationToken = default)
|
||||
{
|
||||
context.TenantBillingStatements.Update(bill);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Linq;
|
||||
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 租户通知仓储。
|
||||
/// </summary>
|
||||
public sealed class EfTenantNotificationRepository(TakeoutAppDbContext context) : ITenantNotificationRepository
|
||||
{
|
||||
public Task<IReadOnlyList<TenantNotification>> SearchAsync(
|
||||
long tenantId,
|
||||
TenantNotificationSeverity? severity,
|
||||
bool? unreadOnly,
|
||||
DateTime? from,
|
||||
DateTime? to,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = context.TenantNotifications.AsNoTracking()
|
||||
.Where(x => x.TenantId == tenantId);
|
||||
|
||||
if (severity.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.Severity == severity.Value);
|
||||
}
|
||||
|
||||
if (unreadOnly == true)
|
||||
{
|
||||
query = query.Where(x => x.ReadAt == null);
|
||||
}
|
||||
|
||||
if (from.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.SentAt >= from.Value);
|
||||
}
|
||||
|
||||
if (to.HasValue)
|
||||
{
|
||||
query = query.Where(x => x.SentAt <= to.Value);
|
||||
}
|
||||
|
||||
return query
|
||||
.OrderByDescending(x => x.SentAt)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ContinueWith(t => (IReadOnlyList<TenantNotification>)t.Result, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<TenantNotification?> FindByIdAsync(long tenantId, long notificationId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return context.TenantNotifications
|
||||
.FirstOrDefaultAsync(x => x.TenantId == tenantId && x.Id == notificationId, cancellationToken);
|
||||
}
|
||||
|
||||
public Task AddAsync(TenantNotification notification, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return context.TenantNotifications.AddAsync(notification, cancellationToken).AsTask();
|
||||
}
|
||||
|
||||
public Task UpdateAsync(TenantNotification notification, CancellationToken cancellationToken = default)
|
||||
{
|
||||
context.TenantNotifications.Update(notification);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user