完成门店管理后端接口与任务
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TakeoutSaaS.Application.App.Stores.Services;
|
||||
using TakeoutSaaS.Domain.Stores.Entities;
|
||||
using TakeoutSaaS.Domain.Stores.Enums;
|
||||
using TakeoutSaaS.Infrastructure.App.Persistence;
|
||||
|
||||
namespace TakeoutSaaS.Infrastructure.App.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 门店定时任务服务实现。
|
||||
/// </summary>
|
||||
public sealed class StoreSchedulerService(
|
||||
TakeoutAppDbContext context,
|
||||
ILogger<StoreSchedulerService> logger)
|
||||
: IStoreSchedulerService
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<int> AutoSwitchBusinessStatusAsync(DateTime now, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 读取候选门店
|
||||
var stores = await context.Stores
|
||||
.Where(store => store.DeletedAt == null
|
||||
&& store.AuditStatus == StoreAuditStatus.Activated
|
||||
&& store.BusinessStatus != StoreBusinessStatus.ForceClosed)
|
||||
.ToListAsync(cancellationToken);
|
||||
if (stores.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 2. (空行后) 读取营业时段与休息日
|
||||
var storeIds = stores.Select(store => store.Id).ToArray();
|
||||
var hours = await context.StoreBusinessHours
|
||||
.AsNoTracking()
|
||||
.Where(hour => storeIds.Contains(hour.StoreId))
|
||||
.ToListAsync(cancellationToken);
|
||||
var today = now.Date;
|
||||
var holidays = await context.StoreHolidays
|
||||
.AsNoTracking()
|
||||
.Where(holiday => storeIds.Contains(holiday.StoreId)
|
||||
&& holiday.IsClosed
|
||||
&& holiday.Date.Date == today)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
// 3. (空行后) 构造查找表
|
||||
var hoursLookup = hours
|
||||
.GroupBy(hour => hour.StoreId)
|
||||
.ToDictionary(group => group.Key, group => (IReadOnlyList<StoreBusinessHour>)group.ToList());
|
||||
var holidaySet = holidays.Select(holiday => holiday.StoreId).ToHashSet();
|
||||
|
||||
// 4. (空行后) 判定状态并更新
|
||||
var updated = 0;
|
||||
foreach (var store in stores)
|
||||
{
|
||||
// 4.1 跳过强制关闭门店
|
||||
if (store.BusinessStatus == StoreBusinessStatus.ForceClosed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4.2 (空行后) 尊重手动歇业原因
|
||||
if (store.ClosureReason.HasValue && store.ClosureReason != StoreClosureReason.OutOfBusinessHours)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4.3 (空行后) 计算营业状态
|
||||
var isHolidayClosed = holidaySet.Contains(store.Id);
|
||||
var hasHours = hoursLookup.TryGetValue(store.Id, out var storeHours) && storeHours.Count > 0;
|
||||
var isOpen = !isHolidayClosed && hasHours && IsWithinBusinessHours(storeHours ?? [], now);
|
||||
if (isOpen)
|
||||
{
|
||||
if (store.BusinessStatus != StoreBusinessStatus.Open)
|
||||
{
|
||||
store.BusinessStatus = StoreBusinessStatus.Open;
|
||||
store.ClosureReason = null;
|
||||
store.ClosureReasonText = null;
|
||||
updated++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4.4 (空行后) 非营业时段切换为休息
|
||||
if (store.BusinessStatus != StoreBusinessStatus.Resting || store.ClosureReason != StoreClosureReason.OutOfBusinessHours)
|
||||
{
|
||||
store.BusinessStatus = StoreBusinessStatus.Resting;
|
||||
store.ClosureReason = StoreClosureReason.OutOfBusinessHours;
|
||||
store.ClosureReasonText = "非营业时间自动休息";
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. (空行后) 保存变更并记录日志
|
||||
if (updated > 0)
|
||||
{
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
logger.LogInformation("定时任务:营业状态自动切换完成,更新 {UpdatedCount} 家门店", updated);
|
||||
return updated;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> CheckQualificationExpiryAsync(DateTime now, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 查询过期门店
|
||||
var expiredStoreIds = await context.StoreQualifications
|
||||
.AsNoTracking()
|
||||
.Where(qualification => qualification.DeletedAt == null
|
||||
&& qualification.ExpiresAt.HasValue
|
||||
&& qualification.ExpiresAt.Value < now)
|
||||
.Select(qualification => qualification.StoreId)
|
||||
.Distinct()
|
||||
.ToListAsync(cancellationToken);
|
||||
if (expiredStoreIds.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 2. (空行后) 加载门店并更新状态
|
||||
var stores = await context.Stores
|
||||
.Where(store => expiredStoreIds.Contains(store.Id)
|
||||
&& store.DeletedAt == null
|
||||
&& store.AuditStatus == StoreAuditStatus.Activated
|
||||
&& store.BusinessStatus != StoreBusinessStatus.ForceClosed)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var updated = 0;
|
||||
foreach (var store in stores)
|
||||
{
|
||||
// 2.1 跳过已标记过期门店
|
||||
if (store.BusinessStatus == StoreBusinessStatus.Resting && store.ClosureReason == StoreClosureReason.LicenseExpired)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2.2 (空行后) 设置资质过期状态
|
||||
store.BusinessStatus = StoreBusinessStatus.Resting;
|
||||
store.ClosureReason = StoreClosureReason.LicenseExpired;
|
||||
store.ClosureReasonText = "证照过期自动休息";
|
||||
updated++;
|
||||
}
|
||||
|
||||
// 3. (空行后) 保存变更并记录日志
|
||||
if (updated > 0)
|
||||
{
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
logger.LogInformation("定时任务:资质过期检查完成,更新 {UpdatedCount} 家门店", updated);
|
||||
return updated;
|
||||
}
|
||||
|
||||
private static bool IsWithinBusinessHours(IReadOnlyList<StoreBusinessHour> hours, DateTime now)
|
||||
{
|
||||
// 1. 提取当前时间
|
||||
var day = now.DayOfWeek;
|
||||
var time = now.TimeOfDay;
|
||||
|
||||
foreach (var hour in hours)
|
||||
{
|
||||
if (hour.HourType == BusinessHourType.Closed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (hour.StartTime == hour.EndTime)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (hour.StartTime < hour.EndTime)
|
||||
{
|
||||
if (hour.DayOfWeek == day && time >= hour.StartTime && time < hour.EndTime)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
var nextDay = NextDay(hour.DayOfWeek);
|
||||
if (hour.DayOfWeek == day && time >= hour.StartTime)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (nextDay == day && time < hour.EndTime)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static DayOfWeek NextDay(DayOfWeek day)
|
||||
{
|
||||
var next = (int)day + 1;
|
||||
return next > 6 ? DayOfWeek.Sunday : (DayOfWeek)next;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user