diff --git a/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StoreStaffContracts.cs b/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StoreStaffContracts.cs
index c2a5dde..4eb85a6 100644
--- a/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StoreStaffContracts.cs
+++ b/src/Api/TakeoutSaaS.TenantApi/Contracts/Store/StoreStaffContracts.cs
@@ -154,7 +154,15 @@ public sealed class StoreStaffScheduleDto
///
/// Templates。
///
- public StoreShiftTemplatesDto Templates { get; set; } = new();
+ public StoreShiftTemplatesDto? Templates { get; set; }
+ ///
+ /// IsTemplateConfigured。
+ ///
+ public bool IsTemplateConfigured { get; set; }
+ ///
+ /// IsScheduleConfigured。
+ ///
+ public bool IsScheduleConfigured { get; set; }
///
/// Schedules。
///
diff --git a/src/Api/TakeoutSaaS.TenantApi/Controllers/StoreStaffController.cs b/src/Api/TakeoutSaaS.TenantApi/Controllers/StoreStaffController.cs
index 2320a35..d4df9c8 100644
--- a/src/Api/TakeoutSaaS.TenantApi/Controllers/StoreStaffController.cs
+++ b/src/Api/TakeoutSaaS.TenantApi/Controllers/StoreStaffController.cs
@@ -135,9 +135,6 @@ public sealed class StoreStaffController(
await dbContext.SaveChangesAsync(cancellationToken);
- var template = await GetOrCreateTemplateDtoAsync(tenantId, parsedStoreId, cancellationToken);
- await EnsureStaffScheduleAsync(tenantId, parsedStoreId, entity, template, cancellationToken);
-
return ApiResponse.Ok(MapStaff(entity));
}
@@ -186,7 +183,7 @@ public sealed class StoreStaffController(
var (tenantId, merchantId) = StoreApiHelpers.GetTenantMerchantContext(storeContextService);
await StoreApiHelpers.EnsureStoreAccessibleAsync(dbContext, tenantId, merchantId, parsedStoreId, cancellationToken);
- var template = await GetOrCreateTemplateDtoAsync(tenantId, parsedStoreId, cancellationToken);
+ var template = await GetTemplateDtoAsync(tenantId, parsedStoreId, cancellationToken);
var staffs = await dbContext.MerchantStaff
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.StoreId == parsedStoreId)
@@ -205,8 +202,10 @@ public sealed class StoreStaffController(
var schedules = staffs.Select(staff =>
{
var shifts = scheduleMap.TryGetValue(staff.Id, out var rows)
- ? NormalizeRowsToShifts(rows, staff, template)
- : CreateDefaultWeekByRole(staff.RoleType, template);
+ ? template is null
+ ? []
+ : NormalizeRowsToShifts(rows, template)
+ : [];
if (staff.Status == StaffStatus.Resigned)
{
@@ -225,6 +224,8 @@ public sealed class StoreStaffController(
StoreId = parsedStoreId.ToString(),
WeekStartDate = StoreApiHelpers.ResolveWeekStartDate(weekStartDate),
Templates = template,
+ IsTemplateConfigured = template is not null,
+ IsScheduleConfigured = scheduleRows.Count > 0,
Schedules = schedules
});
}
@@ -298,19 +299,23 @@ public sealed class StoreStaffController(
throw new BusinessException(ErrorCodes.BadRequest, "员工不存在");
}
- var template = await GetOrCreateTemplateDtoAsync(tenantId, parsedStoreId, cancellationToken);
+ var template = await GetTemplateDtoAsync(tenantId, parsedStoreId, cancellationToken);
+ if (template is null)
+ {
+ throw new BusinessException(ErrorCodes.BadRequest, "请先配置班次模板");
+ }
var existingRows = await dbContext.StoreStaffWeeklySchedules
.Where(x => x.TenantId == tenantId && x.StoreId == parsedStoreId && x.StaffId == parsedStaffId)
.ToListAsync(cancellationToken);
- var fallback = NormalizeRowsToShifts(existingRows, staff, template);
+ var fallback = NormalizeRowsToShifts(existingRows, template);
var shifts = staff.Status == StaffStatus.Resigned
? CreateOffWeekShifts()
: NormalizeShifts(request.Shifts, fallback, template);
dbContext.StoreStaffWeeklySchedules.RemoveRange(existingRows);
await dbContext.StoreStaffWeeklySchedules.AddRangeAsync(
- ToWeeklyEntities(parsedStoreId, parsedStaffId, shifts),
+ ToWeeklyEntities(parsedStoreId, parsedStaffId, shifts, template),
cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
@@ -343,7 +348,11 @@ public sealed class StoreStaffController(
.ToListAsync(cancellationToken);
var staffMap = staffs.ToDictionary(x => x.Id);
- var template = await GetOrCreateTemplateDtoAsync(tenantId, parsedStoreId, cancellationToken);
+ var template = await GetTemplateDtoAsync(tenantId, parsedStoreId, cancellationToken);
+ if (template is null)
+ {
+ throw new BusinessException(ErrorCodes.BadRequest, "请先配置班次模板");
+ }
var existingRows = await dbContext.StoreStaffWeeklySchedules
.Where(x => x.TenantId == tenantId && x.StoreId == parsedStoreId)
.ToListAsync(cancellationToken);
@@ -351,7 +360,7 @@ public sealed class StoreStaffController(
.GroupBy(x => x.StaffId)
.ToDictionary(
x => x.Key,
- x => NormalizeRowsToShifts(x.ToList(), staffMap.GetValueOrDefault(x.Key), template));
+ x => NormalizeRowsToShifts(x.ToList(), template));
var incomingMap = new Dictionary>();
foreach (var schedule in request.Schedules ?? [])
@@ -367,7 +376,7 @@ public sealed class StoreStaffController(
continue;
}
- var fallback = existingMap.GetValueOrDefault(staffId.Value) ?? CreateDefaultWeekByRole(staff.RoleType, template);
+ var fallback = existingMap.GetValueOrDefault(staffId.Value) ?? [];
incomingMap[staffId.Value] = NormalizeShifts(schedule.Shifts, fallback, template);
}
@@ -385,7 +394,7 @@ public sealed class StoreStaffController(
}
else
{
- shifts = existingMap.GetValueOrDefault(staff.Id) ?? CreateDefaultWeekByRole(staff.RoleType, template);
+ shifts = existingMap.GetValueOrDefault(staff.Id) ?? [];
}
finalSchedules.Add(new StaffScheduleDto
@@ -397,7 +406,7 @@ public sealed class StoreStaffController(
dbContext.StoreStaffWeeklySchedules.RemoveRange(existingRows);
var entities = finalSchedules
- .SelectMany(x => ToWeeklyEntities(parsedStoreId, long.Parse(x.StaffId), x.Shifts))
+ .SelectMany(x => ToWeeklyEntities(parsedStoreId, long.Parse(x.StaffId), x.Shifts, template))
.ToList();
await dbContext.StoreStaffWeeklySchedules.AddRangeAsync(entities, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
@@ -407,6 +416,8 @@ public sealed class StoreStaffController(
StoreId = parsedStoreId.ToString(),
WeekStartDate = StoreApiHelpers.ResolveWeekStartDate(null),
Templates = template,
+ IsTemplateConfigured = true,
+ IsScheduleConfigured = entities.Count > 0,
Schedules = finalSchedules
});
}
@@ -451,7 +462,11 @@ public sealed class StoreStaffController(
});
}
- var sourceTemplate = await GetOrCreateTemplateDtoAsync(tenantId, sourceStoreId, cancellationToken);
+ var sourceTemplate = await GetTemplateDtoAsync(tenantId, sourceStoreId, cancellationToken);
+ if (sourceTemplate is null)
+ {
+ throw new BusinessException(ErrorCodes.BadRequest, "源门店未配置班次模板,无法复制");
+ }
var sourceRows = await dbContext.StoreStaffWeeklySchedules
.AsNoTracking()
.Where(x => x.TenantId == tenantId && x.StoreId == sourceStoreId)
@@ -465,14 +480,14 @@ public sealed class StoreStaffController(
.GroupBy(x => x.StaffId)
.ToDictionary(
x => x.Key,
- x => NormalizeRowsToShifts(x.ToList(), sourceStaffMap.GetValueOrDefault(x.Key), sourceTemplate));
+ x => NormalizeRowsToShifts(x.ToList(), sourceTemplate));
var sourceScheduleSequence = sourceStaffs
.OrderBy(x => x.CreatedAt)
.ThenBy(x => x.Name)
.ThenBy(x => x.Id)
.Select(staff => staff.Status == StaffStatus.Resigned
? CreateOffWeekShifts()
- : sourceScheduleMap.GetValueOrDefault(staff.Id) ?? CreateDefaultWeekByRole(staff.RoleType, sourceTemplate))
+ : sourceScheduleMap.GetValueOrDefault(staff.Id) ?? [])
.ToList();
var targetTemplates = await dbContext.StoreStaffTemplates
@@ -526,9 +541,9 @@ public sealed class StoreStaffController(
? CreateOffWeekShifts()
: sourceScheduleSequence.Count > 0
? sourceScheduleSequence[index % sourceScheduleSequence.Count]
- : CreateDefaultWeekByRole(staff.RoleType, sourceTemplate);
+ : [];
- entities.AddRange(ToWeeklyEntities(targetStoreId, staff.Id, shifts));
+ entities.AddRange(ToWeeklyEntities(targetStoreId, staff.Id, shifts, sourceTemplate));
}
}
@@ -602,7 +617,7 @@ public sealed class StoreStaffController(
return normalized;
}
- private async Task GetOrCreateTemplateDtoAsync(long tenantId, long storeId, CancellationToken cancellationToken)
+ private async Task GetTemplateDtoAsync(long tenantId, long storeId, CancellationToken cancellationToken)
{
var entity = await dbContext.StoreStaffTemplates
.AsNoTracking()
@@ -610,7 +625,7 @@ public sealed class StoreStaffController(
if (entity is null)
{
- return CreateDefaultTemplate();
+ return null;
}
return new StoreShiftTemplatesDto
@@ -633,83 +648,44 @@ public sealed class StoreStaffController(
};
}
- private async Task EnsureStaffScheduleAsync(
- long tenantId,
- long storeId,
- MerchantStaff staff,
- StoreShiftTemplatesDto template,
- CancellationToken cancellationToken)
- {
- var existingRows = await dbContext.StoreStaffWeeklySchedules
- .Where(x => x.TenantId == tenantId && x.StoreId == storeId && x.StaffId == staff.Id)
- .ToListAsync(cancellationToken);
-
- if (existingRows.Count == 0)
- {
- var initialShifts = staff.Status == StaffStatus.Resigned
- ? CreateOffWeekShifts()
- : CreateDefaultWeekByRole(staff.RoleType, template);
- await dbContext.StoreStaffWeeklySchedules.AddRangeAsync(
- ToWeeklyEntities(storeId, staff.Id, initialShifts),
- cancellationToken);
- }
- else if (staff.Status == StaffStatus.Resigned)
- {
- dbContext.StoreStaffWeeklySchedules.RemoveRange(existingRows);
- await dbContext.StoreStaffWeeklySchedules.AddRangeAsync(
- ToWeeklyEntities(storeId, staff.Id, CreateOffWeekShifts()),
- cancellationToken);
- }
-
- await dbContext.SaveChangesAsync(cancellationToken);
- }
-
private static StoreShiftTemplatesDto NormalizeTemplate(StoreShiftTemplatesDto source)
{
- var fallback = CreateDefaultTemplate();
+ source ??= new StoreShiftTemplatesDto();
return new StoreShiftTemplatesDto
{
Morning = new ShiftTemplateItemDto
{
- StartTime = NormalizeTime(source.Morning.StartTime, fallback.Morning.StartTime),
- EndTime = NormalizeTime(source.Morning.EndTime, fallback.Morning.EndTime)
+ StartTime = NormalizeTime(source.Morning.StartTime, string.Empty),
+ EndTime = NormalizeTime(source.Morning.EndTime, string.Empty)
},
Evening = new ShiftTemplateItemDto
{
- StartTime = NormalizeTime(source.Evening.StartTime, fallback.Evening.StartTime),
- EndTime = NormalizeTime(source.Evening.EndTime, fallback.Evening.EndTime)
+ StartTime = NormalizeTime(source.Evening.StartTime, string.Empty),
+ EndTime = NormalizeTime(source.Evening.EndTime, string.Empty)
},
Full = new ShiftTemplateItemDto
{
- StartTime = NormalizeTime(source.Full.StartTime, fallback.Full.StartTime),
- EndTime = NormalizeTime(source.Full.EndTime, fallback.Full.EndTime)
+ StartTime = NormalizeTime(source.Full.StartTime, string.Empty),
+ EndTime = NormalizeTime(source.Full.EndTime, string.Empty)
}
};
}
private static List NormalizeRowsToShifts(
List rows,
- MerchantStaff? staff,
StoreShiftTemplatesDto template)
{
- var fallback = staff is null ? CreateOffWeekShifts() : CreateDefaultWeekByRole(staff.RoleType, template);
- var rowMap = rows.ToDictionary(x => x.DayOfWeek, x => x);
var results = new List();
-
- for (var day = 0; day < 7; day++)
+ foreach (var row in rows
+ .Where(x => x.DayOfWeek is >= 0 and <= 6)
+ .OrderBy(x => x.DayOfWeek))
{
- if (!rowMap.TryGetValue(day, out var row))
- {
- results.Add(fallback[day]);
- continue;
- }
-
var shiftType = StoreApiHelpers.ToShiftTypeText(row.ShiftType);
if (row.ShiftType == StoreStaffShiftType.Off)
{
results.Add(new StaffDayShiftDto
{
- DayOfWeek = day,
+ DayOfWeek = row.DayOfWeek,
ShiftType = shiftType,
StartTime = string.Empty,
EndTime = string.Empty
@@ -720,7 +696,7 @@ public sealed class StoreStaffController(
var (defaultStart, defaultEnd) = ResolveShiftTimeRange(row.ShiftType, template);
results.Add(new StaffDayShiftDto
{
- DayOfWeek = day,
+ DayOfWeek = row.DayOfWeek,
ShiftType = shiftType,
StartTime = StoreApiHelpers.ToHHmm(row.StartTime ?? defaultStart ?? TimeSpan.Zero),
EndTime = StoreApiHelpers.ToHHmm(row.EndTime ?? defaultEnd ?? TimeSpan.Zero)
@@ -743,16 +719,11 @@ public sealed class StoreStaffController(
var normalized = new List();
for (var day = 0; day < 7; day++)
{
- var fallbackShift = fallback.FirstOrDefault(x => x.DayOfWeek == day) ?? new StaffDayShiftDto
- {
- DayOfWeek = day,
- ShiftType = "off",
- StartTime = string.Empty,
- EndTime = string.Empty
- };
+ var fallbackShift = fallback.FirstOrDefault(x => x.DayOfWeek == day);
if (!inputMap.TryGetValue(day, out var input))
{
+ if (fallbackShift is null) continue;
normalized.Add(new StaffDayShiftDto
{
DayOfWeek = day,
@@ -781,8 +752,16 @@ public sealed class StoreStaffController(
{
DayOfWeek = day,
ShiftType = StoreApiHelpers.ToShiftTypeText(shiftType),
- StartTime = NormalizeTime(input.StartTime, defaultStart.HasValue ? StoreApiHelpers.ToHHmm(defaultStart.Value) : fallbackShift.StartTime),
- EndTime = NormalizeTime(input.EndTime, defaultEnd.HasValue ? StoreApiHelpers.ToHHmm(defaultEnd.Value) : fallbackShift.EndTime)
+ StartTime = NormalizeTime(
+ input.StartTime,
+ defaultStart.HasValue
+ ? StoreApiHelpers.ToHHmm(defaultStart.Value)
+ : fallbackShift?.StartTime ?? string.Empty),
+ EndTime = NormalizeTime(
+ input.EndTime,
+ defaultEnd.HasValue
+ ? StoreApiHelpers.ToHHmm(defaultEnd.Value)
+ : fallbackShift?.EndTime ?? string.Empty)
});
}
@@ -792,21 +771,22 @@ public sealed class StoreStaffController(
private static IEnumerable ToWeeklyEntities(
long storeId,
long staffId,
- IEnumerable shifts)
+ IEnumerable shifts,
+ StoreShiftTemplatesDto template)
{
foreach (var shift in shifts)
{
var shiftType = StoreApiHelpers.ToShiftType(shift.ShiftType);
- var (defaultStart, defaultEnd) = ResolveShiftTimeRange(shiftType, CreateDefaultTemplate());
+ var (defaultStart, defaultEnd) = ResolveShiftTimeRange(shiftType, template);
var startTime = shiftType == StoreStaffShiftType.Off
? null
: (TimeSpan?)StoreApiHelpers.ParseRequiredTime(
- NormalizeTime(shift.StartTime, defaultStart.HasValue ? StoreApiHelpers.ToHHmm(defaultStart.Value) : "09:00"),
+ NormalizeTime(shift.StartTime, defaultStart.HasValue ? StoreApiHelpers.ToHHmm(defaultStart.Value) : string.Empty),
"shift.startTime");
var endTime = shiftType == StoreStaffShiftType.Off
? null
: (TimeSpan?)StoreApiHelpers.ParseRequiredTime(
- NormalizeTime(shift.EndTime, defaultEnd.HasValue ? StoreApiHelpers.ToHHmm(defaultEnd.Value) : "21:00"),
+ NormalizeTime(shift.EndTime, defaultEnd.HasValue ? StoreApiHelpers.ToHHmm(defaultEnd.Value) : string.Empty),
"shift.endTime");
yield return new StoreStaffWeeklySchedule
@@ -821,34 +801,6 @@ public sealed class StoreStaffController(
}
}
- private static List CreateDefaultWeekByRole(StaffRoleType roleType, StoreShiftTemplatesDto template)
- {
- var pattern = roleType switch
- {
- StaffRoleType.Admin or StaffRoleType.Operator => new[] { "full", "full", "full", "full", "full", "morning", "off" },
- StaffRoleType.FrontDesk => new[] { "morning", "morning", "off", "morning", "evening", "full", "full" },
- StaffRoleType.Courier => new[] { "morning", "evening", "morning", "evening", "morning", "evening", "off" },
- StaffRoleType.Kitchen => new[] { "full", "full", "evening", "off", "full", "full", "morning" },
- _ => new[] { "morning", "morning", "off", "morning", "evening", "full", "full" }
- };
-
- var results = new List();
- for (var day = 0; day < 7; day++)
- {
- var shiftType = StoreApiHelpers.ToShiftType(pattern[day]);
- var (start, end) = ResolveShiftTimeRange(shiftType, template);
- results.Add(new StaffDayShiftDto
- {
- DayOfWeek = day,
- ShiftType = StoreApiHelpers.ToShiftTypeText(shiftType),
- StartTime = start.HasValue ? StoreApiHelpers.ToHHmm(start.Value) : string.Empty,
- EndTime = end.HasValue ? StoreApiHelpers.ToHHmm(end.Value) : string.Empty
- });
- }
-
- return results;
- }
-
private static List CreateOffWeekShifts()
{
return Enumerable.Range(0, 7).Select(day => new StaffDayShiftDto
@@ -883,26 +835,4 @@ public sealed class StoreStaffController(
? StoreApiHelpers.ToHHmm(parsed)
: fallback;
}
-
- private static StoreShiftTemplatesDto CreateDefaultTemplate()
- {
- return new StoreShiftTemplatesDto
- {
- Morning = new ShiftTemplateItemDto
- {
- StartTime = "09:00",
- EndTime = "14:00"
- },
- Evening = new ShiftTemplateItemDto
- {
- StartTime = "14:00",
- EndTime = "21:00"
- },
- Full = new ShiftTemplateItemDto
- {
- StartTime = "09:00",
- EndTime = "21:00"
- }
- };
- }
}