Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple organisations in one database #150

Merged
merged 26 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
78f2c48
Re-implement Organization entity model back to db
yelodevopsi Oct 17, 2023
1274654
begone, migrations!
jonasbjoralt Oct 17, 2023
df365ba
Adding HotKey optional to Department. Used frontend to keybindings.
yelodevopsi Oct 17, 2023
1d19804
Adding Hotkey to be optional
yelodevopsi Oct 17, 2023
c0d5b00
remove org from project
jonasbjoralt Oct 17, 2023
612f02f
Initial migrations on new org-based db-schema
yelodevopsi Oct 17, 2023
75e4cf4
redo migrations, add auth service wip
jonasbjoralt Oct 17, 2023
b4f8dea
unnskyld sigrid
jonasbjoralt Oct 17, 2023
0e8990e
new migrations too
jonasbjoralt Oct 17, 2023
bc12d20
Add organisationId chek to api calls
sigridge Oct 17, 2023
0fa93a6
some cleanup, add caching
jonasbjoralt Oct 17, 2023
afe997d
more cleanup
jonasbjoralt Oct 18, 2023
a3fec33
rename cache key
jonasbjoralt Oct 18, 2023
0275e5b
Correcting namespacing
yelodevopsi Oct 18, 2023
890f169
simplify auth ny postponing tenant-id
jonasbjoralt Oct 18, 2023
1c5af9f
wip: some redirect logic frontend
jonasbjoralt Oct 18, 2023
bb05fcf
fix frontend redirect
jonasbjoralt Oct 18, 2023
ae41b28
Fix dept filter
jonasbjoralt Oct 19, 2023
a8d7e10
added timer to test fetchWithToken
yelodevopsi Oct 19, 2023
7bdf395
Fix caching
jonasbjoralt Oct 19, 2023
a6fee69
Remove timer
jonasbjoralt Oct 19, 2023
c460c2a
prettier
jonasbjoralt Oct 19, 2023
8246f1f
Removing MUI breadcrumbs....
yelodevopsi Oct 19, 2023
4647443
fix tests
jonasbjoralt Oct 19, 2023
2ac458a
fix import
jonasbjoralt Oct 19, 2023
563535d
prettier
jonasbjoralt Oct 19, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions STYLEGUIDE.md

This file was deleted.

3 changes: 2 additions & 1 deletion backend/Api/Cache/CacheKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ namespace Api.Cache;

public enum CacheKeys
{
ConsultantAvailability8Weeks
ConsultantAvailability8Weeks,
OrganisationsPrConsultant
}
27 changes: 16 additions & 11 deletions backend/Api/Consultants/ConsultantController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
using Core.DomainModels;
using Core.Services;
using Database.DatabaseContext;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;

namespace Api.Consultants;

[Route("v0/consultants")]
[Authorize]
[Route("/v0/{orgUrlKey}/consultants")]
[ApiController]
public class ConsultantController : ControllerBase
{
Expand All @@ -26,10 +28,11 @@ public ConsultantController(ApplicationContext context, IMemoryCache cache, Cons

[HttpGet]
public ActionResult<List<ConsultantReadModel>> Get(
[FromRoute] string orgUrlKey,
[FromQuery(Name = "weeks")] int numberOfWeeks = 8,
[FromQuery(Name = "includeOccupied")] bool includeOccupied = true)
{
var consultants = GetConsultantsWithAvailability(numberOfWeeks)
var consultants = GetConsultantsWithAvailability(orgUrlKey, numberOfWeeks)
.Where(c =>
includeOccupied
|| c.IsOccupied
Expand All @@ -56,7 +59,7 @@ public async Task<Results<Created<ConsultantWriteModel>, ProblemHttpResult, Vali

var newConsultant = CreateConsultantFromModel(basicConsultant, selectedDepartment);
await AddConsultantToDatabaseAsync(_context, newConsultant);
ClearConsultantCache();
ClearConsultantCache(selectedDepartment.Organization.UrlKey);

return TypedResults.Created($"/consultant/{newConsultant.Id}", basicConsultant);
}
Expand All @@ -67,24 +70,24 @@ public async Task<Results<Created<ConsultantWriteModel>, ProblemHttpResult, Vali
}
}

private List<ConsultantReadModel> GetConsultantsWithAvailability(int numberOfWeeks)
private List<ConsultantReadModel> GetConsultantsWithAvailability(string orgUrlKey, int numberOfWeeks)
{
if (numberOfWeeks == 8)
{
_cache.TryGetValue(CacheKeys.ConsultantAvailability8Weeks,
_cache.TryGetValue(
$"{orgUrlKey}/{CacheKeys.ConsultantAvailability8Weeks}",
out List<ConsultantReadModel>? cachedConsultants);
if (cachedConsultants != null) return cachedConsultants;
}

var consultants = LoadConsultantAvailability(numberOfWeeks)
var consultants = LoadConsultantAvailability(orgUrlKey, numberOfWeeks)
.Select(c => _consultantService.MapConsultantToReadModel(c, numberOfWeeks)).ToList();


_cache.Set(CacheKeys.ConsultantAvailability8Weeks, consultants);
_cache.Set($"{orgUrlKey}/{CacheKeys.ConsultantAvailability8Weeks}", consultants);
return consultants;
}

private List<Consultant> LoadConsultantAvailability(int numberOfWeeks)
private List<Consultant> LoadConsultantAvailability(string orgUrlKey, int numberOfWeeks)
{
var applicableWeeks = DateService.GetNextWeeks(numberOfWeeks);
var firstDayOfCurrentWeek = DateService.GetFirstDayOfWeekContainingDate(DateTime.Now);
Expand Down Expand Up @@ -119,6 +122,8 @@ private List<Consultant> LoadConsultantAvailability(int numberOfWeeks)
(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)
.Where(c => c.Department.Organization.UrlKey == orgUrlKey)
.Include(c => c.Staffings.Where(s =>
(s.Year <= yearA && minWeekA <= s.Week && s.Week <= maxWeekA)
|| (yearB <= s.Year && minWeekB <= s.Week && s.Week <= maxWeekB)))
Expand Down Expand Up @@ -154,9 +159,9 @@ private static async Task AddConsultantToDatabaseAsync(ApplicationContext db, Co
await db.SaveChangesAsync();
}

private void ClearConsultantCache()
private void ClearConsultantCache(string orgUrlKey)
{
_cache.Remove(CacheKeys.ConsultantAvailability8Weeks);
_cache.Remove($"{orgUrlKey}/{CacheKeys.ConsultantAvailability8Weeks}");
}


Expand Down
9 changes: 5 additions & 4 deletions backend/Api/Consultants/ConsultantService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public ConsultantReadModel MapConsultantToReadModel(Consultant consultant, int w
const double tolerance = 0.1;
var bookedHours = GetBookedHoursForWeeks(consultant, weeks);

var isOccupied = bookedHours.All(b => b.BookedHours >= GetHoursPrWeek() - tolerance);
var isOccupied = bookedHours.All(b => b.BookedHours >= GetHoursPrWeek(consultant) - tolerance);

return new ConsultantReadModel(
consultant.Id,
Expand All @@ -36,7 +36,8 @@ public ConsultantReadModel MapConsultantToReadModel(Consultant consultant, int w

public double GetBookedHours(Consultant consultant, int year, int week)
{
var hoursPrWorkDay = _organizationOptions.HoursPerWorkday;
var hoursPrWorkDay = consultant.Department.Organization.HoursPerWorkday;
// var hoursPrWorkDay = _organizationOptions.HoursPerWorkday;

var holidayHours = _holidayService.GetTotalHolidaysOfWeek(year, week) * hoursPrWorkDay;
var vacationHours = consultant.Vacations.Count(v => DateService.DateIsInWeek(v.Date, year, week)) *
Expand Down Expand Up @@ -73,8 +74,8 @@ public List<BookedHoursPerWeek> GetBookedHoursForWeeks(Consultant consultant, in
.ToList();
}

public double GetHoursPrWeek()
public double GetHoursPrWeek(Consultant consultant)
{
return _organizationOptions.HoursPerWorkday * 5;
return consultant.Department.Organization.HoursPerWorkday * 5;
}
}
16 changes: 0 additions & 16 deletions backend/Api/Departments/DeparmentController.cs

This file was deleted.

42 changes: 42 additions & 0 deletions backend/Api/Organisation/DeparmentController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Database.DatabaseContext;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace Api.Organisation;

[Route("/v0/organisations")]
[ApiController]
public class OrganisationController : ControllerBase
{
private readonly ApplicationContext _applicationContext;

public OrganisationController(ApplicationContext applicationContext)
{
_applicationContext = applicationContext;
}

[HttpGet]
public ActionResult<List<OrganisationReadModel>> Get()
{
return _applicationContext.Organization
.Select(organization => new OrganisationReadModel(organization.Name, organization.UrlKey))
.ToList();
}


[HttpGet]
[Route("{orgUrlKey}/departments")]
public ActionResult<List<DepartmentReadModel>> GetDepartment([FromRoute] string orgUrlKey)
{
return _applicationContext.Organization
.Include(o => o.Departments)
.Single(o => o.UrlKey == orgUrlKey)
.Departments
.Select(d => new DepartmentReadModel(d.Id, d.Name))
.ToList();
}
}

public record DepartmentReadModel(string Id, string Name);

public record OrganisationReadModel(string Name, string UrlKey);
3 changes: 2 additions & 1 deletion backend/Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Api.Options;
using Database.DatabaseContext;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Identity.Web;
using Microsoft.OpenApi.Models;
Expand All @@ -15,7 +16,7 @@

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration);
builder.Services.AddAuthorization(opt => opt.FallbackPolicy = opt.DefaultPolicy);
builder.Services.AddAuthorization(opt => { opt.FallbackPolicy = opt.DefaultPolicy; });

builder.Services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(connection));

Expand Down
1 change: 1 addition & 0 deletions backend/Core/DomainModels/Absence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ public class Absence

public required string Name { get; set; }
public required bool ExcludeFromBillRate { get; set; } = false;
public required Organization Organization { get; set; }
}
1 change: 1 addition & 0 deletions backend/Core/DomainModels/Customer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ public class Customer
public int Id { get; set; }

public required string Name { get; set; }
public required Organization Organization { get; set; }
public required List<Project> Projects { get; set; }
}
2 changes: 2 additions & 0 deletions backend/Core/DomainModels/Department.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ public class Department
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public required string Id { get; set; }
public required string Name { get; set; }
public int? Hotkey { get; set; }
public required Organization Organization { get; set; }
[JsonIgnore] public required List<Consultant> Consultants { get; set; }
}
20 changes: 20 additions & 0 deletions backend/Core/DomainModels/Organization.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Text.Json.Serialization;

namespace Core.DomainModels;

public class Organization
{
public required string Id { get; set; } // guid ? Decide What to set here first =>
public required string Name { get; set; }
public required string UrlKey { get; set; } // "variant-as", "variant-sverige"
public required string Country { get; set; }
public required int NumberOfVacationDaysInYear { get; set; }
public required bool HasVacationInChristmas { get; set; }
public required double HoursPerWorkday { get; set; }

[JsonIgnore] public List<Department> Departments { get; set; }

public required List<Customer> Customers { get; set; }

public List<Absence> AbsenceTypes { get; set; }
}
4 changes: 4 additions & 0 deletions backend/Database/Database.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,8 @@
<ProjectReference Include="..\Core\Core.csproj"/>
</ItemGroup>

<ItemGroup>
<Folder Include="Migrations\" />
</ItemGroup>

</Project>
30 changes: 28 additions & 2 deletions backend/Database/DatabaseContext/ApplicationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public ApplicationContext(DbContextOptions options) : base(options)
public DbSet<Consultant> Consultant { get; set; } = null!;
public DbSet<Competence> Competence { get; set; } = null!;
public DbSet<Department> Department { get; set; } = null!;
public DbSet<Organization> Organization { get; set; } = null!;
public DbSet<PlannedAbsence> PlannedAbsence { get; set; } = null!;
public DbSet<Vacation> Vacation { get; set; } = null!;
public DbSet<Customer> Customer { get; set; } = null!;
Expand All @@ -29,6 +30,19 @@ protected override void ConfigureConventions(ModelConfigurationBuilder configura

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Organization>()
.HasMany(org => org.Departments)
.WithOne(dept => dept.Organization);

modelBuilder.Entity<Organization>()
.HasMany(organization => organization.AbsenceTypes)
.WithOne(absence => absence.Organization);

modelBuilder.Entity<Organization>()
.HasMany(organization => organization.Customers)
.WithOne(customer => customer.Organization);


modelBuilder.Entity<Customer>()
.HasMany(customer => customer.Projects)
.WithOne(project => project.Customer);
Expand All @@ -41,10 +55,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.HasMany(p => p.Consultants)
.WithMany(c => c.Projects)
.UsingEntity<Staffing>(
r => r.HasOne<Consultant>(s => s.Consultant)
staffing => staffing.HasOne<Consultant>(s => s.Consultant)
.WithMany(c => c.Staffings)
.OnDelete(DeleteBehavior.ClientCascade),
l => l
staffing => staffing
.HasOne<Project>(s => s.Project)
.WithMany(c => c.Staffings)
.OnDelete(DeleteBehavior.Cascade)
Expand Down Expand Up @@ -100,6 +114,18 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
new() { Id = "project-mgmt", Name = "Project Management" }
});

modelBuilder.Entity<Organization>()
.HasData(new
{
Id = "variant-as",
Name = "Variant AS",
UrlKey = "variant-as",
Country = "norway",
HoursPerWorkday = 7.5,
HasVacationInChristmas = true,
NumberOfVacationDaysInYear = 25
});

modelBuilder.Entity<Department>()
.HasData(new { Id = "trondheim", Name = "Trondheim", OrganizationId = "variant-as" });

Expand Down
Loading