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" - } - }; - } }