Architektura Aplikacji Internetowych / Programowanie .NET
Rok akademicki: 2026/27
Zadanie laboratoryjne: System Rezerwacji Terminów Obron Projektów "ProjectDefense" (ASP.NET Core Razor Pages, EF Core, Identity, Minimal API, Lokalizacja i Eksport Dokumentów)
Prowadzący: Tomasz Goluch
Wersja: 1.0
Wymagany standard: C# 12 / .NET 8+
Maksymalna ocena zadania: 110 punktów (100 pkt
podstawowych + 10 pkt bonusowych = 110%)
Forma realizacji: Projekt wielowarstwowy tworzony
od zera (Greenfield)
1. Kontekst Biznesowy i Architektoniczny Projektu
Projektujesz scentralizowany system zarządzania zapisami studentów na
obrony i oddawanie projektów w salach laboratoryjnych wydziału –
ProjectDefense.
Tradycyjne zapisy prowadzone w arkuszach kalkulacyjnych prowadzą do szeregu problemów operacyjnych:
- Nakładające się rezerwacje tej samej sali laboratoryjnej przez różnych prowadzących w tym samym oknie czasowym,
- Brak automatycznego dzielenia przedziałów dyżurów na pojedyncze sloty obron oraz brak blokowania terminów historycznych (wygasłych),
- Rezerwowanie przez jednego studenta wielu terminów „na zapas”, co blokuje miejsca pozostałym uczestnikom kursu,
- Brak ustandaryzowanego interfejsu programistycznego (API) oraz
możliwości wygenerowania oficjalnego arkusza obecności w formacie
.xlsxlub.pdf.
Twoim celem jest zaprojektowanie i zaimplementowanie
wieloprojektowego rozwiązania w technologii ASP.NET Core Razor
Pages (.NET 8/9) połączonego z bazą danych Entity
Framework Core, systemem ról ASP.NET Core
Identity, wielojęzycznością (pl-PL /
en-US), punktami końcowymi Minimal API (OpenAPI /
Swagger), generatorem raportów (ClosedXML /
QuestPDF) oraz dedykowanym klientem
konsolowym.
2. Inicjalizacja Środowiska (Krok 0: Przygotowanie Solucji od Zera)
System musi składać się z minimum 3 projektów
wykonawczych (Web, Core,
ConsoleClient) oraz zalecanego projektu testów
automatycznych (Tests). Otwórz terminal w pustym katalogu
roboczym i wykonaj poniższy skrypt inicjalizacyjny:
# 1. Utworzenie pustego pliku rozwiązania (Solution)
dotnet new sln -n ProjectDefense
# 2. Utworzenie współdzielonej biblioteki klas (Encje domenowe, Kontrakty, obiekty DTO)
dotnet new classlib -n ProjectDefense.Core -f net8.0
# 3. Utworzenie głównej aplikacji internetowej ASP.NET Core Razor Pages z kontami Identity
dotnet new webapp -n ProjectDefense.Web -f net8.0 --auth Individual
# 4. Utworzenie aplikacji klienta konsolowego (.NET 8)
dotnet new console -n ProjectDefense.ConsoleClient -f net8.0
# 5. Utworzenie projektu testów jednostkowych i integracyjnych (xUnit)
dotnet new xunit -n ProjectDefense.Tests -f net8.0
# 6. Dołączenie projektów do rozwiązania
dotnet sln add ProjectDefense.Core/ProjectDefense.Core.csproj
dotnet sln add ProjectDefense.Web/ProjectDefense.Web.csproj
dotnet sln add ProjectDefense.ConsoleClient/ProjectDefense.ConsoleClient.csproj
dotnet sln add ProjectDefense.Tests/ProjectDefense.Tests.csproj
# 7. Konfiguracja referencji między projektami
dotnet add ProjectDefense.Web/ProjectDefense.Web.csproj reference ProjectDefense.Core/ProjectDefense.Core.csproj
dotnet add ProjectDefense.ConsoleClient/ProjectDefense.ConsoleClient.csproj reference ProjectDefense.Core/ProjectDefense.Core.csproj
dotnet add ProjectDefense.Tests/ProjectDefense.Tests.csproj reference ProjectDefense.Core/ProjectDefense.Core.csproj
dotnet add ProjectDefense.Tests/ProjectDefense.Tests.csproj reference ProjectDefense.Web/ProjectDefense.Web.csproj
# 8. Instalacja wymaganych pakietów NuGet w aplikacji Web (Swagger, Eksport XLSX/PDF, SendGrid)
dotnet add ProjectDefense.Web/ProjectDefense.Web.csproj package Swashbuckle.AspNetCore
dotnet add ProjectDefense.Web/ProjectDefense.Web.csproj package ClosedXML
dotnet add ProjectDefense.Web/ProjectDefense.Web.csproj package QuestPDF
dotnet add ProjectDefense.Web/ProjectDefense.Web.csproj package SendGrid
# 9. Instalacja bibliotek testowych
dotnet add ProjectDefense.Tests/ProjectDefense.Tests.csproj package Microsoft.AspNetCore.Mvc.Testing
dotnet add ProjectDefense.Tests/ProjectDefense.Tests.csproj package Microsoft.EntityFrameworkCore.InMemory
dotnet add ProjectDefense.Tests/ProjectDefense.Tests.csproj package FluentAssertions
3. Model Domenowy i Architektura Danych
Współdzielony model danych oraz kontrakty DTO umieszczamy w projekcie
ProjectDefense.Core (dzięki czemu zarówno
aplikacja webowa, jak i klient konsolowy operują na tych samych
definicjach typów):
ApplicationUser(dziedziczący zIdentityUser):- Reprezentuje użytkownika systemu przypisanego do roli
"Student"lub"Lecturer"("Prowadzący"). - Zawiera dodatkowe właściwości profilowe, np.
FullName(Imię i nazwisko),StudentIndexNumber(opcjonalny numer albumu) oraz flagęIsBanned(bool).
- Reprezentuje użytkownika systemu przypisanego do roli
Room(Sala):- Przechowuje ewidencję sal laboratoryjnych:
Id,Name(np. "Laboratorium Sieciowe"),RoomNumber(np. "B214"),WorkstationsCount(liczba stanowisk).
- Przechowuje ewidencję sal laboratoryjnych:
LecturerAvailability(DostępnośćProwadzącego):- Definiuje okno dyżuru prowadzącego w konkretnej sali:
Id,LecturerId(FK doApplicationUser),RoomId(FK doRoom),StartDate(DateOnlylubDateTime),EndDate,DailyStartTime(TimeOnlylubTimeSpan),DailyEndTimeorazSlotDurationMinutes(int, np. 15 min).
- Definiuje okno dyżuru prowadzącego w konkretnej sali:
ReservationSlot(Rezerwacja):- Reprezentuje pojedynczy, atomowy slot czasowy wygenerowany
automatycznie z okna dostępności:
Id,LecturerAvailabilityId(FK),StartTime(DateTime),EndTime(DateTime),IsBlockedByLecturer(bool– np. blokada chorobowa) orazStudentId(nullable FK doApplicationUser– wartośćnulloznacza, że termin jest wolny).
- Reprezentuje pojedynczy, atomowy slot czasowy wygenerowany
automatycznie z okna dostępności:
StudentBan(BlokadaStudenta– opcjonalna encja audytowa lub obsługa na poziomie użytkownika):- Przechowuje informację o nałożonej blokadzie:
Id,StudentId,BannedByLecturerId,Reason(powód blokady),BannedUntil(data wygaśnięcia lub blokada permanentna).
- Przechowuje informację o nałożonej blokadzie:
┌───────────────────────────────────────────────────────────────────────────────────┐
│ ARCHITEKTURA SYSTEMU PROJECTDEFENSE (.NET 8/9) │
├───────────────────────────────────────────────────────────────────────────────────┤
│ [Część 1: 20 pkt] Moduł Prowadzącego ──► CRUD Sal, Generowanie Slotów, Ban/Move│
│ [Część 2: 20 pkt] Moduł Studenta ──► Limit 1 Rezerwacji, Anulowanie, Zmiana│
│ [Część 3: 20 pkt] Walidacja i Konflikty──► Wykrywanie Kolizji Sal, Blokada Przesz│
│ [Część 4: 10 pkt] Identity & E-mail ──► Role (Student/Prowadzący), IEmailSend.│
│ [Część 5: 10 pkt] Internacjonalizacja ──► Kultury pl-PL i en-US, Zasoby .resx │
│ [Część 6: 10 pkt] Minimal API & Swagger──► Endpointy DTO, OpenAPI UI, Walidacja │
│ [Część 7: 10 pkt] Klient Konsolowy ──► HttpClient + System.Text.Json (GET/POST│
│ [Część 8: 10 pkt] Eksport Danych (+10%)──► Generowanie .txt, .xlsx, .pdf w RAM │
└───────────────────────────────────────────────────────────────────────────────────┘
4. Szczegółowe Wymagania i Podział Punktów (Łącznie: 110 pkt / 110%)
Część 1: Moduł Prowadzącego i Automatyczny Generator Slotów (20 pkt)
Prowadzący po zalogowaniu uzyskuje dostęp do panelu zarządzania zasobami laboratoryjnymi oraz harmonogramem obron.
Wymagania szczegółowe:
- [4 pkt] Pełny CRUD dla encji
Room(Sala):- Strony Razor (
Pages/Lecturer/Rooms/Index,Create,Edit,Delete,Details) pozwalające na dodawanie, edycję i usuwanie sal laboratoryjnych.
- Strony Razor (
- [6 pkt] Definiowanie okien dostępności i automatyczny
generator slotów (
ReservationSlot):- Prowadzący wybiera z listy rozwijanej (
<select>oparty oSelectListwPageModel) salę, zakres dat (np. od10.11.2026do12.11.2026), dzienny przedział godzinowy (np.08:00–10:00) oraz czas trwania pojedynczego slotu w minutach (np.15min). - Logika biznesowa: Na podstawie przesłanego
formularza serwis automatycznie oblicza i zapisuje w bazie danych
indywidualne rekordy
ReservationSlot(np.08:00–08:15,08:15–08:30, ...) dla każdego dnia ze wskazanego przedziału.
- Prowadzący wybiera z listy rozwijanej (
- [4 pkt] Przegląd i zarządzanie wszystkimi
rezerwacjami:
- Prowadzący widzi kompletną tabelę rezerwacji z filtrowaniem po sali oraz statusie (kto, w jakiej sali, w jakim terminie).
- Prowadzący może anulować dowolną rezerwację (nawet zajętą przez studenta – slot staje się ponownie wolny) lub przepisać studenta na inny wolny termin.
- [3 pkt] Awaryjna blokada okresu (np. z powodu
choroby):
- Prowadzący może zaznaczyć wybrany przedział czasu i go zablokować
(
IsBlockedByLecturer = true), co automatycznie zwalnia przypisanych tam studentów i uniemożliwia dokonywanie nowych rezerwacji w tym oknie.
- Prowadzący może zaznaczyć wybrany przedział czasu i go zablokować
(
- [3 pkt] Mechanizm blokowania (banowania) studentów:
- Prowadzący może nałożyć blokadę na wybranego studenta
(
IsBanned = truelub dodanie rekorduStudentBan), co uniemożliwia studentowi zarezerwowanie jakiegokolwiek terminu (lub logowanie do systemu).
- Prowadzący może nałożyć blokadę na wybranego studenta
(
💡 Wskazówki implementacyjne: Algorytm generowania slotów z okna dostępności (Rozwiń)
public List<ReservationSlot> GenerateSlots(LecturerAvailability availability)
{
var generatedSlots = new List<ReservationSlot>();
for (var date = availability.StartDate.Date; date <= availability.EndDate.Date; date = date.AddDays(1))
{
var currentSlotStart = date.Add(availability.DailyStartTime.ToTimeSpan());
var dayEnd = date.Add(availability.DailyEndTime.ToTimeSpan());
while (currentSlotStart.AddMinutes(availability.SlotDurationMinutes) <= dayEnd)
{
var currentSlotEnd = currentSlotStart.AddMinutes(availability.SlotDurationMinutes);
generatedSlots.Add(new ReservationSlot
{
LecturerAvailability = availability,
StartTime = currentSlotStart,
EndTime = currentSlotEnd,
IsBlockedByLecturer = false,
StudentId = null
});
currentSlotStart = currentSlotEnd;
}
}
return generatedSlots;
}
Część 2: Moduł Studenta i Reguła Pojedynczej Aktywnej Rezerwacji (20 pkt)
Student po zalogowaniu posiada dostęp wyłącznie do widoku harmonogramu oraz zarządzania własnym terminem obrony.
Wymagania szczegółowe:
- [5 pkt] Filtrowana lista dostępnych terminów:
- Student widzi wyłącznie terminy, które są jednocześnie:
wolne (
StudentId == null), niezablokowane (!IsBlockedByLecturer) oraz znajdują się w przyszłości (StartTime > DateTime.Now).
- Student widzi wyłącznie terminy, które są jednocześnie:
wolne (
- [6 pkt] Egzekwowanie limitu dokładnie jednej aktywnej
rezerwacji:
- Student może posiadać maksymalnie jeden aktywny (przyszły) zarezerwowany slot w całym systemie.
- Próba zarezerwowania drugiego terminu bezpośrednio (lub poprzez
zmanipulowane żądanie POST) musi zostać odrzucona przez warstwę logiki
biznesowej z czytelnym komunikatem błędu w
ModelState/TempData.
- [5 pkt] Anulowanie własnej rezerwacji:
- Student może anulować swoją nadchodzącą rezerwację
(
StudentId = null), co natychmiast zwalnia slot w puli dostępnych terminów dla innych studentów.
- Student może anulować swoją nadchodzącą rezerwację
(
- [4 pkt] Transakcyjna zmiana terminu (Reschedule):
- Student posiadający rezerwację może wybrać funkcję „Zmień termin” i wskazać nowy wolny slot – system w ramach jednej spójnej operacji zwalnia dotychczasowy termin i przypisuje studenta do nowego slotu.
Część 3: Zaawansowana Walidacja, Wykrywanie Kolizji Sal i Wygasanie Slotów (20 pkt)
System musi chronić integralność harmonogramu przed sprzecznymi zakresami dat, nakładaniem się dyżurów w tej samej sali oraz edycją zaszłości historycznych.
Wymagania szczegółowe:
- [5 pkt] Walidacja formularzy i spójności
przedziałów:
- Czas trwania pojedynczego slotu musi być dodatni i mieścić się w
rozsądnym zakresie (np.
[Range(5, 180)]minut). EndDatenie może być wcześniejsza niżStartDate, aDailyEndTimemusi być późniejsza niżDailyStartTimeo co najmniej długość jednego slotu.
- Czas trwania pojedynczego slotu musi być dodatni i mieścić się w
rozsądnym zakresie (np.
- [7 pkt] Zapobieganie konfliktom sal laboratoryjnych (Overlap
Detection):
- Żaden prowadzący nie może zdefiniować nowego okna
LecturerAvailabilitydla danej sali, jeśli w bazie danych istnieje już okno dla tej samej sali, którego zakres dat oraz przedział godzinowy nakładają się (kolidują) z nowym wpisem: \[\text{Konflikt} \iff (\text{Start}_A < \text{End}_B) \land (\text{End}_A > \text{Start}_B)\]
- Żaden prowadzący nie może zdefiniować nowego okna
- [5 pkt] Logika wygasania terminów historycznych (Past Slots
Lock):
- Rezerwacje, które już się odbyły
(
StartTime <= DateTime.Now), są traktowane jako archiwalne. - System musi blokować wszelkie próby anulowania, rezerwacji lub modyfikacji przeszłych slotów zarówno przez studenta, jak i przez prowadzącego (w interfejsie UI oraz w warstwie serwisu).
- Rezerwacje, które już się odbyły
(
- [3 pkt] Ergonomia i czytelność interfejsu
formularzy:
- Listy wyboru slotów (np. podczas przepisywania studenta) muszą
prezentować czytelnie sformatowane etykiety tekstowe, np.:
"Sala B214 (Lab. Sieciowe) – Pn. 16.11.2026, godz. 10:15–10:30".
- Listy wyboru slotów (np. podczas przepisywania studenta) muszą
prezentować czytelnie sformatowane etykiety tekstowe, np.:
Część 4: Uwierzytelnianie, Autoryzacja (Role) i Potwierdzenie E-mail (10 pkt)
Wymagania szczegółowe:
- [3 pkt] Konfiguracja ASP.NET Core Identity:
- Wykorzystanie lokalnego kontekstu
IdentityDbContext<ApplicationUser>z opcjąRequireConfirmedAccount = true. - Automatyczny Data Seeder przy starcie aplikacji tworzący
role
"Lecturer"oraz"Student"wraz z przykładowymi kontami testowymi (aby oceniający mógł od razu zalogować się na oba konta).
- Wykorzystanie lokalnego kontekstu
- [4 pkt] Implementacja usługi
IEmailSender:- Zaimplementuj interfejs
IEmailSenderprzy użyciu wybranej bramki (np. SendGrid, MailGun) lub lokalnego kontenera testowego SMTP (Mailpit / Papercut) z poprawną obsługą treści HTML (text/html, brak uszkodzonych linków z&).
- Zaimplementuj interfejs
- [3 pkt] Ochrona zasobów i ról:
- Zabezpieczenie stron prowadzącego atrybutem
[Authorize(Roles = "Lecturer")](lub konwencjąAuthorizeFolder), a stron studenckich rolą"Student". - Użytkownik niezalogowany nie ma dostępu do żadnych widoków operacyjnych (poza stroną logowania, rejestracji i potwierdzenia e-mail).
- Zabezpieczenie stron prowadzącego atrybutem
Część
5: Internacjonalizacja i Lokalizacja (pl-PL oraz
en-US) (10 pkt)
Wymagania szczegółowe:
- [5 pkt] Konfiguracja kultur i przełącznik języka:
- Obsługa co najmniej dwóch kultur:
pl-PLorazen-USza pomocąUseRequestLocalization. - Umieszczenie w pasku nawigacyjnym
_Layout.cshtmlprzełącznika języka (zapisującego wybór w ciasteczku.AspNetCore.Culturelub przekazującego parametr?culture=...).
- Obsługa co najmniej dwóch kultur:
- [5 pkt] Pliki zasobów
.resxoraz formatowanie dat i komunikatów:- Przetłumaczenie nagłówków stron, etykiet formularzy
(
[Display]) oraz komunikatów walidacji za pomocą plików.resxw kataloguResources/. - Poprawne wyświetlanie oraz walidacja dat i godzin zgodnie z aktywnie wybraną kulturą użytkownika.
- Przetłumaczenie nagłówków stron, etykiet formularzy
(
Część 6: Minimal API, Obiekty DTO i Dokumentacja Swagger (10 pkt)
Wymagania szczegółowe:
- [6 pkt] Punkty końcowe Minimal API:
MapGet("/api/slots/available")– zwraca kolekcję wolnych, przyszłych terminów obron w postaci obiektów DTO (AvailableSlotDto).MapGet("/api/rooms")– zwraca listę sal laboratoryjnych (RoomDto).MapPost("/api/slots/{id:int}/book")– pozwala studentowi zarezerwować wskazany termin na podstawie przesłanego obiektuBookSlotRequestDto(z zachowaniem wszystkich reguł walidacji z Części 2 i 3, w tym limitu 1 aktywnej rezerwacji!).
- [2 pkt] Integracja OpenAPI / Swagger:
- Rejestracja
AddEndpointsApiExplorer(),AddSwaggerGen(),UseSwagger()orazUseSwaggerUI().
- Rejestracja
- [2 pkt] Czysta separacja DTO:
- Całkowity zakaz zwracania surowych encji bazodanowych EF Core bezpośrednio z punktów końcowych API.
Część 7:
Aplikacja Klienta Konsolowego (.NET 8) (10 pkt)
Wymagania szczegółowe:
- [5 pkt] Pobieranie danych z API (
GET):- Aplikacja konsolowa (
ProjectDefense.ConsoleClient) łączy się z uruchomionym serwerem webowym przy użyciuHttpClientorazGetFromJsonAsynci wyświetla w terminalu sformatowaną tabelę dostępnych sal oraz wolnych terminów.
- Aplikacja konsolowa (
- [5 pkt] Dokonywanie rezerwacji z konsoli
(
POST):- Po podaniu identyfikatora/adresu e-mail studenta oraz ID wybranego
slotu, klient wysyła żądanie
PostAsJsonAsyncdo/api/slots/{id}/booki czytelnie prezentuje odpowiedź serwera (sukces 200 OK lub komunikat błędu biznesowego 400/409).
- Po podaniu identyfikatora/adresu e-mail studenta oraz ID wybranego
slotu, klient wysyła żądanie
Część
8: Eksport Harmonogramu Rezerwacji (.txt,
.xlsx, .pdf) (10 pkt – Bonus do 110%)
Wymagania szczegółowe:
- [2 pkt] Formularz filtrowania eksportu w panelu
prowadzącego:
- Prowadzący wybiera salę laboratoryjną, zakres dat oraz docelowy format pliku.
- [2 pkt] Eksport do pliku tekstowego
(
.txt):- Wygenerowanie czytelnego zestawienia tekstowego w kodowaniu UTF-8
(
text/plain).
- Wygenerowanie czytelnego zestawienia tekstowego w kodowaniu UTF-8
(
- [3 pkt] Eksport do arkusza kalkulacyjnego Excel
(
.xlsx):- Wykorzystanie biblioteki ClosedXML do utworzenia sformatowanej tabeli z pogrubionym nagłówkiem i dopasowaną szerokością kolumn.
- [3 pkt] Eksport do dokumentu PDF
(
.pdf):- Wykorzystanie biblioteki QuestPDF
(
LicenseType.Community) do wygenerowania oficjalnego arkusza obron gotowego do wydruku i wywieszenia na drzwiach laboratorium.
- Wykorzystanie biblioteki QuestPDF
(
5. Wymagania Dotyczące Pakietu Testów Automatycznych
W projekcie ProjectDefense.Tests przygotuj zestaw testów
weryfikujących kluczowe reguły domenowe:
SlotGenerationTests(Testy jednostkowe):- Sprawdzenie, czy dla okna
08:00–10:00i czasu slotu15 minprzez2 dnigenerator tworzy dokładnie16niepokrywających się slotów.
- Sprawdzenie, czy dla okna
RoomConflictDetectionTests(Testy jednostkowe / InMemory EF Core):- Weryfikacja, czy próba dodania nakładającej się dostępności w tej
samej sali (np.
09:00–11:00przy istniejącym08:00–10:00) zostaje odrzucona.
- Weryfikacja, czy próba dodania nakładającej się dostępności w tej
samej sali (np.
StudentBookingRulesTests:- Sprawdzenie, że student posiadający już aktywną rezerwację nie może zarezerwować drugiego slotu oraz że próba modyfikacji slotu z przeszłości kończy się błędem.
MinimalApiIntegrationTests(WebApplicationFactory):- Test integracyjny wywołujący
GET /api/roomsorazGET /api/slots/availablei weryfikujący status200 OKoraz poprawną strukturę JSON DTO.
- Test integracyjny wywołujący
6. Tabela Podsumowująca Oceny (Suma: 110 pkt / 110%)
| Nr | Zakres modułu | Weryfikowane mechanizmy, technologie i standardy | Punkty |
|---|---|---|---|
| 1 | Moduł Prowadzącego | CRUD sal, formularz z
SelectList, automatyczne generowanie slotów, anulowanie,
przepisywanie i banowanie. |
20 pkt |
| 2 | Moduł Studenta | Widok wolnych przyszłych terminów, blokada >1 rezerwacji na studenta, anulowanie i zmiana terminu. | 20 pkt |
| 3 | Walidacja i Logika Domenowa | Walidacja przedziałów dat/godzin, wykrywanie kolizji sal (Overlap), blokada edycji przeszłych slotów. | 20 pkt |
| 4 | Uwierzytelnianie i Autoryzacja | IdentityDbContext, role
Student i Lecturer, IEmailSender,
pełna blokada dostępu dla niezalogowanych. |
10 pkt |
| 5 | Internacjonalizacja (I18n) | Wsparcie kultur pl-PL i
en-US, pliki .resx, lokalizacja dat, godzin i
komunikatów walidacji. |
10 pkt |
| 6 | Minimal API i Swagger | Endpointy
GET/POST, separacja obiektów DTO, konfiguracja
OpenAPI / Swagger UI. |
10 pkt |
| 7 | Klient Konsolowy (.NET 8) | Komunikacja przez HttpClient
+ System.Net.Http.Json, przeglądanie terminów i rezerwacja
slotu. |
10 pkt |
| 8 | Eksport Danych (Bonus +10%) | Strumieniowy eksport harmonogramu sali i
zakresu dat do formatów .txt, .xlsx
(ClosedXML) i .pdf (QuestPDF). |
10 pkt |
| SUMA | (100 pkt = 100% oceny podstawowej + 10 pkt bonusowych) | 110 pkt (110%) |