ExceptionDaysService
Documentație pentru serviciul ExceptionDaysService
1. Descriere Generală
ExceptionDaysService
este un serviciu pentru gestionarea programului de lucru și a zilelor excepționale pentru instituțiile guvernamentale. Permite configurarea orarului standard săptămânal și definirea excepțiilor precum sărbători legale, zile scurte sau zile lucrătoare speciale.
Caracteristici principale: - Gestionare program de lucru standard (Luni-Duminică) - Definire zile excepționale (sărbători, zile scurte, zile lucrătoare speciale) - CRUD complet pentru excepții - Suport pentru paginare server-side - Validare ore de lucru - Integrare cu componenta FodExceptionDays
2. Configurare și Înregistrare
// Program.cs sau Startup.cs
// Înregistrare automată prin AddFodComponents
builder.Services.AddFodComponents(configuration);
// Sau înregistrare manuală
builder.Services.AddScoped<IExceptionDaysService, ExceptionDaysService>();
builder.Services.AddHttpClient<IExceptionDaysService, ExceptionDaysService>(client =>
{
client.BaseAddress = new Uri(configuration["ApiSettings:BaseUrl"]);
});
3. Interfața IExceptionDaysService
public interface IExceptionDaysService
{
Task<DataResponse<WorkProgramModel>> GetFormatted(DataRequest request);
Task<DataResponse<ExceptionWorkingProgramModel>> GetExceptions(DataRequest request);
Task AddOrUpdateException(ExceptionWorkingProgramDetailsModel model);
Task<ExceptionWorkingProgramDetailsModel> GetExceptionById(Guid id);
Task Delete(Guid id);
}
4. Metode Disponibile
Metodă | Parametri | Return | Descriere |
---|---|---|---|
GetFormatted |
DataRequest request |
Task<DataResponse<WorkProgramModel>> |
Obține programul de lucru formatat |
GetExceptions |
DataRequest request |
Task<DataResponse<ExceptionWorkingProgramModel>> |
Obține lista de excepții cu paginare |
AddOrUpdateException |
ExceptionWorkingProgramDetailsModel model |
Task |
Adaugă sau actualizează o excepție |
GetExceptionById |
Guid id |
Task<ExceptionWorkingProgramDetailsModel> |
Obține detaliile unei excepții |
Delete |
Guid id |
Task |
Șterge o excepție |
5. Modele de Date
WorkProgramModel
public class WorkProgramModel
{
public DayEnum Day { get; set; }
public string Open { get; set; } // Format: "HH:mm"
public string Close { get; set; } // Format: "HH:mm"
public string Pause { get; set; } // Format: "HH:mm - HH:mm"
}
public enum DayEnum
{
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6,
Sunday = 7
}
ExceptionWorkingProgramModel
public class ExceptionWorkingProgramModel
{
public Guid Id { get; set; }
public ExceptionTypeEnum Type { get; set; }
public string Date { get; set; } // Format: "dd.MM.yyyy"
public string Open { get; set; } // Format: "HH:mm"
public string Close { get; set; } // Format: "HH:mm"
}
public enum ExceptionTypeEnum
{
WorkingDay = 0, // Zi lucrătoare (în weekend/sărbătoare)
Holiday = 1, // Sărbătoare legală
ShortDay = 2 // Zi scurtă
}
ExceptionWorkingProgramDetailsModel
public class ExceptionWorkingProgramDetailsModel
{
public Guid? Id { get; set; }
public ExceptionTypeEnum Type { get; set; }
public DateTime Date { get; set; }
public int? OpenHours { get; set; }
public int? OpenMinutes { get; set; }
public int? CloseHours { get; set; }
public int? CloseMinutes { get; set; }
}
6. Exemple de Utilizare
Afișare program de lucru complet
@page "/admin/program-lucru"
@inject IExceptionDaysService ExceptionDaysService
@inject IFodNotificationService NotificationService
<FodContainer>
<FodText Typo="Typo.h4" GutterBottom="true">
Program de Lucru
</FodText>
<!-- Program standard -->
<FodCard Class="mb-4">
<FodCardContent>
<FodText Typo="Typo.h6" GutterBottom="true">
Orar Standard Săptămânal
</FodText>
@if (workingProgram != null)
{
<FodDataTable Items="@workingProgram" Dense="true">
<HeaderContent>
<FodTHeadRow>
<FodTh>Zi</FodTh>
<FodTh>Deschidere</FodTh>
<FodTh>Închidere</FodTh>
<FodTh>Pauză</FodTh>
</FodTHeadRow>
</HeaderContent>
<RowTemplate>
<FodTr>
<FodTd>@GetDayName(context.Day)</FodTd>
<FodTd>@context.Open</FodTd>
<FodTd>@context.Close</FodTd>
<FodTd>@context.Pause</FodTd>
</FodTr>
</RowTemplate>
</FodDataTable>
}
else
{
<FodLoadingLinear Indeterminate="true" />
}
</FodCardContent>
</FodCard>
<!-- Zile excepționale -->
<FodCard>
<FodCardContent>
<div class="d-flex justify-content-between align-items-center mb-3">
<FodText Typo="Typo.h6">
Zile Excepționale
</FodText>
<FodButton Color="FodColor.Primary"
OnClick="ShowAddExceptionModal"
StartIcon="@FodIcons.Material.Filled.Add">
Adaugă Excepție
</FodButton>
</div>
<FodExceptionDays @ref="exceptionDaysComponent" />
</FodCardContent>
</FodCard>
</FodContainer>
<!-- Modal pentru adăugare/editare excepție -->
<FodModal @bind-IsOpen="showExceptionModal">
<FodModalContent>
<FodModalHeader>
<FodText Typo="Typo.h5">
@(editingException?.Id == null ? "Adaugă" : "Editează") Excepție
</FodText>
</FodModalHeader>
<FodModalBody>
<FodGrid Container="true" Spacing="3">
<FodGrid Item="true" xs="12">
<FodSelect @bind-Value="editingException.Type"
Label="Tip excepție"
Items="@exceptionTypes"
Required="true" />
</FodGrid>
<FodGrid Item="true" xs="12">
<FodDatePicker @bind-Date="editingException.Date"
Label="Data"
MinDate="DateTime.Today"
Required="true" />
</FodGrid>
@if (editingException.Type != ExceptionTypeEnum.Holiday)
{
<FodGrid Item="true" xs="6">
<FodTimePicker @bind-Time="openTime"
Label="Ora deschidere"
Required="true" />
</FodGrid>
<FodGrid Item="true" xs="6">
<FodTimePicker @bind-Time="closeTime"
Label="Ora închidere"
Required="true" />
</FodGrid>
}
</FodGrid>
</FodModalBody>
<FodModalActions>
<FodButton Color="FodColor.Default" OnClick="CloseModal">
Anulează
</FodButton>
<FodButton Color="FodColor.Primary" OnClick="SaveException">
Salvează
</FodButton>
</FodModalActions>
</FodModalContent>
</FodModal>
@code {
private List<WorkProgramModel> workingProgram;
private FodExceptionDays exceptionDaysComponent;
private bool showExceptionModal;
private ExceptionWorkingProgramDetailsModel editingException = new();
private TimeSpan? openTime;
private TimeSpan? closeTime;
private List<SelectableItem> exceptionTypes = new()
{
new(ExceptionTypeEnum.Holiday, "Sărbătoare legală"),
new(ExceptionTypeEnum.ShortDay, "Zi scurtă"),
new(ExceptionTypeEnum.WorkingDay, "Zi lucrătoare (excepțional)")
};
protected override async Task OnInitializedAsync()
{
await LoadWorkingProgram();
}
private async Task LoadWorkingProgram()
{
var request = new DataRequest { Page = 1, PageSize = 7 };
var response = await ExceptionDaysService.GetFormatted(request);
workingProgram = response.Data.ToList();
}
private string GetDayName(DayEnum day)
{
return day switch
{
DayEnum.Monday => "Luni",
DayEnum.Tuesday => "Marți",
DayEnum.Wednesday => "Miercuri",
DayEnum.Thursday => "Joi",
DayEnum.Friday => "Vineri",
DayEnum.Saturday => "Sâmbătă",
DayEnum.Sunday => "Duminică",
_ => day.ToString()
};
}
private void ShowAddExceptionModal()
{
editingException = new ExceptionWorkingProgramDetailsModel
{
Date = DateTime.Today.AddDays(1)
};
openTime = new TimeSpan(8, 0, 0);
closeTime = new TimeSpan(17, 0, 0);
showExceptionModal = true;
}
private async Task SaveException()
{
if (editingException.Type != ExceptionTypeEnum.Holiday)
{
editingException.OpenHours = openTime?.Hours;
editingException.OpenMinutes = openTime?.Minutes;
editingException.CloseHours = closeTime?.Hours;
editingException.CloseMinutes = closeTime?.Minutes;
}
try
{
await ExceptionDaysService.AddOrUpdateException(editingException);
NotificationService.Success("Excepție salvată cu succes!");
// Reîncarcă lista
await exceptionDaysComponent.RefreshData();
CloseModal();
}
catch (Exception ex)
{
NotificationService.Error($"Eroare la salvare: {ex.Message}");
}
}
private void CloseModal()
{
showExceptionModal = false;
editingException = new();
}
}
Gestionare sărbători legale
@inject IExceptionDaysService ExceptionDaysService
<FodCard>
<FodCardContent>
<FodText Typo="Typo.h6" GutterBottom="true">
Sărbători Legale @currentYear
</FodText>
<FodButton OnClick="ImportHolidays"
Color="FodColor.Primary"
StartIcon="@FodIcons.Material.Filled.CloudDownload">
Importă Sărbători Oficiale
</FodButton>
@if (holidays.Any())
{
<FodList Class="mt-3">
@foreach (var holiday in holidays.OrderBy(h => h.Date))
{
<FodListItem>
<FodListItemIcon>
<FodIcon>@FodIcons.Material.Filled.EventBusy</FodIcon>
</FodListItemIcon>
<FodListItemText>
<FodText>@holiday.Date.ToString("dd MMMM yyyy")</FodText>
</FodListItemText>
<FodListItemSecondaryAction>
<FodIconButton Icon="@FodIcons.Material.Filled.Delete"
Size="FodSize.Small"
OnClick="() => DeleteHoliday(holiday.Id)" />
</FodListItemSecondaryAction>
</FodListItem>
}
</FodList>
}
</FodCardContent>
</FodCard>
@code {
private int currentYear = DateTime.Now.Year;
private List<ExceptionWorkingProgramDetailsModel> holidays = new();
protected override async Task OnInitializedAsync()
{
await LoadHolidays();
}
private async Task LoadHolidays()
{
var request = new DataRequest
{
Page = 1,
PageSize = 100,
Filters = new List<Filter>
{
new Filter
{
Field = "Type",
Operator = FilterOperator.Equal,
Value = ExceptionTypeEnum.Holiday
},
new Filter
{
Field = "Year",
Operator = FilterOperator.Equal,
Value = currentYear
}
}
};
var response = await ExceptionDaysService.GetExceptions(request);
holidays = response.Data
.Select(async h => await ExceptionDaysService.GetExceptionById(h.Id))
.Select(t => t.Result)
.ToList();
}
private async Task ImportHolidays()
{
// Lista sărbători legale standard pentru Moldova
var standardHolidays = new[]
{
new { Date = new DateTime(currentYear, 1, 1), Name = "Anul Nou" },
new { Date = new DateTime(currentYear, 1, 7), Name = "Nașterea lui Iisus Hristos" },
new { Date = new DateTime(currentYear, 1, 8), Name = "Nașterea lui Iisus Hristos" },
new { Date = new DateTime(currentYear, 3, 8), Name = "Ziua internațională a femeii" },
new { Date = new DateTime(currentYear, 5, 1), Name = "Ziua internațională a solidarității oamenilor muncii" },
new { Date = new DateTime(currentYear, 5, 9), Name = "Ziua Victoriei și a comemorării eroilor" },
new { Date = new DateTime(currentYear, 6, 1), Name = "Ziua Copilului" },
new { Date = new DateTime(currentYear, 8, 27), Name = "Ziua Independenței" },
new { Date = new DateTime(currentYear, 8, 31), Name = "Limba noastră" },
new { Date = new DateTime(currentYear, 12, 25), Name = "Nașterea lui Iisus Hristos" }
};
foreach (var holiday in standardHolidays)
{
// Verifică dacă nu există deja
if (!holidays.Any(h => h.Date.Date == holiday.Date.Date))
{
await ExceptionDaysService.AddOrUpdateException(new ExceptionWorkingProgramDetailsModel
{
Type = ExceptionTypeEnum.Holiday,
Date = holiday.Date
});
}
}
await LoadHolidays();
}
private async Task DeleteHoliday(Guid id)
{
await ExceptionDaysService.Delete(id);
await LoadHolidays();
}
}
Calendar vizual cu excepții
@inject IExceptionDaysService ExceptionDaysService
<FodCard>
<FodCardContent>
<FodCalendar @ref="calendar"
OnDayRender="OnCalendarDayRender"
OnDateSelect="OnDateSelected" />
@if (selectedDateExceptions.Any())
{
<FodPaper Elevation="1" Class="mt-3 p-3">
<FodText Typo="Typo.h6" GutterBottom="true">
Excepții pentru @selectedDate?.ToString("dd MMMM yyyy")
</FodText>
@foreach (var exception in selectedDateExceptions)
{
<FodChip Color="@GetExceptionColor(exception.Type)"
Class="me-2"
OnClose="() => DeleteException(exception.Id)">
@GetExceptionText(exception)
</FodChip>
}
</FodPaper>
}
</FodCardContent>
</FodCard>
@code {
private FodCalendar calendar;
private Dictionary<DateTime, List<ExceptionWorkingProgramModel>> exceptionsByDate = new();
private DateTime? selectedDate;
private List<ExceptionWorkingProgramModel> selectedDateExceptions = new();
protected override async Task OnInitializedAsync()
{
await LoadExceptionsForMonth(DateTime.Now);
}
private async Task LoadExceptionsForMonth(DateTime month)
{
var startDate = new DateTime(month.Year, month.Month, 1);
var endDate = startDate.AddMonths(1).AddDays(-1);
var request = new DataRequest
{
Page = 1,
PageSize = 100,
Filters = new List<Filter>
{
new Filter
{
Field = "Date",
Operator = FilterOperator.GreaterThanOrEqual,
Value = startDate
},
new Filter
{
Field = "Date",
Operator = FilterOperator.LessThanOrEqual,
Value = endDate
}
}
};
var response = await ExceptionDaysService.GetExceptions(request);
exceptionsByDate.Clear();
foreach (var exception in response.Data)
{
var date = DateTime.ParseExact(exception.Date, "dd.MM.yyyy", null);
if (!exceptionsByDate.ContainsKey(date))
{
exceptionsByDate[date] = new List<ExceptionWorkingProgramModel>();
}
exceptionsByDate[date].Add(exception);
}
}
private void OnCalendarDayRender(CalendarDayRenderEventArgs args)
{
if (exceptionsByDate.ContainsKey(args.Date.Date))
{
var exceptions = exceptionsByDate[args.Date.Date];
foreach (var exception in exceptions)
{
args.AddBadge(GetExceptionBadgeColor(exception.Type));
}
args.AddTooltip(string.Join(", ",
exceptions.Select(e => GetExceptionTypeName(e.Type))));
}
}
private void OnDateSelected(DateTime date)
{
selectedDate = date;
selectedDateExceptions = exceptionsByDate.ContainsKey(date.Date)
? exceptionsByDate[date.Date]
: new List<ExceptionWorkingProgramModel>();
}
private FodColor GetExceptionColor(ExceptionTypeEnum type)
{
return type switch
{
ExceptionTypeEnum.Holiday => FodColor.Error,
ExceptionTypeEnum.ShortDay => FodColor.Warning,
ExceptionTypeEnum.WorkingDay => FodColor.Success,
_ => FodColor.Default
};
}
private string GetExceptionText(ExceptionWorkingProgramModel exception)
{
return exception.Type switch
{
ExceptionTypeEnum.Holiday => "Sărbătoare",
ExceptionTypeEnum.ShortDay => $"Zi scurtă: {exception.Open} - {exception.Close}",
ExceptionTypeEnum.WorkingDay => $"Zi lucrătoare: {exception.Open} - {exception.Close}",
_ => "Excepție"
};
}
}
Verificare disponibilitate serviciu
public class ServiceAvailabilityChecker
{
private readonly IExceptionDaysService _exceptionDaysService;
public async Task<bool> IsServiceAvailable(DateTime dateTime)
{
// Verifică mai întâi dacă este o zi excepțională
var exceptions = await _exceptionDaysService.GetExceptions(new DataRequest
{
Filters = new List<Filter>
{
new Filter
{
Field = "Date",
Operator = FilterOperator.Equal,
Value = dateTime.Date
}
}
});
var exception = exceptions.Data.FirstOrDefault();
if (exception != null)
{
switch (exception.Type)
{
case ExceptionTypeEnum.Holiday:
return false; // Închis în sărbători
case ExceptionTypeEnum.WorkingDay:
case ExceptionTypeEnum.ShortDay:
// Verifică dacă ora curentă este în interval
var openTime = TimeSpan.Parse(exception.Open);
var closeTime = TimeSpan.Parse(exception.Close);
var currentTime = dateTime.TimeOfDay;
return currentTime >= openTime && currentTime <= closeTime;
}
}
// Dacă nu e excepție, verifică programul normal
var workProgram = await _exceptionDaysService.GetFormatted(new DataRequest());
var dayProgram = workProgram.Data.FirstOrDefault(w => w.Day == GetDayEnum(dateTime.DayOfWeek));
if (dayProgram == null || string.IsNullOrEmpty(dayProgram.Open))
return false; // Închis în această zi
var standardOpen = TimeSpan.Parse(dayProgram.Open);
var standardClose = TimeSpan.Parse(dayProgram.Close);
var currentTime = dateTime.TimeOfDay;
// Verifică pauza dacă există
if (!string.IsNullOrEmpty(dayProgram.Pause))
{
var pauseParts = dayProgram.Pause.Split(" - ");
if (pauseParts.Length == 2)
{
var pauseStart = TimeSpan.Parse(pauseParts[0]);
var pauseEnd = TimeSpan.Parse(pauseParts[1]);
if (currentTime >= pauseStart && currentTime <= pauseEnd)
return false; // În pauză
}
}
return currentTime >= standardOpen && currentTime <= standardClose;
}
private DayEnum GetDayEnum(DayOfWeek dayOfWeek)
{
return dayOfWeek switch
{
DayOfWeek.Monday => DayEnum.Monday,
DayOfWeek.Tuesday => DayEnum.Tuesday,
DayOfWeek.Wednesday => DayEnum.Wednesday,
DayOfWeek.Thursday => DayEnum.Thursday,
DayOfWeek.Friday => DayEnum.Friday,
DayOfWeek.Saturday => DayEnum.Saturday,
DayOfWeek.Sunday => DayEnum.Sunday,
_ => DayEnum.Monday
};
}
}
7. Integrare cu componenta FodExceptionDays
@page "/admin/exception-days"
<FodContainer>
<FodText Typo="Typo.h4" GutterBottom="true">
Gestionare Zile Excepționale
</FodText>
<!-- Componenta gata configurată -->
<FodExceptionDays
ShowAddButton="true"
ShowEditButton="true"
ShowDeleteButton="true"
PageSize="20"
OnExceptionAdded="OnExceptionAdded"
OnExceptionUpdated="OnExceptionUpdated"
OnExceptionDeleted="OnExceptionDeleted" />
</FodContainer>
@code {
private async Task OnExceptionAdded(ExceptionWorkingProgramDetailsModel model)
{
// Log sau notificare
Console.WriteLine($"Excepție adăugată: {model.Date:dd.MM.yyyy}");
}
private async Task OnExceptionUpdated(ExceptionWorkingProgramDetailsModel model)
{
// Log sau notificare
Console.WriteLine($"Excepție actualizată: {model.Id}");
}
private async Task OnExceptionDeleted(Guid id)
{
// Log sau notificare
Console.WriteLine($"Excepție ștearsă: {id}");
}
}
8. Export și Import
public class ExceptionDaysImportExportService
{
private readonly IExceptionDaysService _exceptionDaysService;
public async Task<byte[]> ExportToExcel(int year)
{
var request = new DataRequest
{
PageSize = 1000,
Filters = new List<Filter>
{
new Filter
{
Field = "Year",
Operator = FilterOperator.Equal,
Value = year
}
}
};
var exceptions = await _exceptionDaysService.GetExceptions(request);
using var package = new ExcelPackage();
var worksheet = package.Workbook.Worksheets.Add("Zile Excepționale");
// Headers
worksheet.Cells[1, 1].Value = "Data";
worksheet.Cells[1, 2].Value = "Tip";
worksheet.Cells[1, 3].Value = "Ora Deschidere";
worksheet.Cells[1, 4].Value = "Ora Închidere";
// Data
var row = 2;
foreach (var exception in exceptions.Data.OrderBy(e => e.Date))
{
worksheet.Cells[row, 1].Value = exception.Date;
worksheet.Cells[row, 2].Value = GetExceptionTypeName(exception.Type);
worksheet.Cells[row, 3].Value = exception.Open;
worksheet.Cells[row, 4].Value = exception.Close;
row++;
}
// Formatting
worksheet.Cells[1, 1, 1, 4].Style.Font.Bold = true;
worksheet.Cells.AutoFitColumns();
return package.GetAsByteArray();
}
public async Task ImportFromExcel(Stream fileStream)
{
using var package = new ExcelPackage(fileStream);
var worksheet = package.Workbook.Worksheets.First();
for (int row = 2; row <= worksheet.Dimension.End.Row; row++)
{
var dateStr = worksheet.Cells[row, 1].Text;
var typeStr = worksheet.Cells[row, 2].Text;
var openStr = worksheet.Cells[row, 3].Text;
var closeStr = worksheet.Cells[row, 4].Text;
if (DateTime.TryParseExact(dateStr, "dd.MM.yyyy", null,
DateTimeStyles.None, out var date))
{
var model = new ExceptionWorkingProgramDetailsModel
{
Date = date,
Type = ParseExceptionType(typeStr)
};
if (!string.IsNullOrEmpty(openStr) && TimeSpan.TryParse(openStr, out var openTime))
{
model.OpenHours = openTime.Hours;
model.OpenMinutes = openTime.Minutes;
}
if (!string.IsNullOrEmpty(closeStr) && TimeSpan.TryParse(closeStr, out var closeTime))
{
model.CloseHours = closeTime.Hours;
model.CloseMinutes = closeTime.Minutes;
}
await _exceptionDaysService.AddOrUpdateException(model);
}
}
}
}
9. Validare și Business Rules
public class ExceptionDaysValidator
{
private readonly IExceptionDaysService _exceptionDaysService;
public async Task<ValidationResult> ValidateException(ExceptionWorkingProgramDetailsModel model)
{
var errors = new List<string>();
// Validare dată
if (model.Date < DateTime.Today)
{
errors.Add("Nu puteți adăuga excepții pentru date din trecut");
}
// Validare ore pentru zile lucrătoare
if (model.Type != ExceptionTypeEnum.Holiday)
{
if (!model.OpenHours.HasValue || !model.OpenMinutes.HasValue)
{
errors.Add("Ora de deschidere este obligatorie");
}
if (!model.CloseHours.HasValue || !model.CloseMinutes.HasValue)
{
errors.Add("Ora de închidere este obligatorie");
}
if (model.OpenHours.HasValue && model.CloseHours.HasValue)
{
var openTime = new TimeSpan(model.OpenHours.Value, model.OpenMinutes ?? 0, 0);
var closeTime = new TimeSpan(model.CloseHours.Value, model.CloseMinutes ?? 0, 0);
if (openTime >= closeTime)
{
errors.Add("Ora de închidere trebuie să fie după ora de deschidere");
}
// Validare pentru zile scurte
if (model.Type == ExceptionTypeEnum.ShortDay)
{
var totalHours = (closeTime - openTime).TotalHours;
if (totalHours >= 8)
{
errors.Add("O zi scurtă trebuie să aibă mai puțin de 8 ore");
}
}
}
}
// Verifică duplicat
var existingExceptions = await _exceptionDaysService.GetExceptions(new DataRequest
{
Filters = new List<Filter>
{
new Filter
{
Field = "Date",
Operator = FilterOperator.Equal,
Value = model.Date
}
}
});
if (existingExceptions.Data.Any(e => e.Id != model.Id))
{
errors.Add("Există deja o excepție pentru această dată");
}
return new ValidationResult
{
IsValid = !errors.Any(),
Errors = errors
};
}
}
10. Monitorizare și Audit
public class AuditedExceptionDaysService : IExceptionDaysService
{
private readonly IExceptionDaysService _innerService;
private readonly IAuditService _auditService;
private readonly ICurrentUserContextService _userContext;
public async Task AddOrUpdateException(ExceptionWorkingProgramDetailsModel model)
{
var isNew = !model.Id.HasValue;
var oldValue = isNew ? null : await _innerService.GetExceptionById(model.Id.Value);
await _innerService.AddOrUpdateException(model);
// Audit
await _auditService.LogAsync(new AuditEntry
{
Action = isNew ? "ExceptionDayAdded" : "ExceptionDayUpdated",
EntityType = "ExceptionWorkingProgram",
EntityId = model.Id?.ToString(),
UserId = _userContext.UserId,
Timestamp = DateTime.UtcNow,
OldValue = JsonSerializer.Serialize(oldValue),
NewValue = JsonSerializer.Serialize(model),
Details = $"{model.Type} - {model.Date:dd.MM.yyyy}"
});
}
public async Task Delete(Guid id)
{
var exception = await _innerService.GetExceptionById(id);
await _innerService.Delete(id);
await _auditService.LogAsync(new AuditEntry
{
Action = "ExceptionDayDeleted",
EntityType = "ExceptionWorkingProgram",
EntityId = id.ToString(),
UserId = _userContext.UserId,
Timestamp = DateTime.UtcNow,
OldValue = JsonSerializer.Serialize(exception),
Details = $"Șters: {exception.Type} - {exception.Date:dd.MM.yyyy}"
});
}
// Implementare pentru celelalte metode...
}
11. Testare
[TestClass]
public class ExceptionDaysServiceTests
{
private Mock<HttpMessageHandler> _httpHandler;
private IExceptionDaysService _service;
[TestMethod]
public async Task GetExceptions_ReturnsPagedResults()
{
// Arrange
var request = new DataRequest { Page = 1, PageSize = 10 };
var expectedResponse = new DataResponse<ExceptionWorkingProgramModel>
{
Data = new[]
{
new ExceptionWorkingProgramModel
{
Id = Guid.NewGuid(),
Type = ExceptionTypeEnum.Holiday,
Date = "01.01.2024",
Open = "",
Close = ""
}
},
TotalCount = 1
};
SetupHttpResponse(expectedResponse);
// Act
var result = await _service.GetExceptions(request);
// Assert
Assert.AreEqual(1, result.TotalCount);
Assert.AreEqual(ExceptionTypeEnum.Holiday, result.Data.First().Type);
}
[TestMethod]
public async Task AddOrUpdateException_Holiday_SuccessfullyCreated()
{
// Arrange
var model = new ExceptionWorkingProgramDetailsModel
{
Type = ExceptionTypeEnum.Holiday,
Date = new DateTime(2024, 12, 25)
};
SetupHttpResponse(HttpStatusCode.OK);
// Act & Assert
await _service.AddOrUpdateException(model);
// Should not throw
}
[TestMethod]
public async Task GetExceptionById_ValidId_ReturnsException()
{
// Arrange
var id = Guid.NewGuid();
var expected = new ExceptionWorkingProgramDetailsModel
{
Id = id,
Type = ExceptionTypeEnum.ShortDay,
Date = DateTime.Today,
OpenHours = 9,
OpenMinutes = 0,
CloseHours = 14,
CloseMinutes = 0
};
SetupHttpResponse(expected);
// Act
var result = await _service.GetExceptionById(id);
// Assert
Assert.AreEqual(id, result.Id);
Assert.AreEqual(9, result.OpenHours);
Assert.AreEqual(14, result.CloseHours);
}
}
12. Best Practices
- Validare strictă - Validați toate datele înainte de salvare
- Audit complet - Înregistrați toate modificările pentru trasabilitate
- Cache inteligent - Cache-uiți programul standard, nu excepțiile
- Notificări - Notificați utilizatorii despre schimbări importante
- Import/Export - Oferiți opțiuni de import/export pentru gestionare în masă
- Verificare conflicte - Nu permiteți excepții duplicate pentru aceeași dată
13. Concluzie
ExceptionDaysService
oferă o soluție completă pentru gestionarea programului de lucru și a zilelor excepționale în instituțiile guvernamentale. Cu suport pentru toate tipurile de excepții, validare robustă și integrare ușoară cu componente UI, serviciul asigură flexibilitate maximă în configurarea orarului de funcționare.