diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index b1076b7..c572325 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -88,7 +88,7 @@ Umfasst alle drei Plattform-Ebenen: OMSORG Desktop, OMSORG Connect, OMSORG Backe | FR-EIN-1 | Stammdaten (Name, Art, Adresse, Rechnungsadresse, Telefon, E-Mail, Website, Status) [Blueprint 19.2] | Sabina, Malik, Sabrina, Sascha (Leads) | Datensatz anlegen/bearbeiten | ✅ (`Facility`-Entity + volles Repository/Service/Controller (`FacilitiesController`, `GET/POST/PUT /api/facilities`) in `omsorgCore`, serverseitige Rechteprüfung über `[RequirePermission(ModuleType.Facilities, ...)]`; Felder Name/Art(`FacilityType`)/Adresse/Rechnungsadresse/Website/Status(`CrmStatus`) abgedeckt — Adresse und Rechnungsadresse sind je strukturierte Felder (Straße/PLZ/Ort/Land, analog Mitarbeiter-Adresse), kein Freitext; UI-Modul in `omsorgapp` vorhanden (`FacilitiesPage`, Sidebar-Tab "Kunden", analog `EmployeesPage`) — Anlegen/Bearbeiten funktionsfähig gegen `omsorgCore`. **Bewusste Abweichung von der Zeile:** kein allgemeines Telefon/E-Mail direkt auf `Facility` — Kontaktwege leben ausschließlich pro Ansprechpartner auf `FacilityContact` (FR-EIN-2), da eine Einrichtung i. d. R. mehrere Ansprechpartner mit je eigenen Kontaktdaten hat und ein zusätzliches, nicht klar zuordenbares "allgemeines" Einrichtungstelefon eine zweite, redundante Kontakt-Datenquelle wäre (widerspricht dem Grundsatz "jede Information wird nur einmal gespeichert", siehe Root-`CLAUDE.md`). Diese Entscheidung ist absichtlich, kein offener Punkt.) | | FR-EIN-2 | Mehrere Ansprechpartner je Einrichtung mit Funktion, Abteilung, Kontaktwegen, Notizen | Sabina, Malik, Sabrina, Sascha | Liste von Ansprechpartnern editierbar, mind. 1:n-Beziehung | ✅ (neue Entität `FacilityContact` (1:n zu `Facility`) in `omsorgCore`, `FacilityContactsController` unter `GET/POST/PUT/DELETE /api/facilities/{facilityId}/contacts[/...]`, gegated über dieselben `ModuleType.Facilities`-Rechte wie die Einrichtung selbst; Felder Name/Funktion(`Role`)/Abteilung(`Department`)/Telefon/E-Mail/Notizen abgedeckt; UI-Liste im `FacilityDetailPanel` (`omsorgapp`) anlegen/bearbeiten/löschen funktionsfähig; Löschen ist Soft-Delete (`IsDeleted`/`DeletedAt`) und über die neue "Papierkorb"-Seite (`ModuleType.Facilities`+`PermissionAction.Recover`) wiederherstellbar) | | FR-EIN-3 | CRM-Status-Pipeline: Lead → kontaktiert → kein Bedarf → Wiedervorlage → Interesse → Angebot → Kunde → Bestandskunde [Blueprint, omsorg.md] | Sascha, Sabrina | Statuswechsel wird protokolliert; bei „kein Bedarf" wird automatisch Wiedervorlage in 14 Tagen erzeugt | ✅ (Statuswechsel-Protokollierung läuft automatisch über den bestehenden `AuditSaveChangesInterceptor`, kein Zusatzcode nötig; neues Feld `Facility.FollowUpDueDate` (nullable), gesetzt über `FacilitiesController.Update`, wenn der neu gewählte `CrmStatus`-Wert das Flag `TriggersFollowUp` trägt (aktuell nur "Kein Bedarf" — generisch statt hartcodiert, damit künftig weitere Status dieselbe Mechanik nutzen können) — serverseitig muss dann ein `FollowUpDays` aus der admin-editierbaren `ValueList` `"FollowUpPeriods"` (7/14/21/31 Tage, Default 14) mitgegeben werden; `omsorgapp` zeigt dafür beim Statuswechsel auf einen solchen Status einen Auswahldialog (`FollowUpDaysDialog.jsx` in `EditFacilityDialog.jsx`, 14 Tage vorausgewählt) und das Fälligkeitsdatum im `FacilityDetailPanel`; Dashboard-Widget "Fällige Wiedervorlagen" (`FollowUpWidget.jsx`, in `HomePage.jsx` eingebunden) zeigt fällige Einrichtungen an; die Statuspipeline erzwingt wie bei `OrderStatus` erlaubte Übergänge (`ValueListItemTransition` für `"CrmStatus"`, geseedet mit der Vorwärtskette der Pipeline plus jederzeitigem Rückfall auf "Kein Bedarf"/"Wiedervorlage", admin-editierbar über dieselbe "Erlaubte Übergänge"-Matrix wie beim Auftragsstatus, `FacilitiesController.Update` prüft serverseitig via `CanTransitionAsync`, das CRM-Status-Dropdown in `FacilityForm.jsx` (nur im `EditFacilityDialog`) zeigt dem Nutzer dabei ebenfalls nur den aktuellen Status plus die laut `GET /api/value-lists/CrmStatus/transitions` erlaubten Zielstatus an — vorher listete das Dropdown ungefiltert alle CRM-Status auf, ein unzulässiger Wechsel wurde erst nach "Speichern" per 400-Fehler vom Server abgelehnt). Die wählbare Frist statt einer starren 14-Tage-Automatik ist eine bewusste, finale Designentscheidung — kein offener Punkt.) | -| FR-EIN-4 | Konditionen (Verrechnungssatz, Zuschläge, Fahrtkosten, Mindeststunden, Zahlungsziel etc.) je Einrichtung [Blueprint 19.2] | Sabina, Malik, Sabrina | Konditionssatz ist Grundlage für Rechnungserstellung (siehe FR-RE-1) | ✅ (Backend: elf der zwölf Blueprint-19.2-Felder als flache, nullable Spalten auf `Facility` — Verrechnungssatz, vier Zuschläge (als Prozent), Fahrtkosten (Pauschale je Einsatz), Mindeststunden, Pausenregelung, Abrechnungsintervall (validiert gegen neue `ValueList` `"BillingInterval"`), Zahlungsziel, individuelle Vereinbarungen —, nur über `PUT /api/facilities/{id}` pflegbar; qualifikationsabhängige Preise als neue 1:n-Unterressource `FacilityQualificationRate` (`GET/POST/PUT/DELETE /api/facilities/{facilityId}/qualification-rates[/...]`, Qualifikation gegen die bestehende `ValueList` `"Qualification"` validiert) nach dem `FacilityContact`-Muster, inkl. Soft-Delete/Papierkorb; Migration `AddFacilityConditionsAndQualificationRates` erfolgreich gegen die echte Postgres-Instanz angewendet (automatisch beim API-Start); `omsorgapp`-UI (Konditionen-Fieldset in `FacilityForm.jsx`, `FacilityQualificationRatesList` im `FacilityDetailPanel`) vorhanden, `omsorgapp/api-client-ts` regeneriert und gebaut (Methodennamen/Feldnamen gegen den generierten Client verifiziert). Akzeptanzkriterium "Grundlage für Rechnungserstellung" im engeren Sinn bewusst weiterhin nicht vollständig erfüllt, da FR-RE-1/`Invoice` diese Daten noch nicht konsumiert — dieser Schritt legt nur die Datenbasis.) | +| FR-EIN-4 | Konditionen (Verrechnungssatz, Zuschläge, Fahrtkosten, Mindeststunden, Zahlungsziel etc.) je Einrichtung [Blueprint 19.2] | Sabina, Malik, Sabrina | Konditionssatz ist Grundlage für Rechnungserstellung (siehe FR-RE-1) | ✅ (Backend: flache, nullable Spalten auf `Facility` — Verrechnungssatz, vier Zuschläge (als Prozent), Fahrtkosten (zwei Abrechnungsarten: `TravelCostMode` "Pauschale" je Einsatz oder "ProKilometer" × `EmployeeFacilityDistance.DistanceKm`, siehe unten), Mindeststunden, Abrechnungsintervall (validiert gegen `ValueList` `"BillingInterval"`), Zahlungsziel, individuelle Vereinbarungen — nur über `PUT /api/facilities/{id}` pflegbar (Pausenregelung wurde am 2026-08-10 als fachlich nicht benötigt wieder entfernt); qualifikationsabhängige Preise als 1:n-Unterressource `FacilityQualificationRate` (`GET/POST/PUT/DELETE /api/facilities/{facilityId}/qualification-rates[/...]`, Qualifikation gegen die `ValueList` `"Qualification"` validiert) nach dem `FacilityContact`-Muster, inkl. Soft-Delete/Papierkorb; Entfernung je Mitarbeiter für die Pro-Kilometer-Abrechnung als weitere 1:n-Unterressource `EmployeeFacilityDistance` (`.../employee-distances[/...]` für Büro-Rollen, zusätzlich `/api/me/facility-distances` als Selbstbedienungs-Endpoint für den Außendienst über `omsorgWeb/mitarbeiter-app`, eigener `ModuleType.EmployeeFacilityDistances`); `omsorgapp`-UI (Konditionen-Fieldset in `FacilityForm.jsx` inkl. Abrechnungsart-Umschalter, `FacilityQualificationRatesList`/`EmployeeFacilityDistancesList` im `FacilityDetailPanel`) vorhanden, beide generierten API-Clients regeneriert und gebaut. Akzeptanzkriterium "Grundlage für Rechnungserstellung" im engeren Sinn bewusst weiterhin nicht vollständig erfüllt, da FR-RE-1/`Invoice` diese Daten noch nicht konsumiert — dieser Schritt legt nur die Datenbasis.) | | FR-EIN-5 | Verknüpfte Historie: Verträge, Aufträge, zugewiesene Mitarbeiter, Nachweise, Rechnungen, Zahlungen, Mahnungen, Kommunikation | alle Büro-Rollen (rechteabhängig) | Einrichtungsakte zeigt konsolidierte Historie ohne Datenduplizierung | ⬜ | ### 4.4 Einsatzmanagement (Auftrag → Einsatz → Zuweisung) diff --git a/omsorgCore/src/OmsorgCore.Infrastructure/Persistence/Migrations/20260810182042_RemoveFacilityBreakPolicy.Designer.cs b/omsorgCore/src/OmsorgCore.Infrastructure/Persistence/Migrations/20260810182042_RemoveFacilityBreakPolicy.Designer.cs new file mode 100644 index 0000000..2847623 --- /dev/null +++ b/omsorgCore/src/OmsorgCore.Infrastructure/Persistence/Migrations/20260810182042_RemoveFacilityBreakPolicy.Designer.cs @@ -0,0 +1,1388 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using OmsorgCore.Infrastructure.Persistence; + +#nullable disable + +namespace OmsorgCore.Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(OmsorgCoreDbContext))] + [Migration("20260810182042_RemoveFacilityBreakPolicy")] + partial class RemoveFacilityBreakPolicy + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.Absence", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminNote") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("EndDate") + .HasColumnType("date"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Substitute") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("EmployeeId"); + + b.ToTable("absences", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.AuditLogEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ActorUserId") + .HasColumnType("uuid"); + + b.Property("ActorUsername") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Category") + .HasColumnType("integer"); + + b.Property("Details") + .HasColumnType("jsonb"); + + b.Property("EntityId") + .HasColumnType("uuid"); + + b.Property("EntityType") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("IpAddress") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("OccurredAtUtc") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ActorUserId"); + + b.HasIndex("OccurredAtUtc"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("audit_log_entries", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.Contract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AllowancesDescription") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ContractType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("EndDate") + .HasColumnType("date"); + + b.Property("FacilityId") + .HasColumnType("uuid"); + + b.Property("HourlyWage") + .HasPrecision(10, 2) + .HasColumnType("numeric(10,2)"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("OvertimeRules") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ProbationPeriodMonths") + .HasColumnType("integer"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VacationDaysPerYear") + .HasColumnType("integer"); + + b.Property("WeeklyHours") + .HasPrecision(10, 2) + .HasColumnType("numeric(10,2)"); + + b.HasKey("Id"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("FacilityId"); + + b.ToTable("contracts", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.Document", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("EntityId") + .HasColumnType("uuid"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(260) + .HasColumnType("character varying(260)"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.Property("StorageKey") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UploadedByUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UploadedByUserId"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("documents", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.Employee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AvatarFileName") + .HasColumnType("text"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateOfBirth") + .HasColumnType("date"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("EmergencyContactName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("EmergencyContactPhone") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("EmergencyContactRelation") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("EmploymentType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("EntryDate") + .HasColumnType("date"); + + b.Property("ExitDate") + .HasColumnType("date"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("PhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Qualification") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Street") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("employees", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.EmployeeFacilityDistance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DistanceKm") + .HasColumnType("decimal(10,2)"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("FacilityId") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("FacilityId"); + + b.ToTable("employee_facility_distances", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.Facility", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BillingCity") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("BillingCountry") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("BillingInterval") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BillingPostalCode") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("BillingRate") + .HasColumnType("decimal(10,2)"); + + b.Property("BillingStreet") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("City") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Country") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CrmStatus") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FacilityType") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("FollowUpDueDate") + .HasColumnType("timestamp with time zone"); + + b.Property("HolidaySurchargePercent") + .HasColumnType("decimal(5,2)"); + + b.Property("IndividualAgreements") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("MinimumHours") + .HasColumnType("decimal(5,2)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("NightSurchargePercent") + .HasColumnType("decimal(5,2)"); + + b.Property("PaymentTermDays") + .HasColumnType("integer"); + + b.Property("PostalCode") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("SaturdaySurchargePercent") + .HasColumnType("decimal(5,2)"); + + b.Property("Street") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("SundaySurchargePercent") + .HasColumnType("decimal(5,2)"); + + b.Property("TravelCostMode") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("TravelCostPerKm") + .HasColumnType("decimal(10,2)"); + + b.Property("TravelCostRate") + .HasColumnType("decimal(10,2)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Website") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.HasKey("Id"); + + b.ToTable("facilities", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.FacilityContact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Department") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Email") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("FacilityId") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("PhoneNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Role") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("FacilityId"); + + b.ToTable("facility_contacts", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.FacilityQualificationRate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FacilityId") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("Qualification") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Rate") + .HasColumnType("decimal(10,2)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("FacilityId"); + + b.ToTable("facility_qualification_rates", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.Invoice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BillingPeriodEnd") + .HasColumnType("date"); + + b.Property("BillingPeriodStart") + .HasColumnType("date"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DueDate") + .HasColumnType("date"); + + b.Property("FacilityId") + .HasColumnType("uuid"); + + b.Property("GrossAmount") + .HasColumnType("numeric(12,2)"); + + b.Property("InvoiceNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("NetAmount") + .HasColumnType("numeric(12,2)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("FacilityId"); + + b.HasIndex("InvoiceNumber") + .IsUnique(); + + b.ToTable("invoices", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.LoginAttempt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AttemptedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IpAddress") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Succeeded") + .HasColumnType("boolean"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("IpAddress", "AttemptedAt"); + + b.ToTable("login_attempts", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Conditions") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndDate") + .HasColumnType("date"); + + b.Property("FacilityContactId") + .HasColumnType("uuid"); + + b.Property("FacilityId") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("Priority") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RequiredHeadcount") + .HasColumnType("integer"); + + b.Property("RequiredQualification") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ShiftType") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("StatusId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("FacilityContactId"); + + b.HasIndex("FacilityId"); + + b.HasIndex("StatusId"); + + b.ToTable("orders", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.PasswordResetCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AttemptCount") + .HasColumnType("integer"); + + b.Property("CodeHash") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ConsumedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("InvalidatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ResetTokenExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ResetTokenHash") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ResetTokenUsedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ResetTokenHash"); + + b.HasIndex("UserId"); + + b.ToTable("password_reset_codes", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.RefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ReplacedByTokenId") + .HasColumnType("uuid"); + + b.Property("RevokedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TokenHash") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ReplacedByTokenId"); + + b.HasIndex("TokenHash") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("refresh_tokens", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("roles", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.RolePermission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .HasColumnType("integer"); + + b.Property("Module") + .HasColumnType("integer"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.Property("Scope") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RoleId", "Module", "Action") + .IsUnique(); + + b.ToTable("role_permissions", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminNote") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("BreakDuration") + .HasColumnType("interval"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("date"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("End") + .HasColumnType("time without time zone"); + + b.Property("HolidayHours") + .HasColumnType("numeric(6,2)"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("NightHours") + .HasColumnType("numeric(6,2)"); + + b.Property("OrderId") + .HasColumnType("uuid"); + + b.Property("SaturdayHours") + .HasColumnType("numeric(6,2)"); + + b.Property("Start") + .HasColumnType("time without time zone"); + + b.Property("StatusId") + .HasColumnType("uuid"); + + b.Property("SundayHours") + .HasColumnType("numeric(6,2)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("OrderId"); + + b.HasIndex("StatusId"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EmployeeId") + .HasColumnType("uuid"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("MustChangePassword") + .HasColumnType("boolean"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.Property("SecurityStamp") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("RoleId"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.UserPermissionOverride", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .HasColumnType("integer"); + + b.Property("Effect") + .HasColumnType("integer"); + + b.Property("Module") + .HasColumnType("integer"); + + b.Property("Scope") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "Module", "Action") + .IsUnique(); + + b.ToTable("user_permission_overrides", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.ValueList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("value_lists", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.ValueListItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("IsDefault") + .HasColumnType("boolean"); + + b.Property("IsEditableByOwner") + .HasColumnType("boolean"); + + b.Property("IsInitial") + .HasColumnType("boolean"); + + b.Property("IsTerminal") + .HasColumnType("boolean"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("TriggersFollowUp") + .HasColumnType("boolean"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ValueListId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ValueListId", "Value") + .IsUnique(); + + b.ToTable("value_list_items", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.ValueListItemTransition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FromItemId") + .HasColumnType("uuid"); + + b.Property("RequiresApproval") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("ToItemId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ToItemId"); + + b.HasIndex("FromItemId", "ToItemId") + .IsUnique(); + + b.ToTable("value_list_item_transitions", (string)null); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.Absence", b => + { + b.HasOne("OmsorgCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.Contract", b => + { + b.HasOne("OmsorgCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("OmsorgCore.Domain.Entities.Facility", "Facility") + .WithMany() + .HasForeignKey("FacilityId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Employee"); + + b.Navigation("Facility"); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.Document", b => + { + b.HasOne("OmsorgCore.Domain.Entities.User", "UploadedByUser") + .WithMany() + .HasForeignKey("UploadedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("UploadedByUser"); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.EmployeeFacilityDistance", b => + { + b.HasOne("OmsorgCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("OmsorgCore.Domain.Entities.Facility", "Facility") + .WithMany() + .HasForeignKey("FacilityId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("Facility"); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.FacilityContact", b => + { + b.HasOne("OmsorgCore.Domain.Entities.Facility", "Facility") + .WithMany() + .HasForeignKey("FacilityId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Facility"); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.FacilityQualificationRate", b => + { + b.HasOne("OmsorgCore.Domain.Entities.Facility", "Facility") + .WithMany() + .HasForeignKey("FacilityId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Facility"); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.Invoice", b => + { + b.HasOne("OmsorgCore.Domain.Entities.Facility", "Facility") + .WithMany() + .HasForeignKey("FacilityId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Facility"); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.Order", b => + { + b.HasOne("OmsorgCore.Domain.Entities.FacilityContact", "FacilityContact") + .WithMany() + .HasForeignKey("FacilityContactId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("OmsorgCore.Domain.Entities.Facility", "Facility") + .WithMany() + .HasForeignKey("FacilityId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("OmsorgCore.Domain.Entities.ValueListItem", "Status") + .WithMany() + .HasForeignKey("StatusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Facility"); + + b.Navigation("FacilityContact"); + + b.Navigation("Status"); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.PasswordResetCode", b => + { + b.HasOne("OmsorgCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.RefreshToken", b => + { + b.HasOne("OmsorgCore.Domain.Entities.RefreshToken", null) + .WithMany() + .HasForeignKey("ReplacedByTokenId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("OmsorgCore.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.RolePermission", b => + { + b.HasOne("OmsorgCore.Domain.Entities.Role", "Role") + .WithMany("RolePermissions") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.TimeEntry", b => + { + b.HasOne("OmsorgCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("OmsorgCore.Domain.Entities.Order", "Order") + .WithMany() + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("OmsorgCore.Domain.Entities.ValueListItem", "Status") + .WithMany() + .HasForeignKey("StatusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("Order"); + + b.Navigation("Status"); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.User", b => + { + b.HasOne("OmsorgCore.Domain.Entities.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("OmsorgCore.Domain.Entities.Role", "Role") + .WithMany("Users") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Employee"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.UserPermissionOverride", b => + { + b.HasOne("OmsorgCore.Domain.Entities.User", "User") + .WithMany("PermissionOverrides") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.ValueListItem", b => + { + b.HasOne("OmsorgCore.Domain.Entities.ValueList", "ValueList") + .WithMany("Items") + .HasForeignKey("ValueListId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ValueList"); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.ValueListItemTransition", b => + { + b.HasOne("OmsorgCore.Domain.Entities.ValueListItem", "FromItem") + .WithMany() + .HasForeignKey("FromItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("OmsorgCore.Domain.Entities.ValueListItem", "ToItem") + .WithMany() + .HasForeignKey("ToItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("FromItem"); + + b.Navigation("ToItem"); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.Role", b => + { + b.Navigation("RolePermissions"); + + b.Navigation("Users"); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.User", b => + { + b.Navigation("PermissionOverrides"); + }); + + modelBuilder.Entity("OmsorgCore.Domain.Entities.ValueList", b => + { + b.Navigation("Items"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/omsorgCore/src/OmsorgCore.Infrastructure/Persistence/Migrations/20260810182042_RemoveFacilityBreakPolicy.cs b/omsorgCore/src/OmsorgCore.Infrastructure/Persistence/Migrations/20260810182042_RemoveFacilityBreakPolicy.cs new file mode 100644 index 0000000..9aec539 --- /dev/null +++ b/omsorgCore/src/OmsorgCore.Infrastructure/Persistence/Migrations/20260810182042_RemoveFacilityBreakPolicy.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace OmsorgCore.Infrastructure.Persistence.Migrations +{ + /// + public partial class RemoveFacilityBreakPolicy : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "BreakPolicy", + table: "facilities"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "BreakPolicy", + table: "facilities", + type: "character varying(1000)", + maxLength: 1000, + nullable: true); + } + } +}