diff --git a/backend/Api/Api.csproj b/backend/Api/Api.csproj index c426627c..f4186b36 100644 --- a/backend/Api/Api.csproj +++ b/backend/Api/Api.csproj @@ -10,28 +10,35 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + + + + + + + - + - + + + + + <_ContentIncludedByDefault Remove="Routes\obj\Api.csproj.nuget.dgspec.json"/> + <_ContentIncludedByDefault Remove="Routes\obj\project.assets.json"/> + <_ContentIncludedByDefault Remove="Routes\obj\project.packagespec.json"/> diff --git a/backend/Api/Routes/ConsultantApi.cs b/backend/Api/Consultants/ConsultantController.cs similarity index 55% rename from backend/Api/Routes/ConsultantApi.cs rename to backend/Api/Consultants/ConsultantController.cs index ae384918..712fbd31 100644 --- a/backend/Api/Routes/ConsultantApi.cs +++ b/backend/Api/Consultants/ConsultantController.cs @@ -1,5 +1,4 @@ using Api.Cache; -using Api.Validators; using Core.DomainModels; using Core.Services; using Database.DatabaseContext; @@ -8,71 +7,92 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; -namespace Api.Routes; +namespace Api.Consultants; -public static class ConsultantApi +[Route("v0/variants")] +[ApiController] +public class ConsultantController : ControllerBase { - public static void MapConsultantApi(this RouteGroupBuilder group) + private readonly IMemoryCache _cache; + private readonly ConsultantService _consultantService; + private readonly ApplicationContext _context; + + public ConsultantController(ApplicationContext context, IMemoryCache cache, ConsultantService consultantService) { - group.MapGet("/", GetAllConsultants); - group.MapGet("/{id}", GetConsultantById); - group.MapPost("/", AddBasicConsultant); + _context = context; + _cache = cache; + _consultantService = consultantService; } - private static Ok> GetAllConsultants(ApplicationContext context, IMemoryCache cache, + [HttpGet] + public ActionResult> Get( [FromQuery(Name = "weeks")] int numberOfWeeks = 8, [FromQuery(Name = "includeOccupied")] bool includeOccupied = false) { - var consultants = GetConsultantsWithAvailability(context, cache, numberOfWeeks) + var consultants = GetConsultantsWithAvailability(numberOfWeeks) .Where(c => includeOccupied - || c.HasAvailability - ).ToList(); + || c.IsOccupied + ) + .ToList(); - return TypedResults.Ok(consultants); + return Ok(consultants); } - private static Ok GetConsultantById(ApplicationContext context, IMemoryCache cache, int id, + [HttpGet("{id}")] + public Ok GetConsultantById(int id, [FromQuery(Name = "weeks")] int numberOfWeeks = 8) { - var consultants = GetConsultantsWithAvailability(context, cache, numberOfWeeks); + var consultants = GetConsultantsWithAvailability(numberOfWeeks); return TypedResults.Ok(consultants.Single(c => c.Id == id)); } - private static ConsultantReadModel MapToReadModel(this Consultant consultant, int weeks) + [HttpPost] + public async Task, ProblemHttpResult, ValidationProblem>> AddBasicConsultant( + [FromBody] ConsultantWriteModel basicVariant) { - const double tolerance = 0.1; - var bookedHours = consultant.GetBookedHoursForWeeks(weeks); - - var hasAvailability = bookedHours.Any(b => b.BookedHours <= consultant.GetHoursPrWeek() - tolerance); - - return new ConsultantReadModel( - consultant.Id, - consultant.Name, - consultant.Email, - consultant.Competences.Select(comp => comp.Name).ToList(), - consultant.Department.Name, - bookedHours, - hasAvailability); + try + { + var selectedDepartment = await GetDepartmentByIdAsync(basicVariant.DepartmentId); + if (selectedDepartment == null) return TypedResults.Problem("Department does not exist", statusCode: 400); + + var consultantList = await GetAllConsultantsAsync(_context); + var validationResults = ConsultantValidators.ValidateUniqueness(consultantList, basicVariant); + + if (validationResults.Count > 0) return TypedResults.ValidationProblem(validationResults); + + var newConsultant = CreateConsultantFromModel(basicVariant, selectedDepartment); + await AddConsultantToDatabaseAsync(_context, newConsultant); + ClearConsultantCache(); + + return TypedResults.Created($"/variant/{newConsultant.Id}", basicVariant); + } + catch + { + // Adding exception handling later + return TypedResults.Problem("An error occurred while processing the request", statusCode: 500); + } } - private static List GetConsultantsWithAvailability(ApplicationContext context, - IMemoryCache cache, int numberOfWeeks) + private List GetConsultantsWithAvailability(int numberOfWeeks) { if (numberOfWeeks == 8) { - cache.TryGetValue(CacheKeys.ConsultantAvailability8Weeks, out List? cachedConsultants); + _cache.TryGetValue(CacheKeys.ConsultantAvailability8Weeks, + out List? cachedConsultants); if (cachedConsultants != null) return cachedConsultants; } - var consultants = LoadConsultantAvailability(context, numberOfWeeks); + var consultants = LoadConsultantAvailability(numberOfWeeks) + .Select(c => _consultantService.MapConsultantToReadModel(c, numberOfWeeks)).ToList(); - cache.Set(CacheKeys.ConsultantAvailability8Weeks, consultants); + + _cache.Set(CacheKeys.ConsultantAvailability8Weeks, consultants); return consultants; } - private static List LoadConsultantAvailability(ApplicationContext context, int numberOfWeeks) + private List LoadConsultantAvailability(int numberOfWeeks) { var applicableWeeks = DateService.GetNextWeeks(numberOfWeeks); @@ -95,59 +115,23 @@ private static List LoadConsultantAvailability(ApplicationC var maxWeekB = weeksInB.Max(); - return context.Consultant + return _context.Consultant .Include(c => c.Vacations) .Include(c => c.Competences) .Include(c => c.PlannedAbsences.Where(pa => (pa.Year <= yearA && minWeekA <= pa.WeekNumber && pa.WeekNumber <= maxWeekA) || (yearB <= pa.Year && minWeekB <= pa.WeekNumber && pa.WeekNumber <= maxWeekB))) .Include(c => c.Department) - .ThenInclude(d => d.Organization) .Include(c => c.Staffings.Where(s => (s.Year <= yearA && minWeekA <= s.Week && s.Week <= maxWeekA) || (yearB <= s.Year && minWeekB <= s.Week && s.Week <= maxWeekB))) - .Select(c => c.MapToReadModel(numberOfWeeks)) .ToList(); } - private static async Task, ProblemHttpResult, ValidationProblem>> AddBasicConsultant( - ApplicationContext db, - IMemoryCache cache, - [FromBody] ConsultantWriteModel basicVariant) - { - try - { - var selectedDepartment = await GetDepartmentByIdAsync(db, basicVariant.DepartmentId); - if (selectedDepartment == null) - { - return TypedResults.Problem("Department does not exist", statusCode: 400); - } - - var consultantList = await GetAllConsultantsAsync(db); - var validationResults = ConsultantValidators.ValidateUniqueness(consultantList, basicVariant); - - if (validationResults.Count > 0) - { - return TypedResults.ValidationProblem(validationResults); - } - - var newConsultant = CreateConsultantFromModel(basicVariant, selectedDepartment); - await AddConsultantToDatabaseAsync(db, newConsultant); - ClearConsultantCache(cache); - - return TypedResults.Created($"/variant/{newConsultant.Id}", basicVariant); - } - catch (Exception ex) - { - // Adding exception handling later - return TypedResults.Problem("An error occurred while processing the request", statusCode: 500); - } - } - - private static async Task GetDepartmentByIdAsync(ApplicationContext db, string departmentId) + private async Task GetDepartmentByIdAsync(string departmentId) { - return await db.Department.SingleOrDefaultAsync(d => d.Id == departmentId); + return await _context.Department.SingleOrDefaultAsync(d => d.Id == departmentId); } private static async Task> GetAllConsultantsAsync(ApplicationContext db) @@ -172,15 +156,11 @@ private static async Task AddConsultantToDatabaseAsync(ApplicationContext db, Co await db.SaveChangesAsync(); } - private static void ClearConsultantCache(IMemoryCache cache) + private void ClearConsultantCache() { - cache.Remove(CacheKeys.ConsultantAvailability8Weeks); + _cache.Remove(CacheKeys.ConsultantAvailability8Weeks); } public record ConsultantWriteModel(string Name, string Email, string DepartmentId); - - private record ConsultantReadModel(int Id, string Name, string Email, List Competences, string Department, - List Bookings, bool HasAvailability); - } \ No newline at end of file diff --git a/backend/Api/Consultants/ConsultantReadModel.cs b/backend/Api/Consultants/ConsultantReadModel.cs new file mode 100644 index 00000000..126a1c96 --- /dev/null +++ b/backend/Api/Consultants/ConsultantReadModel.cs @@ -0,0 +1,6 @@ +using Core.DomainModels; + +namespace Api.Consultants; + +public record ConsultantReadModel(int Id, string Name, string Email, List Competences, string Department, + List Bookings, bool IsOccupied); \ No newline at end of file diff --git a/backend/Api/Consultants/ConsultantService.cs b/backend/Api/Consultants/ConsultantService.cs new file mode 100644 index 00000000..0b62667f --- /dev/null +++ b/backend/Api/Consultants/ConsultantService.cs @@ -0,0 +1,80 @@ +using Api.Options; +using Core.DomainModels; +using Core.Services; +using Microsoft.Extensions.Options; + +namespace Api.Consultants; + +public class ConsultantService +{ + private readonly HolidayService _holidayService; + private readonly OrganizationOptions _organizationOptions; + + public ConsultantService(IOptions orgOptions, HolidayService holidayService) + { + _organizationOptions = orgOptions.Value; + _holidayService = holidayService; + } + + public ConsultantReadModel MapConsultantToReadModel(Consultant consultant, int weeks) + { + const double tolerance = 0.1; + var bookedHours = GetBookedHoursForWeeks(consultant, weeks); + + var isOccupied = bookedHours.All(b => b.BookedHours >= GetHoursPrWeek() - tolerance); + + return new ConsultantReadModel( + consultant.Id, + consultant.Name, + consultant.Email, + consultant.Competences.Select(comp => comp.Name).ToList(), + consultant.Department.Name, + bookedHours, + isOccupied + ); + } + + public double GetBookedHours(Consultant consultant, int year, int week) + { + var hoursPrWorkDay = _organizationOptions.HoursPerWorkday; + + var holidayHours = _holidayService.GetTotalHolidaysOfWeek(year, week) * hoursPrWorkDay; + var vacationHours = consultant.Vacations.Count(v => DateService.DateIsInWeek(v.Date, year, week)) * + hoursPrWorkDay; + + var plannedAbsenceHours = consultant.PlannedAbsences + .Where(pa => pa.Year == year && pa.WeekNumber == week) + .Select(pa => pa.Hours) + .Sum(); + + var staffedHours = consultant.Staffings + .Where(s => s.Year == year && s.Week == week) + .Select(s => s.Hours) + .Sum(); + + var bookedHours = holidayHours + vacationHours + plannedAbsenceHours + staffedHours; + return Math.Min(bookedHours, 5 * hoursPrWorkDay); + } + + public List GetBookedHoursForWeeks(Consultant consultant, int weeksAhead) + { + return Enumerable.Range(0, weeksAhead) + .Select(offset => + { + var year = DateTime.Today.AddDays(7 * offset).Year; + var week = DateService.GetWeekAhead(offset); + + return new BookedHoursPerWeek( + year, + week, + GetBookedHours(consultant, year, week) + ); + }) + .ToList(); + } + + public double GetHoursPrWeek() + { + return _organizationOptions.HoursPerWorkday * 5; + } +} \ No newline at end of file diff --git a/backend/Api/Validators/ConsultantValidators.cs b/backend/Api/Consultants/ConsultantValidators.cs similarity index 80% rename from backend/Api/Validators/ConsultantValidators.cs rename to backend/Api/Consultants/ConsultantValidators.cs index be1fd430..ff9d946b 100644 --- a/backend/Api/Validators/ConsultantValidators.cs +++ b/backend/Api/Consultants/ConsultantValidators.cs @@ -1,8 +1,7 @@ using System.Net.Mail; -using Api.Routes; using Core.DomainModels; -namespace Api.Validators; +namespace Api.Consultants; public static class ConsultantValidators { @@ -11,8 +10,8 @@ public static bool IsValidEmail(string email) return MailAddress.TryCreate(email, out _); } - public static IDictionary ValidateUniqueness(List consultantList, - ConsultantApi.ConsultantWriteModel body) + public static IDictionary ValidateUniqueness(List consultantList, + ConsultantController.ConsultantWriteModel body) { var results = new Dictionary(); diff --git a/backend/Api/Consultants/HolidayService.cs b/backend/Api/Consultants/HolidayService.cs new file mode 100644 index 00000000..dd81385d --- /dev/null +++ b/backend/Api/Consultants/HolidayService.cs @@ -0,0 +1,51 @@ +using Api.Options; +using Core.Services; +using Microsoft.Extensions.Options; +using PublicHoliday; + +namespace Api.Consultants; + +public class HolidayService +{ + private readonly OrganizationOptions _organizationOptions; + + public HolidayService(IOptions options) + { + _organizationOptions = options.Value; + } + + public int GetTotalHolidaysOfWeek(int year, int week) + { + var datesOfThisWeek = DateService.GetDatesInWorkWeek(year, week); + return datesOfThisWeek.Count(IsHoliday); + } + + private bool IsHoliday(DateOnly day) + { + var holidayCountry = GetHolidayCountry(); + var isPublicHoliday = holidayCountry.IsPublicHoliday(day.ToDateTime(TimeOnly.MinValue)); + return isPublicHoliday || IsVariantChristmasHoliday(day); + } + + private PublicHolidayBase GetHolidayCountry() + { + var country = _organizationOptions.Country; + + return country switch + { + "norway" => new NorwayPublicHoliday(), + "sweden" => new SwedenPublicHoliday(), + _ => new NorwayPublicHoliday() + }; + } + + private bool IsVariantChristmasHoliday(DateOnly date) + { + if (!_organizationOptions.HasVacationInChristmas) return false; + + var startDate = new DateOnly(date.Year, 12, 24); + var endDate = new DateOnly(date.Year, 12, 31); + + return date >= startDate && date <= endDate; + } +} \ No newline at end of file diff --git a/backend/Api/Options/OrganizationOptions.cs b/backend/Api/Options/OrganizationOptions.cs new file mode 100644 index 00000000..f30d889b --- /dev/null +++ b/backend/Api/Options/OrganizationOptions.cs @@ -0,0 +1,9 @@ +namespace Api.Options; + +public class OrganizationOptions +{ + public double HoursPerWorkday { get; set; } + public bool HasVacationInChristmas { get; set; } + public int NumberOfVacationDaysInYear { get; set; } + public string Country { get; set; } +} \ No newline at end of file diff --git a/backend/Api/Program.cs b/backend/Api/Program.cs index 45ccc5c4..78ad8d80 100644 --- a/backend/Api/Program.cs +++ b/backend/Api/Program.cs @@ -1,6 +1,6 @@ using Api.BuildHelpers; +using Api.Consultants; using Api.Options; -using Api.Routes; using Database.DatabaseContext; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; @@ -21,8 +21,14 @@ builder.Services.AddMemoryCache(); + +builder.Services.Configure(builder.Configuration.GetSection("OrganizationSettings")); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); + var adOptions = builder.Configuration.GetSection("AzureAd").Get(); if (adOptions == null) throw new Exception("Required AzureAd options are missing"); @@ -34,7 +40,8 @@ var app = builder.Build(); -app.MapGroup("/v0").ApiGroupVersionBeta(); +app.UsePathBase("/v0"); +app.MapControllers(); app.UseSwagger(); app.UseSwaggerUI(c => diff --git a/backend/Api/Routes/RouteGroupBuilderExtensions.cs b/backend/Api/Routes/RouteGroupBuilderExtensions.cs deleted file mode 100644 index 2bcb0e0a..00000000 --- a/backend/Api/Routes/RouteGroupBuilderExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -namespace Api.Routes; - -public static class RouteGroupBuilderExtensions -{ - private static RouteGroupBuilder MapApiGroup(this IEndpointRouteBuilder endpoints, - [StringSyntax("Route")] string routePrefix, string? groupTag = null) - { - var group = endpoints - .MapGroup(routePrefix) - .WithOpenApi(); - - if (groupTag is not null) - group.WithTags(groupTag); - - return group; - } - - public static RouteGroupBuilder ApiGroupVersionBeta(this RouteGroupBuilder group) - { - group.MapApiGroup("variants", "Varianter").MapConsultantApi(); - return group; - } -} \ No newline at end of file diff --git a/backend/Api/appsettings.Local.json.template b/backend/Api/appsettings.Local.json.template index 33a8634e..e9adf5fb 100644 --- a/backend/Api/appsettings.Local.json.template +++ b/backend/Api/appsettings.Local.json.template @@ -5,6 +5,12 @@ "TenantId": "azure_tenant_id_for_organization", "ApiScope": "Azure Ad -> App Registrations -> [app] -> Expose an Api" }, + "OrganizationSettings" :{ + "HoursPerWorkday": 7.5 or 8, + "NumberOfVacationDaysInYear": 25 or 30, + "HasVacationInChristmas": true or false, + "Country": "norway or sweden" + }, "ConnectionStrings": { "VibesDb": "" } diff --git a/backend/Api/appsettings.json b/backend/Api/appsettings.json index fdd8de79..9cd22673 100644 --- a/backend/Api/appsettings.json +++ b/backend/Api/appsettings.json @@ -2,6 +2,12 @@ "AzureAd": { "Instance": "https://login.microsoftonline.com/" }, + "OrganizationSettings": { + "HoursPerWorkday": 7.5, + "NumberOfVacationDaysInYear": 25, + "HasVacationInChristmas": true, + "Country": "norway" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/backend/Core/Core.csproj b/backend/Core/Core.csproj index f2291091..7ea7c4f6 100644 --- a/backend/Core/Core.csproj +++ b/backend/Core/Core.csproj @@ -9,19 +9,19 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + + + + + + diff --git a/backend/Core/DomainModels/Absence.cs b/backend/Core/DomainModels/Absence.cs index ef3610b9..42f36a2f 100644 --- a/backend/Core/DomainModels/Absence.cs +++ b/backend/Core/DomainModels/Absence.cs @@ -9,5 +9,4 @@ public class Absence public required string Name { get; set; } public required bool ExcludeFromBillRate { get; set; } = false; - public required Organization Organization { get; set; } } \ No newline at end of file diff --git a/backend/Core/DomainModels/Consultant.cs b/backend/Core/DomainModels/Consultant.cs index 1db7dd08..2e332154 100644 --- a/backend/Core/DomainModels/Consultant.cs +++ b/backend/Core/DomainModels/Consultant.cs @@ -1,5 +1,4 @@ using System.ComponentModel.DataAnnotations.Schema; -using Core.Services; namespace Core.DomainModels; @@ -28,48 +27,6 @@ public class Consultant public List Projects { get; set; } = new(); public List Staffings { get; set; } = new(); - - public double GetBookedHours(int year, int week) - { - var hoursPrWorkDay = Department.Organization.HoursPerWorkday; - var holidayHours = Holiday.GetTotalHolidaysOfWeek(year, week) * hoursPrWorkDay; - var vacationHours = Vacations.Count(v => DateService.DateIsInWeek(v.Date, year, week)) * hoursPrWorkDay; - - var plannedAbsenceHours = PlannedAbsences - .Where(pa => pa.Year == year && pa.WeekNumber == week) - .Select(pa => pa.Hours) - .Sum(); - - var staffedHours = Staffings - .Where(s => s.Year == year && s.Week == week) - .Select(s => s.Hours) - .Sum(); - - var bookedHours = holidayHours + vacationHours + plannedAbsenceHours + staffedHours; - return Math.Min(bookedHours, 5 * hoursPrWorkDay); - } - - public List GetBookedHoursForWeeks(int weeksAhead) - { - return Enumerable.Range(0, weeksAhead) - .Select(offset => - { - var year = DateTime.Today.AddDays(7 * offset).Year; - var week = DateService.GetWeekAhead(offset); - - return new BookedHoursPerWeek( - year, - week, - GetBookedHours(year, week) - ); - }) - .ToList(); - } - - public double GetHoursPrWeek() - { - return Department.Organization.HoursPerWorkday * 5; - } } public class Competence diff --git a/backend/Core/DomainModels/Customer.cs b/backend/Core/DomainModels/Customer.cs index 5057b840..b0666ff2 100644 --- a/backend/Core/DomainModels/Customer.cs +++ b/backend/Core/DomainModels/Customer.cs @@ -8,8 +8,5 @@ public class Customer public int Id { get; set; } public required string Name { get; set; } - - public required Organization Organization { get; set; } - public required List Projects { get; set; } } \ No newline at end of file diff --git a/backend/Core/DomainModels/Department.cs b/backend/Core/DomainModels/Department.cs index 7020f261..97d7a5c8 100644 --- a/backend/Core/DomainModels/Department.cs +++ b/backend/Core/DomainModels/Department.cs @@ -7,8 +7,6 @@ public class Department { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public required string Id { get; set; } - - public required Organization Organization { get; set; } public required string Name { get; set; } [JsonIgnore] public required List Consultants { get; set; } } \ No newline at end of file diff --git a/backend/Core/DomainModels/Holiday.cs b/backend/Core/DomainModels/Holiday.cs deleted file mode 100644 index 23e3a69b..00000000 --- a/backend/Core/DomainModels/Holiday.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Core.Services; -using PublicHoliday; - -namespace Core.DomainModels; - -public static class Holiday -{ - public static int GetTotalHolidaysOfWeek(int year, int week) - { - var datesOfThisWeek = DateService.GetDatesInWorkWeek(year, week); - - return datesOfThisWeek.Count(IsHoliday); - } - - private static bool IsHoliday(DateOnly day) - { - var isPublicHoliday = new NorwayPublicHoliday().IsPublicHoliday(day.ToDateTime(TimeOnly.MinValue)); - - return isPublicHoliday || IsVariantChristmasHoliday(day); - } - - - private static bool IsVariantChristmasHoliday(DateOnly date) - { - var startDate = new DateOnly(date.Year, 12, 24); - var endDate = new DateOnly(date.Year, 12, 31); - - return date >= startDate && date <= endDate; - } -} \ No newline at end of file diff --git a/backend/Core/DomainModels/Organization.cs b/backend/Core/DomainModels/Organization.cs deleted file mode 100644 index ea8e11fd..00000000 --- a/backend/Core/DomainModels/Organization.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.ComponentModel.DataAnnotations.Schema; -using System.Text.Json.Serialization; - -namespace Core.DomainModels; - -public class Organization -{ - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public required string Id { get; set; } - - public required string Name { get; set; } - public required double HoursPerWorkday { get; set; } - - [JsonIgnore] public List Departments { get; set; } - - public required List Customers { get; set; } - - public List AbsenceTypes { get; set; } -} \ No newline at end of file diff --git a/backend/Database/DatabaseContext/ApplicationContext.cs b/backend/Database/DatabaseContext/ApplicationContext.cs index 7b56ede4..98159c0a 100644 --- a/backend/Database/DatabaseContext/ApplicationContext.cs +++ b/backend/Database/DatabaseContext/ApplicationContext.cs @@ -13,7 +13,6 @@ public ApplicationContext(DbContextOptions options) : base(options) public DbSet Consultant { get; set; } = null!; public DbSet Competence { get; set; } = null!; public DbSet Department { get; set; } = null!; - public DbSet Organization { get; set; } = null!; public DbSet PlannedAbsence { get; set; } = null!; public DbSet Vacation { get; set; } = null!; public DbSet Customer { get; set; } = null!; @@ -30,18 +29,6 @@ protected override void ConfigureConventions(ModelConfigurationBuilder configura protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity() - .HasMany(org => org.Departments) - .WithOne(dept => dept.Organization); - - modelBuilder.Entity() - .HasMany(organization => organization.AbsenceTypes) - .WithOne(absence => absence.Organization); - - modelBuilder.Entity() - .HasMany(organization => organization.Customers) - .WithOne(customer => customer.Organization); - modelBuilder.Entity() .HasMany(customer => customer.Projects) .WithOne(project => project.Customer); @@ -113,9 +100,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) new() { Id = "project-mgmt", Name = "Project Management" } }); - modelBuilder.Entity() - .HasData(new { Name = "Variant AS", HoursPerWorkday = 7.5, Id = "variant-as" }); - modelBuilder.Entity() .HasData(new { Id = "trondheim", Name = "Trondheim", OrganizationId = "variant-as" }); diff --git a/backend/Database/Migrations/20231004195542_RemovedOrganization.Designer.cs b/backend/Database/Migrations/20231004195542_RemovedOrganization.Designer.cs new file mode 100644 index 00000000..162c4307 --- /dev/null +++ b/backend/Database/Migrations/20231004195542_RemovedOrganization.Designer.cs @@ -0,0 +1,413 @@ +// +using System; +using Database.DatabaseContext; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace backend.Migrations +{ + [DbContext(typeof(ApplicationContext))] + [Migration("20231004195542_RemovedOrganization")] + partial class RemovedOrganization + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("CompetenceConsultant", b => + { + b.Property("CompetencesId") + .HasColumnType("nvarchar(450)"); + + b.Property("ConsultantId") + .HasColumnType("int"); + + b.HasKey("CompetencesId", "ConsultantId"); + + b.HasIndex("ConsultantId"); + + b.ToTable("CompetenceConsultant"); + }); + + modelBuilder.Entity("Core.DomainModels.Absence", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ExcludeFromBillRate") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Absence"); + }); + + modelBuilder.Entity("Core.DomainModels.Competence", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Competence"); + + b.HasData( + new + { + Id = "frontend", + Name = "Frontend" + }, + new + { + Id = "backend", + Name = "Backend" + }, + new + { + Id = "design", + Name = "Design" + }, + new + { + Id = "project-mgmt", + Name = "Project Management" + }); + }); + + modelBuilder.Entity("Core.DomainModels.Consultant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Degree") + .HasColumnType("nvarchar(max)"); + + b.Property("DepartmentId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("GraduationYear") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("DepartmentId"); + + b.ToTable("Consultant"); + + b.HasData( + new + { + Id = 1, + Degree = "Master", + DepartmentId = "trondheim", + Email = "j@variant.no", + GraduationYear = 2019, + Name = "Jonas", + StartDate = new DateTime(2020, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified) + }); + }); + + modelBuilder.Entity("Core.DomainModels.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Customer"); + }); + + modelBuilder.Entity("Core.DomainModels.Department", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Department"); + + b.HasData( + new + { + Id = "trondheim", + Name = "Trondheim" + }); + }); + + modelBuilder.Entity("Core.DomainModels.PlannedAbsence", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AbsenceId") + .HasColumnType("int"); + + b.Property("ConsultantId") + .HasColumnType("int"); + + b.Property("Hours") + .HasColumnType("float"); + + b.Property("WeekNumber") + .HasColumnType("int"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AbsenceId"); + + b.HasIndex("ConsultantId"); + + b.ToTable("PlannedAbsence"); + }); + + modelBuilder.Entity("Core.DomainModels.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CustomerId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("State") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.ToTable("Project"); + }); + + modelBuilder.Entity("Core.DomainModels.Staffing", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ConsultantId") + .HasColumnType("int"); + + b.Property("Hours") + .HasColumnType("float"); + + b.Property("ProjectId") + .HasColumnType("int"); + + b.Property("Week") + .HasColumnType("int"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ConsultantId"); + + b.HasIndex("ProjectId"); + + b.ToTable("Staffing"); + }); + + modelBuilder.Entity("Core.DomainModels.Vacation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ConsultantId") + .HasColumnType("int"); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("ConsultantId"); + + b.ToTable("Vacation"); + }); + + modelBuilder.Entity("CompetenceConsultant", b => + { + b.HasOne("Core.DomainModels.Competence", null) + .WithMany() + .HasForeignKey("CompetencesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Core.DomainModels.Consultant", null) + .WithMany() + .HasForeignKey("ConsultantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Core.DomainModels.Consultant", b => + { + b.HasOne("Core.DomainModels.Department", "Department") + .WithMany("Consultants") + .HasForeignKey("DepartmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Department"); + }); + + modelBuilder.Entity("Core.DomainModels.PlannedAbsence", b => + { + b.HasOne("Core.DomainModels.Absence", "Absence") + .WithMany() + .HasForeignKey("AbsenceId") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + + b.HasOne("Core.DomainModels.Consultant", "Consultant") + .WithMany("PlannedAbsences") + .HasForeignKey("ConsultantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Absence"); + + b.Navigation("Consultant"); + }); + + modelBuilder.Entity("Core.DomainModels.Project", b => + { + b.HasOne("Core.DomainModels.Customer", "Customer") + .WithMany("Projects") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("Core.DomainModels.Staffing", b => + { + b.HasOne("Core.DomainModels.Consultant", "Consultant") + .WithMany("Staffings") + .HasForeignKey("ConsultantId") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + + b.HasOne("Core.DomainModels.Project", "Project") + .WithMany("Staffings") + .HasForeignKey("ProjectId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Consultant"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("Core.DomainModels.Vacation", b => + { + b.HasOne("Core.DomainModels.Consultant", "Consultant") + .WithMany("Vacations") + .HasForeignKey("ConsultantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Consultant"); + }); + + modelBuilder.Entity("Core.DomainModels.Consultant", b => + { + b.Navigation("PlannedAbsences"); + + b.Navigation("Staffings"); + + b.Navigation("Vacations"); + }); + + modelBuilder.Entity("Core.DomainModels.Customer", b => + { + b.Navigation("Projects"); + }); + + modelBuilder.Entity("Core.DomainModels.Department", b => + { + b.Navigation("Consultants"); + }); + + modelBuilder.Entity("Core.DomainModels.Project", b => + { + b.Navigation("Staffings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/Database/Migrations/20231004195542_RemovedOrganization.cs b/backend/Database/Migrations/20231004195542_RemovedOrganization.cs new file mode 100644 index 00000000..dcdeb30d --- /dev/null +++ b/backend/Database/Migrations/20231004195542_RemovedOrganization.cs @@ -0,0 +1,142 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace backend.Migrations +{ + /// + public partial class RemovedOrganization : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Absence_Organization_OrganizationId", + table: "Absence"); + + migrationBuilder.DropForeignKey( + name: "FK_Customer_Organization_OrganizationId", + table: "Customer"); + + migrationBuilder.DropForeignKey( + name: "FK_Department_Organization_OrganizationId", + table: "Department"); + + migrationBuilder.DropTable( + name: "Organization"); + + migrationBuilder.DropIndex( + name: "IX_Department_OrganizationId", + table: "Department"); + + migrationBuilder.DropIndex( + name: "IX_Customer_OrganizationId", + table: "Customer"); + + migrationBuilder.DropIndex( + name: "IX_Absence_OrganizationId", + table: "Absence"); + + migrationBuilder.DropColumn( + name: "OrganizationId", + table: "Department"); + + migrationBuilder.DropColumn( + name: "OrganizationId", + table: "Customer"); + + migrationBuilder.DropColumn( + name: "OrganizationId", + table: "Absence"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "OrganizationId", + table: "Department", + type: "nvarchar(450)", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "OrganizationId", + table: "Customer", + type: "nvarchar(450)", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "OrganizationId", + table: "Absence", + type: "nvarchar(450)", + nullable: false, + defaultValue: ""); + + migrationBuilder.CreateTable( + name: "Organization", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + HoursPerWorkday = table.Column(type: "float", nullable: false), + Name = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Organization", x => x.Id); + }); + + migrationBuilder.UpdateData( + table: "Department", + keyColumn: "Id", + keyValue: "trondheim", + column: "OrganizationId", + value: "variant-as"); + + migrationBuilder.InsertData( + table: "Organization", + columns: new[] { "Id", "HoursPerWorkday", "Name" }, + values: new object[] { "variant-as", 7.5, "Variant AS" }); + + migrationBuilder.CreateIndex( + name: "IX_Department_OrganizationId", + table: "Department", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_Customer_OrganizationId", + table: "Customer", + column: "OrganizationId"); + + migrationBuilder.CreateIndex( + name: "IX_Absence_OrganizationId", + table: "Absence", + column: "OrganizationId"); + + migrationBuilder.AddForeignKey( + name: "FK_Absence_Organization_OrganizationId", + table: "Absence", + column: "OrganizationId", + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Customer_Organization_OrganizationId", + table: "Customer", + column: "OrganizationId", + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Department_Organization_OrganizationId", + table: "Department", + column: "OrganizationId", + principalTable: "Organization", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/backend/Database/Migrations/ApplicationContextModelSnapshot.cs b/backend/Database/Migrations/ApplicationContextModelSnapshot.cs index 8d734bde..946bfb53 100644 --- a/backend/Database/Migrations/ApplicationContextModelSnapshot.cs +++ b/backend/Database/Migrations/ApplicationContextModelSnapshot.cs @@ -37,7 +37,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("CompetenceConsultant"); }); - modelBuilder.Entity("backend.Core.DomainModels.Absence", b => + modelBuilder.Entity("Core.DomainModels.Absence", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -52,18 +52,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("nvarchar(max)"); - b.Property("OrganizationId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - b.HasKey("Id"); - b.HasIndex("OrganizationId"); - b.ToTable("Absence"); }); - modelBuilder.Entity("backend.Core.DomainModels.Competence", b => + modelBuilder.Entity("Core.DomainModels.Competence", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -100,7 +94,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); }); - modelBuilder.Entity("backend.Core.DomainModels.Consultant", b => + modelBuilder.Entity("Core.DomainModels.Consultant", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -151,7 +145,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); }); - modelBuilder.Entity("backend.Core.DomainModels.Customer", b => + modelBuilder.Entity("Core.DomainModels.Customer", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -163,18 +157,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("nvarchar(max)"); - b.Property("OrganizationId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - b.HasKey("Id"); - b.HasIndex("OrganizationId"); - b.ToTable("Customer"); }); - modelBuilder.Entity("backend.Core.DomainModels.Department", b => + modelBuilder.Entity("Core.DomainModels.Department", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -184,52 +172,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("nvarchar(max)"); - b.Property("OrganizationId") - .IsRequired() - .HasColumnType("nvarchar(450)"); - b.HasKey("Id"); - b.HasIndex("OrganizationId"); - b.ToTable("Department"); b.HasData( new { Id = "trondheim", - Name = "Trondheim", - OrganizationId = "variant-as" + Name = "Trondheim" }); }); - modelBuilder.Entity("backend.Core.DomainModels.Organization", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(450)"); - - b.Property("HoursPerWorkday") - .HasColumnType("float"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Organization"); - - b.HasData( - new - { - Id = "variant-as", - HoursPerWorkday = 7.5, - Name = "Variant AS" - }); - }); - - modelBuilder.Entity("backend.Core.DomainModels.PlannedAbsence", b => + modelBuilder.Entity("Core.DomainModels.PlannedAbsence", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -261,7 +216,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("PlannedAbsence"); }); - modelBuilder.Entity("backend.Core.DomainModels.Project", b => + modelBuilder.Entity("Core.DomainModels.Project", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -287,7 +242,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Project"); }); - modelBuilder.Entity("backend.Core.DomainModels.Staffing", b => + modelBuilder.Entity("Core.DomainModels.Staffing", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -319,7 +274,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Staffing"); }); - modelBuilder.Entity("backend.Core.DomainModels.Vacation", b => + modelBuilder.Entity("Core.DomainModels.Vacation", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -342,33 +297,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("CompetenceConsultant", b => { - b.HasOne("backend.Core.DomainModels.Competence", null) + b.HasOne("Core.DomainModels.Competence", null) .WithMany() .HasForeignKey("CompetencesId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("backend.Core.DomainModels.Consultant", null) + b.HasOne("Core.DomainModels.Consultant", null) .WithMany() .HasForeignKey("ConsultantId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); - modelBuilder.Entity("backend.Core.DomainModels.Absence", b => - { - b.HasOne("backend.Core.DomainModels.Organization", "Organization") - .WithMany("AbsenceTypes") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("backend.Core.DomainModels.Consultant", b => + modelBuilder.Entity("Core.DomainModels.Consultant", b => { - b.HasOne("backend.Core.DomainModels.Department", "Department") + b.HasOne("Core.DomainModels.Department", "Department") .WithMany("Consultants") .HasForeignKey("DepartmentId") .OnDelete(DeleteBehavior.Cascade) @@ -377,37 +321,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Department"); }); - modelBuilder.Entity("backend.Core.DomainModels.Customer", b => + modelBuilder.Entity("Core.DomainModels.PlannedAbsence", b => { - b.HasOne("backend.Core.DomainModels.Organization", "Organization") - .WithMany("Customers") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("backend.Core.DomainModels.Department", b => - { - b.HasOne("backend.Core.DomainModels.Organization", "Organization") - .WithMany("Departments") - .HasForeignKey("OrganizationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Organization"); - }); - - modelBuilder.Entity("backend.Core.DomainModels.PlannedAbsence", b => - { - b.HasOne("backend.Core.DomainModels.Absence", "Absence") + b.HasOne("Core.DomainModels.Absence", "Absence") .WithMany() .HasForeignKey("AbsenceId") .OnDelete(DeleteBehavior.ClientCascade) .IsRequired(); - b.HasOne("backend.Core.DomainModels.Consultant", "Consultant") + b.HasOne("Core.DomainModels.Consultant", "Consultant") .WithMany("PlannedAbsences") .HasForeignKey("ConsultantId") .OnDelete(DeleteBehavior.Cascade) @@ -418,9 +340,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Consultant"); }); - modelBuilder.Entity("backend.Core.DomainModels.Project", b => + modelBuilder.Entity("Core.DomainModels.Project", b => { - b.HasOne("backend.Core.DomainModels.Customer", "Customer") + b.HasOne("Core.DomainModels.Customer", "Customer") .WithMany("Projects") .HasForeignKey("CustomerId") .OnDelete(DeleteBehavior.Cascade) @@ -429,15 +351,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Customer"); }); - modelBuilder.Entity("backend.Core.DomainModels.Staffing", b => + modelBuilder.Entity("Core.DomainModels.Staffing", b => { - b.HasOne("backend.Core.DomainModels.Consultant", "Consultant") + b.HasOne("Core.DomainModels.Consultant", "Consultant") .WithMany("Staffings") .HasForeignKey("ConsultantId") .OnDelete(DeleteBehavior.ClientCascade) .IsRequired(); - b.HasOne("backend.Core.DomainModels.Project", "Project") + b.HasOne("Core.DomainModels.Project", "Project") .WithMany("Staffings") .HasForeignKey("ProjectId") .OnDelete(DeleteBehavior.Cascade) @@ -448,9 +370,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Project"); }); - modelBuilder.Entity("backend.Core.DomainModels.Vacation", b => + modelBuilder.Entity("Core.DomainModels.Vacation", b => { - b.HasOne("backend.Core.DomainModels.Consultant", "Consultant") + b.HasOne("Core.DomainModels.Consultant", "Consultant") .WithMany("Vacations") .HasForeignKey("ConsultantId") .OnDelete(DeleteBehavior.Cascade) @@ -459,7 +381,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Consultant"); }); - modelBuilder.Entity("backend.Core.DomainModels.Consultant", b => + modelBuilder.Entity("Core.DomainModels.Consultant", b => { b.Navigation("PlannedAbsences"); @@ -468,26 +390,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Vacations"); }); - modelBuilder.Entity("backend.Core.DomainModels.Customer", b => + modelBuilder.Entity("Core.DomainModels.Customer", b => { b.Navigation("Projects"); }); - modelBuilder.Entity("backend.Core.DomainModels.Department", b => + modelBuilder.Entity("Core.DomainModels.Department", b => { b.Navigation("Consultants"); }); - modelBuilder.Entity("backend.Core.DomainModels.Organization", b => - { - b.Navigation("AbsenceTypes"); - - b.Navigation("Customers"); - - b.Navigation("Departments"); - }); - - modelBuilder.Entity("backend.Core.DomainModels.Project", b => + modelBuilder.Entity("Core.DomainModels.Project", b => { b.Navigation("Staffings"); }); diff --git a/backend/Tests/AbsenceTest.cs b/backend/Tests/AbsenceTest.cs index 4636cd56..0b07863c 100644 --- a/backend/Tests/AbsenceTest.cs +++ b/backend/Tests/AbsenceTest.cs @@ -1,5 +1,8 @@ +using Api.Consultants; +using Api.Options; using Core.DomainModels; using Core.Services; +using Microsoft.Extensions.Options; using NSubstitute; namespace Tests; @@ -24,9 +27,6 @@ public void AvailabilityCalculation(int vacationDays, double plannedAbsenceHours double expectedBookedHours) { var department = Substitute.For(); - var organization = Substitute.For(); - organization.HoursPerWorkday = 7.5; - department.Organization = organization; var consultant = new Consultant { Id = 1, @@ -75,17 +75,21 @@ public void AvailabilityCalculation(int vacationDays, double plannedAbsenceHours Hours = staffedHours }); - var availability = consultant.GetBookedHours(year, week); - Assert.That(availability, Is.EqualTo(expectedBookedHours)); + var organization = new OrganizationOptions(); + organization.HoursPerWorkday = 7.5; + organization.HasVacationInChristmas = true; + var orgOptions = Options.Create(organization); + var holidayService = new HolidayService(orgOptions); + + var bookedHours = + new ConsultantService(orgOptions, holidayService).GetBookedHours(consultant, year, week); + Assert.That(bookedHours, Is.EqualTo(expectedBookedHours)); } [Test] public void MultiplePlannedAbsences() { var department = Substitute.For(); - var organization = Substitute.For(); - organization.HoursPerWorkday = 7.5; - department.Organization = organization; var consultant = new Consultant { Id = 1, @@ -110,7 +114,14 @@ public void MultiplePlannedAbsences() Hours = 15 }); - var availability = consultant.GetBookedHours(year, week); - Assert.That(availability, Is.EqualTo(7.5)); + var organization = new OrganizationOptions(); + organization.HoursPerWorkday = 7.5; + var orgOptions = Options.Create(organization); + var holidayService = new HolidayService(orgOptions); + + var bookedHours = + new ConsultantService(orgOptions, holidayService).GetBookedHours(consultant, year, week); + + Assert.That(bookedHours, Is.EqualTo(30)); } } \ No newline at end of file diff --git a/backend/Tests/Tests.csproj b/backend/Tests/Tests.csproj index b9517ff0..ab9f7fc9 100644 --- a/backend/Tests/Tests.csproj +++ b/backend/Tests/Tests.csproj @@ -10,17 +10,18 @@ - - - - - - + + + + + + - + +