feat(marketing): implement marketing calendar backend
All checks were successful
Build and Deploy TenantApi + SkuWorker / build-and-deploy (push) Successful in 1m59s
All checks were successful
Build and Deploy TenantApi + SkuWorker / build-and-deploy (push) Successful in 1m59s
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
namespace TakeoutSaaS.TenantApi.Contracts.Marketing;
|
||||
|
||||
/// <summary>
|
||||
/// 营销日历总览查询请求。
|
||||
/// </summary>
|
||||
public sealed class MarketingCalendarOverviewRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 门店 ID。
|
||||
/// </summary>
|
||||
public string StoreId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 年份。
|
||||
/// </summary>
|
||||
public int Year { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 月份(1-12)。
|
||||
/// </summary>
|
||||
public int Month { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 营销日历总览响应。
|
||||
/// </summary>
|
||||
public sealed class MarketingCalendarOverviewResponse
|
||||
{
|
||||
public string Month { get; set; } = string.Empty;
|
||||
|
||||
public int Year { get; set; }
|
||||
|
||||
public int MonthValue { get; set; }
|
||||
|
||||
public string MonthStartDate { get; set; } = string.Empty;
|
||||
|
||||
public string MonthEndDate { get; set; } = string.Empty;
|
||||
|
||||
public int TodayDay { get; set; }
|
||||
|
||||
public List<MarketingCalendarDayResponse> Days { get; set; } = [];
|
||||
|
||||
public List<MarketingCalendarLegendResponse> Legends { get; set; } = [];
|
||||
|
||||
public MarketingCalendarStatsResponse Stats { get; set; } = new();
|
||||
|
||||
public MarketingCalendarConflictBannerResponse? ConflictBanner { get; set; }
|
||||
|
||||
public List<MarketingCalendarConflictResponse> Conflicts { get; set; } = [];
|
||||
|
||||
public List<MarketingCalendarActivityResponse> Activities { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class MarketingCalendarDayResponse
|
||||
{
|
||||
public int Day { get; set; }
|
||||
|
||||
public bool IsWeekend { get; set; }
|
||||
|
||||
public bool IsToday { get; set; }
|
||||
}
|
||||
|
||||
public sealed class MarketingCalendarLegendResponse
|
||||
{
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
public string Color { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public sealed class MarketingCalendarStatsResponse
|
||||
{
|
||||
public int TotalActivityCount { get; set; }
|
||||
|
||||
public int OngoingCount { get; set; }
|
||||
|
||||
public int MaxConcurrentCount { get; set; }
|
||||
|
||||
public decimal EstimatedDiscountAmount { get; set; }
|
||||
}
|
||||
|
||||
public sealed class MarketingCalendarActivityResponse
|
||||
{
|
||||
public string ActivityId { get; set; } = string.Empty;
|
||||
|
||||
public string SourceType { get; set; } = string.Empty;
|
||||
|
||||
public string SourceId { get; set; } = string.Empty;
|
||||
|
||||
public string CalendarType { get; set; } = string.Empty;
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string Color { get; set; } = string.Empty;
|
||||
|
||||
public string Summary { get; set; } = string.Empty;
|
||||
|
||||
public string DisplayStatus { get; set; } = string.Empty;
|
||||
|
||||
public bool IsDimmed { get; set; }
|
||||
|
||||
public string StartDate { get; set; } = string.Empty;
|
||||
|
||||
public string EndDate { get; set; } = string.Empty;
|
||||
|
||||
public decimal EstimatedDiscountAmount { get; set; }
|
||||
|
||||
public List<MarketingCalendarActivityBarResponse> Bars { get; set; } = [];
|
||||
|
||||
public MarketingCalendarActivityDetailResponse Detail { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class MarketingCalendarActivityBarResponse
|
||||
{
|
||||
public string BarId { get; set; } = string.Empty;
|
||||
|
||||
public int StartDay { get; set; }
|
||||
|
||||
public int EndDay { get; set; }
|
||||
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
public bool IsMilestone { get; set; }
|
||||
|
||||
public bool IsDimmed { get; set; }
|
||||
}
|
||||
|
||||
public sealed class MarketingCalendarActivityDetailResponse
|
||||
{
|
||||
public string ModuleName { get; set; } = string.Empty;
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public List<MarketingCalendarDetailFieldResponse> Fields { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class MarketingCalendarDetailFieldResponse
|
||||
{
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
public string Value { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public sealed class MarketingCalendarConflictBannerResponse
|
||||
{
|
||||
public string ConflictId { get; set; } = string.Empty;
|
||||
|
||||
public int StartDay { get; set; }
|
||||
|
||||
public int EndDay { get; set; }
|
||||
|
||||
public int ActivityCount { get; set; }
|
||||
|
||||
public int MaxConcurrentCount { get; set; }
|
||||
|
||||
public int ConflictCount { get; set; }
|
||||
}
|
||||
|
||||
public sealed class MarketingCalendarConflictResponse
|
||||
{
|
||||
public string ConflictId { get; set; } = string.Empty;
|
||||
|
||||
public int StartDay { get; set; }
|
||||
|
||||
public int EndDay { get; set; }
|
||||
|
||||
public int ActivityCount { get; set; }
|
||||
|
||||
public int MaxConcurrentCount { get; set; }
|
||||
|
||||
public List<string> ActivityIds { get; set; } = [];
|
||||
|
||||
public List<MarketingCalendarConflictActivityResponse> Activities { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class MarketingCalendarConflictActivityResponse
|
||||
{
|
||||
public string ActivityId { get; set; } = string.Empty;
|
||||
|
||||
public string CalendarType { get; set; } = string.Empty;
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string Summary { get; set; } = string.Empty;
|
||||
|
||||
public string Color { get; set; } = string.Empty;
|
||||
|
||||
public string DisplayStatus { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TakeoutSaaS.Application.App.Coupons.Calendar.Dto;
|
||||
using TakeoutSaaS.Application.App.Coupons.Calendar.Queries;
|
||||
using TakeoutSaaS.Application.App.Stores.Services;
|
||||
using TakeoutSaaS.Infrastructure.App.Persistence;
|
||||
using TakeoutSaaS.Module.Authorization.Attributes;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
using TakeoutSaaS.Shared.Web.Api;
|
||||
using TakeoutSaaS.TenantApi.Contracts.Marketing;
|
||||
|
||||
namespace TakeoutSaaS.TenantApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 营销中心营销日历。
|
||||
/// </summary>
|
||||
[ApiVersion("1.0")]
|
||||
[Authorize]
|
||||
[Route("api/tenant/v{version:apiVersion}/marketing/calendar")]
|
||||
public sealed class MarketingCalendarController(
|
||||
IMediator mediator,
|
||||
TakeoutAppDbContext dbContext,
|
||||
StoreContextService storeContextService)
|
||||
: BaseApiController
|
||||
{
|
||||
private const string ViewPermission = "tenant:marketing:calendar:view";
|
||||
private const string ManagePermission = "tenant:marketing:calendar:manage";
|
||||
|
||||
/// <summary>
|
||||
/// 获取营销日历总览。
|
||||
/// </summary>
|
||||
[HttpGet("overview")]
|
||||
[PermissionAuthorize(ViewPermission, ManagePermission)]
|
||||
[ProducesResponseType(typeof(ApiResponse<MarketingCalendarOverviewResponse>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<MarketingCalendarOverviewResponse>> Overview(
|
||||
[FromQuery] MarketingCalendarOverviewRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var storeId = StoreApiHelpers.ParseRequiredSnowflake(request.StoreId, nameof(request.StoreId));
|
||||
await EnsureStoreAccessibleAsync(storeId, cancellationToken);
|
||||
|
||||
var result = await mediator.Send(new GetMarketingCalendarOverviewQuery
|
||||
{
|
||||
StoreId = storeId,
|
||||
Year = request.Year,
|
||||
Month = request.Month
|
||||
}, cancellationToken);
|
||||
|
||||
return ApiResponse<MarketingCalendarOverviewResponse>.Ok(MapOverview(result));
|
||||
}
|
||||
|
||||
private async Task EnsureStoreAccessibleAsync(long storeId, CancellationToken cancellationToken)
|
||||
{
|
||||
var (tenantId, merchantId) = StoreApiHelpers.GetTenantMerchantContext(storeContextService);
|
||||
await StoreApiHelpers.EnsureStoreAccessibleAsync(dbContext, tenantId, merchantId, storeId, cancellationToken);
|
||||
}
|
||||
|
||||
private static MarketingCalendarOverviewResponse MapOverview(MarketingCalendarOverviewDto source)
|
||||
{
|
||||
return new MarketingCalendarOverviewResponse
|
||||
{
|
||||
Month = source.Month,
|
||||
Year = source.Year,
|
||||
MonthValue = source.MonthValue,
|
||||
MonthStartDate = StoreApiHelpers.ToDateOnly(source.MonthStartDate),
|
||||
MonthEndDate = StoreApiHelpers.ToDateOnly(source.MonthEndDate),
|
||||
TodayDay = source.TodayDay,
|
||||
Days = source.Days
|
||||
.Select(item => new MarketingCalendarDayResponse
|
||||
{
|
||||
Day = item.Day,
|
||||
IsWeekend = item.IsWeekend,
|
||||
IsToday = item.IsToday
|
||||
})
|
||||
.ToList(),
|
||||
Legends = source.Legends
|
||||
.Select(item => new MarketingCalendarLegendResponse
|
||||
{
|
||||
Type = item.Type,
|
||||
Label = item.Label,
|
||||
Color = item.Color
|
||||
})
|
||||
.ToList(),
|
||||
Stats = new MarketingCalendarStatsResponse
|
||||
{
|
||||
TotalActivityCount = source.Stats.TotalActivityCount,
|
||||
OngoingCount = source.Stats.OngoingCount,
|
||||
MaxConcurrentCount = source.Stats.MaxConcurrentCount,
|
||||
EstimatedDiscountAmount = source.Stats.EstimatedDiscountAmount
|
||||
},
|
||||
ConflictBanner = source.ConflictBanner is null
|
||||
? null
|
||||
: new MarketingCalendarConflictBannerResponse
|
||||
{
|
||||
ConflictId = source.ConflictBanner.ConflictId,
|
||||
StartDay = source.ConflictBanner.StartDay,
|
||||
EndDay = source.ConflictBanner.EndDay,
|
||||
ActivityCount = source.ConflictBanner.ActivityCount,
|
||||
MaxConcurrentCount = source.ConflictBanner.MaxConcurrentCount,
|
||||
ConflictCount = source.ConflictBanner.ConflictCount
|
||||
},
|
||||
Conflicts = source.Conflicts
|
||||
.Select(MapConflict)
|
||||
.ToList(),
|
||||
Activities = source.Activities
|
||||
.Select(MapActivity)
|
||||
.ToList()
|
||||
};
|
||||
}
|
||||
|
||||
private static MarketingCalendarActivityResponse MapActivity(MarketingCalendarActivityDto source)
|
||||
{
|
||||
return new MarketingCalendarActivityResponse
|
||||
{
|
||||
ActivityId = source.ActivityId,
|
||||
SourceType = source.SourceType,
|
||||
SourceId = source.SourceId,
|
||||
CalendarType = source.CalendarType,
|
||||
Name = source.Name,
|
||||
Color = source.Color,
|
||||
Summary = source.Summary,
|
||||
DisplayStatus = source.DisplayStatus,
|
||||
IsDimmed = source.IsDimmed,
|
||||
StartDate = StoreApiHelpers.ToDateOnly(source.StartDate),
|
||||
EndDate = StoreApiHelpers.ToDateOnly(source.EndDate),
|
||||
EstimatedDiscountAmount = source.EstimatedDiscountAmount,
|
||||
Bars = source.Bars.Select(item => new MarketingCalendarActivityBarResponse
|
||||
{
|
||||
BarId = item.BarId,
|
||||
StartDay = item.StartDay,
|
||||
EndDay = item.EndDay,
|
||||
Label = item.Label,
|
||||
IsMilestone = item.IsMilestone,
|
||||
IsDimmed = item.IsDimmed
|
||||
}).ToList(),
|
||||
Detail = new MarketingCalendarActivityDetailResponse
|
||||
{
|
||||
ModuleName = source.Detail.ModuleName,
|
||||
Description = source.Detail.Description,
|
||||
Fields = source.Detail.Fields.Select(item => new MarketingCalendarDetailFieldResponse
|
||||
{
|
||||
Label = item.Label,
|
||||
Value = item.Value
|
||||
}).ToList()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static MarketingCalendarConflictResponse MapConflict(MarketingCalendarConflictDto source)
|
||||
{
|
||||
return new MarketingCalendarConflictResponse
|
||||
{
|
||||
ConflictId = source.ConflictId,
|
||||
StartDay = source.StartDay,
|
||||
EndDay = source.EndDay,
|
||||
ActivityCount = source.ActivityCount,
|
||||
MaxConcurrentCount = source.MaxConcurrentCount,
|
||||
ActivityIds = source.ActivityIds.ToList(),
|
||||
Activities = source.Activities.Select(item => new MarketingCalendarConflictActivityResponse
|
||||
{
|
||||
ActivityId = item.ActivityId,
|
||||
CalendarType = item.CalendarType,
|
||||
Name = item.Name,
|
||||
Summary = item.Summary,
|
||||
Color = item.Color,
|
||||
DisplayStatus = item.DisplayStatus
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
namespace TakeoutSaaS.Application.App.Coupons.Calendar.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// 营销日历总览。
|
||||
/// </summary>
|
||||
public sealed class MarketingCalendarOverviewDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 月份标识(yyyy-MM)。
|
||||
/// </summary>
|
||||
public string Month { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 年份。
|
||||
/// </summary>
|
||||
public int Year { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 月份(1-12)。
|
||||
/// </summary>
|
||||
public int MonthValue { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 月初日期(UTC)。
|
||||
/// </summary>
|
||||
public DateTime MonthStartDate { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 月末日期(UTC)。
|
||||
/// </summary>
|
||||
public DateTime MonthEndDate { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 今天所在日(不在当月则为 0)。
|
||||
/// </summary>
|
||||
public int TodayDay { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 日期头列表。
|
||||
/// </summary>
|
||||
public IReadOnlyList<MarketingCalendarDayDto> Days { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 图例。
|
||||
/// </summary>
|
||||
public IReadOnlyList<MarketingCalendarLegendDto> Legends { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 顶部统计。
|
||||
/// </summary>
|
||||
public MarketingCalendarStatsDto Stats { get; init; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 冲突横幅。
|
||||
/// </summary>
|
||||
public MarketingCalendarConflictBannerDto? ConflictBanner { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 冲突区间。
|
||||
/// </summary>
|
||||
public IReadOnlyList<MarketingCalendarConflictDto> Conflicts { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 活动列表。
|
||||
/// </summary>
|
||||
public IReadOnlyList<MarketingCalendarActivityDto> Activities { get; init; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 日期头。
|
||||
/// </summary>
|
||||
public sealed class MarketingCalendarDayDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 日(1-31)。
|
||||
/// </summary>
|
||||
public int Day { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否周末。
|
||||
/// </summary>
|
||||
public bool IsWeekend { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否今日。
|
||||
/// </summary>
|
||||
public bool IsToday { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 图例。
|
||||
/// </summary>
|
||||
public sealed class MarketingCalendarLegendDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 图例类型。
|
||||
/// </summary>
|
||||
public string Type { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 图例名称。
|
||||
/// </summary>
|
||||
public string Label { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 图例颜色。
|
||||
/// </summary>
|
||||
public string Color { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 顶部统计。
|
||||
/// </summary>
|
||||
public sealed class MarketingCalendarStatsDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 本月活动数。
|
||||
/// </summary>
|
||||
public int TotalActivityCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 进行中活动数。
|
||||
/// </summary>
|
||||
public int OngoingCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大并行活动数。
|
||||
/// </summary>
|
||||
public int MaxConcurrentCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 本月预计优惠金额。
|
||||
/// </summary>
|
||||
public decimal EstimatedDiscountAmount { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 活动。
|
||||
/// </summary>
|
||||
public sealed class MarketingCalendarActivityDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 活动唯一键(跨模块)。
|
||||
/// </summary>
|
||||
public string ActivityId { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 来源模块(full_reduction/flash_sale/seckill/coupon/punch_card)。
|
||||
/// </summary>
|
||||
public string SourceType { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 来源标识。
|
||||
/// </summary>
|
||||
public string SourceId { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 日历类型(reduce/gift/second_half/flash_sale/seckill/coupon/punch_card)。
|
||||
/// </summary>
|
||||
public string CalendarType { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 活动名称。
|
||||
/// </summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 活动颜色。
|
||||
/// </summary>
|
||||
public string Color { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 活动摘要。
|
||||
/// </summary>
|
||||
public string Summary { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 展示状态(ongoing/upcoming/ended/disabled)。
|
||||
/// </summary>
|
||||
public string DisplayStatus { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 是否弱化。
|
||||
/// </summary>
|
||||
public bool IsDimmed { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 活动开始日期(UTC)。
|
||||
/// </summary>
|
||||
public DateTime StartDate { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 活动结束日期(UTC)。
|
||||
/// </summary>
|
||||
public DateTime EndDate { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 预计优惠金额。
|
||||
/// </summary>
|
||||
public decimal EstimatedDiscountAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 活动条。
|
||||
/// </summary>
|
||||
public IReadOnlyList<MarketingCalendarActivityBarDto> Bars { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 二级抽屉详情。
|
||||
/// </summary>
|
||||
public MarketingCalendarActivityDetailDto Detail { get; init; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 活动条。
|
||||
/// </summary>
|
||||
public sealed class MarketingCalendarActivityBarDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 条标识。
|
||||
/// </summary>
|
||||
public string BarId { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 开始日(1-31)。
|
||||
/// </summary>
|
||||
public int StartDay { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 结束日(1-31)。
|
||||
/// </summary>
|
||||
public int EndDay { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 条文案。
|
||||
/// </summary>
|
||||
public string Label { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 是否里程碑。
|
||||
/// </summary>
|
||||
public bool IsMilestone { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否弱化。
|
||||
/// </summary>
|
||||
public bool IsDimmed { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 活动详情。
|
||||
/// </summary>
|
||||
public sealed class MarketingCalendarActivityDetailDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 模块名称。
|
||||
/// </summary>
|
||||
public string ModuleName { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 详情描述。
|
||||
/// </summary>
|
||||
public string Description { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 明细字段。
|
||||
/// </summary>
|
||||
public IReadOnlyList<MarketingCalendarDetailFieldDto> Fields { get; init; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 详情字段。
|
||||
/// </summary>
|
||||
public sealed class MarketingCalendarDetailFieldDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 标签。
|
||||
/// </summary>
|
||||
public string Label { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 值。
|
||||
/// </summary>
|
||||
public string Value { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 冲突横幅。
|
||||
/// </summary>
|
||||
public sealed class MarketingCalendarConflictBannerDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 冲突标识。
|
||||
/// </summary>
|
||||
public string ConflictId { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 开始日。
|
||||
/// </summary>
|
||||
public int StartDay { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 结束日。
|
||||
/// </summary>
|
||||
public int EndDay { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 同时进行活动数。
|
||||
/// </summary>
|
||||
public int ActivityCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大并行活动数。
|
||||
/// </summary>
|
||||
public int MaxConcurrentCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 冲突区间数。
|
||||
/// </summary>
|
||||
public int ConflictCount { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 冲突区间。
|
||||
/// </summary>
|
||||
public sealed class MarketingCalendarConflictDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 冲突标识。
|
||||
/// </summary>
|
||||
public string ConflictId { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 开始日。
|
||||
/// </summary>
|
||||
public int StartDay { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 结束日。
|
||||
/// </summary>
|
||||
public int EndDay { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 同时进行活动数。
|
||||
/// </summary>
|
||||
public int ActivityCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大并行活动数。
|
||||
/// </summary>
|
||||
public int MaxConcurrentCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 活动标识集合。
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> ActivityIds { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 冲突活动摘要。
|
||||
/// </summary>
|
||||
public IReadOnlyList<MarketingCalendarConflictActivityDto> Activities { get; init; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 冲突活动摘要。
|
||||
/// </summary>
|
||||
public sealed class MarketingCalendarConflictActivityDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 活动唯一键。
|
||||
/// </summary>
|
||||
public string ActivityId { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 日历类型。
|
||||
/// </summary>
|
||||
public string CalendarType { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 活动名称。
|
||||
/// </summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 活动摘要。
|
||||
/// </summary>
|
||||
public string Summary { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 活动颜色。
|
||||
/// </summary>
|
||||
public string Color { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 展示状态。
|
||||
/// </summary>
|
||||
public string DisplayStatus { get; init; } = string.Empty;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Coupons.Calendar.Dto;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Coupons.Calendar.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 查询营销日历总览。
|
||||
/// </summary>
|
||||
public sealed class GetMarketingCalendarOverviewQuery : IRequest<MarketingCalendarOverviewDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// 门店 ID。
|
||||
/// </summary>
|
||||
public long StoreId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 年份。
|
||||
/// </summary>
|
||||
public int Year { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 月份(1-12)。
|
||||
/// </summary>
|
||||
public int Month { get; init; }
|
||||
}
|
||||
@@ -38,6 +38,14 @@ public interface IPunchCardRepository
|
||||
IReadOnlyCollection<long> templateIds,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 查询单门店全部次卡模板。
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<PunchCardTemplate>> GetTemplatesByStoreAsync(
|
||||
long tenantId,
|
||||
long storeId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 按模板批量统计售卖与在用信息。
|
||||
/// </summary>
|
||||
|
||||
@@ -92,6 +92,20 @@ public sealed class EfPunchCardRepository(TakeoutAppDbContext context) : IPunchC
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<PunchCardTemplate>> GetTemplatesByStoreAsync(
|
||||
long tenantId,
|
||||
long storeId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await context.PunchCardTemplates
|
||||
.AsNoTracking()
|
||||
.Where(item => item.TenantId == tenantId && item.StoreId == storeId)
|
||||
.OrderByDescending(item => item.UpdatedAt ?? item.CreatedAt)
|
||||
.ThenByDescending(item => item.Id)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Dictionary<long, PunchCardTemplateAggregateSnapshot>> GetTemplateAggregateByTemplateIdsAsync(
|
||||
long tenantId,
|
||||
|
||||
Reference in New Issue
Block a user