Capitolul 1 ……………………………………………………………………………………………………………………… 2… [606247]
1
Cuprins
Capitolul 1 ……………………………………………………………………………………………………………………… 2
1.1 Introducere ……………………………………………………………………………………………………………..2
1.2 Motivație ………………………………………………………………………………………………………………..2
1.3 Tehnologii folosite …………………………………………………………………………………………………..3
Capitolul 2 ……………………………………………………………………………………………………………………… 4
2.1 Descriere generală ……………………………………………………………………………………………………4
2.2 Cerințe funcționale …………………………………………………………………………………………………..4
2.2.1 Portal clienți………………………………………………………………………………………………………4
2.2.2 Portal gestionare ………………………………………………………………………………………………11
2.2.3 Portal administrare……………………………………………………………………………………………16
2.3 Decizii arhitecturale ……………………………………………………………………………………………….16
2.3.1 Arhitectură stratificată ……………………………………………………………………………………..16
2.3.2 Șabloane arhitecturale ………………………………………………………………………………………17
2.3.3. Arhitectura serviciilor ………………………………………………………………………………………18
2.3.4Injectarea dependințelor …………………………………………………………………………………….19
2.4 Dezvoltarea aplicației ……………………………………………………………………………………………..19
2.4.1 Limbaje de programare…………………………………………………………………………………….19
2.4.2 Mediul de dezvoltare ………………………………………………………………………………………..20
2.4.3 Spring Boot ……………………………………………………………………………………………………..20
2.4.4 Biblioteci ………………………………………………………………………………………………………..21
2.4.5 Utilitare și plugin-uri ………………………………………………………………………………………..22
2.4.6 Modularizarea aplicației ……………………………………………………………………………………24
2.5 Implementare ………………………………………………………………………………………………………..25
2.5.1 Noțiuni generale ………………………………………………………………………………………………25
2.5.2 Portal clienți…………………………………………………………………………………………………….26
2.5.3 Portal gestionare ………………………………………………………………………………………………53
2.5.4 Portal administare …………………………………………………………………………………………….74
2.5.5 Controlul excepțiilor și servicii reutilizabile ………………………………………………………..76
2
Capitolul 3 ……………………………………………………………………………………………………………………. 79
3.1 Utilitatea aplicației …………………………………………………………………………………………………79
3.2 Dezvoltări ulterioare ……………………………………………………………………………………………….80
3.1 Bibliografie ……………………………………………………………………………………………………………81
Anexă
3
CAPITOLUL 1
1.1 Introducere
eRestaurant este o platformă web dezvoltată cu scopul de a gestiona și automatiza
procesele ce au loc în cadrul unui restaurant. Clienții pot plasa comenzi sau rezervări noi, fiind
notificați în permanență de ultimile schimbări din cadrul restaurantelor.
În același timp platformă vine în ajutorul managerilor care pot gestiona mai ușor
procesele zilnice care au loc într-un restaurant. De exemplu pot vedea statusul meselor, câte sunt
rezervate, de cine și în ce interval orar.
1.2 Motivație
eRestaurant este o platformă care vine în ajutorul restaurantelor ce doresc să își
îmbunătățească semnificativ imaginea cât și serviciile, în același timp oferind clienților
sentimentul de transparență și încredere. Dorința de a pune bazele unei astfel de tehnologii
provin din simpla necesitate de a standardiza și a ordona toate serviciile oferite de un restaurant,
începând de la echipa de gestionare, la sistemul de administrare a serviciilor, iar nu în cele din
urmă, până la client.
Consider că tema abordată aduce o valoare reală întreprinderilor de tip restaurant și le
aduce un plus vizibil în fața concurenței, încât pe piața din România acest tip de administrare
prin intermediul unei aplicații web nu este momentan exploatat la maximum, lispa acesteia
rezultând doar în pierderi de profit și timp, atât din partea personalului cât și a clientului.
Luând în considerare beneficiile pe termen lung aduse de o asemenea platformă, câteva
din avantajele acesteia sunt următoarele :
Îmbunătățirea serviciilor oferite
Fidelizarea clientelei
Îmbunătățirea și simplifcarea sistemelor administrative
Ușurarea accesului la o bază de date securizată
Pe baza considerentelor menționate mai sus, am decis dezvoltarea unei platformei de
acest tip – eRestaurant.
4
1.3 Tehnologii folosite
eRestaurant este o paltformă web, ceea ce înseamnă că este compusă din două părți :
client și server.
Partea de client reprezintă în cazul nostru interfața afișată în browser-ul utilizatorului.
Aceasta face apeluri pentru informații către aplicația de pe server (back-end). Pentru
implementarea afișării datelor primite de la server am utilizat următoarele tehnologii :
1. HTML
HTML este prescurtarea de la Hypertext markup language, care este limbajul de bază
pentru crearea documentelor web. Este un set de etichete care ulterior sunt interpretate de
browser pentru afișarea informațiilor în pagină.
2. Javascript / jQuery
Javascript este un limbaj de scripting cu ajutorul căruia paginile web statice sunt
transformate în pagini web interactive, cu animații și conținut dinamic. jQuery este o
bibliotecă Javascript care ușurează scrierea codului. Aceasta oferă funcții deja definite pentru
a crea apeluri către server sau pentru manipularea elementelor HTML. Deși există foarte
multe biblioteci pentru dezvoltarea pe partea de client, am ales jQuery datorită comunității
enorme pe care o are.
Fig 1.0 Utilizare jQuery 2015
3. CSS
Este un acronim de la Cascading Style Sheets. Cu ajutorul CSS-ului putem personaliza
conținutul paginilor web, de la culori până la aranjarea în pagină.
Pentru implementarea păr
1. Java – Spring Boot Framework
Java este un limbaj de programare orientat obiect cu care se pot dezvolta at
desktop cât și web. În cazul de față am optat să folosesc un framework(care poate fi definit ca
o colecție de biblioteci) pentru o dezvoltare m
Spring Boot este un framework dedicat aplica
integrare cu alte module, cum ar fi baza de date sau cu alte aplica
nevoie (de exemplu Facebook).
În formă finală aplica ția va fi livra
Archive) care este un fi șier ce conține clasele java împreună cu fișierele XML și paginile
web ale aplica ției (în cazul în care există).
Fig 1.1 Utilizare Spring Boot în 2016
2. Tomcat – deployment container
Pentru a putea accesa aplica
pentru că este un server open- source, compatibil cu Java.
Pentru implementarea păr ții de backend a aplicației s- au folosit următoarele tehnologii
Spring Boot Framework
Java este un limbaj de programare orientat obiect cu care se pot dezvolta at
și web. În cazul de față am optat să folosesc un framework(care poate fi definit ca
ție de biblioteci) pentru o dezvoltare m ai ușoară.
un framework dedicat aplica țiilor web oferind modalități ușoare de
integrare cu alte module, cum ar fi baza de date sau cu alte aplica ții în cazul în care este
nevoie (de exemplu Facebook).
ția va fi livra tă într-un format de tip WAR ( Web Application
șier ce conține clasele java împreună cu fișierele XML și paginile
ției (în cazul în care există).
Fig 1.1 Utilizare Spring Boot în 2016
deployment container
Pentru a putea accesa aplica ția aceasta trebuie încărcată pe un server. Am ales Tomcat
source, compatibil cu Java.
5 au folosit următoarele tehnologii :
Java este un limbaj de programare orientat obiect cu care se pot dezvolta at ât aplicații
și web. În cazul de față am optat să folosesc un framework(care poate fi definit ca
țiilor web oferind modalități ușoare de
ții în cazul în care este
un format de tip WAR ( Web Application
șier ce conține clasele java împreună cu fișierele XML și paginile
ția aceasta trebuie încărcată pe un server. Am ales Tomcat
6
3. MySQL – baze de date
Este unul dintre cele mai populare sistemeopen source de gestionare a bazelor de date.
Utilitarele pe care le oferă (MySQL Workbench) fac interogrările și accesul la baza de date
foarte ușoar.
4. Git
Este un sistem de versionare a codului. Cu ajutorul acestui utilitar se pot crea ramuri
diferite, așadar se poate lucra în paralel la diferite funcționalități ale aplicației. În cazul în care se
șterg fișiere, permite restaurarea codului de la o anumită dată.
CAPITOLUL 2
2.1 Descriere generala
eRestaurant este o platformă web folosită pentru automatizarea proceselor din cadrul
restaurantelor. Această platformă are public țintă atât clineții restaurantelor cât și partea de
management a restaurantelor.
Aplicația oferă o interfață intuitivă utilizatorilor, fiind accesebilă de pe orice device smart
sau computere, având un design responsive și plăcut.
Pentru a putea beneficia de funcționalitățile platformei, utilizatorii acesteia au nevoie de
un cont valid cu care să poată accesa aplicația. Utilizatorii sunt împărțiți în 3 categorii :
● Client
● Manager
● Administrator
Tip de utilizator client :
În calitate de client, utilizatorul v-a putea să-și înregistreze un nou cont cu care poate
accesa aplicația. Acesta are opțiunile de a-și modifica detaliile contului, exceptând username-ul
ales inițial. Totodată, acesta poate folosi servicii precum plasarea unei comenzi, rezervarea unei
mese în cadrul unui restaurant sau chiar să comunice cu personal al restaurantului prin
intermediul aplicației.
Tip de utilizator manager :
Contul de manager se creează la cerere de către un admin. Trebuie precizată o adresă de
e-mail corectă pe care utilizatorul de tip manager va primi credențialele pentru logare. La prima
logare acesta este nevoit să configureze detalii despre restaurantul său prin intermediul aplicației
(nume, locație, program, etc.). Utilizatorii de tip manager vor putea adăuga și modifica noi
7
angajați, produse, detalii despre restaurant, meniuri, newslettere și să vadă statistici despre
restaurantul lor.
Tip de utilizator admin :
Contul de admin va fi creat la pornirea aplicației. Credențialele de logare ale acestuia vor
fi inserate în baza de date la prima pornire a aplicației. Această categorie de user are acces la
baza de date și poate crea noi restaurante/manageri la cerere.
2.2 Cerințe Software
2.2.1 Portal clienți
2.2.1.1 Înregistrare
Prin intermediul platformei clienții au posibilitatea de a-și crea un cont pentru a putea
folosi funcționalitățile acesteia.
Datele necesare înregistrării sunt :
a. username : câmp unic în baza de date
b. email : câmp unic în baza de date ce trebuie să respecte șablonul de email (de
exemplu : [prefix-email].[@].[domeniu]
c. nume : pentru validarea acestui câmp s-a stabilit să aibă un număr de caractere
cuprins între 3 și 15
d. prenume : pentru validarea acestui câmp s-a stabilit să aibă un număr de caractere
cuprins între 3 și 15
e. parolă : câmp salvat codificat în baza de date, cu o lungime de minim 5 caractere
f. număr de telefon : acest câmp trebuie să aibă 10 caractere numerice
Completarea corectă a câmpurilor duce la salvarea în baza de date a utilizatorului. În
cazul în care unul sau mai multe câmpuri sunt completate incorect, utilizatorul v-a primi un
mesaj de alertă.
2.2.1.2 Logare
Pentru a beneficia de funcționalitățile aplicației utilizatorul trebuie să aibă un cont valid și
să fie logat pe acesta. Pentru procesul de logare el va completa următoarele câmpuri :
a. username : numele cu care s-a înregistrat
b. parola : parola contului
În cazul în care informațiile completate sunt incorecte acesta va primi un mesaj de eroare
sugestiv, în caz contrar va fi redirectat către pagina de landing a aplicației.
2.2.1.3 Secțiunea profil
În momentul în care utilizatorul s-a logat în aplicație, acesta are posibilitatea de a
modifica datele profilului :
8
a. Încărcarea unei imagini care să deservească ca imagine de profil. Format-urile
acceptate sunt jpg, jpeg, png.
b. Nume
c. Prenume
d. Număr de telefon
e. Adresă de email
Pentru a salva noile date acesta are la dispoziție butonul “Salveaza modificarile” .
2.2.1.4 Secțiunea adrese
Utilizatorulare la dispoziție o secțiune unde poate adăuga sau șterge adresele sale.
Acestea sunt folosite ulterior pentru a plasa o comandă la una dintre adresele selectate.
Câmpurile care trebuie completate sunt :
a. Numele adresei
b. Orașul
c. Strada
d. Apartamentul
e. Etajul – acest camp trebuie să conțină doar caractere numerice
Adresele vor fi paginate, fiind vizibile 10 per pagină. Pentru a vedea detaliile adresei
utilizatorul trebuie să selecteze una din listă.
Pentru a șterge una dintre adrese acesta trebuie să apese butonul cu label-ul “Sterge”.
2.2.1.5 Secțiunea carduri
User-ul are la dispoziție o secțiune de website unde poate adăuga sau șterge cardurile
sale. Acestea sunt folosite ulterior pentru a plăti o comandă.
În cazul adăugării unui nou card acesta trebuie să completeze următoarele câmpuri :
a. Numele cardului
b. Tipul cardului
c. Numărului cardului : 16 caractere numerice
d. Luna expirării : 2 caractere numerice cuprinse între 0-12
e. Anul expirării : 4 caractere numerice, valoare fiind mai mare sau egală cu anul
curent
f. Cod CVC : 3 caractere numerice
g. Numele de pe card
Cardurile vor fi paginate, fiind vizibile 10 per pagină.Pentru a vedea detaliile cardului
utilizatorul trebuie să selecteze unul din listă.
Pentru a șterge unul dintre acestea trebuie să apese butonul cu label-ul “Sterge”.
9
2.2.1.6 Secțiunea restaurante
Utilizatorul are la dispoziție posibilitatea de a vedea toate restaurantele înregistrate în
aplicație.
Lista de restaurant v-a fi paginate, fiind afișate 10 restaurante per pagină. În momentul în
care utilizatorul selectează unul dintre restaurante acesta v-a vedea detaliile în partea dreaptă a
paginei.
Partea de detalii conține :
a. Descrierea restaurantului
b. Media rating-urilor restaurantului pentru următoarele categorii : Mancare, Client
Service, Atmosfera în cazul în care există cel puțin un feedback
c. butoane cu următoarele label-uri
d. Contact
e. Pareri
f. Comanda acasa
g. Rezervare
Butonul “Contact” :
Afișează o fereastră nouă cu adresa de e-mail, numărul de telefon și amplasarea pe hartă a
restaurantului.
Butonul “Pareri” :
Afișează o fereastră nouă împărțită în 2 secțiuni, plasate pe orizontal.
Secțiunea 1 conține părerile utilizatorilor ce au intrat în contact cu restaurantul, datele
conținute fiind notele date restaurantului, comentarii și numele împreună cu data la care a fost
adăugat feedback-ul.
Secțiunea 2 conține formularul de plasare a unei noi opinii. Se cere completarea cu note
de la 1-5 pentru fiecare dintre cele 3 caracteristici (Mancare, Client Service, Atmosfera) și un
comentariu care să conțină cel puțin 10 caractere.
Butonul “ Comanda acasa” :
La apăsarea acestui buton se începe procesul de plasare a unei comenzi, proces desfășurat
în 4 pași. Sunt afișate 12 produse per pagină, fiecare în box-ul său care conține informații precum
nume, preț si imaginea produsului.În partea de sus a paginii utilizatorul v-a avea posibilitatea să
caute un produs bazat pe numele său. De asemenea v-a fi prezentă și o listă de filtrare între
produse și meniuri.
Pasul 1 :
Sunt afișate produsele/meniurile sub formă de box-uri. În fiecare box sunt prezente două
butoane, unul care permite adăugarea în coș al produsului și celălalt care generează o pagină
nouă cu următoarele detalii :
10
a. Descrierea produsului
b. Tipul produsului
c. Numărul de produse în stoc
d. Greutatea produsului
La apăsarea butonului de adăugare în coș, dacă cererea se sfârșește cu succes atunci
utilizatorul primește o notificare care să confirme acest lucru, în caz contrar este anunțat ce a
generat eroarea.
Pentru a continua la următorul pas, coșul de cumpărături al utilizatorului trebuie să
conțină cel puțin un produs.
Pasul 2 :
În pasul 2 utilizatorul are posibilitatea fie de a se întoarce la pasul 1, fie de a selecta
adresa de livrare a comenzii și a continua la pasul 3.
Pasul 3 :
În pasul 3 utilizatorul are posibilitatea fie de a se întoarce la pasul 2, fie de a selecta
cardul cu care se face plata comenzii și poate continua la pasul 4.
Pasul 4 :
Este afișat sumarul comenzii. Va conține un tabel cu toate produsele adăugate în coș.
Acestea pot fi șterse din coș. Vor fi afișate și detaliile comenzii – adresa de livrare, cartea de
credit.
Utilizatorul are posibilitatea de adăuga detalii (de ex. cartierul în care se află strada). La
apăsarea butonului “Plasează comanda” , angajatul cu cele mai puține comenzi în așteptare este
alocat automat, utilizatorul fiind notificat printr-un mesaj.
De asemenea prin intermediul unui serviciu de mailing utilizatorul primește sumarul
comenzii pe adresa de email cu care este înregistrat.
Butonul “Rezervare” :
La apăsarea acestui buton se începe procesul de plasare a unei rezervări,.
Sunt puse la dispoziția utilizatorului 3 câmpuri care trebuie completate obligatoriu :
a. Data rezervării
b. Ora de început
c. Ora de sfârșit
Timpul minim de rezervare este de 30 de minute. La apăsarea butonului “ Cauta” se v-a
returna o listă cu toate mesele restaurantului.
Cele rezervate în intervalul de timp selectat de utilizator sunt marcate cu culoarea roșie și
nu sunt selectabile. Cele libere, în momentul selectării sunt marcate cu coloarea verde.
11
După selectarea cel puțin al unei mese utilizatorul poate termina procesul de rezervare
prin butonul “ Termina rezervarea” sau poate plasa o rezervare cu comandă de produse.
La apăsarea butonului “ Adauga produse la rezervare” se instanțiază procesul descris mai
devreme de selectare a produselor.
2.2.1.7 Secțiunea mesaje
În cadrul aplicației se vor implementa 3 tipuri de mesaje :
a. Personale : trimise de către angajații restaurantelor
b. Mesaje de tip newsletter : mesaj trimis de către restaurante către toți userii aplicației
c. Mesaje de sistem : mesaj trimis de administrator către toate categoriile useri
În secțiunea de mesaje utilizator v-a putea vizualiza oricare dintre aceste 3 categorii.
Pentru toate categoriile de mesaje utilizatorul v-a avea posibilitatea să vadă :
Subiectul mesajului
Data la care a fost trimis
Emițătorul mesajului
Text-ul mesajului
În cazul mesajelor personale acesta are posibilitatea de a răspunde mesajelor. Implementarea
se dorește a fi de tip Întrebare-Răspuns, adică oricărui mesaj personal îi va corespunde un singur
alt mesaj răspuns.
Răspunsul venit din partea utilizatorului trebuie să conțină minim 5 caractere.
Pentru celelalte două categorii de mesaje(newsletter și system) nu există posibilitatea de a
răspunde, ele fiind trimise doar cu scop informativ.
2.2.1.8 Secțiunea comenzi
În această secțiune utilizatorul poate vizualiza comenzile sale anterioare. Comenzile vor
fi paginate câte 10 pe pagină, în partea stângă a ecranului, fiecare item conținând data și numele
restaurantului unde a fost plasată comanda.
În partea dreaptă vor fi afișate toate detaliile comenzii, după cum urmează :
a. Data
b. Restaurantul
c. Cartea de credit folosită pentru achitarea comenzii
d. Adresa la care a fost plasată comanda
e. Sub formă tabelară produsele din comandă, fiind specificate următoarele atribute :
nume, preț, cantitate.
12
2.2.1.9 Secțiunea rezervări
În această secțiune utilizatorul poate vizualiza toate rezervările sale efectuate prin
intermediul aplicației. Acestea vor fi paginate câte 10 pe pagină, în partea stângă a ecranului.
În partea dreaptă vor fi afișate detaliile rezervării, în următoare ordine :
a. Numele restaurantului
b. Data
c. Ora de începere a rezervării
d. Ora de final a rezervării
e. Mesele rezervate, afișate sub formă tabelară cu următoarele atribute : descriere
mesei, numărul de locuri și precizarea dacă este sau nu o masă dedicată fumătorilor
De asemenea utilizatorul are dreptul de a anula o rezervare cu cel puțin 5 ore înainte ca
aceasta să aibă loc prin intermediul unui buton cu label-ul “ Anulează rezervarea” .
2.2.1 Portal gestionare
2.2.1.1 Secțiunea logare
Pentru procesul de autentificare clientul cu tipul de manager v-a trebui să completeze
formularul cu câmpurile username și parolă. Ulterior acesta este redirectat către bordul de
management.
2.2.2.2 Secțiunea profil
Utilizatorul de tip manager are posibilitatea de a-și modifica datele aferente contului :
Nume
Prenume
Parola
Email
Număr de telefon
Imaginea de profil
Pentru a-și modifica parola acesta trebuie să introducă parola veche și două parole noi care să
conțină cel puțin 5 caractere și să fie identice.
2.2.2.3 Secțiunea restaurant
În panel-ul restaurant acesta are posibilitatea de a :
Modifica programul de lucru (atât zile cât și intervalul orar în care este deschis
restaurantul)
De a modifica imaginea de profil a restaurantului
De a modifica descrierea restaurantului
De a modifica coordonatele/adresa la care se află restaurantul
13
2.2.2.4Secțiunea angajați
Secțiunea angajați este împărțită în 4 sub-categorii :
1. Afișarea angajaților
Angajații trebuie afișați 10 per pagină cu detaliile de Nume, Prenume și Rol. Este posibilă
căutarea angajaților după nume și prenume. De asemenea este disponibilă și vizualizarea mai
multor detalii ale acestora care pot fi modificate în același ecran.
Prin apăsarea unui buton Modifica câmpurile devin editabile. Câmpurile editable sunt :
Nume
Prenume
Email
Numar de telefon
2. Adăugare unui angajat
Pentru adăugarea unui angajat trebuie completat formularul cu următoarele câmpuri :
Nume
Prenume
Username
Parolă
Email
Număr de telefon
Rol
În cazul în care rolurile angajaților nu au fost definite atunci procesul de înregistrare a unui
angajat va returna eroare.
3. Vizualizarea rolurilor
Pentru vizualizarea rolurilor se va crea un tabel care conține câmpurile :
Nume rol
Descrie Rol
Acțiune de șterge a rolului
Această categorie nu poate fi modificată. În cazul în care unui rol îi sunt deja asignați
angajați, acesta nu va fi șters până nu li se adaugă un alt rol angajaților respectivi.
4. Adăugarea rolurilor
Pentru adăugarea unui nou rol utilizatorul trebuie să completeze un formular ce conține
următoarele câmpuri :
14
Numele rolului : minim 3 caractere
Descrierea rolului : minim 10 caractere
2.2.2.5 Secțiunea mese
Această secțiunea este împărțită în 3 subcategorii :
1. Adaugă masă
Pentru adăugarea unei noi mese utilizatorul trebuie să completeze un formular care conține
următoarele câmpuri :
Descrierea mesei : date de tip text, minim 10 caractere
Număr de locuri : în primă fază utilizatorul are posibilitatea de a defini mese cu un număr
de locuri între 1 și 10
Masă de fumători : utilizatorul are la dispoziție funcționalitatea de a selecta dacă o masă
este destinată fumătorilor sau nu
2. Vezi mese
Această funcționalitate va fi afișată sub formă tabelară. Vor fi afișate câmpurile :
Id
Descriere
Număr locuri
Masă de fumători (Da sau Nu)
Prin selectarea unui câmp din tabel care reprezintă una din mese, utilizatorul are posibilitatea
de a modifica caracteristicile acestia.
3. Status astăzi
În această categorie vor fi afișate mesele rezervate din ziua curentă, sub formă tabelară
conținând următoarele detalii :
ID-ul mesei
Ora de început a rezervării
Ora de sfârșit a rezervării
Numele
Prenumele
Emailul
ID-ul rezervării
2.2.2.6 Secțiunea rezervări
În această secțiune utilizatorul are posibilitatea de a vizualiza rezervările din cadrul
restaurantului și de filtra după numele clientului care a plasat rezervarea. De asemenea există
15
posibilitatea de a defini o rezervare nouă, în acest fel mesele fiind marcate în aplicație ca fiind
ocupate.
2.2.2.7 Secțiunea mesagerie
În această secțiune utilizatorul are la dispoziții următoarele 3 funcționalități :
Trimiterea unui mesaj nou
Pentru trimiterea unui nou mesaj client-ul trebuie să caute bazat pe nume, prenume sau
username, utilizator-ul către care transmite mesajul. Ulterior acesta v-a trebui să selecteze
utilizatorul respectiv și să completeze câmpurile :
o Subiect
o Mesaj
Trimiterea unui mesaj de tip newsletter
Acest tip de mesaj va fi transmis tuturor userilor din baza de date, acesta fiind cu scop
informativ(de exemplu modificarea locației restaurantului, sau adăugarea a noi oferte în catalog).
Mesagerie inbox
În această rubrică utilizatorul poate vedea conversațiile saleși notificările primite din diferite
servicii(de exemplu la plasarea unei comenzi / plasarea unei rezervări)
2.2.2.8 Secțiunea mesagerie
În această secțiune utilizatorul poate vedea produsele și de asemenea are posibilitatea de
adăuga produse noi.
Adăugarea unui nou produs
Pentru adăugarea unui nou produs trebuie completat formularul cu următoarele câmpuri :
o Nume produs
o Descriere produs
o Pret produs
o Tip produs
o Stoc produs
o Greutate produs
o Imagine produs
Vizualizare și modificarea produselor
În categoria de vizualizare a produselor, acestea vor fi afișate 10 per pagină sub formă
tabelară, cu următoarele coloane prezente :
ID produs
16
Nume produs
Descriere produs
Tip produs
Pret
Număr în stoc
Masa
În funcție de numărul prezent în stoc se va notifica :
>500 produse : background alb
<50 produse : background roșu
>=50 &<=500 : background galben
În momentul selectării unui produs se pot modifica detaliile aferente ale acestuia, care au fost
inserate la crearea lui.
2.2.2.9 Secțiunea meniuri
Adăugarea unui meniu
Pentru această funcționalitate se cere ca ecranul să fie împărțit în două divizuni. În partea
stângă a ecranului există un modul de căutare al produselor. În momentul efectuării unei căutări
se vor afișa 5 coloane cu următoarele caracteristici :
o Id produs
o Nume produs
o Descriere
o Tip
o Masa
o Buton de adăugare în meniu a produslui
În partea dreaptă a ecranului se va completa formularul care conține :
o Numele meniului
o Prețul meniului
o Descrierea meniului
o Sub formă tabelară numele și descrierea produselor din care este alcătuit meniul
cu posibilitatea de a fi șterse
Vizualizare meniuri
Acestea vor fi afișate sub formă tabelară cu detaliile specificate anterior prezente. În
momentul selectării unui meniu acesta devine editabil cu posibilitatea de a adăuga sau șterge
produse din el.
În cazul în care sunt șterse toate produsele din el și se apasă butonul de Salveaza se va returna un
mesaj de eroare, pentru că un meniu nu poate exista fără produse.
17
2.2.3 Portal administrare
Portalul de administrare va beneficia de următoarele funcționalități :
Crearea unui restaurant / manager
Pentru crearea unui restaurant se va defeni un formular cu următoarele câmpuri
o Nume
o Prenume
o Număr de telefon
o Email
o Username
Acest formular v-a crea un cont de tip Manager pentru care parola va fi generată în mod
automat și trimisă prin e-mail.
Trimiterea unui mesaj de tip system
Formularul de trimitere a unui mesaj de tip sistem va conține :
o Subiect
o Conținut
Acest mesaj va fi trimis către toți utilizatorii din baza de date.
2.3 Decizii arhitecturale
2.3.1 Arhitectură stratificată
Fiind un produs nou, platforma eRestaurant este volatilă, suferind în permanență
schimbari. Din acest motiv s-a decis să fie dezvoltată sub formă de monolit, cu o arhitectură
bazată pe straturi.
Stratul de prezentare : reprezintă partea grafica aplicației cu care interacționeaza utilizatorii.
Acesta poate fi diferită in functie de device-ul de pe care este accestă.
Stratul de aplicație : reprezintă un intermediar între stratul de prezentare și cel de logică. Stratul
de aplicație expune serviciile REST.
Stratul de logică : reprezintă nucleul aplicației. Este modulul în care se
prelucrează/valideazădatele aplicației. Are acces la stratul de conectare și interogare al bazei de
date.
Stratul de persistență : reprezintă conexiunea aplicației la baza de date. Prin acest strat se
efectuează operațiile cu baza de date.
18
Fig 2.3.1.0 Comunicare între straturile aplicației
Când aplicația ajunge la maturitate este posibilă separarea straturilor in aplicații diferite.
Folosind adaptoare între straturi aceastea pot fi expuse ca servicii, îndreptându-ne foarte ușor
către o arhitectură de tip Service Oriented Architecture (SOA) cu un efort minim de
refactorizare.
Pornirea directă a aplicației pe o astfel de arhitectură nu este benefică deoarece nu
cunoaștem în totalitate dependințele intre module.
2.3.2 Șabloane arhitecturale
Model-View-Controller :
Este un șablon architectural folosit pentru organizarea codului bazat pe funcționalitatea
acestuia, pentru a spori mentenabilitatea și separarea în funcție de rolul obiectelor.
În acest tip de arhitectură, partea de model semnifică defapt logica și management-ul
obiectelor (de exemplu conexiunea la baza de date, aplicare de logică asupra obiectelor,
persistarea acestora, etc.)
Partea de view este destinată utilizatorului final, aceasta fiind partea vizuală unde sunt
returnate rezultatele (de exemplu: jsp-uri, gsp-uri).
Prin intermediul Controller-ului se preia conținutul de la client și se trimite către layer-ul
de model, după care se întoarce înapoi către view.
Deși platforma eRestaurant este bazată pe șablonul EBC descris mai jos, partea de view
este defapt interfața prin care utilizatorul comunică cu aplicația backend, dezvoltată ca o
aplicație separată cu bibliotecile de javascript și jQuery.
19
Entity-Bound-Control :
Este o variație a cunoscutului șablon Model View Controller.
Fig 2.3.2.0 Diagrama șablonului EBC
Spre deosebire de Model View Controller, acestui șablon îi lipsește partea de view, aplicația
expunând servicii REST și nu o interfață grafică pentru utilizator.
● Boundry – reprezintă punctul de intrare în aplicație
● Controller – reprezintă nivelul de servicii care transmite mai departe apelul către stratul
de persistență
● Entity – reprezintă stratul cel mai inferior unde se află entitățile mapate la baza de date,
respectiv stratul de persistență folosit pentru conexiunea/interogarea bazei de date.
Dezvoltând aplicația prin acest șablon putem expune serviciile și logica către alte mașini care
pot face diferite apeluri la aplicația eRestaurant doar prin URI.
Data-Transfer-Object
Data-Transfer-Object este un șablon enterprise folosit pentru comunicarea cu terțele
exterioare (de exemplu aplicația web). Printre beneficiile aduse de acest șablon se numără și :
● Expunerea doar a datelor necesare
● Ușurează procesul de testare
● Crește performanța datorită faptului că pot fi imbricate mai multe informații într-un
singur call
2.3.3 Arhitectura serviciilor
Platforma eRestaurant comunică cu alte aplicații terțe prin intermediul serviciilor REST.
Representational State Transfer este un tip de arhitectură pentru aplicațiile web care vine
cu beneficii precum performanță, scalabilitate și mentenanță ușoară. Spre deosebire de serviciile
SOAP, acestea au un mesaj mult mai scurt, într-un format ușor interpretabil atât de om cât și de
mașină, cel mai popular fiind formatul de tip JSON (Javascript Object Notation).
20
REST se bazează în special pe protocul HTTP, accesul către resurse făcându-se cu
ajutorul verbelor GET, PUT, POST, DELETE.
Să presupunem că avem următorul end-point asupra căruia facem un request de tip GET :
● http://www.manage.com/user/details/address
Server-ul o să răspundă cu următorul mesaj :
[
{
"id":1,
"addressName" :"My first address" ,
"city":"Iasi",
"street":"Buridava" ,
"flatNumber" :"30",
"floor":5
}
]
JSON(Javascript Object Notation) este o modalitate de a stoca informațiile într-u mod
organizat și ușor de accesat. Putem converti obiectele Java sau Javascript în acest format și să le
trimitem către server sau client. Este foarte pliabil pe limbajul Javascript și ajută în special în
cazul paginilor de tip Single Page Application unde au loc multe request-uri AJAX.
AJAX este un acronim pentru Asynchronous JavaScript and XML. Este o modalitate de a
crea pagini web dinamice. În trecut, după completarea unui formular utilizatorul era redirecționat
către o nouă pagină unde ar fi putut vedea răspunsul primit de la server. Apelurile AJAX se
întâmplă în background, pagina modificându-se în timp real la primirea unui răspuns de la server
fără a mai fi nevoie de refresh sau redirect.
2.3.4 Injectarea dependințelor
Orice aplicație Java presupune că obiectele definite vor colabora între ele, ceea ce poate
duce la crearea de dependințe. Din cauza acestui fapt este posibil să dezvoltăm cod cuplat, în
care o singură schimbare se poate propaga în mai multe locuri.
Injectarea dependințelor este un principiu de dezvoltare care ajută la scrierea codului
decuplat, lasând container-ul ce se ocupă de gestiunea bean-urilor să livreze obiectele
cerute.Aceste obiecte pot fi injectate fie din fișiere externe (de exemplu XML) sau pot fi definte
într-o clasă adnontată cu @Configuration . ‚
2.4 Dezvoltarea aplicației
2.4.1 Limbajul de programare
Pentru dezvoltarea aplicației s-a folosit Java Development Kit versiunea 1.8.0_91.
Java este un limbaj orientat obiect și open source creat de Sun care mai târziu a fost
achiziționat de către Oracle.
21
Codul sursă al programelor Java este compilat în bytecode care poate fi rulat de pe orice
mașină sau client care are instalat un JVM (Java virtual machine), spre deosebire de C++ sau alte
limbaje de programare care compilează codul într-un fișier binar dependent de platformă.
În martie 2014 s-a lansat o nouă versiune de Java – Java 8.
Am ales dezvoltarea sub această versiune deoarece s-au adus îmbunătățiri la capitole
precum performanța, viteza de dezvoltare, comunitate și nu în ultimul rând pentru că îmi doresc
să rămân la curent cu schimbările tehnologice.
2.4.2 Mediul de dezvoltare
Mediul de dezvoltare folosit este IntelliJ Community Edition. Este un IDE inteligent care
facilitează procesul de dezvoltare, oferind posibilitatea ușoară de depanare(debbuging),
autocompletare a codului scris dar mai ales pentru funcționalitate de refactoring, un proces des
întâlnit în dezvoltarea aplicațiilor noi.
2.4.3 Framework – Spring Boot
Framework-ul ales pentru dezvoltarea aplicației este Spring Boot. Este un framework
destinat dezvoltării de aplicații web și enterprise.
În continuare sunt enumerate motivele deciziei de a alege acest framework :
● Reduce codul duplicat prin abstractizarea layelor (de exemplu pentru conectarea la baza
de date)
● Dispar fișiele XML imense folosite pentru configurarea aplicației. Spring Boot este
capabil să se autoconfigureze prin intermediul anotației @SpringBootApplication. În
momentul în care Spring Boot vede această anotare, scanează classpath-ul după
componentele declarate și le înregistrează ca bean-uri care ulterior pot fi folosite.
● Permite injectarea dependințelor și stabilește un proces bine definit de management al
dependințelor prin integrarea cu tool-uri precum Maven/Grade
● Are un server embedded (Tomcat în cazul aplicației eRestaurant) care este capabil să facă
hot-swap la orice schimbare în cod – simplifică procesul de dezvoltare
● Ușor de integrat cu alte framework-uri (de exemplu ORM-ul Hibernate)
Adnotarea @SpringBootApplication este un wrapper peste adnotările @Configuration,
@EnableAutoConfiguration și @ComponentScan.
Adnotarea de @EnableAutoConfiguration încearcă să “ghicească” bean-urile de care are
aplicația nevoie. De exemplu dăcă există o dependință în proiect pentru o bază de date de tip
mySql atunci adnotarea va configura automat un serviciu de comunicare cu baza de date de tip
mySql.
Dat faptul că în Spring bean-urile pot fi definite în clase Java și nu în xml, cu ajutorul
adnotării @ComponentScan framework-ul scanează, detectează și instanțiază bean-urile
necesare.
22
Bean-urile Spring sunt obiecte Java ale cărui life-cycle este controlat de container-ul Spring.
Acestea sunt instanțiate la nevoie prin procese cum ar fi reflection-ul.
2.4.4 Biblioteci
2.4.4.1 Spring Data
Facilitează accesul la baza de date prin interfețe generice. Asigură un nivel mare de
abstractizare asupra codului SQL și HQL (Hibernate query language), interogările la baza de
date fiind asigurate prin metode care trebuie să respecte o anumită convenție de numire.
De exemplu :
Customer findByUsername (String username) ;
Metoda returnează un obiect de tip Customer chiar dacă aceasta nu are corp.
Este o librărie extensibilă oferind posibilitatea de a defeni interogări customizate.
@Query(value = "select e from Employee e where e.restaurant = ?1 and e.firstName like %?2%
or e.lastName like %?2% " )
Page<Employee> findAllByRestaurantAndFirstNameOrLastNameContainingIgnoreCase (Restaur
ant restaurant ,
String name , Pageable pageable) ;
Metoda de mai sus returnează un obiect de tip Page cu toți angajații a căror nume de
familie sau prenume conține string-ul dat ca parametru.
2.4.4.2 Hibernate 5.0.12
Hibernate este o implementare Java Persistance API folosită pentru maparea obiectelor
de tip domeniu într-o bază de date relaționară. Posedă mecanisme bine definite pentru persistența
datelor și interogarea lor asigurând performanțe ridicate ( de exemplu mecanismul de lazy
loading al obiectelor).
Lazy-loading este procesul prin care într-o relație One-to-Many între un părinte și copiii
săi, pentru a accesa un membru copil, ORM-ul Hibernate nu mai trebuie să încarce toată colecția
și să itereze prin ea ci încarcă doar obiectul cerut.
@Entity
@Inheritance (strategy = InheritanceType. JOINED)
public class User extends BaseEntity
23
Datorită adnotării @Entity Hibernate se creează în bază de date un nou tabel numit “user”,
numele fiind același cu al clasei dacă nu este precizat ca parametru al adnotării (de exemplu
@Entity(name=table_users) va genera un tabel cu numele table_users). Toate câmpurile
obiectului Java sunt scrise în baza de date ca și coloane.
2.4.5 Tool-uri și plugin-uri
2.4.5.1Maven
Utilitar folosit pentru management-ul proiectului. Acesta permite declarearea
dependințelor și proprietăților proiectului într-un fișier de tip xml(numit pom.xml). Prin
intermediul acestei funcționalități ne asigurăm că toată echipa folosește aceleași versiuni de
librării externe, descărcate de pe un repository comun.
În trecut, în cazul aplicaților deployate pe servere de tip Weblogic, jar-urile externe
trebuiau descărcate local și adăugate în classpath ca și shared libraries. Fiecare programator
putea sfârși cu o versiune diferită de cod în funcție de jar-urile descărcate.
pom.xml:
<properties>
<project.build.sourceEncoding> UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding> UTF-8</project.reporting.outputEncoding>
<java.version> 1.8</java.version>
<org.mapstruct.version> 1.2.0.Beta3 </org.mapstruct.version>
</properties>
<dependency>
<groupId> org.mapstruct </groupId>
<artifactId> mapstruct-jdk8 </artifactId>
<version> ${org.mapstruct.version} </version>
</dependency>
Prin tag-urile xml libraria mapstrut-jdk8 cu versiunea precizată între tagurile de
properties este descărcată în repository-ul maven local.
2.4.5.2 MapStruct
MapStruct este un generator de cod care simplifică procesul de scriere a codului pentru
maparea între obiect și DTO-urile acestora. Oferă posibilitatea de a simula update-uri parțiale
care sunt foarte des întâlnite în aplicații care expun servicii REST.
MapStruct se bazează pe adnotări care generează mapping-ul între obiecte în momentul
în care se face build la aplicație.
24
Să presupunem că avem următorul obiect de tip Restaurant :
@Entity
public class Restaurant extends BaseEntity{
private String name;
private String description ;
private ContentManager manager;
private List<RestaurantTable> tables;
private List<Reservation> reservation ;
private List<Employee> employeers ;
private List<EmployeeRole> employeeRoles ;
private Location location;
private Schendule schendule ;
private List<Product> products;
private List<News> news;
private List<Menu> menus;
private boolean isConfigured =false;
}
Și obiectul de transfer al acestuia acestuia.
public class RestaurantDTO {
private String name;
private String description ;
private LocationDTO location;
}
Pentru a realiza mapping-ul între cele două obiecte este de ajuns să definim o interfață
Mapper.
@Mapper (uses = LocationMapper. class)
public interface RestaurantMapper {
RestaurantMapper INSTANCE = Mappers. getMapper (RestaurantMapper. class);
RestaurantDTO toDto(Restaurant restaurant) ;
}
Și atunci când rulăm comandarea de build a aplicației MapStruct generează
implmentarea acestei interfețe, rezultatul fiind prezentat mai jos :
public class RestaurantMapperImpl implements RestaurantMapper {
private final LocationMapper locationMapper = Mappers. getMapper ( LocationMapper. class );
@Override
public RestaurantDTO toDto(Restaurant restaurant) {
if ( restaurant == null ) {
return null;
}
RestaurantDTO restaurantDTO = new RestaurantDTO() ;
25
restaurantDTO.setName( restaurant.getName() ) ;
restaurantDTO.setDescription( restaurant.getDescription() ) ;
restaurantDTO.setLocation( locationMapper .toDto( restaurant.getLocation() ) ) ;
return restaurantDTO ;
}
}
2.4.6 Modularizarea aplicației
Aplicația este împărțită în 6 pachete în funcție de tipul de funcționalitate. În acest fel dacă
ulterior se dorește decuplarea în module separate (de exemplu modul de comunicare cu baza de
date, modul de logică), procesul nu ar fi costisitor din punct de vedere al timpului.
Dat faptul că cele 3 roluri pot fi interpretate ca aplicații diferite, avem următoarele pachete :
● Admin : conține funcționalitate dedicată doar admin-ului
● Customer : conține funcționalitate dedicată doar clientului
● Restaurant : conține funcționalitate dedicată doar părții de managament din restaurant
Următoarele pachete conțin funcționalitate comună :
● Generic : conține servicii comune
● Jobs : conține job-uri care rulează recurent
● Utils : conține clase utilitare pentru proiect
Aceste 3 pachete pot fi ulterior compilate ca jar-uri și adăugate ca dependințe proiectului;
Pachelete admin, customer și restaurant urmează structura prezentată în figura :
26
● Controller : conține toate clasele ce deservesc rolul de controller. S-a aplicat conveția de
a adauga la sfârșitul numelui clasei sufixul Controller pentru a fi sugestiv.
● Domain : conține obiectele domeniu (mapate la baza de date)
● DTO : conține obiectele de transfer al obiectelor de tip domain
● Exception : conține excepțiile definite
● Repository : conține interfețele Spring DATA prin care se asigură accesul la baza de date
● Service : conține interfețele seviciilor de business
● ServiceImplementation : conține implementarea serviciilor de business
2.5 Implementare
2.5.0 Noțiuni generale
Adnotări :
a. @Entity – marchează clasa ca fiind un entity bean, fiind salvată în baza de date
b. @Column – adnotare cu ajutorul căruia un atribut al unui obiect poate fi mapat în baza
de date cu un anumit nume
c. @NotNull – adnotare reprezintă o restricție Hibernate care va arunca o excepție în cazul
în care câmpul pe care este folosită primește valoarea null
d. @Size – adnotarea reprezintă o restricție Hibernate care va arunca o excepție în cazul în
care câmpul pe care este folosită primește valoarea mai mică sau mai mare decât cea
precizată ca parametru
e. @Pattern – adnotarea reprezintă o restricție Hibernate, câmpul nefiind salvat dacă nu
respectă șablonul precizat ca parametru
f. @Email – adnotarea reprezită o restricție Hibernate care verifică dacă valoarea câmpului
este o adresă de email validă.
g. @OneToOne – adnotarea Hibernate care creează o legătură de unul-la-unul între obiecte
h. @OneToMany – adnotarea Hibernate care creează o legătură de unul-la-mai-multe între
obiecte
i. @ManyToOne – adnotarea Hibernate care creează o legătură mai-multe-la-unul între
obiecte
j. @Service – marchează clasa respectivă ca fiind un obiect al cărui life cycle este
controlat de containerul Spring.
k. @RestController – este o adnotarea wrapper peste adnotările @Controller și
@ResponseBody. Prin intermediul acestei adnotări clasa este marcată ca fiind o clasă
Spring Controller.
l. @GetMapping – adnotare
m. @PostMapping – adnotare
n. @PutMapping – ad notare
o. @DeleteMapping – adnotare
2.5.1 Portal clienți
Aplica ția destinată utilizatorilor de tip client este accesibilă atât de pe device
smartphone cât și de pe device- uri cu rezolu
folosit doar capturi de ecran de pe versiunea smartphone.
2.5.1.0 Secțiunea profil
Pentru a putea face vizibilă sec
câmpul Profil.
Fig 2.1 Meniu eRestaurant
Logica necesar ă pentru acest screen pe partea de client a fost definită în fi
routing.js . Cererea și trimiterea datelor se face prin endpoint
Pentru implementarea pe partea de server, s
a. ProfileController – deserve
acestuia să instan țieze o anumită metodă.
b. CustomerService – layer –
c. Customer – clasa care instan
Clasa Customer
@Entity
@Table(name="customer" )
@PrimaryKeyJoinColumn (name="ID"
adnotare folosită pentru routing- ul metodelor HTTP GET
adnotare folosită pentru routing- ul metodelor HTTP POST
notare folosită pentru routing- ul metodelor HTTP PUT
adnotare folosită pentru routing- ul metodelor HTTP
ția destinată utilizatorilor de tip client este accesibilă atât de pe device
uri cu rezolu ții mari(de exemplu PC). În scop demonstrativ s
folosit doar capturi de ecran de pe versiunea smartphone.
face vizibilă sec țiunea de profil, utilizatorul trebuie să se lecteze din m
Meniu eRestaurant Fig 2.2 Ecran profil
ă pentru acest screen pe partea de client a fost definită în fi
și trimiterea datelor se face prin endpoint -ul “/user/details” .
implementarea pe partea de server, s -au definit următoarele clase:
deservește rolul de a prelua apelul HTTP și în funcție de URI
țieze o anumită metodă.
-ul intermediar între cel de persistență și cel de afișare.
clasa care instan țiază un obiect de tip Customer (utilizator).
"ID")
27 ul metodelor HTTP GET
ul metodelor HTTP POST
ul metodelor HTTP PUT
ul metodelor HTTP DELETE
ția destinată utilizatorilor de tip client este accesibilă atât de pe device -uri de tip
ții mari(de exemplu PC). În scop demonstrativ s -au
lecteze din m eniu
Fig 2.2 Ecran profil
ă pentru acest screen pe partea de client a fost definită în fi șierul user-
și în funcție de URI -ul
și cel de afișare.
28
public class Customer extends User{
@OneToOne (cascade = {CascadeType. ALL})
CustomerDetails customerDetails ;
@OneToOne (cascade = CascadeType. ALL, mappedBy = "customer" )
private ShoppingCart shoppingCart ;
}
Această clasă extinde clasa User care conține atributele comune a oricăuri tip de user (de
exemplu, câmpurile username și password se regăsesc în toate tipurile de useri).
Clasa ProfileController
@RestController
@RequestMapping ("/user/details" )
public class ProfileController {
/*Injected services
* ..
* ..*/
@GetMapping
public ResponseEntity<UserDTO> getUserInfo (Principal principal) {
Customer customer = customerService .findByUsername(principal.getName()) ;
return new ResponseEntity<UserDTO>
(DTOTransformers. toUserTransferObject (customer) , HttpStatus. OK);
}
@RequestMapping (method=RequestMethod. PUT)
public ResponseEntity<Customer> updateCustomer (@RequestBody Customer
updatedCustomer ,Principal principal) {
Customer customer = customerService .findByUsername(principal.getName()) ;
customerService .update(customer , updatedCustomer) ;
return new ResponseEntity<Customer>(updatedCustomer ,HttpStatus. OK);
}
}
În momentul în care se face un request GET către “ /user/details” metoda getUserInfo
este apelată. Parametrul acesteia este un obiect de tipul Principal provenit din package-ul
javax.security și conține informații despre user-ul logat și sesiunea curentă.
Prin intermediul serivicului customerService putem găsi bazat pe numele user-ului logat
toate informațiile acestuia. Ulterior ele sunt transmite sub forma unui obiect de transfer pe partea
de client.
În cazul în care se efectuează un request PUT, pentru a updata anumite informații,
metoda updateCustomer este apelată și primește ca parametru un obiect de tip Customer, care
conține noile informații și parametrul Principal descris mai sus.
Ulterior prin metoda update a serviciului customerService , se efectuează schimbarea
vechilor valori a userului.
@Override
public Customer update(Customer customer , Customer updatedCustomer) {
customer.setFirstName(updatedCustomer.getFirstName()) ;
customer.setLastName(updatedCustomer.getLastName()) ;
customer.setPhoneNumber(updatedCustomer.getPhoneNumber()) ;
customer. setEmail(updatedCustomer.getEmail())
return customerRepo .save(customer)
}
2.5.1.2 Secțiunea adrese
Pentru a afi șa secțiunea de adrese utilizatorul trebuie să selecteze din meniu câmpul
Adrese. Acestea sunt afi șate 10 per pagină, fiind afișat în primă
Fig 2.3 Adresele paginate
În figura 2 .3 este prezentată prima pagină cu prim
prezentate ultimile 4 elemente. Pentru a vizualiza detalii despre o anumită adresă, aceasta trebuie
selectată din listă. Răspunsul acestei ac
Fig 2.5 Detalii adresă
setEmail(updatedCustomer.getEmail()) ;
.save(customer) ;
șa secțiunea de adrese utilizatorul trebuie să selecteze din meniu câmpul
șate 10 per pagină, fiind afișat în primă fază doar numele dat adresei.
Adresele paginate – pag 1 Fig 2.4 Adresele paginate
.3 este prezentată prima pagină cu prim ele 10 elemente, iar în figura 2
prezentate ultimile 4 elemente. Pentru a vizualiza detalii despre o anumită adresă, aceasta trebuie
selectată din listă. Răspunsul acestei ac țiuni poate fi vizualizat în figura 2 .5 prezentată mai jos.
Fig 2.6 Formular creare adresă Fig 2.7 Răspuns la salvarea
adresei
29 șa secțiunea de adrese utilizatorul trebuie să selecteze din meniu câmpul
fază doar numele dat adresei.
Adresele paginate – pag 2
ele 10 elemente, iar în figura 2 .4 sunt
prezentate ultimile 4 elemente. Pentru a vizualiza detalii despre o anumită adresă, aceasta trebuie
.5 prezentată mai jos.
Fig 2.7 Răspuns la salvarea
adresei
30
Prin apăsarea butonului de creare a unei noi adrese se afișează formularul prezentat în
figura 2.6. În scop demonstrativ acesta a fost deja completat. Pentru salvarea adresei se face click
pe butonul cu simbolul de ‘verificat’ și datele sunt trimise către server. Răspunsul primit înapoi
este afișat cu ajutorul bibliotecii notify.js, după cum este prezentat în figura 2.7.
Logica necesară pentru implementarea funcționalităților pe partea de client s-a definit în
fișierul user-address.js . De exemplu request-ul de creare a unei noi adrese poate fi vizualizat in
codul de mai jos.
$(document ).on('click' , '#save-address' , function () {
/* Colectarea datelor din formular si salvarea lor intr-un obiect denumit address */
$.ajax({
type: "POST", /* metoda HTTP folosita */
url: "/address" , /* endpoint-ul apelat */
data: JSON. stringify (address) ,
contentType : "application/json; charset=utf-8" ,
dataType : "json",
success : function (data) {
$.notify("Adresa a fost adaugata cu succes" , "info");
}
}
).done(function () {
getUserAddress (0, testIfMobile ());
$("#address-create" ).find('input' ).val("")
});
});
Pentru implemenatrea funcționalității în backend s-au definit următoarele clase:
a. AddressController – deservește rolul de a prelua request-ul HTTP și în funcție de URI-ul
acestuia să instanțieze o anumită metodă.
b. AddressService – layer-ul intermediar între cel de persistență și cel de afișare.
c. Address – clasa care instanțiază un obiect de tip Customer (utilizator).
Clasa Address:
Extinde clasa abstractă BaseEntity și conține câmpurile necesare descrierii unei adrese.
Prin intermediul anotării @ManyToOne se creează o legătură n..1 cu obiectul CustomerDetails.
În acest mod, unui singur user îi pot corespunde mai multe adrese.
@Entity
public class Address extends BaseEntity {
private String addressName ;
private String city;
private String street;
private String flatNumber ;
private int floor;
private boolean deleted ;
@JsonIgnore
@ManyToOne
@JoinColumn (name = "customerDetails_id" )
private CustomerDetails customerDetails ;
}
31
Clasa AddressController:
Afișarea adreselor
@RequestMapping ("/address" )
@RestController
public class AddressController {
/*
* Injection of services
* */
@GetMapping
public Page<AddressDTO> getAddress (Principal principal , @RequestParam ("page") int page,
@RequestParam ("size") int size) {
Customer customer = getCustomer(principal) ;
PageRequest pageRequest = new PageRequest(page , size);
List<Address> addresses =
customer.getCustomerDetails().getAddress().stream().filter(address ->
!address.isDeleted()).collect(Collectors. toList());
int start = pageRequest.getOffset() ;
int end = (start + pageRequest.getPageSize()) > addresses.size() ?
addresses.size() : (start + pageRequest.getPageSize()) ;
Page<Address> addressPage = new PageImpl<Address>(addresses.subList(start , end),
pageRequest , addresses.size()) ;
Page<AddressDTO> addressDTOS = addressPage.map(AddressMapper. INSTANCE ::toDto) ;
return addressDTOS ;
}
}
În metoda getAddress() obiectul Customer este deja încărcat cu toate datele, pentru a
evita încă un apel la baza de date putem procesa datele în memorie. Acest lucru ar putea fi un
generator de erori în cazul în care fiecărui utilizator i-ar corespunde un număr imens de adrese.
List<Address> addresses =
customer.getCustomerDetails().getAddress().stream().filter(address ->
!address.isDeleted()).collect(Collectors. toList());
Cu ajutorul expresiilor lambda definite în Java8, putem filtra lista de adrese pentru a le
adăuga într-una noua folosind o singură linie de cod. În cazul de față filtrarea se face pe câmpul
deleted; dacă acesta este fals, obiectul este adăugat în noua listă.
Înainte de a întoarce răspunsul către client, are loc transformarea obiectelor din Address în
AddressDTO cu ajutorul clasei AddressMapper .
Ștergea unei adrese
În controller-ul AddressController este definită următoarea metodă :
@DeleteMapping
public void deleteAddress (@PathVariable Long id) {
addressService .delete(id) ;
}
Această metodă primește ca parametru un obiect Long care reprezintă ID-ul adresei ce se
dorește a fi ștearsă.
Implementarea metodei de delete a seriviciului addressService conține logica necesară
pentru modificarea obiectului :
@Override
public void delete(Long id) {
Address address = addressRepository
address.setDeleted( true);
addressRepository .save(address)
}
Prin intermediul serviciului
dat, a cărui câmp deleted este setat cu valoarea
2.5.1.3 Sec țiunea cărți de credit
Pentru accesarea acestei sec
rubrica ‘Carduri’. Pentru a p ăstra consisten
implementat asemănător cu cel al ecranului de adrese.
Fig 2.8 Cardurile paginate
Logica necesară păr ții de client s
Afișarea cardurilor
@GetMapping
public Page<CreditCard> getCreditCard
page, @RequestParam ("size") int
return
cardService .findByCustomerDetails(
PageRequest(page , size));
}
Prin metod getCreditCard se extrag din baza de date cardurile aferente utilizatorului înregistrat.
addressRepository .findById(id) ;
.save(address) ;
Prin intermediul serviciului addressRepository se caută în baza de date adresa cu id
deleted este setat cu valoarea true, ulterior salvându- se modificările.
țiunea cărți de credit
Pentru accesarea acestei sec țiuni utilizatorul trebuie să navigheze prin meniul aplicației la
ăstra consisten ța aplicației, design- ul acestui ecran a fost
implementat asemănător cu cel al ecranului de adrese.
Fig 2.9 Formular adăugare
card Fig 2.10 Vizualizare detalii
card
ții de client s -a definit în fișierul user-cards.js .
getCreditCard (Principal principal , @RequestParam (
int size) {
.findByCustomerDetails( userService .findByUsername(principal.getName())
se extrag din baza de date cardurile aferente utilizatorului înregistrat.
32 se caută în baza de date adresa cu id -ul
se modificările.
țiuni utilizatorul trebuie să navigheze prin meniul aplicației la
ul acestui ecran a fost
Fig 2.10 Vizualizare detalii
card
("page") int
.findByUsername(principal.getName()) , new
se extrag din baza de date cardurile aferente utilizatorului înregistrat.
2.5.1.4Secțiunea Restaurante
Pentru navigarea către această pagină utilizatorul trebuie să selecteze din meniul
aplicației câmpul ‘Restaurante’. În primă fază acestea vor apărea
care există mai mult de 10 restaurante înregistrate.
Pentru afi șarea detaliilor și posibilitatea de a plasa o comandă/rezervare utilizatorul
trebuie să selecteze din această listă unul dintre restaurante. Modul de afi
este prezentat în figura 2.11. Cu
Eclipse. Detaliile aferente ale acestuia sunt prezentate în figura
Fig 2.11 Listarea restaurantelor
Pentru implementarea vizualizării restaurantelor
RestaurantController.
@RestController
@RequestMapping ("/restaurants"
public class RestaurantController {
@Autowired
RestaurantService restaurantService
@Autowired
RestaurantMapper restaurantMapper
@GetMapping
public Page<RestaurantDTO> findAllRestaurants
@RequestParam ("size") int size) {
Page<Restaurant> restaurants =
Page<RestaurantDTO> result = restaurants.map(
return result;
}
@GetMapping (value="/{id}" )
public RestaurantDTO getRestaurant
Pentru navigarea către această pagină utilizatorul trebuie să selecteze din meniul
În primă fază acestea vor apărea paginate per pagină
restaurante înregistrate.
șarea detaliilor și posibilitatea de a plasa o comandă/rezervare utilizatorul
trebuie să selecteze din această listă unul dintre restaurante. Modul de afi șare al restaurantelor
.11. Cu scop demonstrativ în continuare o s ă selectăm restaurantul
. Detaliile aferente ale acestuia sunt prezentate în figura 2.12.
Fig 2.11 Listarea restaurantelor Fig 2.12 Afi șare detalii restaurant
vizualizării restaurantelor și detaliilor s-a definit clasa
"/restaurants" )
RestaurantController {
restaurantService ;
restaurantMapper ;
findAllRestaurants (@RequestParam ("page") int page
size) {
Page<Restaurant> restaurants = restaurantService .findAllPageable(page
Page<RestaurantDTO> result = restaurants.map( restaurantMapper ::toDto) ;
getRestaurant (@PathVariable Long id) {
33 Pentru navigarea către această pagină utilizatorul trebuie să selecteze din meniul
aginate per pagină în cazul în
șarea detaliilor și posibilitatea de a plasa o comandă/rezervare utilizatorul
șare al restaurantelor
ă selectăm restaurantul
șare detalii restaurant
clasa
page,
.findAllPageable(page ,size);
Restaurant restaurant =
return restaurantMapper
}
}
În acastă clasă au fost injectate două servicii,
informații din baza de date și restaurantMapper
Restaurant în obiecte de transfer
În consecință un apel către endpointul
findAllRestaurants care returnează o listă paginată de obiecte de tip
către client sub format JSON , răspuns procesat de script
pagina HTML precum în figura 2
detaliile unui singur restaurant.
În figura 2.12 s- a definit butonul cu id
inițializarea unei ferestre modale(o fereastră peste pagina ht
de a vizualiza și oferi un rating/comentariu către restaurant, după cum poate fi văzut în figura
2.13.
Fig 2.13 Vizualizare feedback
Știm din cerințele funcționale că utilizatorul are
dată la 30 de zile. În cazul în care procesul se sfâr
ajutorul librări ei notifiy.js precum în figura 2
user-reviews.js și pot fi găsite integral în anexa acestui document.
S-a definit claza de bază Review
public class Review extends BaseEntity{
private LocalDateTime localDateTime
@OneToMany (cascade = CascadeType.
Restaurant restaurant = restaurantService .findById(id) ;
restaurantMapper .toDto(restaurant) ;
În acastă clasă au fost injectate două servicii, restaurantService cu scopul de a extrage
restaurantMapper , folosit pentru a converti obiectele domeniu
în obiecte de transfer RestaurantDTO.
către endpointul /restaurants duce la invocarea metodei
findAllRestaurants care returnează o listă paginată de obiecte de tip RestaurantDTO
, răspuns procesat de script -urile Javascript și afișat ulterior î
precum în figura 2 .11.Apelarea endpoint-ului “/restaurants/{id}”
a definit butonul cu id -ul #restaurant-feedback care are ca scop
țializarea unei ferestre modale(o fereastră peste pagina ht ml) unde utilizatorul are posibilitatea
și oferi un rating/comentariu către restaurant, după cum poate fi văzut în figura
Vizualizare feedback Fig 2.14 Adăugare feedback
Știm din cerințele funcționale că utilizatorul are dreptul la rating- ul aceluia
dată la 30 de zile. În cazul în care procesul se sfâr șește cu eroare, utilizatorul este anunțat cu
ei notifiy.js precum în figura 2 .14. Scripturile necesare au fost scris în fi
și pot fi găsite integral în anexa acestui document.
a definit claza de bază Review :
BaseEntity{
localDateTime = java.time.LocalDateTime. now();
= CascadeType. ALL)
34 cu scopul de a extrage
, folosit pentru a converti obiectele domeniu
duce la invocarea metodei
RestaurantDTO returnate
și afișat ulterior î n
“/restaurants/{id}” v-a returna
care are ca scop
ml) unde utilizatorul are posibilitatea
și oferi un rating/comentariu către restaurant, după cum poate fi văzut în figura
Fig 2.14 Adăugare feedback
ul aceluia și restaurant o
șește cu eroare, utilizatorul este anunțat cu
.14. Scripturile necesare au fost scris în fi șierul
35
List<Feedback> feedbackList ;
@NotNull (message = "Message can't be null" )
@Size(min = 0, max = 255, message = "Size of the message must be between 10 and 255
characters" )
private String message ;
@ManyToOne
@JoinColumn (name="user_id" )
private User author;
@ManyToOne
@JoinColumn (name="restaurant_id" )
private Restaurant restaurant ;
}
Conține o listă de Feedback-uri care reprezintă notele userilor acordate restaurantelor în
funcție de unul din cele 3 criterii (FeedbackType).
public class Feedback extends BaseEntity {
private Review review;
private FeedbackType feedbackType ;
private int feedbackNote ;
}
FeedbackType a fost conceput sub forma unei enumerații java cu 3 tip-uri, FOOD,
CLIENTSERVICE și ATMOSPHERE.
public enum FeedbackType {
FOOD,
CLIENTSERVICE ,
ATMOSPHERE ;
public static FeedbackType getEnum (String param) {
if (FOOD.name().equals(param)) {
return FOOD;
}
if (ATMOSPHERE .name().equals(param)) {
return ATMOSPHERE ;
}
if (CLIENTSERVICE .name().equals(param)) {
return CLIENTSERVICE ;
}
throw new IllegalArgumentException( "Enum with given type does not exist" );
}
}
Pentru obținerea request-urilor venite de pe partea de client, s-a implementat un
controller REST cu numele ReviewController :
@RestController
@RequestMapping ("/reviews" )
public class ReviewController {
/*
Injection of services being done here.
*/
@Autowired
ReviewMapper reviewMapper ;
@PostMapping
public ReviewDTO createReview (@RequestBody ReviewDTO reviewDTO) {
Review review = reviewMapper .createDomainObject(reviewDTO) ;
if (reviewService .userAllowedToAddNewReview(review.getRestaurant() ,
review.getAuthor())) {
reviewService .save(review) ;
return reviewMapper .toDto(review) ;
} else {
36
throw new ReviewGivenException( "A review has already been given in last 30 days" );
}
}
@GetMapping
public List<ReviewDTO> getReviewsForRestaurant (@RequestParam ("restaurantId" ) long
restaurantId) {
Restaurant restaurant = restaurantService .findById(restaurantId) ;
List<Review> reviews = reviewService .findAllByRestaurant(restaurant) ;
List<ReviewDTO> reviewDTOS =
reviews.stream().map( reviewMapper ::toDto).collect(Collectors. toList());
return reviewDTOS ;
}
@GetMapping (value = "/average" )
public ResponseEntity<Map<FeedbackType ,
Double>> getFeedbackAverages (@RequestParam ("restaurantId" ) long restaurantId) {
Restaurant restaurant = restaurantService .findById(restaurantId) ;
Map<FeedbackType , Double> averages = reviewService .getAverageRatings(restaurant) ;
return new ResponseEntity<Map<FeedbackType , Double>>(averages , HttpStatus. OK);
}
}
În acesta avem 3 metode :
createReview : metodă folosită pentru adăugarea unui nou review. Primește ca parametru
un ReviewDTO, obiect care trebuie să vină din partea de client. Cu ajutorul serviciului
injectat reviewService se verifică dacă userul are dreptul să acorde un nou review.
@Override
public boolean userAllowedToAddNewReview (Restaurant restaurant , User user) {
Review lastUserReview =
reviewRepository .findTopByRestaurantAndAuthorOrderByLocalDateTimeDesc(restaurant ,
user);
if (lastUserReview == null) {
return true;
} else {
LocalDate now = LocalDate. now();
long daysBetween =
DAYS.between(lastUserReview.getLocalDateTime().toLocalDate() , now);
return daysBetween > 30;
}
}
.. dacă acesta nu are niciun review pentru acest restaurant, este automat eligibil pentru a oferi
unul. Dacă deja există cel puțin un review pentru acest restaurant, atunci se verifică dacă au
trecut minim 30 de zile.
getReviewsForRestaurant : metoda este folosită pentru a returna toate review-urile unui
restaurant dat. Primește ca parametru un id, care reprezintă cheia primară a restaurantului
în baza de date
@GetMapping
public List<ReviewDTO> getReviewsForRestaurant (@RequestParam ("restaurantId" ) long
restaurantId) {
Restaurant restaurant = restaurantService .findById(restaurantId) ;
List<Review> reviews = reviewService .findAllByRestaurant(restaurant) ;
List<ReviewDTO> reviewDTOS =
37
reviews.stream().map( reviewMapper ::toDto).collect(Collectors. toList());
return reviewDTOS ;
}
..rezultatul este transformat cu ajutorul serviciului reviewMapper într-o listă de obiecte de tip
ReviewDTO.
getFeedbackAverages : metoda este folosită pentru a returna media aritmetică a
rezultatelor pentru cele 3 criterii ale unui restaurant.
@GetMapping (value = "/average" )
public ResponseEntity<Map<FeedbackType ,
Double>> getFeedbackAverages (@RequestParam ("restaurantId" ) long restaurantId) {
Restaurant restaurant = restaurantService .findById(restaurantId) ;
Map<FeedbackType , Double> averages = reviewService .getAverageRatings(restaurant) ;
return new ResponseEntity<Map<FeedbackType , Double>>(averages , HttpStatus. OK);
}
Logica de calcul are loc în serviciul reviewService.
@Override
public Map<FeedbackType , Double> getAverageRatings (Restaurant restaurant) {
List<Review> reviews = reviewRepository .findAllByRestaurant(restaurant) ;
List<Feedback> feedback = new ArrayList<>() ;
reviews.forEach(review -> feedback .addAll(review.getFeedbackList())) ;
Map<FeedbackType , Double> average =
feedback.stream().collect(Collectors. groupingBy (Feedback::getFeedbackType ,
Collectors. averagingInt (Feedback::getFeedbackNote))) ;
return average ;
}
Pentru început se caută toate review-urile unui restaurant în baza de date. Ulterior cu ajutorul
expresiilor lambda se stochează într-o listă nouă feedback-urile fiecărui Review.
Pentru gruparea și calcularea mediilor aritmetice ne folosim de noile API-uri din Java8, mai
exact Collectors. Feedback-urile sunt filtrate după tipul acestora și pentru fiecare tip se
calculeaza media aritmetică. Rezultatele sunt stocate și grupate sub forma unui Map, ele fiind în
final returnate de către Controller în format JSON :
{
"CLIENTSERVICE" :3.5625,
"ATMOSPHERE" :4.0625,
"FOOD":2.875
}
Vizualizarea produselor/meniurilor, detaliilor și adăugarea acestora în coș :
Pentru accesul la lista de produse și meniuri a unui restaurant utilizatorul trebuie să
selecteze cel de al 3-lea buton din pagina de detalii, acesta fiind încercuit cu scop demonstrativ în
figura 2.15 prezentată mai jos. Apăsarea butonului duce utilizatorul-ul la următorul ecran,
prezentat în figura 2.16. Utilizatorul are posibilitatea de a filtra produsele prin nume.
Fig 2.15 Buton comandă acasă
Boxurile în care se află detaliile fiecărui produs/meniu sunt generate automat prin
folosirea unei func ții javascript regăsită în fișierul
function drawProductBox (obj) {
var htmlObject = [] ;
htmlObject. push('<div class="col
htmlObject. push('<div class="thumbnail" id="'
htmlObject. push('<img alt="100%x200" style="height: 100px; display: block;" src="'
obj.image + '" data-holder- render
htmlObject. push('<div class="caption">'
htmlObject. push('<p>' + obj.product
htmlObject. push('<p>Pret: ' + obj.
htmlObject. push('<p><a href="#" class="btn btn
data-productid="' + obj.product
product- detail" role="button" data
htmlObject. push('</div>' );
htmlObject. push('</div>' );
htmlObject. push('</div>' );
return htmlObject. join("");
}
Asemănător s-a definit și o metodă
primit de la server.
Vizualizarea detaliilor fiecărui produs se face prin apăsarea butonului marcat cu “?”.
Folosind acest buton se declan șează o
ale meniului.
Buton comandă acasă Fig 2.16 Afi șare produse
Boxurile în care se află detaliile fiecărui produs/meniu sunt generate automat prin
ții javascript regăsită în fișierul user-products.js .
(obj) {
'<div class="col -sm-3 col-md-2">' );
'<div class="thumbnail" id="' + product .id + '">');
'<img alt="100%x200" style="height: 100px; display: block;" src="'
rendered="true"/>' );
'<div class="caption">' );
product .name + '</p>');
+ obj.product .price + ' lei</p>' );
'<p><a href="#" class="btn btn -primary add-to- cart" role="button"
product .id + '"></a><a href="#" class="btn btn- default
detail" role="button" data -productid="' + obj.product .id + '"></a></p>'
și o metodă drawMenuBox care prime ște ca parametru un obiect
Vizualizarea detaliilor fiecărui produs se face prin apăsarea butonului marcat cu “?”.
șează o fereastră modal unde se regăsesc detaliile produsului sau
38
șare produse
Boxurile în care se află detaliile fiecărui produs/meniu sunt generate automat prin
'<img alt="100%x200" style="height: 100px; display: block;" src="' +
cart" role="button"
default
'"></a></p>' );
ște ca parametru un obiect
Vizualizarea detaliilor fiecărui produs se face prin apăsarea butonului marcat cu “?”.
fereastră modal unde se regăsesc detaliile produsului sau
Fig 2.17 Detalii produs
În detaliile meniului se regăsesc informa
produse, la rândul lor generează o fereastră de
de produsul respectiv.
S-a definit un controller cu numele ProductControllerAPI cu urm
findProductsByRestaurant
restaurant dat.
@GetMapping
public Page<ProductDTO> findProductsByRestaurant
restaurantId ,
@RequestParam ("page") int
Page<Product> products =
page,size);
Page<ProductDTO> productDTOS = products.map(DTOTransformers::
return productDTOS ;
}
searchProducts : metodă folosită pentru extragerea produselor unui restaurant a căror
nume con ține valoarea parametrului trimis name
@GetMapping ("/search" )
public Page<ProductDTO> searchProducts
@RequestParam ("restaurant"
@RequestParam ("size") int
Page<Product> products=
restaurantService .findPaginat
Page<ProductDTO> productDTOS = products.map(DTOTransformers::
return productDTOS ;
}
filterProducts : metod ă folosită pentru extragerea produselor unui restaurant a căror tip
este cel trimis ca parametru
@GetMapping ("/filter" )
public Page<ProductDTO> filterProducts
@RequestParam ("restaurant"
Detalii produs Fig 2.18 Detalii meniu
În detaliile meniului se regăsesc informa ții despre produsele care le conțin. Aceste
produse, la rândul lor generează o fereastră de tip pop-up unde se regăsesc informa
a definit un controller cu numele ProductControllerAPI cu urm ătoarele metode
findProductsByRestaurant : metoda folosită pentru extragerea și afișarea produselor unui
findProductsByRestaurant (@RequestParam ("restaurant"
int page, @RequestParam ("size") int size) {
Page<Product> products = restaurantService .findPaginatedProducts(restaura
Page<ProductDTO> productDTOS = products.map(DTOTransformers:: toProductDTO
searchProducts : metodă folosită pentru extragerea produselor unui restaurant a căror
ține valoarea parametrului trimis name
searchProducts (@RequestParam ("name") String name
"restaurant" ) Long id , @RequestParam ("page") int page,
int size) {
Page<Product> products=
.findPaginat edProductsWithNameLike(id ,name, page, size)
Page<ProductDTO> productDTOS = products.map(DTOTransformers:: toProductDTO
ă folosită pentru extragerea produselor unui restaurant a căror tip
parametru
filterProducts (@RequestParam ("filter" ) String filter
"restaurant" ) Long id , @RequestParam ("page") int page,
39
Detalii meniu
ții despre produsele care le conțin. Aceste
up unde se regăsesc informa ții legate doar
ătoarele metode :
și afișarea produselor unui
"restaurant" ) Long
.findPaginatedProducts(restaura ntId,
toProductDTO );
searchProducts : metodă folosită pentru extragerea produselor unui restaurant a căror
) String name ,
,
size);
toProductDTO );
ă folosită pentru extragerea produselor unui restaurant a căror tip
) String filter ,
,
40
@RequestParam ("size") int size) {
Page<Product> products = restaurantService .findAllByTypeAndRestaurantId(filter ,
id, page, size);
Page<ProductDTO> productDTOS = products.map(DTOTransformers:: toProductDTO );
return productDTOS ;
}
findProduct : metodă folosită pentru a returna un anumit produs în funcție de id-ul trimis
@GetMapping (value = "/{id}" )
public ProductDTO findProduct (@PathVariable Long id) {
Product product = restaurantService .findProductById(id) ;
return productMapper .toDto(product) ;
}
Pentru vizualizarea meniurilor s-a definit un controller MenuController cu următoarele metode :
getMenu : metodă folosită pentru a returna un anumit meniu în funcție de id-ul trimis
@GetMapping (value = "/{id}" )
public MenuDTO getMenu (@PathVariable ("id") Long id) {
Menu menu = menuService .findOne(id) ;
return menuMapper .toDto(menu) ;
}
getMenus : metodă folosită pentru a returna o listă de obiecte de tip Menu în funcție de
id-ul restaurantului trimis
@GetMapping
public Page<MenuDTO> getMenus (Principal principal , @RequestParam (value =
"restaurantId" , required = false) Long id , @RequestParam ("size") int
size@RequestParam ("page") int page) {
List<Menu> menus ;
Long restaurantId = null;
User user = userService .findByUsername(principal.getName()) ;
if (user instanceof Manager) {
restaurantId =((Manager) user).getRestaurant().getId() ;
}
if(restaurantId!= null) {
menus = restaurantService .findById(restaurantId).getMenus() ;
} else {
menus = restaurantService .findById(id).getMenus() ;
}
List<MenuDTO> menuDTOS =
menus.stream().map( menuMapper ::toDto).collect(Collectors. toList());
Page<MenuDTO> menuDTOPage = new PageImpl<MenuDTO>(menuDTOS , new PageRequest(page ,
size), menuDTOS.size()) ;
return menuDTOPage ;
}
2.5.1.5 Adăugarea produselor în coș
Pot fi adăugate în coșul de cumpărături cu ajutorul folosirii butonului prezentat în figura
2.19. Această acțiune poate avea două rezultate, fie plasarea produslui în coș se termină cu
succes, fie acesta nu mai există pe stoc, caz în care utilizatorul este notificat.
Fig 2.19 Adăugarea în co ș cu succes
Apelul căt re server se face printr
atunci se notifică user- ul că produsul a fost adăugat cu succes. În cazul în care server
o excepție de tip ProductOutOfStockException
este în stoc.
Deoarece pot exista o multitudine de erori, s
un mod generic, utilizatorul fiind notificat cu mesajul
$.ajax({
type: "POST",
url: "/user/restaurant/shoppingItem"
data: JSON. stringify (shoppingItem)
contentType : "application/json; charset=utf
dataType : "json",
success : function (data) {
$.notify("Produsul a fost adaugat in cos"
},
error: function (data) {
var error = data.responseJSON ;
var exc = error.exception ;
if(exc.includes( "ProductOutOfStockException"
$.notify("Produsul nu mai este in stoc."
} else {$.notify(exc, "error")
Pe partea de backend s- a definit un obiect de tip
public class ShoppingItem extends
@OneToOne
private Product product ;
private int quantity ;
@OneToOne
private Menu menu;
@JsonIgnore
@ManyToOne
@JoinColumn (name = "order_id"
private Order order;
ș cu succes Fig 2.20 Notificare mesaj de eroare
re server se face printr -un apel AJAX . Dacă răspunsul este unul de succes,
ul că produsul a fost adăugat cu succes. În cazul în care server
ProductOutOfStockException atunci acesta este notificat că produsul nu mai
Deoarece pot exista o multitudine de erori, s -a ales ca momentan restul să fie tratate într
un mod generic, utilizatorul fiind notificat cu mesajul implicit al acesteia.
"/user/restaurant/shoppingItem" ,
(shoppingItem) ,
"application/json; charset=utf -8",
"Produsul a fost adaugat in cos" , "success" );
;
"ProductOutOfStockException" )) {
"Produsul nu mai este in stoc." , "error" );
); } } } );
a definit un obiect de tip ShoppingItem cu următoarele câmpuri.
extends BaseEntity {
"order_id" )
41
Notificare mesaj de eroare
. Dacă răspunsul este unul de succes,
ul că produsul a fost adăugat cu succes. În cazul în care server -ul întoarce
produsul nu mai
a ales ca momentan restul să fie tratate într –
cu următoarele câmpuri.
42
@ManyToOne
@JoinColumn (name = "shopping_cart_id" )
private ShoppingCart shoppingCart ;
}
Acest obiect poate conține atât un obiect de tip Product cât și de tip Menu. Are referință
către comanda pentru care a fost plasat și referință la coșul de cumpărături al utilizatorului logat.
Controller-ul care se ocupă cu inițializarea aceste funcționalități este OrderController
unde este definită metoda addShoppingItemToCart
@PostMapping (value = "user/restaurant/shoppingItem" )
public ShoppingItemDTO addShoppingItemToCart (@RequestBody ShoppingItemDTO
shoppingItem , Principal principal) throws ShoppingCartException {
ShoppingCart shoppingCart =
customerService .findByUsername(principal.getName()).getShoppingCart() ;
shoppingCartService .addItemToCart(shoppingCart ,
shoppingItemMapper .toDomainObject(shoppingItem)) ;
return shoppingItem ;
}
Prin intermediul seviciului shoppingCartService și a metodei sale addItemToCart
produsele sunt adăugate în coșul de cumpărături al utilizatorului.
public ShoppingCart addItemToCart (ShoppingCart shoppingCart , ShoppingItem
shoppingItem) throws ShoppingCartException {
if (shoppingItem.getProduct() != null) {
Product persistedProduct =
productRepository .findOne(shoppingItem.getProduct().getId()) ;
if (!isProductInStock(shoppingItem.getQuantity() , persistedProduct)) {
throw new ProductOutOfStockException( "Product out of stock" );
}
}
if (shoppingItem.getMenu() != null) {
List<Product> products = shoppingItem.getMenu().getProductsInMenu() ;
for (Product product : products) {
Product dbProduct = productRepository .findOne(product.getId()) ;
if (!isProductInStock(shoppingItem.getQuantity() , dbProduct)) {
throw new ProductOutOfStockException( "Product out of stock" );
}
}
}
if (shopCartContainsItem(shoppingCart , shoppingItem)) {
return shoppingCartRepository .save(shoppingCart) ;
} else {
shoppingCart.getCartItem().add(shoppingItem) ;
shoppingItem.setShoppingCart(shoppingCart) ;
}
shoppingCart.getCartItem().add(shoppingItem) ;
shoppingItem.setShoppingCart(shoppingCart) ;
return shoppingCartRepository .save(shoppingCart) ;
În interiorul metodei are loc logica de verificare a produsului adăugat. Dacă acesta există
deja, îi va fi incrementată doar cantitatea. Dacă nu există atunci acesta este adăugat.
În cazul în careacest produs nu există în stoc, va fi aruncată o excepție de tip
ProductOutOfStockException .
Plasarea unei comenzi
După adăugarea produselor în co
plasare a comenzii. Urm ătorii pa
Pasul 1 : selectarea adresei unde tr
Pasul 2 : selectarea cardului cu
Pasul 3 : rezumat comand
Fig 2.21 Listarea adreselor la
comanda acasă
În ultimul ecran se poate modifica co
produse.
Pentru plasarea unei comenzi s
OrderController .
@PostMapping (value = "/user/restaurant/order"
public ResponseEntity<Order> addOrder
Principal principal) throws AccessDeniedException {
Customer c = customerService
Order order = orderMapper .toDomainObject(managerOrderDTO)
orderService .save(order , c.getShoppingCart())
return new ResponseEntity<Order>(order
}
Această metodă a șteaptă ca și parametru un obiect de tip CreateOrderDTO care repezintă
un obiect de transfer ce con ține id
Acest obiect de transfer este transformat într
componentei OrderMapper. Salvarea comenzii în baza de date se face cu ajutorul serviciului
orderService ce apelează metoda
După adăugarea produselor în co ș, utilizatorul poate continua mai departe cu procesul de
ătorii pași sunt ilustrați în figurile de mai jos după cum urmează
Pasul 1 : selectarea adresei unde tr ebuie făcută livrarea – figura 2.21
Pasul 2 : selectarea cardului cu care se va face plata – figura 2.22
Pasul 3 : rezumat comand ă și finalizarea acesteia – figura 2.23
Fig 2.21 Listarea cardurilor la
comanda acasă Fig 2.23 Sumarul
acasă
În ultimul ecran se poate modifica co șul de cumpărături, existând opțiunea de a șterge
Pentru plasarea unei comenzi s -a definit în backend metoda addOrder în Controller
"/user/restaurant/order" )
addOrder (@RequestBody CreateOrderDTO managerOrderDTO
AccessDeniedException {
customerService .findByUsername(principal.getName()) ;
toDomainObject(managerOrderDTO) ;
c.getShoppingCart()) ;
ResponseEntity<Order>(order , HttpStatus. OK);
șteaptă ca și parametru un obiect de tip CreateOrderDTO care repezintă
ține id-urile aferente creării unei comenzi.
Acest obiect de transfer este transformat într -un obiect de domeniu prin intermediul
componentei OrderMapper. Salvarea comenzii în baza de date se face cu ajutorul serviciului
ce apelează metoda de save.
43 ș, utilizatorul poate continua mai departe cu procesul de
și sunt ilustrați în figurile de mai jos după cum urmează :
Fig 2.23 Sumarul comenzii
acasă
șul de cumpărături, existând opțiunea de a șterge
în Controller -ul
CreateOrderDTO managerOrderDTO ,
șteaptă ca și parametru un obiect de tip CreateOrderDTO care repezintă
un obiect de domeniu prin intermediul
componentei OrderMapper. Salvarea comenzii în baza de date se face cu ajutorul serviciului
44
@Override
public Order save(Order order , ShoppingCart shoppingCart) {
Restaurant restaurant = order.getRestaurant() ;
Employee employee =
employeeService .findFirstByRestaurantOrderByActiveOrders(restaurant) ;
employee.setActiveOrders(employee.getActiveOrders() + 1);
order.setEmployee(employee) ;
order.setPrice(calculateCartItemsPrice(shoppingCart)) ;
Map<String , String> emailContent = createMessageForOrderEmail(order , shoppingCart) ;
Order persistedOrder = orderAndEmptyShoppingCart(order) ;
persistedOrder.setReservation(persistedOrder.getReservation()) ;
mailService .prepareAndSend(order.getCustomer().getEmail() ,
emailContent.get( "emailHeader" ), emailContent.get( "emailBody" ),
emailContent.get( "emailFooter" ), "Comanda dumneavoastra a fost plasata" );
Message message = new Message() ;
message.setSubject(MessageFormat. format(messageSource .getMessage( "comanda.plasata.subj
ect", null, Locale. ENGLISH ), persistedOrder.getId())) ;
message.setText(MessageFormat. format(messageSource .getMessage( "comanda.plasata.body" ,
null, Locale. ENGLISH ), employee.getFirstName())) ;
messageService .sendMessage(message , order.getRestaurant().getManager() ,
order.getCustomer()) ;
Message messageRestaurant = new Message() ;
messageRestaurant.setSubject( messageSource .getMessage( "comanda.plasata.system.message.
subject" , null, Locale. ENGLISH ));
messageRestaurant.setText( messageSource .getMessage( "comanda.plasata.system.message.bod
y", null, Locale. ENGLISH ));
messageService .sendMessage(messageRestaurant , null, restaurant.getManager()) ;
return persistedOrder ;
}
În cadrul acestei metode se află logica de calcul a prețului final, este asignat angajatul cu
cele mai puține comenzi active și se pornește sistemul de notificare prin e-mail și mesaj în
aplicație cu ajutorul serviciilor messageService și mailService .
Plasarea unei rezervări
Pentru plasarea unei rezervări utilizatorul trebuie în primul pas să selecteze restaurantul
în cadrul căruia dorește să efectueze rezervarea. După selectarea restaurantului acesta trebuie să
selecteze butonul încercuit în figura 2.24 care generează ulterior ecranul afișat în figura 2.25
Fig 2.24 Buton pentru plasarea rezervării
O dată cu completarea și apăsarea b
la server care returneaz ă toate o listă de obiecte ce reprezintă mesele aferete restaurantului,
acestea venind înso țite de un flag care semnifică statusul mesei, rezervată sau nu.
function drawObject (object) {
var content = [] ;
content. push('<div class="col –
if (object.reserved) {
content. push('<div class="table
toggle" data-content="' +object.
}
else {
content. push('<div class="table
data-tableId="' + object. id +
}
content. push('<input type="checkbox" style="display:none" data
object. id + '"/>');
content. push('Number of seats: '
content. push('</div>' );
content. push('</div>' );
return content. join('');
}
Cu ajutorul funcției drawObject()
cele rezervate fiind marcate cu culoarea ro
Buton pentru plasarea rezervării Fig 2.25 Modul de căutare al meselor
restaurantului
și apăsarea b utonului de căutare din figura 2 .25 se creează un apel
ă toate o listă de obiecte ce reprezintă mesele aferete restaurantului,
țite de un flag care semnifică statusul mesei, rezervată sau nu.
-xs-4 col-md-2" style="padding- bottom:5px">'
'<div class="table -object reserved" data- toggle="table
+object. description +'">');
'<div class="table -object free" data-toggle="table –
+ '" data-content="' +object. description +'">'
'<input type="checkbox" style="display:none" data -tableId="'
'Number of seats: ' + object. numberOfSeats );
drawObject() acestea sunt procesate și desenate pe partea de client,
cele rezervate fiind marcate cu culoarea ro șie și cele libere având un background alb.
45
Modul de căutare al meselor
restaurantului
.25 se creează un apel
ă toate o listă de obiecte ce reprezintă mesele aferete restaurantului,
țite de un flag care semnifică statusul mesei, rezervată sau nu.
bottom:5px">' );
toggle="table -obj-
-obj-toggle"
'">');
tableId="' +
și desenate pe partea de client,
șie și cele libere având un background alb.
Fig 2
Pentru vizualizarea detaliilor despre masă utilizatorul trebuie pe
selecteze, pe când pe device- urile desktop fiind destul să treacă (hover) peste ele.
Fig 2.27 Detalii masă
În figura 2 .27 este prezentată o masă rezervată pentru data de
este liberă pentru data de 28-08-2017
legată de o comandă de produse este suficient ca utilizatorul să folosească butonul marcat cu
‘verificat’.
Fig 2.26 Masa ocupată în intervalul dat
Pentru vizualizarea detaliilor despre masă utilizatorul trebuie pe platformele mobile să le
urile desktop fiind destul să treacă (hover) peste ele.
Detalii masă Fig 2.28 Masă selectată pentru rezervare
.27 este prezentată o masă rezervată pentru data de 29-08-2017
2017 în figura 2.28. Pentru salvarea acestei rezervări fără a fi
legată de o comandă de produse este suficient ca utilizatorul să folosească butonul marcat cu
46 platformele mobile să le
urile desktop fiind destul să treacă (hover) peste ele.
Fig 2.28 Masă selectată pentru rezervare
2017, aceeași masă
acestei rezervări fără a fi
legată de o comandă de produse este suficient ca utilizatorul să folosească butonul marcat cu
Fig 2.29 Adăugare rezervare cu
În figura 2 .29 este prezentată salvarea unei rezervări fără comandă. Pentru ca utilizatorul
să-și poată adăuga produse la rezervare, acesta trebuie să folosească butonul cu label
produse la rezervare” , fiind redirec
În figura 2 .30 se poate vedea rezumatul comenzii adăugate la rezervare. De această dată
adresa de livrare și cartea de credit nu mai sunt completate, din motive evidente.
Pe partea de backend p entru filtrarea meselor libere s
restaurantTableDTOsFiltered care returnează o listă de mese ce con
adevărat/fals.
public List<RestaurantTableDTO>
LocalDate reservationDate , LocalTime reservationStartTime
reservationEndTime) {
List<Reservation> reservations =
reservationRepo .findAllByRestaurantAndReservationDate(restaurant
List<RestaurantTable> restaurantTables = rest
List<RestaurantTableDTO> restaurantTableDTOS =
Map<Long ,RestaurantTableDTO> restaurantTableDTOMap =
HashMap<Long ,RestaurantTableDTO>()
for(RestaurantTable restaurantTable : restaurantTables) {
restaurant TableDTOMap.put(restaurantTable.getId()
DTOTransformers. toDtoTable (restaurantTable))
}
for(Reservation r : reservations) {
if((r.getStartTime().isBefore(reservationEndTime)) &&
(reservationStartTime.isBefore(r.getEndTime()))) {
for(RestaurantTable rt : r .getRestaurantTableList()) {
RestaurantTableDTO dto = restaurantTableDTOMap.get(rt.getId())
dto.setReserved( true);
}
}
}
restaurantTableDTOS = new
return restaurantTableDTOS ;
}
Fig 2.29 Adăugare rezervare cu succes Fig 2.30 Sumar comandă cu rezervare
.29 este prezentată salvarea unei rezervări fără comandă. Pentru ca utilizatorul
și poată adăuga produse la rezervare, acesta trebuie să folosească butonul cu label
, fiind redirec ționat către ecranul de selectare meniuri/produse.
.30 se poate vedea rezumatul comenzii adăugate la rezervare. De această dată
și cartea de credit nu mai sunt completate, din motive evidente.
entru filtrarea meselor libere s -a implementat metoda
care returnează o listă de mese ce con țin un status de tip
List<RestaurantTableDTO> restaurantTableDTOsFiltered (Restaurant restaurant
LocalTime reservationStartTime , LocalTime
List<Reservation> reservations =
.findAllByRestaurantAndReservationDate(restaurant , reservationDate)
List<RestaurantTable> restaurantTables = rest aurant.getTables() ;
List<RestaurantTableDTO> restaurantTableDTOS = new ArrayList<>() ;
RestaurantTableDTO> restaurantTableDTOMap = new
RestaurantTableDTO>() ;
(RestaurantTable restaurantTable : restaurantTables) {
TableDTOMap.put(restaurantTable.getId() ,
(restaurantTable)) ;
(Reservation r : reservations) {
((r.getStartTime().isBefore(reservationEndTime)) &&
(reservationStartTime.isBefore(r.getEndTime()))) {
.getRestaurantTableList()) {
RestaurantTableDTO dto = restaurantTableDTOMap.get(rt.getId())
new ArrayList<>(restaurantTableDTOMap.values())
;
47
Fig 2.30 Sumar comandă cu rezervare
.29 este prezentată salvarea unei rezervări fără comandă. Pentru ca utilizatorul
și poată adăuga produse la rezervare, acesta trebuie să folosească butonul cu label -ul “Adauga
ționat către ecranul de selectare meniuri/produse.
.30 se poate vedea rezumatul comenzii adăugate la rezervare. De această dată
și cartea de credit nu mai sunt completate, din motive evidente.
a implementat metoda
un status de tip
(Restaurant restaurant ,
reservationDate) ;
RestaurantTableDTO dto = restaurantTableDTOMap.get(rt.getId()) ;
ArrayList<>(restaurantTableDTOMap.values()) ;
În primă fază se creează un obiect de tip cheie
ce conține toate mesele restaurantului. Știind că id
cheie al map-ului.
În a doua parte a algoritmului se preiau rezervările
pentru mesele ocupate, flag- ul de reserved fiind setat pe
2.5.1.7 Secțiunea mesaje
Această sec țiune este împărțită în 3 tab
a. Inbox
În secțiunea de inbox, utilizatorul are posibilitatea de a vedea și răspunde
din partea restaurantelor. Mesajele necitite sunt afi
Pentru citirea unui mesaj utilizatorul trebuie să
precum în figura 2.32.
Fig 2.31 Mesajul este necitit
În figura 2 .32 apare un formular înso
răspunderii la mesaj. O dată scris
modificare și răspunsul apare în timp rea
În primă fază se creează un obiect de tip cheie -valoare(Map – implementare HashMap)
ține toate mesele restaurantului. Știind că id -ul mesei este unic, acesta s- a folosit ca
În a doua parte a algoritmului se preiau rezervările din ziua și intervalul orar r
ul de reserved fiind setat pe true.
țiune este împărțită în 3 tab -uri.
țiunea de inbox, utilizatorul are posibilitatea de a vedea și răspunde la mesajele ce vin
din partea restaurantelor. Mesajele necitite sunt afi șate cu font îngro șat, precum în figura 2
Pentru citirea unui mesaj utilizatorul trebuie să -l selecteze, generând un nou box în josul paginii,
Mesajul este necitit Fig 2.32 Citirea mesajului
.32 apare un formular înso țit de butonul Trimite mesaj care oferă posibilitatea
răspunderii la mesaj. O dată scris și trimis, printr-un apel AJAX această sec țiune suferă
răspunsul apare în timp rea l, lucru vizibil în figura 2.33.
48 implementare HashMap)
a folosit ca și
și intervalul orar r espectiv,
la mesajele ce vin
șat, precum în figura 2 .31.
l selecteze, generând un nou box în josul paginii,
Citirea mesajului
care oferă posibilitatea
țiune suferă o
b. Newsletter
Secțiunea de newsletter conține mesaje cu caracter informativ ce vin din partea
restaurantelor. La această categorie de mesaje utilizatorul nu poate răspunde.
Fig
c. System
În această sec țiune se vor afișa mesajele de tip sistem, categorie ce conține mesaje provenite
doar din partea administratorului. Mesajele de acest tip nu acceptă răspuns.
Fig 2.33 Răspus mesaj
țiunea de newsletter conține mesaje cu caracter informativ ce vin din partea
estaurantelor. La această categorie de mesaje utilizatorul nu poate răspunde.
Fig 2.34 Mesaj de tip newsletter
țiune se vor afișa mesajele de tip sistem, categorie ce conține mesaje provenite
doar din partea administratorului. Mesajele de acest tip nu acceptă răspuns.
49 țiunea de newsletter conține mesaje cu caracter informativ ce vin din partea
țiune se vor afișa mesajele de tip sistem, categorie ce conține mesaje provenite
Pe partea de client s-a definit o metodă generică pentru aceste 3 categorii, fiind
reutilizabilă pentru fiecare dintre ele.
function getMessage (type) {
var messageType = type. toUpperCase
$.ajax({
type: "GET",
url: "/message?type=" + messageType
success : function (data) {
var htmlObject = [] ;
for (var i = 0; i < data. length
var subject = data[i]. subject ;
var status = data[i]. seen;
if (subject. length >20) {
subject = subject.
}
if (status == false) {
subjec t =
}
htmlObject. push
message" data-id="' + data[i].
'</li>' );
}
$('.message-list ').html(htmlObject.
$(".message-list ").children ().
}
});
}
Metoda getMessage primește un parametru de tip String tipul mesajului.
Request-urile sunt controlate din backend cu ajutorul unui controller denumit
MessageController .
Fig 2.35
definit o metodă generică pentru aceste 3 categorii, fiind
reutilizabilă pentru fiecare dintre ele.
toUpperCase ();
+ messageType ,
length; i++) {
;
subject = subject. substring (0, 20) + "…";
t = '<b>' + subject + '</b>';
push('<li class="list-group-item list-group- item
+ data[i]. id + '" data-status="' + status + '">' + subject +
(htmlObject. join(""));
().first().click();
ște un parametru de tip String tipul mesajului.
urile sunt controlate din backend cu ajutorul unui controller denumit
50 definit o metodă generică pentru aceste 3 categorii, fiind
item-action
+ subject +
urile sunt controlate din backend cu ajutorul unui controller denumit
În acesta s- au definit metodele necesare pentru trimiterea
baza tipului. Pentru afi șarea mesajelor s
principal) .
@GetMapping
public ResponseEntity<List<MessageDTO>>
Principal principal) {
Customer customer = customerService
List<Message> messageList = customer.getMessage().stream().filter(message
MessageType. getEnum (type).equals(mess
!message.isDeleted()).collect(
List<MessageDTO> mslDTO =
messageList.stream().map(DTOTransformers::
return new ResponseEntity<List<MessageDTO>>(mslDTO
}
Cu ajutorul expresiilor lambda mesajele unui utilizator sunt filtrate pe baza criteriilor de
tip și câmpului isDeleted care reprezintă un flag dacă mesajul este șters sau nu.
2.5.1.7Secțiunea istoric comenzi
În această sec țiune utilizatorul poate vizualiza comenzile
se afișează doar data la care s- a făcut rezervarea
detaliile aferente unei comenzi utilizatorul trebuie să o
afișată lista comenzilor a nterioare, iar în figura 2
Fig 2.36 Listă comenzi anterioare
au definit metodele necesare pentru trimiterea și afișarea mesajelor, filtrate pe
șarea mesajelor s -a definit metoda getMessages(String type, Principal
ResponseEntity<List<MessageDTO>> getMessages (@RequestParam ("type") String type
customerService .findByUsername(principal.getName())
List<Message> messageList = customer.getMessage().stream().filter(message
).equals(mess age.getMsType()) &&
!message.isDeleted()).collect( toList());
messageList.stream().map(DTOTransformers:: toMessageDTO ).collect( toList())
ResponseEntity<List<MessageDTO>>(mslDTO , HttpStatus. OK);
expresiilor lambda mesajele unui utilizator sunt filtrate pe baza criteriilor de
și câmpului isDeleted care reprezintă un flag dacă mesajul este șters sau nu.
istoric comenzi
țiune utilizatorul poate vizualiza comenzile sale anterioare. În lista paginată
a făcut rezervarea și numele restaurantului. Pentru a vizualiza
detaliile aferente unei comenzi utilizatorul trebuie să o selecteze din listă. În figura 2
nterioare, iar în figura 2 .37 sunt afi șate detaliile comnezii.
Listă comenzi anterioare Fig 2.37 Detalii comandă anterioară
51 și afișarea mesajelor, filtrate pe
getMessages(String type, Principal
) String type ,
.findByUsername(principal.getName()) ;
List<Message> messageList = customer.getMessage().stream().filter(message ->
());
expresiilor lambda mesajele unui utilizator sunt filtrate pe baza criteriilor de
sale anterioare. În lista paginată
și numele restaurantului. Pentru a vizualiza
selecteze din listă. În figura 2 .36 este
șate detaliile comnezii.
Detalii comandă anterioară
Funcțiile javascript fac apel
preluate de către controlerul ProfileController
profilului.
@GetMapping ( value = "/order" ,
params = { "page", "size" })
public Page<OrderCustomerDTO> findPaginated
@RequestParam ("size") int size
Customer customer = customerService
Page<Order> orderPage = orderService
Page<OrderCustomerDTO> resultPage =
orderPage.map(DTOTransformers::
return resultPage ;
}
@GetMapping (value="/order/{id}"
public ResponseEntity<OrderCustomerDTO>
Principal principal) {
Customer customer = customerService
Order order = orderService .findOrderByCustomerAndId(customer
return new
ResponseEntity<OrderCustomerDTO>(DTOTransformers.
OK);
}
2.5.1.8 Sec țiunea istoric rezervări / Anularea unei rezervări
În această sec țiune utilizatorul are opțiunea de a naviga prin rezervările anterioare. De
asemenea pentru rezervările salvate dar care încă nu au avut loc, acesta are
anula cu condi ția ca durata între anulare și rezervare să fie de minim cinci ore.
Modalitate afișării re zervărilor este cea din figura 2
pagină. Pentru vizualizarea detaliilor, la fel ca
selecteze unul dintre membrii listei.
Fig 2.38 Listă istoric rezervări
țiile javascript fac apel -uri AJAX către endpointu- ul /user/details/order/ care sunt
ProfileController , considerându- se că acestea fac parte din detaliile
,
findPaginated (@RequestParam ("page") int page
size, Principal principal) {
customerService .findByUsername(principal.getName())
orderService .findAllByCustomer(customer , page, size)
Page<OrderCustomerDTO> resultPage =
orderPage.map(DTOTransformers:: toOrderTransferDTO );
"/order/{id}" )
ResponseEntity<OrderCustomerDTO> getOrderDetails (@PathVariable Long
customerService .findByUsername(principal.getName())
.findOrderByCustomerAndId(customer ,id);
ResponseEntity<OrderCustomerDTO>(DTOTransformers. toOrderTransferDTO (orde
țiunea istoric rezervări / Anularea unei rezervări
țiune utilizatorul are opțiunea de a naviga prin rezervările anterioare. De
asemenea pentru rezervările salvate dar care încă nu au avut loc, acesta are posibilitatea de a le
ția ca durata între anulare și rezervare să fie de minim cinci ore.
zervărilor este cea din figura 2 .38, rezervările fiind afi
pagină. Pentru vizualizarea detaliilor, la fel ca și în listele anterioare, utilizatorul trebuie să
unul dintre membrii listei.
Listă istoric rezervări Fig 2.39 Detalii rezervare
52 ul /user/details/order/ care sunt
se că acestea fac parte din detaliile
page,
.findByUsername(principal.getName()) ;
size);
Long id,
.findByUsername(principal.getName()) ;
(order),HttpStatus.
țiune utilizatorul are opțiunea de a naviga prin rezervările anterioare. De
posibilitatea de a le
.38, rezervările fiind afi șate 10 per
ele anterioare, utilizatorul trebuie să
Detalii rezervare
Anularea unei rezervări
Pentru anularea unei rezerv
cazul în care condi țiile de anulare se satisfac, acesta este informat că rezervarea a fost anulată și
este redirec ționat către lista de rezervări.
Fig 2.39 Notificare că rezervarea a fost anulată
Pentru scop informativ utilizatorul poate totu
rezervarea dar a anulat- o ulterior,
CANCELED .
Pe partea de backend controlul acestor rezervări se face tot prin intermediul clasei
ReservationController. În serviciul
cancelReservation care este folosită pentru anularea rezervării. Acesta prime
ul rezervării cât și utilizatorul logat.
@Override
public boolean cancelReservation
Reservation reservation = reservationRepo
if (reservation.getCustomer().getId() == customer.getId()) {
long hours = ChronoUnit. HOURS.between(LocalDateTime.
LocalDateTime. of(reservation.getReservationDate()
if (hours >= 5) {
reservation.setStatus(ReservationStatusType.
reservation.setRestaurantTableList(
reservationRepo .save(reservation)
return true;
} else {
return false;
}
} else {
try {
throw new IllegalAccessException(
} catch (IllegalAccessException e) {
Pentru anularea unei rezerv ări utilizatorul trebuie să facă click pe butonul de
țiile de anulare se satisfac, acesta este informat că rezervarea a fost anulată și
ționat către lista de rezervări.
Notificare că rezervarea a fost anulată Fig 2.40 Con ținutul rezervării după ce acea
a fost anulată
Pentru scop informativ utilizatorul poate totu și vizualiza data și ora la care a plasat
o ulterior, de această dată statusul rezervării fiind schimbat
Pe partea de backend controlul acestor rezervări se face tot prin intermediul clasei
În serviciul injectat ReservationService, s-a definit metoda
care este folosită pentru anularea rezervării. Acesta prime ște ca par
și utilizatorul logat.
cancelReservation (Long id , Customer customer) {
reservationRepo .findOne(id) ;
(reservation.getCustomer().getId() == customer.getId()) {
.between(LocalDateTime. now(),
(reservation.getReservationDate() ,reservation.getStartTime()))
reservation.setStatus(ReservationStatusType. CANCELED );
reservation.setRestaurantTableList( null);
.save(reservation) ;
IllegalAccessException( "Not allowed" );
(IllegalAccessException e) {
53 ări utilizatorul trebuie să facă click pe butonul de Anuleaza. În
țiile de anulare se satisfac, acesta este informat că rezervarea a fost anulată și
ținutul rezervării după ce acea sta
a fost anulată
și ora la care a plasat
schimbat pe
Pe partea de backend controlul acestor rezervări se face tot prin intermediul clasei
a definit metoda
ște ca par ametru id-
reservation.getStartTime())) ;
54
e.printStackTrace() ;
}
}
return false;
}
Se verifică dacă într-adevăr rezervarea este una dintre rezervările clientului urmată de o
verificare a intervalului de timp între momentul în care se dorește anularea acesteia și momentul
rezervării. Dacă prima condiție nu este satisfăcută atunci o excepție de tip
IllegalAccessException este aruncată. În cazul în care cea de a doua condiție nu este satisfăcută,
se întoarce un răspuns de tip fals către controller care prelucrează această inforamție.
Dacă ambele condiții sunt satisfăcute atunci statusul rezervării este setat ca fiind
CANCELED , mesele aferente acestei rezervări sunt setate pe null pentru a fi eliberate și
rezervarea modificată este salvată în baza de date.
2.5.2 Portal gestionare
Deoarece portalul eRestaurantManagement este un utilitar administrativ, majoritatea
ecranelor sunt structurate sub formă tabelară. În consecință această platformă este dedicată în
special desktop-urilor și tabletelor, fiind utilizabil și pe device-uri de tip telefon smartphone dar
nu este recomandat.
Figurile adăugate sunt preluate de pe versiunea de tabletă iPad în mod landscape a
aplicației. Pentru a menține un aspect curat, anumite imagini au fost tăiate, fiind afișate doar
porțiuni ale aplicației.
2.5.2.1 Secțiunea Profilul meu
În această secțiune utilizatorul are posibilitatea de a-și modifica anumite date legate de contul său
prin completarea formularului prezentat în figura 2.41.
Fig 2.41 Profil manager
55
Pentru că acest tip de utilizator primește o parolă de tip hashed (de exemplu bc38d3bf-
5f88-42cb-aecb-fb3c3a749f8c ), beneficiază de funcționalitatea de a-și modifica parola. Pentru a
face acest lucru trebuie să introducă vechea parolă și de două ori parola nouă care să respecte
standardul de minim 5 caractere.
În cazul în care validarea nu are loc cu succes pe partea de client, se verifică pe backend
în metoda de updateDomainObjectFromDto , metodă ce face parte din clasa abastracă
UserMapper .
if (userDTO.getPassword() != null) {
if(!SecurityUtility. passwordMatcher (userDTO.getPassword() , user.getPassword())) {
throw new PasswordNotMatchException( "Password not match" );
}
if(!userDTO.getNewPassword().equals(userDTO.getConfirmPassword())) {
throw new PasswordNotMatchException( "Password not match" );
}
user.setPassword(SecurityUtility. passwordEncoder ().encode(userDTO.getConfirmPassword
2.5.2.2 Secțiunea Restaurantul meu
Această secțiune reprezintă modulul de configurare al restaurantului. Utilizatorul poate
modifica caracteristicile restaurantului. Sunt puse la dispoziție patru box-uri care faciliteaza
schimbarea :
Programului
Descrierii
Locației
Imaginii de profil a restaurantului
Fig 2.42 Dashboard pentru modificarea restaurantului
56
Locația marcată pe hartă a fost implementată cu ajutorul bibliotecii Gmaps.js .
var map = new GMaps({
div: '#map',
lat: data. location .xCoordinate ,
lng: data. location .yCoordinate
});
map.addMarker ({
lat: data. location .xCoordinate ,
lng: data. location .yCoordinate ,
title: 'Lima',
infoWindow : {
content : '<p>You can find us here:<br> Street: ' + data. location .street + ' </p>'
}
});
Se instanțiază un obiect de tip GMaps cu parametrii :
Elementul HTML de tip div care v-a reprezenta harta
Latitudinea, obiect de tip long
Longitudinea, obiect de tip long
Pentru modificarea detaliilor legate de locație s-a construit și în aplicația back-end un obiect
Java cu următoarea structură :
public class Location extends BaseEntity {
private double xCoordinate ;
private double yCoordinate ;
private String city;
private String street;
}
2.5.2.3 Secțiunea Management angajați
În această secțiune s-au definit ecranele necesare managementului de angajați dispuse sub
formă de tab-uri. Validările pentru partea de client s-au definit în fișierul employee-view.js
a. Adaugare roluri
Pentru funcționalitate de adăugarea a unui rol s-a implementat un formular cu 2 câmpuri,
numele rolului și descrierea acestuia. Prin apăsarea butonului de adaugă se lansează un apel către
aplicația din backend cu cererea de a salva acest rol.
Fig 2.42 Formular adăugare roluri
$(document ).on('click' , "#role
/* validare input */
$.ajax({
type: "POST",
url: "/manager/roles" ,
data: JSON. stringify (role),
contentType : "application/json; charset=utf
dataType : "json",
async: true,
success : function (data) {
$.notify("Rolul a fost adaugat cu succes"
$('.role-add-input ').find('input'
}, error: function (data) {
S- a definit pe partea de backend un obiect de tip controller cu numele
EmployeeRoleController prin care se face accesul la serviciul de management al rolurilor
angajaților.
@PostMapping
private EmployeeRoleDTO createEmployee
Principal principal) {
employeeRoleDTO.setRestaurant(getRestaurant(principal))
EmployeeRole role = EmployeeRoleMapper.
EmployeeRoleDTO persistedRole =
EmployeeRoleMapper. INSTANCE .toDto(
return persistedRole ;
}
EmployeeRoleMapper.INSTANCE este instan
folosită pentru transformarea obiectului role într
b. Vizualizare și ștergere roluri
Fig 2.42 Formular adăugare roluri
"#role-submit-btn" , function () {
"application/json; charset=utf -8",
"Rolul a fost adaugat cu succes" , "success" );
'input' ).val("");
(data) { /* afisare eroare */ }});});
a definit pe partea de backend un obiect de tip controller cu numele
prin care se face accesul la serviciul de management al rolurilor
createEmployee (@RequestBody EmployeeRoleDTO employeeRoleDTO
employeeRoleDTO.setRestaurant(getRestaurant(principal)) ;
EmployeeRole role = EmployeeRoleMapper. INSTANCE .toDomainObject(employeeRoleDTO)
EmployeeRoleDTO persistedRole =
.toDto( employeeRoleService .save(role)) ;
EmployeeRoleMapper.INSTANCE este instan țierea implementării interfeței cu acest nume,
folosită pentru transformarea obiectului role într -un obiect de transfer EmployeeRoleDTO
roluri
57
prin care se face accesul la serviciul de management al rolurilor
EmployeeRoleDTO employeeRoleDTO ,
.toDomainObject(employeeRoleDTO) ;
țierea implementării interfeței cu acest nume,
ployeeRoleDTO .
În această sec țiune sunt prezentate rolurile însoțite de descriere sub form
figura 2.4.
Fig 2.44 Formular vizualizare
Acțiunea de ștergere a unui rol se poate sfârși
care rolul este deja asignat anumitor angaja
pentru utilizator.
Fig 2.45 Mesaj eroare la ștergerea unui rol
$(document ).on('click' , '.emp-
var id = $(this).data('id');
return $.ajax({
type: "DELETE" ,
url: "/manager/roles/" + id,
success : function (data) {
$.notify("Rolul a fost sters" ,
$(this).parent().parent().remove
getRoles ();
},
error: function (data) {
var message = data.responseJSON.exception
if(message.includes( "org.springframework.dao.DataIntegrityViolationException"
$.notify("Rolul este asignat unui angajat"
c. Adaugare angajat
Pentru ad ăugarea unui angajat s
Rolurile sunt unice per restaurant
AJAX către server.
țiune sunt prezentate rolurile însoțite de descriere sub form ă tabelară, precum în
44 Formular vizualizare și ștergere roluri
țiunea de ștergere a unui rol se poate sfârși prin succes cât și prin eroare, în cazul în
care rolul este deja asignat anumitor angaja ți. În consecință s- au definit două mesaje suggestive
ștergerea unui rol
Fig 2.46 Mesaj succes la șterg
-role', function () {
, "success" );
remove();
message = data.responseJSON.exception ;
"org.springframework.dao.DataIntegrityViolationException"
"Rolul este asignat unui angajat" , "error" ); }}});});
ăugarea unui angajat s -a implementat for mularul prezentat în figura 2.
Rolurile sunt unice per restaurant și sunt preluate în mod dinamic cu ajutorul unui apel
58 ă tabelară, precum în
și prin eroare, în cazul în
au definit două mesaje suggestive
ștergerea unui rol
"org.springframework.dao.DataIntegrityViolationException" )) {
mularul prezentat în figura 2. 47.
și sunt preluate în mod dinamic cu ajutorul unui apel
59
Fig 2.47
d. Vizualizare angajati
Angajații sunt afișati sub formă tabelară, un număr de 5 per pagină, proprietate care poate fi
modificată ușor în funcție de cerințele viitoare. S-a definit o metodă javascript pentru generarea
paginării.
function getPagination (data, liClass , container , datapage , datasearch , dataquery) {
var paginationObject = [] ;
if (data.totalPages > 1) {
for (var j = 0; j < data.totalPages ; j++) {
paginationObject. push(`<li><a href="#" class= ${liClass} data-
search= ${datasearch} data-query=" ${dataquery} " data-page= ${j}>${j + 1}</a></li>` );
}
$(`${container} `).html(paginationObject. join(""));
if (datapage === 0) {
$(`${container} `).children().first().addClass( "active" );
} else {
$(`${container} `).find( `[data-page ='${datapage} ']`).parent().addClass( 'active' );
}
}
if (paginationObject. length >1) {
$(container). css('visibility' , 'visible' );
} else {
$(container). css('visibility' , 'hidden' );
}
}
Primește o listă de parametrii prin care se deduce dacă paginarea se face în urma unui
căutări bazată pe un anumit filtru sau este o paginare completă, care conține toate obiectele listei.
60
Fig. 2.48 Afișare listă angajați
Utilizatorul are la dispoziție un formular de căutare pe baza căruia poate extrage doar
angajații cu un anumit nume/prenume, în figura 2.49 trimițându-se ca filtru de căutare String-ul
rares.
Fig 2.49 Filtrare angajați
Datele angajaților pot fi modificate, selectând un anumit angajat se instanțiază o fereastră
de tip modal ce conține formularul cu datele angajaților. Aceasta poate fi văzută mai jos, în
figura 2.50.
Fig 2.50 Modificare angajat
61
Prin apăsarea butonului Salveaza se trimite un apel la server cu scopul de modificare a
datelor.
Pentru implementarea acestor funcționalități s-au definit în aplicația backend controllere
EmployeeController și EmployeeRoleController care au ca scop expunerea și primirea datelor.
De exemplu pentru căutarea angajaților s-a definit în EmployeController metoda
prezentată mai jos.
@GetMapping (value = "/search" )
public Page<EmployeeDTO> searchEmployee (@RequestParam ("name") String name ,
@RequestParam ("size") int size,
@RequestParam ("page") int page, Principal principal) {
Restaurant restaurant = getRestaurant(principal) ;
Page<Employee> employees =
employeeService .findAllByRestaurantAndFirstNameOrLastNameContainingIgnoreCase(restaura
nt,
name, page, size);
Page<EmployeeDTO> result = employees.map( empMapper ::toEmployeeDto) ;
return result;
}
EmployeeService expune metoda de cautare bazată pe nume sau prenume, ignorând felul
în care este scris cuvântul, cu majuscule sau nu. Ulterior se întoarce un obiect de tip Page, cu o
anumită dimensiune care conține obiecte de tip EmployeeDTO .
2.5.2.4 Secțiunea Management mese
În secțiunea de management mese, utilizatorul are la dispoziție următoarele funcționalități :
a. Adăugarea unei mese
Se face prin completarea formului prezentat în figura 2.51. Toate câmpurile sunt obligatorii și
intervalul numărului de locuri este momentan între 1 și 10. Ulterior se poate modifica în funcție
de cerințe.
Fig 2.51 Formular creare masă
62
b. Vizualizare și modificarea meselor
Mesele sunt afișate sub formă tabelară cu detaliile prezentate în figura 2.52. Acestea sunt
editabile, în momentul în care utilizatorul selectează una dintre ele fiind deschisă o fereastră de
tip modal, în care este present un formular de modificare a datelor.
c. Vizualizarea statusului meselor în ziua curentă
Pentru a avea un sumar ușor de urmărit a meselor ocupate din ziua curentă s-a definit un tab
separat în meniul de management al meselor. Modul de vizualizare poate fi revăzut în figura
2.53 prezentată mai jos.
Fig 2.53 Afișarea statusului meselor bazat pe id și oră
Pe partea de client s-a definit funcția getStatistics() pentru afișarea datelor sub formă
tabelară.
function getStatistics () {
return $.ajax({
type: "GET",
url: "/table/daily" ,
Fig 2.52 Afișarea meselor
63
success : function (data) {
var htmlObject = [] ;
Object. keys(data). forEach (function (key) {
var tableName = key ;
var objects = data[key] ;
var row = [] ;
row.push(`<tr><td rowspan=" ${data[key].length} ">${tableName} </td>`);
for (var i = 0; i < objects. length; i++) {
console .log(objects[i]) ;
if (i == 0) {
row. push(generateRow (objects[i]. startTime .hour,
objects[i]. endTime .hour, objects[i].customer , objects[i]. id));
row.push('</tr>' );
} else {
row. push(generateRow (objects[i]. startTime .hour,
objects[i]. endTime .hour, objects[i].customer , objects[i]. id));
row.push('</tr>' );
}
}
htmlObject. push(row.join(""));
});
$('#table-daily' ).html(htmlObject. join(""));
}
});
}
Iterația prin setul de date s-a făcut prin forEach aplicată pe cheile obiectului, acesta
venind în format JSON cu următoarea structură :
{"10": [{Conținut}], “11”:[{Conținut}]};
Unde “10”,”11” reprezintă cheile și conținutul reprezintă obiectul de tip Table(masă).
În backend s-a implementat controller-ul TableController . Metoda de afișare a statusului
pentru ziua curentă acceptă request-uri de tip GET pe endpoint-ul /tables/daily.
@GetMapping ("/daily" )
public ResponseEntity<Map<Long ,
List<TableStatisticsDTO>>> showDailyStatisticsForTables (Principal principal) {
Manager m = (Manager) userService .findByUsername(principal.getName()) ;
Map<Long , List<TableStatisticsDTO>> map =
reservationService .showDailyStatisticsForTables(m.getRestaurant()) ;
return new ResponseEntity<Map<Long , List<TableStatisticsDTO>>>(map ,
HttpStatus. OK);
}
Aceasta apelază metoda showDailyStatisticsForTables din serviciul reservationService .
Metoda returnează un obiect de tip Map cu tipurile Long pentru cheie și o colecție de tip
List<TableStatisticsDTO> pentru valoare.
public Map<Long , List<TableStatisticsDTO>> showDailyStatisticsForTables (Restaurant
restaurant) {
List<Reservation> reservations =
reservationRepo .findAllByRestaurantAndReservationDate(restaurant , LocalDate. now());
Map<Long , List<TableStatisticsDTO>> map = new HashMap<>() ;
for (Reservation r : reservations) {
List<RestaurantTable> rt = r.getRestaurantTableList() ;
for (RestaurantTable table : rt) {
if(map.containsKey(table.getId())) {
List<TableStatisticsDTO> reservations1 = map.get(table.getId()) ;
reservations1.add(TableStatisticsMapper. INSTANCE .toDto(r)) ;
64
map.put(table.getId() , reservations1) ;
} else {
List<TableStatisticsDTO> reservations2 = new ArrayList<>() ;
reservations2.add(TableStatisticsMapper. INSTANCE .toDto(r)) ;
map.put(table.getId() ,reservations2) ;
}
}
}
return map;
}
În implementarea metodei se extrag din baza de date rezervările din ziua curentă pentru
utilizatorul (restaurantul) logat. În continuare se iterează prin aceste rezervări, ulterior prin
mesele fiecărei rezervări și dacă obiectul de tip map conține deja id-ul mesei, atunci se adaugă în
lista deja există detalii despre rezervare, în caz contrar se creează un element nou de tip
ArrayList și este adăugat în map.
2.5.2.5 Secțiunea Management comenzi
În acest ecran se pot vedea comenzile plasate acasă. S-a implementat un modul de căutare
pe baza numelui clientului care a plasat comanda.
Fig 2.54 Afișare comenzi
2.5.2.6 Secțiunea Management rezervări
Acest ecran este împărțit în 2 funcționalități:
a. Adăugarea unei rezervări
Pentru adăugarea unei rezervări utilizatorul trebuie să formularul prezetat în figura de mai jos.
65
Fig 2.55 Listare mese restaurant
S-a folosit același modul descris în capitolul 2.5.1.6 – Plasare rezervare .
b. Afișare rezervări și anulare rezervări
S-a definit un tabel ca cel prezentat în figura de mai jos, unde toate rezervările sunt listate într-un
mod paginat.
Fig 2.56 Listare rezervări
În cazul în care se dorește vizualizarea detaliilor, utilizatorul trebuie să selecteze una dintre
rezervări. Pentru scop demonstrativ am ales o rezervare care și o comandă legată de ea. De
asemenea utilizatorul dispune de anularea rezervării, de data aceasta fiind din parte restaurantului
nu mai trebuie să se respecte intervalul de minim 5 ore.
În momentul în care o rezervare este anulată de către manager se pornește un lanț de acțiuni :
Se marchează rezervarea cu statusul canceled
Se eliberează mesele asignate rezervării
Se trimite un mesaj personal către utilizatorul care a plasat rezervarea prin intermediul
aplicației
Se trimite un email către utilizatorul care a plasat rezervarea, fiind folosită adresa de e-
mail cu care acesta s-a înregistrat
66
Fig 2.57 Afișare detalii rezervare
public void cancelReservationByManager (Long id) {
Reservation reservation = reservationRepo .findOne(id) ;
reservation.setStatus(ReservationStatusType. CANCELED );
reservation.setRestaurantTableList( null);
reservationRepo .save(reservation) ;
String subject = messageSource .getMessage( "rezervare.anulata.subject" , null,
Locale. ENGLISH );
String messageBody =
MessageFormat. format(messageSource .getMessage( "rezervare.anulata.body" , null,
Locale. ENGLISH ), reservation.getReservationDate() ,
reservation.getRestaurant().getName() , reservation.getRestaurant().getManager()) ;
Message message = new Message() ;
message.setSubject(subject) ;
message.setText(messageBody) ;
messageService .sendMessage(message , reservation.getRestaurant().getManager() ,
reservation.getCustomer()) ;
mailService .prepareAndSend(reservation.getCustomer().getEmail() , subject , messageBody ,
"", subject) ;
S-au definit două servicii separate pentru trimiterea mesaje și email-uri.
2.5.2.7 Secțiunea Mesagerie
În această secțiune utilizatorul are la dispoziție funcționalitățile de a :
a. Trimite mesaje personale către alți utilizatori ai aplicației. Prin apăsarea butonului Mesaj
nou se va deschide un formular care permite căutarea utilizatorului folosind ca filtru
numele acestora.
67
Fig 2.58 Căutare utilizator după nume
Fig 2.59 Formuarul de trimitere a unui
mesja peronal
b. Trimiterea mesajelor de tip newsletter. Aceste mesaje sunt trimise cu scop informativ
către toți utilizatorii aplicației.
Fig 2.60 Trimiterea unui mesaj de tip newsletter
c. Timeline
Utilizatorul beneficiază de un timeline al tuturor mesajelor pentru a putea urmări clar
conversațiile și anunțurile trimise. Acestea sunt ordonate în ordine cronologică și sunt însoțite de
detalii precum data, ora, subiectul.
Implementarea logicii necesare pe partea de client a fost definită în fișierul javascript
denumit message-view.js
68
Fig 2.61 Fragment din timeline-ul unui cont
function getAllMessages () {
return $.ajax({
type: "GET",
url: "/message/all" ,
success : function (data) {
var content = [] ;
for (var i = 0; i < data. length; i++) {
if (data[i]. length === 2) {
var date = moment(data[i][ 0].sentDate). format('DD-MM-YYY HH:mm' );
var details = date + " – " + data[i][ 0].senderName ;
var htmlObject = drawTimeLineItem (data[i][ 0].subject , details , data[i][ 1].text,
data[i][ 0].text, i);
content. push(htmlObject) ;
} else {
var date = moment(data[i][ 0].sentDate). format('DD-MM-YYY HH:mm' ) +
data[i][ 0].senderName ;
var htmlObject = drawTimeLineItem (data[i][ 0].subject , details , "" , data[i][ 0].text,
i);
content. push(htmlObject) ;
}
}
$("ul.timeline ").html(content. join(""));
}
});
}
În metoda getAllMessages() se verifică prin condiția data[i].length===2 dacă mesajul
are și răspuns sau nu.
Pe partea de server s-a definit controller-ul MessageController . În acesta există metoda
getMessage care expune datele necesare pentru generarea timeline-ului.
@GetMapping (value="/all")
public ResponseEntity<List<ArrayList<MessageDTO>>> getMessage (Principal principal) {
69
List<Message> ms = messageService .findMessageByReciever(getUser(principal)) ;
List<ArrayList<MessageDTO>> composedList = new ArrayList<>() ;
for (Message m : ms) {
if (m.getAnswer() == null) {
List<MessageDTO> touple = new ArrayList<MessageDTO>() ;
touple.add(toMessageDTO(m)) ;
Message question = messageService .findByAnswerId(m.getId()) ;
if (question != null) {
touple.add(toMessageDTO(question)) ;
}
composedList.add((ArrayList<MessageDTO>) touple) ;
}
}
return new ResponseEntity<List<ArrayList<MessageDTO>>>(composedList ,HttpStatus. OK);
}
Metoda returnează o colecție de liste. Fiecare listă poate avea unul sau două elemente,
fiecare listă reprezentând un tuplu mesaj-răspuns.
2.5.2.8 Secțiunea Produse
Secțiunea produse este împărțită în 2 tab-uri.
a. Adaugare produs
În această secțiune s-a definit un formular ce permite adăugarea unui nou produs.
Fig 2.62 Formular adăugare produs
Butonul marcat cu Choose File reprezintă un input de tip file care facilitează încărcarea
unei imagini de profil pentru produs. Prin apăsarea butonului Salvează se creează un request
către server care asigură salvarea obiectului în baza de date.
b. Vizualizare și editare produse
70
Produsele sunt afișate sub formă tabelară și paginate câte 10 per pagină. În funcție de
cerințele software prezentate în capitolul 2.2, fiecare rând a fost colorat cu o culoare specifică
numărului de produse existente în stoc, fapt vizibil în figura 2.63.
Modulul de căutare ignoră majusculele din cuvinte, pentru a asigura o căutare flexibilă.
De asemnea acesta returnează toate produsele care conțin valoarea introdusă în câmpul de
căutare.
Pentru vizualizarea detaliilor este suficient ca utilizatorul să selecteze rândul unuia dintre
produse. Fereastra de detalii este prezentată în figura 2.64.
Fig 2.63 Listare produse
Fig 2.64 Formular editare produs
Pentru modificarea unui produs s-a definit următoarea funcție javascript pe partea de client :
$(document ).on('click' , '#product-update-btn' , function () {
var product = {} ;
product. id = $("#modal-product-id" ).val();
product. name = $("#modal-product-name" ).val();
product. description = $("#modal-product-description" ).val();
product. price = $("#modal-product-price" ).val();
71
product. type = $("#modal-product-type" ).val();
product. stockSize = $("#modal-product-stock" ).val();
product. weight = $("#modal-product-weight" ).val();
return $.ajax({
type: "PUT",
url: "/manager/products" ,
data: JSON. stringify (product) ,
contentType : "application/json; charset=utf-8" ,
dataType : "json",
async: true,
success : function (data) {
$("#product-details-modal" ).modal('hide');
getProducts (0);
},
error: function (data) {
console .log(data);
}
}). done(function () {
var formData = new FormData ();
var file = $("#product-pic-input-update" )[0].files[0];
formData. append('productPic' , file);
$.ajax({
url: '/image/product/' + product. id,
type: 'POST',
contentType : false,
data: formData ,
processData : false,
success : function (data) {
getProduct (product. id);
$('.filename ').html("");
}
}) ;
});
});
Se creează 2 apeluri AJAX succesive, prima dată trimițându-se detaliile alfa-numerice
către endpoint-ul /manager/products după care ulterior se trimite imaginea către endpoint-ul
/image/product .
În backend s-a definit controller-ul ProductController care poate fi regăsit în totalitate în
anexă. În continuare este prezentată metoda de căutare a produselor.
@GetMapping (value = "/search" )
public Page<ProductDTO> searchProduct (Principal principal ,
@RequestParam ("searchString" ) String searchString , @RequestParam ("page") int page,
@RequestParam ("size") int size) {
Manager cm = (Manager) userService .findByUsername(principal.getName()) ;
Page<Product> products =
restaurantService .findPaginatedProductsWithNameLike(cm.getRestaurant().getId() ,
searchString , page, size);
Page<ProductDTO> productDTOS = products.map(ProductMapper. INSTANCE ::toDto) ;
return productDTOS ;
}
Metoda findPaginatedProductsWithNameLike a serviciului restaurantService este
descrisă în continuare.
@Override
public Page<Product> findPaginatedProductsWithNameLike (long restaurantId , String name ,
int page, int size) {
72
return
productRepo .findAllByRestaurantAndNameContaining( restaurantRepo .findById(restaurantId)
, name, new PageRequest(page ,size));
}
Aceasta folosește stratul de conectare la baza de date, productRepo . Folosind framework-
ul SpringData putem efectua o căutare în baza de date definind o metodă prin conveție. Metoda
din cazul de față va căuta toate produsele( findAll) cu filtrul derestaurant și numele să conțină
(andNameContaining) valorile parametrilor trimiși.
2.5.2.9 Secțiunea Meniuri
Secțiunea de management a meniurilor este împărțită în două categorii :
a. Adăugare meniu
Acestă secțiune este împărțită din punct de vedere vizual în două părți. În partea stângă este
prezent modulul de căutare al produselor, iar în partea dreaptă este modulul de creare a meniului.
Fig 2.65 Formulare creare meniu
Afișarea produselor se face într-un afișaj tabular, cum a fost prezentat și în ecranele
anterioare. Fiecare rând din tabel are pe ultima coloană un buton de adăugare a produsului în
meniu. În figura 2.66 este afișat cu scop demonstrative un formular de creare a unui meniu
completat.
73
Fig 2.66
Pentru implementarea adăugării unui produs în meniu pe partea de client s-a definit
următoare funcție javascript :
$(document ).on('click' , '.fa.fa-plus-circle.product.menu' , function () {
var tr = $(this).parent().parent().clone();
$(tr).find('td').eq(5).html('<span class="fa fa-minus-circle product menu"></span>' );
$("#product-in-menu-table" ).append(tr);
});
Această funcție creează un eveniment pe butonul de pe ultima coloană din tabelul cu
produse, și la fiecare click mută conținutul în tabela cu produsele ce aparțin meniului.
Pentru eliminarea unui produs din meniu s-a creat o altă metodă care declară un
eveniment pe clasele ‘.fa.fa-minus-circle.product.menu’.
$(document ).on('click' , '.fa.fa-minus-circle.product.menu' , function () {
var tr = $(this).parent().parent();
$(tr).remove();
});
Pentru salvarea meniului s-a declarant un eveniment pe butonul cu label-ul Salveaza.
var obj = {} ;
obj.name = $("#menu-name" ).val();
obj.price = $("#menu-price" ).val();
obj.description = $("#menu-description" ).val();
var products = [] ;
$('#product-in-menu-table tr').each(function () {
products. push($(this).data('productid' ));
});
obj.productIds = products ;
Se preiau datele din formular și toate id-urile produselor din tabelul cu produse prin
iterarea rândurilor tabelului. Ulterior se transmite acest obiect către backend. În back-end acest
obiect de transfer este convertit în obiect domeniu cu ajutorul clasei abstracte MenuMappper .
@Mapper (componentModel = "spring" , nullValueCheckStrategy =
NullValueCheckStrategy. ALWAYS)
public abstract class MenuMapper {
public Menu toObject (MenuDTO menuDTO) {
if (menuDTO == null) {
return null;
}
Menu menu = new Menu();
menu.setName(menuDTO.getName()) ;
menu.setPrice(menuDTO.getPrice()) ;
menu.setDescription(menuDTO.getDescription()) ;
List<Long> restaurantIds = menuDTO.getProductIds() ;
List<Product> productList = new ArrayList<>() ;
for (Long i : restaurantIds) {
Product product = restaurantService .findProductById(i) ;
productList.add(product) ;
}
menu.setProductsInMenu(productList) ;
}
Prin adnotarea de @Mapper cu parametrul componentModel=”spring” acest obiect
devine controlat de container-ul Spring, ceea ce înseamnă că poate fi injectat atunci când este
nevoie și folosit ca serviciu.
74
b. Vizualizare și editare meniuri
Meniurile sunt afișate cu detaliile prezentate în figura 2.67. Acestea pot fi șterse prin
selectarea butonului de pe ultima coloană. Pentru modificarea lor, unul dintre meniuri trebuie
selectat și o fereastră nouă de tip modal este instanțiată.
Fig 2.67 Afișare meniuri
În modul de editare s-a introdus funcționalitate de căutare a produselor pentru a oferi
utilizatorului o experiență ușoară de modificare a meniurilor. Produsele sunt paginate și sunt
însoțite de un buton de adăugare în meniu. În momentul în care produsul este plasat în meniu
acesta va fi însoțit de label-ul NOU.
Fig 2.68 Formular editare meniuri
Implementarea pe partea de client este asemănătoare cu cea prezentată la secțiunea de
Adaugare meniu .
Pe partea de server s-a definit metoda updateMenu .
@PutMapping
public ResponseEntity<MenuDTO> updateMenu (@RequestBody MenuDTO menuDTO , Principal
principal) {
Manager cm = (Manager) userService .findByUsername(principal.getName()) ;
75
Restaurant restaurant = restaurantService .findByManager(cm) ;
Menu menu = menuService .findOne(menuDTO.getId()) ;
if (restaurant.getMenus().contains(menu)) {
Menu updatedMenu = menuMapper .updateFromDto(menuDTO , menu);
restaurantService .saveMenu(restaurant , updatedMenu) ;
return new ResponseEntity<MenuDTO>(HttpStatus. OK);
} else {
return new ResponseEntity<MenuDTO>(HttpStatus. UNAUTHORIZED );
}
}
În MenuMapper s-a definit metoda updateFromDto care primește ca parametrii obiectul
vechi și obiectul nou updatat, cu ajutorul ei simulându-se un update parțial. De exempu dacă un
câmp ar fi trimis null din front-end(ceea ce nu este de dorit în cazul de față), datorită
implementării se va păstra valoarea veche și doar câmpurile completate vor fi modificate.
2.5.3 Portal administrare
Acest portal este dedicate userilor de tip administrator. La lansarea aplicației se crează în
mod automat un utilizator de acest tip. Ulterior aceștia pot fi creați prin crearea unui script SQL
de inserare în baza de date.
Funcționalitățile de care beneficiază acesta prin intermediul platformei sunt :
Trimiterea unui mesaj de sistem
Acest mesaj este transmis către toți userii din baza de date. Formularul este prezentat mai jos :
Fig 2.69 Formular pentru trimiterea unui mesaj de sistem
S-a definit o metodă în serviciul messageService specială pentru acest tip de mesaj.
public void sendSystemMessage (Message message , User sender) {
message.setMsType(MessageType. SYSTEM);
message.setSender(sender) ;
Iterable<User> userList = userRepository .findAll() ;
for(User u: userList) {
persistMessage(u ,message) ;
}
}
Din enumerația MessageType se setează tipul mesajului ca fiind un mesaj de sistem, după
care este ulterior trimis către toți userii din baza de date.
Crearea unui restaurant/manager
Pentru satisfacerea acestei cerințe s-a definit următoarea pagină :
76
Fig 2.70 Formular creare manager
Pentru crearea acestui tip de user, pe partea de back-end s-a implementat o metodă
createManager în clasa UserServiceImpl . Această metodă primește ca parametru manager-ul cu
datele trimise din formula.
public Manager createManager (Manager manager) {
manager.setUserRole( new
UserRole(manager ,roleRepository .findByname( "ROLE_MANAGER" )));
String randomPassword = UUID. randomUUID ().toString() ;
manager.setPassword(SecurityUtility. passwordEncoder ().encode(randomPassword + ""));
try {
Manager persistedManager = managerRepository .save(manager) ;
mailService .prepareAndSend(persistedManager.getEmail() , "", "Parola dumneavoastra
este: " + randomPassword , "", "Cont creat" );
} catch (Exception e) {
System. out.println(e) ;
}
return manager ;
}
Mai departe acestui tip de user îi este setat rolul și se generează o parolă aleatoare care
este setată și criptată cu ajutorul clasei SecurityUtility .
@Bean
public static BCryptPasswordEncoder passwordEncoder () {
return new BCryptPasswordEncoder( 12, new SecureRandom( SALT.getBytes())) ;
}
Metoda statică passwordEncoder returnează un obiect de tip BCryptPasswordEncoder ,
obiect ce se află în pachetul de Spring Security. Acest obiect are în spate un algoritm de criptare
bazat pe cifrul Blowfish.
Pentru trimiterea parolei prin e-mail s-a definit serviciul MailService, un serviciu utilizat
în mai multe dintre funcționalitățile aplicației.
public void prepareAndSend (String recipient , String header , String body , String
footer, String subject) {
MimeMessagePreparator messagePreparator = mimeMessage -> {
MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage) ;
messageHelper.setFrom( "antofierares@gmail.com" );
messageHelper.setTo( recipient );
messageHelper.setSubject( subject );
String content = mailHelper .build(
messageHelper.setText(content , true
};
try {
mailSender .send(messagePreparator)
} catch (MailException e) {
}
}
În cazul de față s- a definit metoda de
necesare pentru definerea unui email.
2.5.4 Controlul excep țiilor și servicii reutilizabile
Controlul excepțiilor
Pentru controlul excepțiilor s- a definit un obiect special adnotat cu
public RestExceptionResponse () {
super();
}
@ExceptionHandler ({IncorrectPasswordException.
UserNotFoundException. class, ShoppingCartException.
ReservationExists. class, ReviewGivenException.
protected ResponseEntity<Object>
request) {
String bodyOfResponse = ex.getMessage()
if (ex instanceof ReviewGivenException) {
bodyOfResponse = ex.getClass().toString()
}
return handleExceptionInternal(ex
new HttpHeaders() ,
}
@ExceptionHandler (MySQLIntegrityConstraintViolationException.
protected ResponseEntity<Object>
String bodyOfResponse = ex.getMessage()
.build( header, body, footer);
, true);
.send(messagePreparator) ;
a definit metoda de prepareAndSend care conține toate rubricile
necesare pentru definerea unui email. Email-ul generat poate fi vizualizat în figura de mai jos.
Fig 2.71 Email primit cu parola
țiilor și servicii reutilizabile
a definit un obiect special adnotat cu @ControllerAdvice
() {
({IncorrectPasswordException. class, IOException. class,
ShoppingCartException. class, NoReservationFound.
ReviewGivenException. class, IllegalAccessException.
ResponseEntity<Object> handleConflict (RuntimeException ex , WebRequest
ex.getMessage() ;
ReviewGivenException) {
bodyOfResponse = ex.getClass().toString() ;
handleExceptionInternal(ex , bodyOfResponse ,
, HttpStatus. CONFLICT , request) ;
LIntegrityConstraintViolationException. class)
ResponseEntity<Object> handleSql (RuntimeException ex , WebRequest request) {
String bodyOfResponse = ex.getMessage() ;
77 ține toate rubricile
ul generat poate fi vizualizat în figura de mai jos.
@ControllerAdvice .
class,
NoReservationFound. class,
IllegalAccessException. class})
WebRequest
WebRequest request) {
78
return handleExceptionInternal(ex , bodyOfResponse ,
new HttpHeaders() , HttpStatus. BAD_REQUEST , request) ;
}
Acest obiect fucționează ca un interceptor, în momentul în care se aruncă o excepție precizată ca
parametru al adnotării @ExceptionHandler , obiectul RestExceptionResponse preia conținutul
acesteia și îl serializează, trimițându-l în format JSON către partea de client.
Serviciul de logare
Pentru logare în aplicație s-a definit un singur endpoint pentru toate categoriile de utilizatori.
@RestController
public class IndexController {
@Autowired
UserService userService ;
@Autowired
UserSecurityService userSecurityService ;
@PostMapping (value = "/doLogin" )
public ResponseEntity<HttpRedirect> doLogin (@RequestBody User user , HttpServletResponse
httpServletResponse) throws IncorrectPasswordException , UserNotFoundException ,
IOException , URISyntaxException {
User dbUser = userService .findByUsername(user.getUsername()) ;
if (dbUser!= null) {
if(SecurityUtility. passwordMatcher (user.getPassword() ,dbUser.getPassword())) {
UserDetails userDetails =
userSecurityService .loadUserByUsername(user.getUsername()) ;
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails ,
userDetails.getPassword() ,
userDetails.getAuthorities()) ;
SecurityContextHolder. getContext ().setAuthentication(authentication) ;
return new ResponseEntity<HttpRedirect>( new
HttpRedirect( userService .redirectToControllerByRole(dbUser) , HttpStatus. OK),
HttpStatus. OK);
} else {
throw new IncorrectPasswordException( "Incorrect password" );
}
} else {
throw new UserNotFoundException( "User not found" );
}
}
}
Cu ajutorul framework-ului Spring Security se testează credențialele introduse de
utilizator. Ulterior, cu ajutorul metodei redirectToControllerByRole , utilizatorul este redirectat
către o anumită pagină în funcție de rolul său.
@Override
public String redirectToControllerByRole (User user) {
UserRole userRole = user.getUserRole() ;
if (userRole.getRole().getName().equals( "ROLE_ADMIN" )){
return "/admin" ;
} else if (userRole.getRole().getName().equals( "ROLE_CUSTOMER" )) {
79
return "/user_spa" ;
}
else if (userRole.getRole().getName().equals( "ROLE_MANAGER" )){
if (user instanceof Manager) {
if (managerRestaurantExist((Manager) user)) {
return "/dashboard" ;
} else {
return "/configure_restaurant" ;
}
}
return "/dashboard" ;
}
return null;
}
Serviciul de încărcare a unei imagini
S-a definit serviciul PictureService pentru încărcarea și salvarea imaginilor în aplicație.
Imaginile sunt salvate pe disc, prin metoda writeFile.
private void writeFile (Object restaurant , MultipartFile restaurantPicture , String
path) throws IOException {
byte[] bytes = restaurantPicture.getBytes() ;
File f = new File(path) ;
if (!f.exists() && !f.isDirectory()) {
new File(path).mkdirs() ;
}
BufferedOutputStream stream = new BufferedOutputStream(
new FileOutputStream( new File(path + generateName(restaurant)))) ;
stream.write(bytes) ;
stream.close() ;
}
Fișierul de tip imagine trimis de pe partea de client este convertit într-un șir de bytes care
ulterior sunt scriși pe disc ca și imagine. Pentru retragerea imaginilor de pe server acest șiri de
bytes este convertit în format BASE64.
S-au stabilit 3 foldere în care imaginile să fie salvate în funcție de proveniența lor,
numele folderelor fiind definite prin String-urile enumerate mai jos.
public static final String PROFILE_PICTURES = "profilePictures" ;
public static final String PRODUCT_PICTURES = "productPictures" ;
public static final String RESTAURANT_COVER = "restaurantCovers" ;
Restricționarea accesului în funcție de rol
Cu ajutorul framework-ului Spring Security s-a adăugat restricționarea accesului pe metode sau
pe controllere cu ajutorul adnotărilor definite în pachetul org.springframework.security.access .
De exemplu pentru apelarea endpoint-ului /restaurant-info s-a adăugat următoarea restricție :
@PreAuthorize ("hasRole('ROLE_MANAGER')" )
public class RestaurantInfoController {
80
Ceea ce înseamnă că doar utilizatorii cu rolul MANAGER au acces la metodele definite în
acestă clasă.Dacă încercăm să apelăm endpoint-ul de pe un cont de tip CUSTOMER, header-ul
răspunsului de la server vine cu următoarele informații :
Fig 2.72 Răspuns de la server pe un endpoint restricționat
Conținând statusul 403, un cod HTTP ce semnifică Forbidden Access .
CAPITOLUL 3
3.1 Utilitatea aplicației
Pentru clienți este o cale ușoară să aleagă un restaurant, nemaifiind nevoiți să apeleze la
mai mult de o aplicație pentru a face rezervarea și pentru a citi părerile altor persoane. Prin
utilizarea aplicației se reduce și factorul uman de eroare, totul fiind clar când se adresează
problema de produsele comandate, în acest fel dispărând evenimentele în care o comandă nu este
cea cerută pentru că s-a-nțeles greșit.
Din prisma managerilor această aplicație vine cu funcționalități de promovare, prin
intermediul mesajelor de tip newsletter. Pot rămâne în contact cu clienții lor prin modulul de
mesagerie implementat. Aceștia pot împărtăși print-o modalitate ușoară noutățile și beneficiile
care le aduc restaurantele lor. Utilitarele de management ale angajaților pot avea impact asupra
serviciilor care le oferă(de exemplu pregătirea mesei pentru o rezervare din punct de vedere al
timpului de procesare). Cu ajutorul aplicației aceștia își pot ține de asemenea și evidența
produselor, comenzilor sau rezervărilor prin meniuri particularizate pentru acest scop.
3.2 Dezvoltări ulterioare
Portalul eRestaurant este într-o fază pilot, putând fi perfecționată din multe puncte de
vedere. Pentru automatizarea completă a unui proces ce are loc în restaurante este necesară
crearea unui canal de comunicare între aplicația eRestaurant și aplicații terțe care se ocupă de
exemplu cu printarea bonului fiscal.
Implementarea unor sarcini recurente ar fi o altă modalitate de a crește aplicația din punct
de vedere a utilității. Prin aceste job-uri recurente angajații restaurantelor pot fi anunțați de
exemplu cu două ore înainte ca o rezervare să aibă loc.
De asemenea platforma eRestaurantManagement poate suferi îmbunătățiri drastice la
capitolul management angajați, momentan lucrând doar cu cazuri ideale. Clasele de baza au fost
implementate pentru a suferi modificări majore ușor în viitor, oferind o dezvoltare
81
scalabilă.Modulul de management angajați ar putea fi decuplat și transformat într-o aplicație
nouă destinată departamentului de resurse umane de exemplu.
Din punct de vedere arhitectural, aplicația ar putea fi migrată în viitor pe o arhitectură de
tip SOA (Arhitectură orientată pe servicii), lucru care ar face posibilă particularizarea
implementării pentru diferite mașini care consumă servicii ale aplicației eRestaurant.
Aplicația de administrare poate fi îmbunătățită cu noi ecrane pentru o administrare mai
simplă. De exemplu crearea unui dashboard cu metrici ale aplicației : câți useri logați există, cât
timp petrec aceștia pe pagină, ce excepții se aruncă și din ce cauză.
Pentru o dezvoltare pe termen foarte lung este necesară adăugarea de teste unitare și de
integrare pentru a asigura că viitoare funcționalități nu le afectează pe cele vechi.
3.3 Bibliografie
https://ashleynolan.co.uk/blog/frontend-tooling-survey-2015-results – Statistică utilizare jQuery
2015
https://www.quora.com/Are-Spring-and-Hibernate-still-popular – Statistică utilizare Spring Boot
2016
https://docs.spring.io/spring-boot/ – Documentație oficială Spring Boot
http://mapstruct.org/documentation/dev/reference/html/ – Documentație oficială MapStruct
https://en.wikipedia.org/wiki/Blowfish_(cipher) – Algoritm BCryptPasswordEncode
implementat de Spring
http://hibernate.org/orm/documentation/5.2/ – Documentație oficială Hibernate
https://www.mkyong.com/java/javamail-api-sending-email-via-gmail-smtp-example/ – JavaMail
tutorial
Copyright Notice
© Licențiada.org respectă drepturile de proprietate intelectuală și așteaptă ca toți utilizatorii să facă același lucru. Dacă consideri că un conținut de pe site încalcă drepturile tale de autor, te rugăm să trimiți o notificare DMCA.
Acest articol: Capitolul 1 ……………………………………………………………………………………………………………………… 2… [606247] (ID: 606247)
Dacă considerați că acest conținut vă încalcă drepturile de autor, vă rugăm să depuneți o cerere pe pagina noastră Copyright Takedown.
