Jak stworzyłam model-driven app do zarządzania wydarzeniami w organizacji

Model-driven apps w Power Apps to rozwiązania oparte na danych, które pozwalają tworzyć zaawansowane aplikacje biznesowe bez konieczności pisania dużej ilości kodu. Bazują na strukturze danych w Dataverse oraz umożliwiają m.in zarządzanie rekordami, tworzenie formularzy, widoków, dashboardów, implementowanie logiki biznesowej i uprawnień użytkowników.
Projekt opisany we wpisie to aplikacja EventFlow, której celem jest kompleksowe zarządzanie wydarzeniami w organizacji.
1. Założenia projektu
Celem projektu jest stworzenie profesjonalnej aplikacji model-driven, która umożliwi pracownikom firmy:
Zarządzanie wydarzeniami firmowymi (np. szkolenia, konferencje, integracje).
Rejestrację uczestników i śledzenie frekwencji w odniesieniu do dostępnej liczby miejsc wydarzenia.
Dodawanie anonimowych ocen po zakończeniu wydarzenia.
Wizualizację danych dotyczących wydarzeń, uczestników i ocen w widokach tabel i dashboardach.
Tworząc aplikację należy pamiętać o następujących założeniach:
Uprawnienia użytkowników
Zwykły użytkownik może zapisać się na wydarzenie, dodać ocenę wydarzenia oraz przeglądać listę wydarzeń.
Tylko użytkownicy z rolą Organizatora mogą tworzyć i zarządzać wydarzeniami.
Daty wydarzenia
Wydarzenie nie może zostać utworzone z datą wsteczną.
Data zakończenia wydarzenia musi być późniejsza niż data jego rozpoczęcia.
Statusy wydarzeń
Każde wydarzenie może znajdować się w jednym z następujących statusów: “Planned”, “Registration”, “Full”, “In Progress”, “Completed”.
Status “Full” ustawiany jest automatycznie, gdy wydarzenie znajduje się w fazie “Registration” i liczba dostępnych miejsc wynosi 0.
Status “In Progress” ustawiany jest automatycznie, gdy bieżąca data mieści się w przedziale między datą rozpoczęcia a datą zakończenia wydarzenia.
Status “Completed” ustawiany jest automatycznie po upływie daty i godziny zakończenia wydarzenia.
Dostępność miejsc wydarzenia
Każde wydarzenie ma określoną maksymalną liczbę miejsc.
Liczba dostępnych miejsc wyliczana jest automatycznie na podstawie całkowitej pojemności oraz liczby zapisanych uczestników.
Użytkownik może zapisać się tylko na wydarzenie w statusie “Registration” i jedynie wtedy, gdy liczba dostępnych miejsc jest większa od zera.
Oceny wydarzeń
- Dodanie oceny do wydarzenia możliwe jest wyłącznie po jego zakończeniu (status “Completed”).
2. Tabele i relacje
Sercem każdej aplikacji model-driven są dane przechowywane w Dataverse. Dataverse to nowoczesna platforma do przechowywania danych w chmurze Microsoft, która umożliwia tworzenie tabel, definiowanie atrybutów, relacji oraz logiki biznesowej. Aplikacje model-driven mogą w prosty sposób korzystać z tych danych, prezentować je w widokach, formularzach i dashboardach oraz integrować z innymi aplikacjami i usługami w ekosystemie Microsoft Power Platform.
2.1. Tabela Event
Ta tabela zawiera wszystkie informacje dotyczące wydarzeń w firmie. Zawiera kolumny takie jak:
Event Indicator - (Text) Primary Name Column, nazwa wydarzenia + data, rekord służący do wyszukiwania wydarzenia w Lookupach (wymagane)
Event Name - (Text) nazwa wydarzenia (wymagane)
Start Date - (Date and time) początek wydarzenia (wymagane)
End Date - (Date and time) koniec wydarzenia (wymagane)
Capacity - (Whole number) maksymalna liczba uczestników wydarzenia (wymagane)
Event Status - (Choice) status wydarzenia: “Planned“, “Registration“, “Full“, “In Progress“, “Completed“ (wymagane)
Organizer - (Lookup do systemowej tabeli Contact) osoba organizująca wydarzenie (wymagane)
Available Seats - (Whole number - Calculated: Capacity - Number of Participants) dostępna liczba miejsc do zapisania
Average Rating - (Decimal - Rollup: średnia Feedback.Rating dla powiązanych rekordów tabeli Event) średnia ocen
Number Of Participants - (Whole number - Rollup: Policz powiązane rekordy z tabeli Participant gdzie Registration Status ≠ Cancelled) liczba zapisanych uczestników
Number Of Responses - (Whole number - Rollup: Policz powiązane rekordy z tabeli Feedback) liczba wystawionych ocen
2.2. Tabela Participant
Ta tabela zawiera wszystkie informacje na temat uczestników wydarzeń. Zawiera kolumny takie jak:
First Name - (Text) imię uczestnika (wymagane)
Last Name - (Text) nazwisko uczestnika (wymagane)
Email - (Email) adres email uczestnika (wymagane)
Phone - (Phone number) telefon kontaktowy uczestnika (wymagane)
Registration Status - (Choice) status deklaracji uczestnictwa: “Registered“, “Confirmed“, “Canceled“ (wymagane)
Event - (Lookup do tabeli Event) wydarzenie na które zapisuje się uczestnik (wymagane)
2.3. Tabela Feedback
Ta tabela zawiera wszystkie informacje na temat anonimowych ocen wydarzeń. Zawiera kolumny takie jak:
Rating - (Whole number) ocena wydarzenia od 1 do 5 (wymagane)
Comment - (Text) komentarz do oceny (opcjonalne)
Event - (Lookup do tabeli Event) oceniane wydarzenie (wymagane)
2.4. Relacje między tabelami
Tabele w aplikacji są ze sobą ściśle powiązane, co umożliwia tworzenie spójnego modelu danych:
Event (1:N) Participant – jedno wydarzenie może mieć wielu uczestników, uczestnik należy zawsze do jednego wydarzenia.
Event (1:N) Feedback – jedno wydarzenie może mieć wiele ocen, ocena zawsze przypisana jest do jednego wydarzenia.
Dzięki temu model danych jest prosty, a jednocześnie pozwala na:
monitorowanie frekwencji,
kontrolę dostępnych miejsc,
analizę ocen i rankingów wydarzeń.
3. Widoki
W aplikacjach model-driven bardzo ważnym elementem są widoki (Views), które pozwalają użytkownikowi szybko przeglądać i analizować dane. W projekcie EventFlow przygotowałam zestaw widoków dla każdej z tabel.
3.1. Widoki tabeli Event
Upcoming Events – prezentuje nadchodzące wydarzenia, które mają status Planned, Registration lub Full sortowane po dacie rozpoczęcia wydarzenia rosnąco.
Completed Events – pokazuje tylko zakończone wydarzenia, które mają status Completed. Widok posortowany jest według daty zakończenia malejąco.
Events - All – pełny katalog wszystkich wydarzeń, który jest domyślnym widokiem tabeli Event.
3.2. Widoki tabeli Participant
Participants – Active – pokazuje wszystkich uczestników z aktywnym statusem rejestracji (Registered, Confirmed).
Participants - All – pełny katalog wszystkich uczestników, niezależnie od statusu rejestracji. Domyślny widok dla tabeli Participant.
3.3. Widoki tabeli Feedback
Feedback – All – lista wszystkich ocen wystawionych w aplikacji. Domyślny widok dla tabeli Feedback.
Feedback – By Event – widok pogrupowany według wydarzeń, co umożliwia szybkie porównanie ocen między poszczególnymi szkoleniami.
4. Formularze
Formularze w aplikacji model-driven pełnią kluczową rolę w pracy użytkowników – to właśnie one stanowią główny interfejs do przeglądania i edycji danych zapisanych w tabelach Dataverse. Odpowiednio zaprojektowany formularz nie tylko pozwala na wprowadzanie i aktualizację rekordów, ale także prowadzi użytkownika przez proces biznesowy, ukrywając pola zbędne w danym kontekście i eksponując te, które są najważniejsze. Dzięki zakładkom, sekcjom, subgridom i regułom widoczności możliwe jest stworzenie interfejsu czytelnego, intuicyjnego i zgodnego z logiką aplikacji.
4.1. Formularz tabeli Event
Formularz podzielono na 4 sekcje:
Event Details - zawiera podstawowe informacje dotyczące wydarzenia: Event Name, Organizer, Start Date, End Date, Capacity, Event Status.
Metrics - zawiera kluczowe wskaźniki liczbowe: Number Of Participants, Available Seats, Average Rating
Participants - subgrid wskazujący uczestników przypisanych do danego wydarzenia ze statusem uczestnictwa innym niż “Cancelled”. Formularz zawiera funkcję “Add New” umożliwiającą na szybkie stworzenie nowego rekordu uczestnika dla wybranego wydarzenia.
Feedback - subgrid wskazujący na oceny powiązane z wydarzeniem. Formularz zawiera funkcję “Add New” umożliwiającą na szybkie stworzenie nowego rekordu oceny dla wybranego wydarzenia.
Logika widoczności danych pól i sekcji w formularzu została zaimplementowana w skrypcie eventForm.js który został dodany jako Web resource.
Biorąc pod uwagę, że sekcja Metrics zawiera jedynie pola obliczeniowe, nie jest ona widoczna w momencie tworzenia nowego rekordu. Logika ta została zaimplementowana w funkcji setMetricsSectionVisibility(), gdzie sprawdzany jest typ formularza (wartość 1 wskazuje na formularz tworzenia rekordu) i na jego podstawie ustawiana jest widoczność sekcji. Funkcja ta została podpięta do zdarzenia OnLoad formularza.
function setMetricsSectionVisibility(executionContext) {
const formContext = executionContext.getFormContext();
const formType = formContext.ui.getFormType();
const metricsSection = formContext.ui.tabs
.get("General")
.sections.get("metrics_section");
if (metricsSection) {
if (formType === 1) {
metricsSection.setVisible(false);
} else {
metricsSection.setVisible(true);
}
}
}
Dodatkowo dodana została funkcja setAvaliableSeatsVisibility(), odpowiadająca za widoczność pola Available Seats. Sprawdzany jest typ formularza (czy jest to formularz edycji rekordu lub read-only) a następnie status wydarzenia. Biorąc pod uwagę nieistotność wartości odpowiadającej za dostępność miejsc dla wydarzenia które jest już zakończone, wartość ta jest ukrywana gdy status wydarzenia jest “Completed”.
function setAvaliableSeatsVisibility(executionContext) {
const formContext = executionContext.getFormContext();
const formType = formContext.ui.getFormType();
if (formType === 2 || formType === 3) {
const availableSeats = formContext.getControl("kr_availableseats");
const eventStatus = formContext.getAttribute("kr_eventstatus").getValue();
if (availableSeats && eventStatus) {
if (eventStatus !== 124070004) {
availableSeats.setVisible(true);
} else {
availableSeats.setVisible(false);
}
}
}
}
Kolejnym polem obliczeniowym sekcji Metrics, którego widoczność została uwarunkowana w skrypcie, jest pole AverageRating. Biorąc pod uwagę, że użytkownik nie może dodać oceny do wydarzenia którego status jest inny niż “Completed“ pole to jest ukrywane do momentu aż wydarzenie osiągnie wspomniany status.
function setAverageRatingVisibility(executionContext) {
const formContext = executionContext.getFormContext();
const formType = formContext.ui.getFormType();
if (formType === 2 || formType === 3) {
const averageRating = formContext.getControl("kr_averagerating");
const eventStatus = formContext.getAttribute("kr_eventstatus").getValue();
if (averageRating && eventStatus) {
if (eventStatus === 124070004) {
averageRating.setVisible(true);
} else {
averageRating.setVisible(false);
}
}
}
}
Jeżeli chodzi o widoczność sekcji Participants, zastosowano bliźniaczą logikę jak w przypadku sekcji Metrics.
function setparticipantsSectionVisibility(executionContext) {
const formContext = executionContext.getFormContext();
const formType = formContext.ui.getFormType();
const participantsSection = formContext.ui.tabs
.get("General")
.sections.get("participants_section");
if (participantsSection) {
if (formType === 1) {
participantsSection.setVisible(false);
} else {
participantsSection.setVisible(true);
}
}
}
Nieco inne podejście zastosowano przy widoczności sekcji Feedback. Sprawdzany jest typ formularza (czy jest to formularz edycji rekordu lub read-only) oraz status wydarzenia. Sekcja ta jest widoczna jedynie przy statusie wydarzenia “Completed“ - biorąc pod uwagę fakt, że użytkownik nie może dodawać oceny do wydarzenia z innym statusem.
function setFeedbackSectionVisibility(executionContext) {
const formContext = executionContext.getFormContext();
const formType = formContext.ui.getFormType();
const feedbackSection = formContext.ui.tabs
.get("General")
.sections.get("feedback_section");
if (formType === 2 || formType === 3) {
const eventStatus = formContext.getAttribute("kr_eventstatus").getValue();
if (eventStatus === 124070004) {
feedbackSection.setVisible(true);
} else {
feedbackSection.setVisible(false);
}
} else {
feedbackSection.setVisible(false);
}
}
4.2. Formularz Tabeli Participants
Formularz podzielono na 2 sekcje:
Participant Details - zawiera podstawowe dane osobowe uczestnika oraz status uczestnictwa: First Name, Last Name, Email, Phone, Registration Status
Event Info - zawiera informacje o wydarzeniu w którym użytkownik chce uczestniczyć: Event
4.3. Formularz Tabeli Feedback
Formularz podzielono na 2 sekcje:
Feedback Details - zawiera ocenę wydarzenia oraz opcjonalny komentarz: Rating, Comment
Event Info - zawiera informacje o wydarzeniu którego dotyczy ocena: Event
5. Walidacja formularzy
Walidacja formularzy w aplikacjach model-driven jest kluczowa, aby zapewnić spójność i poprawność danych wprowadzanych przez użytkowników. W projekcie EventFlow logika walidacyjna została zrealizowana głównie z wykorzystaniem skryptów JavaScript, które pozwalają na implementację niestandardowych reguł biznesowych. Dodatkowo, w celu zaprezentowania możliwości konfiguracji wizualnej, w aplikacji została zaimplementowana jedna reguła biznesowa (Business Rule).
5.1. Walidacja formularza tabeli Event
Walidacja w formularzu wydarzeń polega głównie na walidacji daty. Należało pamiętać o kilku zasadach. Między innymi o uniemożliwieniu użytkownikowi wprowadzenia daty zakończenia wydarzenia późniejszej niż daty rozpoczęcia. Walidacja ta została zaimplementowana za pomocą Business Rule w tabeli Event.
Kolejną zasadą jest uniemożliwienie użytkownikowi dodania wydarzenia z datą wsteczną. Walidacja ta została zaimplementowana w funkcji validateEventDate() w skrypcie eventForm.js. Funkcja ta została podpięta do zdarzenia OnChange wartości Start Date.
function validateEventDate(executionContext) {
const formContext = executionContext.getFormContext();
const startDateValue = formContext.getAttribute("kr_startdate").getValue();
const startDateCtrl = formContext.getControl("kr_startdate");
if(startDateValue &&startDateCtrl ) {
const now = new Date();
if(startDateValue < now) {
startDateCtrl.setNotification("Start Date cannot be in the past.", "startDateError");
} else {
startDateCtrl.clearNotification("startDateError");
}
}
}
Dodatkowo zaimplementowana została funkcja setEndDateSugestion() która ustawia datę zakończenia wydarzenia na datę początkową, po wybraniu wartości Star Date. Z reguły wydarzenia kończą się tego samego dnia, dzięki temu wybór zakończenia wydarzenia najprawdopodobniej ograniczy się do ustawiania odpowiedniej godziny zakończenia. Funkcja ta została podpięta do zdarzenia OnChange wartości StartDate.
function setEndDateSuggestion(executionContext) {
const formContext = executionContext.getFormContext();
const startDate = formContext.getAttribute("kr_startdate").getValue();
const endDate = formContext.getAttribute("kr_enddate");
if (startDate && endDate) {
endDate.setValue(startDate);
endDate.setSubmitMode("always");
}
}
5.2. Walidacja formularza tabeli Participant
Walidacja formularza tabeli Participant została zaimplementowana w skrypcie participantForm.js który został dodany jako jeden z Web resource projektu.
Funkcja validatePhoneNumber() sprawdza czy wprowadzony numer telefonu użytkownika ma poprawny format (zgodny z zasadami polskich numerów telefonów). Funkcja ta została dodana do zdarzenia OnChange pola Phone oraz OnSave formularza.
function validatePhoneNumber(executionContext) {
const formContext = executionContext.getFormContext();
const phoneAttr = formContext.getAttribute("kr_phone");
const ctrl = formContext.getControl("kr_phone");
if (!phoneAttr || !ctrl) return;
const phoneValue = phoneAttr.getValue();
const phoneRegex = /^(?:\+48|0048)?[\s-]?\d{3}[\s-]?\d{3}[\s-]?\d{3}$/;
if (phoneValue && !phoneRegex.test(phoneValue)) {
ctrl.setNotification("Wrong phone number.", "phoneError");
} else {
ctrl.clearNotification("phoneError");
}
}
Oprócz tego, w skrypcie zaimplementowano funkcje setLookupFilter() oraz validateEventOnSave().
Funkcja setLookupFilter() ogranicza listę wydarzeń widocznych w polu Event tylko do tych, które mają status “Registration” oraz posiadają wolne miejsca (Available Seats ≠ 0). Funkcja ta jest podłączona do zdarzenia OnLoad formularza.
function setEventLookupFilter(executionContext) {
const formContext = executionContext.getFormContext();
const eventLookupAttr = formContext.getAttribute("kr_event");
const eventLookupCtrl = formContext.getControl("kr_event");
if (eventLookupAttr && eventLookupCtrl) {
const registrationEventStatus = 124070001;
eventLookupCtrl.addPreSearch(function () {
const filter = `
<filter type="and">
<condition attribute="kr_eventstatus" operator="eq" value="${registrationEventStatus}" />
<condition attribute="kr_availableseats" operator="neq" value="0" />
</filter>
`;
eventLookupCtrl.addCustomFilter(filter);
});
}
}
Z kolei validateEventOnSave() działa podczas zapisywania formularza (zdarzenie OnSave) uczestnika i sprawdza, czy wybrane wydarzenie spełnia warunki rejestracji. Weryfikuje ona, czy status wydarzenia to “Registration” oraz czy istnieją wolne miejsca. Jeśli którykolwiek z warunków nie jest spełniony, zapis zostaje zablokowany, a użytkownik otrzymuje komunikat ostrzegawczy. Ze względu na ograniczenia planu Developer, który nie obsługuje wywołań asynchronicznych, implementacja musiała zostać wykonana w trybie synchronicznym.
Funkcja ta została zaimplementowana, ponieważ użytkownik ma możliwość dodania uczestnika bezpośrednio z poziomu widoku wydarzenia. W takiej sytuacji istnieje ryzyko, że uczestnik zostanie przypisany do wydarzenia niespełniającego założonych warunków (np. o statusie innym niż “Registration” lub z brakiem dostępnych miejsc). Funkcja ta pełni rolę mechanizmu zabezpieczającego – wymusza ponowną weryfikację stanu wydarzenia w momencie zapisu i zapobiega wprowadzeniu niepoprawnych danych do systemu.
function validateEventOnSave(executionContext) {
const formContext = executionContext.getFormContext();
const selectedEvent = formContext.getAttribute("kr_event").getValue();
if (!selectedEvent) return;
const eventId = selectedEvent[0].id.replace(/[{}]/g, "");
const req = new XMLHttpRequest();
req.open("GET", Xrm.Utility.getGlobalContext().getClientUrl() +
`/api/data/v9.2/kr_events(${eventId})?$select=kr_eventstatus,kr_availableseats`, false);
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.send();
if (req.status === 200) {
const eventRecord = JSON.parse(req.responseText);
const eventStatus = eventRecord["kr_eventstatus"];
const availableSeats = eventRecord["kr_availableseats"];
if (eventStatus !== 124070001 || availableSeats === 0) {
executionContext.getEventArgs().preventDefault();
Xrm.Navigation.openAlertDialog({
text: "Cannot register - the event is not in Registration status or there are no available seats."
});
}
} else {
console.error("Error fetching event:", req.statusText);
executionContext.getEventArgs().preventDefault();
}
}
5.3. Walidacja formularza tabeli Feedback
Walidacja formularza tabeli Feedback została zaimplementowana w skrypcie feedbackForm.js, dodanym jako zasób typu Web Resource. Podobnie jak w przypadku formularza Participants, konieczne było ograniczenie listy dostępnych wydarzeń w polu Event tak, aby użytkownik mógł powiązać ocenę wyłącznie z poprawnym rekordem. Zgodnie z założeniami, oceny mogą być dodawane tylko do wydarzeń o statusie „Completed”.
Logika ta została zaimplementowana w funkcji setEventLookupFilter(), wywoływanej podczas zdarzenia OnLoad formularza. Funkcja dynamicznie filtruje zawartość pola lookup i udostępnia użytkownikowi jedynie wydarzenia zakończone.
function setEventLookupFilter(executionContext) {
const formContext = executionContext.getFormContext();
const eventLookupAttr = formContext.getAttribute("kr_event");
const eventLookupCtrl = formContext.getControl("kr_event");
if (eventLookupAttr && eventLookupCtrl) {
const completeEventStatus = 124070004;
eventLookupCtrl.addPreSearch(function () {
const filter = `
<filter type="and">
<condition attribute="kr_eventstatus" operator="eq" value="${completeEventStatus}" />
</filter>
`;
eventLookupCtrl.addCustomFilter(filter);
});
}
}
Warto zauważyć, że w tym przypadku nie ma konieczności dodawania dodatkowych mechanizmów zabezpieczających (tak jak w tabeli Participants). Wynika to z faktu, iż subgrid tabeli Feedback na formularzu Event jest widoczny wyłącznie wtedy, gdy wydarzenie posiada status „Completed”. Tym samym sama logika aplikacji uniemożliwia dodanie niepoprawnego wpisu.
6. Tworzenie wartości Event Indicator (Primary Name Column dla tabeli Event)
W każdej tabeli w Dataverse istnieje jedno pole oznaczone jako Primary Name Column. Jest to kolumna główna, której wartości służą m.in do identyfikowania rekordów w interfejsie użytkownika (np. w polach typu lookup).
W tabeli Event funkcję tę pełni pole Event Indicator. Funkcja setEventIndicator() została stworzona w celu automatycznego generowania spójnej wartości dla tego pola, bez konieczności ingerencji użytkownika. Dzięki temu wartość jest łatwa do zidentyfikowania podczas dodawania oceny lub rejestracji uczestnika na wydarzenie. Sama nazwa wydarzenia może być niewystarczająca, ponieważ istnieje prawdopodobieństwo, że w dłuższej perspektywie czasu powstaną dwa wydarzenia o takiej samej nazwie (np. cykliczne szkolenia lub warsztaty).
Z tego powodu pole Event Indicator budowane jest w oparciu o dwie wartości: Event Name oraz Start Date. Dzięki temu każdemu rekordowi przypisany zostaje jednoznaczny identyfikator (np. Warsztaty CRM 15-03-2025), co ułatwia rozróżnianie poszczególnych wydarzeń.
function setEventIndicator(executionContext) {
const formContext = executionContext.getFormContext();
const eventName = formContext.getAttribute("kr_eventname").getValue();
const startDate = formContext.getAttribute("kr_startdate").getValue();
if (eventName && startDate) {
const day = String(startDate.getDate()).padStart(2, "0");
const month = String(startDate.getMonth() + 1).padStart(2, "0");
const year = startDate.getFullYear();
formContext
.getAttribute("kr_eventnumber")
.setValue(eventName + " " + `${day}-${month}-${year}`);
formContext.getAttribute("kr_eventnumber").setSubmitMode("always");
}
}
Funkcja została powiązana ze zdarzeniem OnSave formularza, dzięki czemu wartość pola Event Indicator generowana jest automatycznie w momencie zapisywania rekordu do bazy danych.
7. Zarządzanie statusami wydarzenia
W projekcie przewidziano funkcję automatycznego ustawiania statusów wydarzeń, która ułatwia monitorowanie cyklu życia każdego eventu. Na potrzeby naszego projektu została zaimplementowana funkcja setProperEventStatus().
Funkcja ta pobiera daty rozpoczęcia i zakończenia wydarzenia, aktualny status oraz liczbę dostępnych miejsc i na tej podstawie ustawia status wydarzenia według następujących zasad:
Jeśli wydarzenie jest w statusie „Registration” i nie ma już wolnych miejsc, status zmienia się na „Full”.
Jeśli aktualna data mieści się pomiędzy datą rozpoczęcia a datą zakończenia wydarzenia, status zmienia się na „In Progress”.
Jeśli data zakończenia wydarzenia jest wcześniejsza niż aktualna data, status ustawia się na „Completed”.
Funkcja została podpięta do zdarzeń OnLoad i OnSave formularza oraz OnChange wartości Start Date. Dzięki temu status wydarzenia jest aktualizowany zarówno w momencie otwarcia rekordu, jak i podczas jego zapisywania, a także przy zmianie daty rozpoczęcia, co zapewnia częściową automatyzację cyklu życia wydarzenia.
function setProperEventStatus(executionContext) {
const formContext = executionContext.getFormContext();
const startDateValue = formContext.getAttribute("kr_startdate").getValue();
const endDateValue = formContext.getAttribute("kr_enddate").getValue();
const eventStatusAtt = formContext.getAttribute("kr_eventstatus");
const eventStatusValue = eventStatusAtt ? eventStatusAtt.getValue() : null;
const availableSeatsValue = formContext
.getAttribute("kr_availableseats")
.getValue();
const statuses = {
registration: 124070001,
full: 124070005,
inProgress: 124070003,
complete: 124070004,
};
if (startDateValue && endDateValue && eventStatusAtt !== null) {
const now = new Date();
if (
eventStatusValue === statuses.registration &&
availableSeatsValue === 0
) {
eventStatusAtt.setValue(statuses.full);
} else if (startDateValue <= now && endDateValue >= now) {
eventStatusAtt.setValue(statuses.inProgress);
} else if (endDateValue < now) {
eventStatusAtt.setValue(statuses.complete);
}
}
}
Należy zaznaczyć, że jest to rozwiązanie nieidealne – statusy są aktualizowane wyłącznie w momencie wejścia w formularz danego rekordu. W sytuacji, gdy użytkownik nie otwiera rekordu, status nie zostaje automatycznie zaktualizowany.
Idealnym rozwiązaniem byłoby wykorzystanie funkcji Azure uruchamianej okresowo (np. co pół godziny), która przechodziłaby przez wszystkie rekordy w tabeli Event i automatycznie aktualizowała statusy zgodnie z logiką biznesową. Niestety, ze względu na ograniczenia planu deweloperskiego nie mamy dostępu do pełnej funkcjonalności chmurowej, w tym do cyklicznych funkcji Azure, co wymusiło implementację rozwiązania w postaci skryptu uruchamianego lokalnie na formularzu.
8. Wykresy i dashboardy
W aplikacjach model-driven wykresy i dashboardy pełnią kluczową rolę w prezentacji danych w sposób wizualny i przystępny. Dzięki nim użytkownicy mogą szybko analizować informacje, wyciągać wnioski i podejmować decyzje bez konieczności przeglądania długich list rekordów.
Wykresy (Charts) – to wizualizacje oparte na danych z tabel, które pozwalają przedstawić trendy, porównania czy rankingi. Mogą być wyświetlane samodzielnie lub jako część formularza czy dashboardu.
Dashboardy – to zbiory widoków, wykresów i list, które w jednym miejscu dostarczają użytkownikowi całościowego obrazu sytuacji biznesowej. Dzięki nim możliwe jest monitorowanie kluczowych wskaźników w czasie rzeczywistym.
W projekcie EventFlow przygotowano główny dashboard, którego celem jest dostarczenie organizatorom i uczestnikom szybkiego wglądu w najważniejsze informacje o wydarzeniach:
Wykres 1 – Top Rated Events - Ranking pięciu najlepiej ocenianych wydarzeń. Dzięki niemu można szybko sprawdzić, które szkolenia czy konferencje były najlepiej odbierane przez uczestników.
Widok Upcoming Events - Lista wszystkich zaplanowanych wydarzeń, które jeszcze się nie rozpoczęły. Widok ten umożliwia szybkie sprawdzenie harmonogramu i dostępności miejsc.
Wykres 2 – Attendance - Ranking pięciu wydarzeń z największą frekwencją. Wykres ten pozwala ocenić, które wydarzenia cieszyły się największym zainteresowaniem.
9. Business Process Flow
Business Process Flow w aplikacjach model-driven to mechanizm pozwalający definiować i wizualizować kolejne etapy procesu biznesowego, które użytkownik powinien wykonać podczas pracy z rekordem. BPF prowadzi użytkownika krok po kroku, podpowiadając wymagane pola i działania na każdym etapie, a także zapewnia spójność danych i ułatwia ich kompletność.
W aplikacji EventFlow zdefiniowano Business Process Flow o nazwie Event Lifecycle Process, który odzwierciedla cykl życia wydarzenia i prowadzi użytkownika przez kolejne etapy.
Planning – pierwszy etap, w którym użytkownik wprowadza podstawowe informacje o wydarzeniu. Wymagane pola to: Event Name, Start Date, End Date, Capacity oraz Organizer.
Event Preparation – etap przygotowania wydarzenia, w którym należy określić status wydarzenia.
Post-Event Evaluation – etap końcowy, odpowiadający zakończeniu wydarzenia i możliwości jego oceny. Do jego zamknięcia konieczne jest aby rekord posiadał wartości w polach: Average Rating oraz Number Of Responses, co umożliwia pełną ocenę i analizę wydarzenia.
Event Lifecycle Process wspiera użytkowników w uporządkowany sposób, od planowania po ocenę wydarzenia, odzwierciedlając rzeczywiste etapy cyklu życia wydarzenia.
10. Site Map
W aplikacjach model-driven nawigacja opiera się na tzw. Site Map – jest to mapa witryny, która określa, jakie obszary i grupy funkcjonalne widzi użytkownik w menu aplikacji. Site Map pozwala uporządkować tabele, formularze, dashboardy i raporty w logiczne sekcje, ułatwiając dostęp do danych i funkcji.
W projekcie EventFlow nawigacja została podzielona na dwa główne obszary:
Event Management → Insights
Udostępnia dashboard Events Overview, który wizualizuje dane o wydarzeniach, uczestnikach i ocenach, wspierając analizę i monitorowanie wydarzeń.Event Management → Operations
Zawiera dostęp do głównych tabel: Event, Participant i Feedback, umożliwiając zarządzanie wydarzeniami, rejestrację uczestników oraz przegląd anonimowych ocen.
Takie rozdzielenie funkcji pozwala użytkownikom szybko odnaleźć potrzebne informacje i wykonywać zadania zgodnie z ich rolą w firmie.
11. Uprawnienia użytkowników
W aplikacji EventFlow wyróżniono dwie główne role systemowe:
Participant / Evaluator – rola przeznaczona dla pracowników biorących udział w wydarzeniach i wystawiających oceny.
Organizer – rola dla osób odpowiedzialnych za organizację wydarzeń i zarządzanie uczestnikami.
Podział ról został zaprojektowany w taki sposób, aby każdy użytkownik posiadał wyłącznie niezbędne uprawnienia, a jednocześnie dane w systemie były w pełni bezpieczne.
11.1. Rola Participant / Evaluator
Rola ta skupia się na użytkownikach końcowych, którzy korzystają z aplikacji w celu uczestniczenia w wydarzeniach oraz oceniania ich po zakończeniu.
Tabela Event:
Read (Organization) – dostęp tylko do odczytu wszystkich wydarzeń.
Brak możliwości tworzenia, edycji czy usuwania wydarzeń.
Append/Append To (Organization) – na potrzeby relacji lookup.
Tabela Participant:
Create (User) – użytkownik może zapisać się na wydarzenie.
Read (Organization) – dostęp do listy uczestników wydarzenia.
Write (User) – możliwość edycji własnej rejestracji (np. zmiana statusu na “Cancelled”).
Tabela Feedback:
Create (User) – możliwość dodania oceny i komentarza do wydarzenia.
Read (Organization) – możliwość przeglądania ocen powiązanych z wydarzeniem.
Write (User) – edycja własnych ocen.
Tabela Contact:
Read (Organization) – wgląd w podstawowe dane kontaktowe organizatora (Lookup).
Append/Append To (Organization) – na potrzeby relacji lookup.
11.2. Rola Organizer
Rola ta została zaprojektowana dla osób zarządzających wydarzeniami. Obejmuje szersze uprawnienia, które umożliwiają pełną kontrolę nad procesem planowania, organizacji i ewaluacji.
Tabela Event:
Create (Organization) – możliwość tworzenia nowych wydarzeń.
Read (Organization) – pełny wgląd we wszystkie wydarzenia.
Write (Organization) – edycja wszystkich wydarzeń.
Delete (Organization) – możliwość usuwania wszystkich wydarzeń.
Append/Append To (Organization) – na potrzeby relacji lookup.
Tabela Participant:
Create (Organization) – możliwość dodania uczestnika do wydarzenia.
Read (Organization) – pełny dostęp do listy uczestników.
Write (Organization) – edycja dowolnych rekordów uczestników (np. potwierdzanie uczestnictwa).
Delete (Organization) – możliwość usuwania wszystkich rekordów uczestników.
Tabela Feedback:
Read (Organization) – dostęp do wszystkich ocen.
Brak możliwości usuwania i edycji rekordów.
Tabela Contact:
Read (Organization) – dostęp do pełnej listy kontaktów.
Append/Append To (Organization) – na potrzeby relacji lookup.
12. Wnioski i podsumowanie
Projekt EventFlow pokazał, że aplikacje model-driven w Microsoft Power Platform pozwalają w szybki i prosty sposób tworzyć kompleksowe rozwiązania biznesowe oparte na danych z Dataverse.
W trakcie realizacji projektu udało się między innymi:
Zaprojektować struktury danych aplikacji.
Stworzyć formularze z logiką walidacji i regułami widoczności pól oraz sekcji.
Zaimplementować podstawową automatyzację statusów wydarzeń w oparciu o daty i liczbę dostępnych miejsc.
Skonfigurować role i uprawnienia użytkowników.
Utworzyć dashboardy, wykresy i widoki umożliwiające szybki wgląd w najważniejsze wskaźniki wydarzeń.
Projekt wykazał też ograniczenia planu deweloperskiego, np. brak możliwości pełnej automatyzacji zmiany statusów bez wejścia w rekord, co w środowisku produkcyjnym mogłoby być realizowane np. przez Azure Functions lub Power Automate.
Podsumowując, EventFlow stanowi przykład profesjonalnej aplikacji biznesowej typu model-driven, która łączy intuicyjny interfejs z wydajną logiką biznesową i umożliwia kompleksowe zarządzanie wydarzeniami w firmie. Pomimo pewnych ograniczeń środowiska deweloperskiego, aplikacja w pełni spełnia założenia projektowe i może być w prosty sposób rozszerzana w przyszłości.
Subscribe to my newsletter
Read articles from Karolina Ruda directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Karolina Ruda
Karolina Ruda
Zagłębiam się w świat Power Platform i dzielę się swoimi doświadczeniami, tworząc praktyczne i kreatywne rozwiązania.