= Имплементација на кориснички сценарија Заклучно со оваа фаза се имплементирани сите предвидени кориснички сценарија, односно: ||= ID =||= Use Case =|| || 1 || Се најавува на системот || || 2 || Се регистрира на системот || || 3 || Листа сместување || || 4 || Листа превоз || || 5 || Листа ресторани || || 6 || Резервира сместување || || 7 || Резервира превоз || || 8 || Резервира ресторани || || 9 || Откажува услуга || || 10 || Пријавува сместување || || 11 || Пријавува превоз || || 12 || Пријавува ресторан || || 13 || Менаџира резервации || || 14 || Регистрира нов бизнис || || 15 || Менува понуда за сместување || || 16 || Менува понуда за превоз || || 17 || Менува понуда за ресторан || || 15 || Управува со профили || || 16 || Верифицира профили || Дополнително е имплементирано сценариото ||= ID =||= Use Case =|| || 17 || Внесува оценка || == Општи информации Нашата апликација, како што е наведено и во претходните фази, се темели на клиент-сервер архитектура. Во овој случај клиентската страна е „претставена“ преку React апликација, додека пак серверската страна е Spring Boot апликација. Во основа на оваа архитектура е комуникација меѓу клиентот и серверот преку HTTP барања. За визуелизација и давање можност за интеракција на крајниот корисник, кој управува директно со апликацијата на клиентска страна, беше потребно да воспоставуваме конекција и соодветно да ги обработуваме простите податоци кои пристигнуваат од серверската страна. Ова го постигнавме преку Axios HTTP клиентот кој е инсталиран на клиенстката апликација и неговата инстанца во продолжение е онаа преку која ги испраќаме сите барања до серверот. {{{#!javascript import axios from "axios"; const instance = axios.create({ baseURL: "http://localhost:8080/", withCredentials: true, maxRedirects: 1, }) export default instance }}} На овој начин обезбедуваме при секое барање основниот URL да биде основниот URL на серверската апликација, додека пак со поставување на параметарот withCredentials овозможуваме препраќање на колачињата и XMLHttpRequest, што овозможува прибавување на нови податоци без целосно одновно вчитување на страницата. === Custom React Hook-и За поедноставно справување со HTTP GET и POST барањата, кои како што претходно споменавме се главната врска меѓу клиентската и серверската страна и практично не е можно да постои компонента каде не се употребуваат, креиравме наши React Hook-и кои се реискористливи и можат да се употребуваат во различни сценарија со едноставна промена на URL-то ==== useGet {{{#!javascript import axios from "../../axios"; import {useState, useEffect, useContext} from 'react'; const useGet = (url) => { const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [changed, setChanged] = useState(0) const getData = async (uurl) => { await axios.get(uurl}).then((res) => { setData(res.data); }).catch((error) => { console.log(error) window.location.href = '/error' }) .finally(() => { setIsLoading(false); }); }; useEffect(() => { setIsLoading(true); getData(url); }, [dep, url, changed]); return { data, setData, isLoading, getData, setChanged }; }; export default useGet; }}} Како што и самото име кажува, овој Hook го користиме за GET барања кон серверската страна. При инстанцирање се наведува URL кон кое се праќаат барањата. За чување на податоците кои се добиваат како одговор се користи концептот на useState од React што овозможува ререндерирање на сите компоненти при промена на состојбата на објектот. На сличен начин се постапува и со знаменцето за вчитување на податоци, кое при испраќање на барањето се поставува на true, додека пак кога ќе заврши обработката се враќа на false. Истото служи како guard clause за компонентите кои ги користат податоците кои се чекаат од GET барањето, како би се избегнале исклучоци во случај кога рендерирањето на компонентата ќе заврши пред податоците да бидат вчитани. Асинхроната функција која ги прибавува податоците од серверот е ставена во useEffect callback, што овозможува нејзино реизвршување при промена на некој од елементите во низата зависности. Овој концепт го искористивме за да овозможиме на едноставен начин ререднерирање на компонентите во кои додаваме нови записи, преку вметнување на дополнителна вредност/состојба changed во низата зависноти која пак, при праќање на барање кое очекуваме да предизвика промена на податоците достапни на страницата, се менува и со тоа автоматски се испраќа ново GET барање и промените се одразуваат на компонените кои се зависни од нив. GET барањето испратено преку Axios HTTP клиентот враќа Promise со кој соодветно се справуваме на горенаведениот начин: - во then сегментот состојбата на data се поставува на податоците кои се вратени од серверот (ова значи дека барањето поминало со статус 200); - catch сегментот се извршува кога одговорот на барањето е со статус 4ХХ, односно грешка, па во овој случај корисникот се пренасочува до страница со грешка - finally сегментот се извршува во секој случај, односно означува дека комуникацијата е завршена, вратен е одговор од серверот, па тука се поставува знаменцето isLoading назад на false ==== usePost {{{#!javascript import axios from "../../axios"; const useCreate = () => { const createEntity = async (url, entity, getData) => { console.log(entity) await axios .post(url, null, { params: entity, }) .then((res) => { getData(prev => ++prev) }) .catch((err) => { console.log(err); }) .finally(() => { }); } return { createEntity }; } export default useCreate; }}} Овој Hook го користиме за POST барања кон серверската страна. Од него постојат неколку модифицирани верзии во зависоност од тоа дали парамтерите се испраќаат во телото на барањето, како URL параметри или како променливи во патеката, но во основа се работи за иста имплементација. За разлика од useGet, тука, URL-то се испраќа како аргумент на функцијата, заедно со податоците кои треба да се испратат како параметри и состојбата на зависната променлива за која зборувавме во претходната компонент. Откако ќе се испрати POST, se враќа Promise со кој соодветно се справуваме на горенаведениот начин, со единствна разлика што во then сегментот, кога статусот означува успех, ја менуваме состојбата на зависната променлива за да се ререндерираат зависните компоненти. ==== useFormData {{{#!javascript import { useState } from 'react'; const useFormData = (editData) => { const [formData, setData] = useState({ ...editData }); const onFormChange = (e) => { setData({ ...formData, [e.target.name]: e.target.value, }); }; const onCheckBoxChange = (e) => { setData((prevData) => ({ ...prevData, [e.target.name]: !prevData[e.target.name], })); } const setFormData = (newData) => { setData({...newData}); } return { formData, onFormChange, onCheckBoxChange, setFormData }; }; export default useFormData; }}} Во секој формулар каде што имаме кориснички влез потребно е да се справиме со него и да го проследиме до серверот во утврдената форма. Ова може да се постигне на стариот добар начин, со користење на концептот useState од React и посебна состојба за секое од input полињата. За да го поедноставиме ова, успешно имплементиравме општа функција за справување со корисничкиот влез која работи на следниот начин. Наместо посебни состојби за секое поле, чуваме состојба на еден објект, каде клучот е соодветно name атрибутот на полето, а вредноста е неговиот value. За процесирање на текстуалните полиња, при промена на вредноста одговорна е функцијата onFormChange, каде се менува состојбата на записот од објектот кој е засегнат според name атрибутот. Оваа функција се извршува на секој onChange настан испален од било која input компонента. onCheckBoxChange е ништо повеќе од специјална имплементација на onFormChange функцијата за checkbox input. Како и во претходно наведените Hook-и, и тука, референци до функциите и состојбите кои треба да бидат пристапени од другите компоненти се враќаат од функцијата. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// == '''Имплементација''' == Се најавува на системот [[Image(login.png)]] Најавата на системот е имплементирана директно на серверската апликација односно на адресата http://localhost:8080/login, каде што се прикажува страницата од сликата. За реализација на ова сценарио користиме Spring Security каде синџирот филтри е соодветно конфигуриран да одговори на нашите потреби, односно да ги филтрира барањата во зависност од тоа дали корисницте се автентицирани, имаат одредена привилегија или пристапуваат до нивен ресурс. {{{#!java @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeHttpRequests((authz) -> { try { authz .requestMatchers(new AntPathRequestMatcher("/{userId}/hasBusiness")).access(userSecurity) .requestMatchers(new AntPathRequestMatcher("/business/*/unapproved")).authenticated() .requestMatchers(new AntPathRequestMatcher("/register")).permitAll() .requestMatchers(new AntPathRequestMatcher("/hotel/search")).permitAll() .requestMatchers(new AntPathRequestMatcher("/transport/search")).permitAll() .requestMatchers(new AntPathRequestMatcher("/restaurant/search")).permitAll() .requestMatchers(new AntPathRequestMatcher("/upload")).permitAll() .requestMatchers(new AntPathRequestMatcher("/business/approve/*")).hasAnyAuthority("SUPERADMIN") .requestMatchers(new AntPathRequestMatcher("/users/unlock/*")).hasAnyAuthority("SUPERADMIN") .requestMatchers(new AntPathRequestMatcher("/users/approve/*")).hasAnyAuthority("SUPERADMIN") .requestMatchers(new AntPathRequestMatcher("/business/unapproved")).hasAnyAuthority("SUPERADMIN") .requestMatchers(new AntPathRequestMatcher("/business/add/*")).authenticated() .requestMatchers(new AntPathRequestMatcher("/*/user/{userId}")).access(userSecurity) .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .successHandler(oAuth2SuccessHandler) .permitAll() .permitAll() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.ALWAYS) .and() .logout().logoutSuccessHandler((new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK))) .permitAll(); } catch (Exception e) { throw new RuntimeException(e); } } ).httpBasic(); return http.build(); } } }}} За да се провери идентитетот на корисникот, односно дали тој е сопственик на ресурсите кои се обидува да ги пристапи е имплементирана следната компонента. {{{#!java @Component public class UserSecurity implements AuthorizationManager { @Override public AuthorizationDecision check(Supplier authenticationSupplier, RequestAuthorizationContext ctx) { Long userId = Long.parseLong(ctx.getVariables().get("userId")); Authentication authentication = (Authentication) authenticationSupplier.get(); return new AuthorizationDecision(hasUserId(authentication, userId)); } public boolean hasUserId(Authentication authentication, Long userId) { Long id; User user = (User) authentication.getPrincipal(); id = user.getUserID(); return userId == id; } } }}} По успешната најава, серверот го пренасочува корисникот до клиентската апликација односно http://localhost:8080/login-callback по што, преку Hook-от useLogin се испраќа барање за добивање на податоците и нивно зачувување во AuthContext-от, од каде можат сите компоненти да ги пристапат. {{{#!javascript const useLogin = () => { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const Auth = useAuth(); const handleLoginCallback = async () => { setLoading(true); try { const response = await axios.get("http://localhost:8080/principal"); const { id, role, username } = response.data; Auth.userLogin({userId: id, username: username, role: role}) } catch (err) { setError(err.message); } finally { setLoading(false); } }; return { loading, error, handleLoginCallback, }; }; export default useLogin; }}} \\ {{{#!javascript const AuthContext = createContext() const AuthProvider = ({ children }) => { const [user, setUser] = useState(null) useEffect(() => { const storedUser = JSON.parse(localStorage.getItem('user')) setUser(storedUser) }, []) const getUser = () => { return JSON.parse(localStorage.getItem('user')) } const userIsAuthenticated = () => { return localStorage.getItem('user') !== null } const userLogin = user => { localStorage.setItem('user', JSON.stringify(user)) setUser(user) } const userLogout = () => { localStorage.removeItem('user') setUser(null) } const contextValue = { user, getUser, userIsAuthenticated, userLogin, userLogout, } return ( {children} ) } export default AuthContext export function useAuth() { return useContext(AuthContext) } export { AuthProvider } }}} За да се заштитат рутите за кои е потребна автентикација, имплементирана е wrapper-компонента којашто проверува дали корисникот е најавен и во спротивно го пренасочува кон страницата за најава. {{{#!javascript function PrivateRoute({ children }) { const { userIsAuthenticated } = useAuth() if(userIsAuthenticated()) { return children; } else { window.location.href = "http://localhost:8080/login"; } } export default PrivateRoute }}} == Се регистрира на системот [[Image(registartion.png)]] Регистрацијата не е никаков специјален случај, туку обично POST барање кое се испраќа по пополнување на формуларот. Него го пречекува следниот контролер и го препраќа на обработка до сервисот и Dao-то {{{#!java @PostMapping(path = "/register") public List add(@RequestBody User user) { usersManager.createUser(user.getName(), user.getSurname(), user.getEmail(), user.getBirthDate(), user.getAddress(), user.getContact()); return usersManager.getCreatedUsers(); } }}} == Корисник со основни привилегии === Листа сместувања [[Image(SearchHotel.png)]] {{{#!java public List getRoomsAvailibilityByDateAndLocation(String hotelLocation, Date dateFrom, Date dateTo, int numberOfBeds, Boolean flexible) { if(flexible) { Calendar calendar = Calendar.getInstance(); Calendar calendar1 = Calendar.getInstance(); calendar.setTime(dateTo); calendar1.setTime(dateFrom); return em.createQuery("SELECT hr FROM HotelRoomAvailable hr WHERE " + "((hr.dateFrom <= :dateTo AND hr.dateTo >= :dateFrom) OR " + "(hr.dateFrom <= :dateToMinus1 AND hr.dateTo >= :dateFromMinus1) OR " + "(hr.dateFrom <= :dateToMinus2 AND hr.dateTo >= :dateFromMinus2) OR " + "(hr.dateFrom <= :dateToMinus3 AND hr.dateTo >= :dateFromMinus3) OR " + "(hr.dateFrom <= :dateToPlus1 AND hr.dateTo >= :dateFromPlus1) OR " + "(hr.dateFrom <= :dateToPlus2 AND hr.dateTo >= :dateFromPlus2) OR " + "(hr.dateFrom <= :dateToPlus3 AND hr.dateTo >= :dateFromPlus3)) " + "AND hr.hotelRoom.hotel.hotelLocation LIKE :hotelLocation " + "AND hr.hotelRoom.numOfBeds >= :numBeds") .setParameter("hotelLocation", hotelLocation) .setParameter("dateFrom", dateFrom) .setParameter("dateTo", dateTo) .setParameter("dateToMinus1", subtractDays(calendar, 1)) .setParameter("dateToMinus2", subtractDays(calendar, 2)) .setParameter("dateToMinus3", subtractDays(calendar, 3)) .setParameter("dateToPlus1", addDays(calendar, 1)) .setParameter("dateToPlus2", addDays(calendar, 2)) .setParameter("dateToPlus3", addDays(calendar, 3)) .setParameter("dateFromMinus1", subtractDays(calendar1, 1)) .setParameter("dateFromMinus2", subtractDays(calendar1, 2)) .setParameter("dateFromMinus3", subtractDays(calendar1, 3)) .setParameter("dateFromPlus1", addDays(calendar1, 1)) .setParameter("dateFromPlus2", addDays(calendar1, 2)) .setParameter("dateFromPlus3", addDays(calendar1, 3)) .setParameter("numBeds", numberOfBeds) .getResultList(); } else { return em.createQuery("SELECT hr FROM HotelRoomAvailable hr WHERE " + "((hr.dateFrom <= :dateTo AND hr.dateTo >= :dateFrom)) " + "AND hr.hotelRoom.hotel.hotelLocation LIKE :hotelLocation " + "AND hr.hotelRoom.numOfBeds >= :numBeds") .setParameter("hotelLocation", hotelLocation) .setParameter("dateFrom", dateFrom) .setParameter("dateTo", dateTo) .setParameter("numBeds", numberOfBeds) .getResultList(); } } }}} Ова е кверито од кое се земаат хотелите по криетриум, каде што првиот дел е додека флексибилноста од 3 дена има вредност true, па тогаш се листаат +/- (1,2,3) дена од бараните критериуми, а доколку има вредност false тогаш се листаат според точните дати кои се внесени. {{{#!java public List getRoomsAvailibilityByDateAndLocation(String hotelLocation, Date dateFrom, Date dateTo, int numberOfBeds, Boolean flexible) { long numberOfNights = Duration.between(dateFrom.toInstant(), dateTo.toInstant()).toDays(); List roomsAvailible = hotelDao.getRoomsAvailibilityByDateAndLocation(hotelLocation, dateFrom, dateTo, numberOfBeds, flexible); Map> roomsByHotels = roomsAvailible.stream().collect(Collectors.groupingBy(x -> x.getHotelRoom().getHotel())); List hotelsList = roomsByHotels.keySet().stream() .map(x -> new HotelDto( x.getHotelId(), x.getHotelName(), x.getHotelDescripiton(), x.getHotelLocation(), x.getHotelEDBS(), x.getParking(), x.getPetFriendly(), x.getInternetAvailable(), roomsByHotels.get(x).stream().mapToDouble(y -> y.getHotelRoom().getPrice()).min().getAsDouble() * numberOfNights, roomsByHotels.get(x), getReviewsForHotel(x.getHotelId()), getReviewsForHotel(x.getHotelId()).stream().mapToDouble(Reviews::getNumStar).average().orElse(0), getHotelImages(x.getHotelId()) )).toList(); return hotelsList; } }}} Во сервисот за да не ги користиме сите податоци од вистинската класа користиме класа HotelDto односно data transfer object. Со ова овозможуваме да за пренос на податоци меѓу backend и frontend. Во функцијата ги листаме сите слободни соби од сите хотели со наведените криетриуми. {{{#!java @GetMapping(path = "/hotel/search") public List searchAvailibleRooms(@RequestParam(name = "hotelLocation") String hotelLocation, @RequestParam(name = "dateFrom") @DateTimeFormat(pattern = "yyyy-MM-dd") Date dateFrom, @RequestParam(name = "dateTo") @DateTimeFormat(pattern = "yyyy-MM-dd") Date dateTo, @RequestParam(name = "numBeds") int numBeds, @RequestParam(name = "flexible") Boolean flexible) { return hotelManager.getRoomsAvailibilityByDateAndLocation(hotelLocation, dateFrom, dateTo, numBeds, flexible); } }}} Контролерот ги прима потребните податоци и ја повикува функцијата од сервисот. === Листа ресторани [[Image(RestaurantSearch.png)]] {{{#!java public List getTablesByDateAndLocation(String restaurantLocation, Date hourFrom, Date hourTo, int noSeats){ return em.createQuery("select hr from RestaurantsAvailible hr where hr.hourFrom <= :hourFrom and hr.hourTo >= :hourTo " + "and hr.restaurantTable.restaurant.restaurantLocation LIKE :restaurantLocation and hr.restaurantTable.noSeats >= :noSeats") .setParameter("restaurantLocation", restaurantLocation) .setParameter("hourFrom", hourFrom) .setParameter("hourTo", hourTo) .setParameter("noSeats", noSeats) .getResultList(); } }}} Ова е кверито кое ги листа сите маси во одредена локација, со одредено време на доаѓање, заминување и бројка колку луѓе ќе дојдат. {{{#!java public List getTablesByDateAndLocation(String restaurantLocation, Date date, String hourFrom, String hourTo, int noSeats) { Date dateFrom = date; Date dateTo = Date.from(date.toInstant()); String[] splittedFrom = hourFrom.split(":"); String[] splittedTo = hourTo.split(":"); dateFrom.setHours(Integer.parseInt(splittedFrom[0])); dateFrom.setMinutes(Integer.parseInt(splittedFrom[1])); dateTo.setHours(Integer.parseInt(splittedTo[0])); dateTo.setMinutes(Integer.parseInt(splittedTo[1])); List restaurantsAvailibles = restaurantDao.getTablesByDateAndLocation(restaurantLocation, hourFrom, hourTo, noSeats); Map> tablesByRestaurants = restaurantsAvailibles.stream().collect(Collectors.groupingBy(x -> x.getRestaurantTable().getRestaurant())); List restaurantsList = tablesByRestaurants.keySet().stream() .map(x -> new RestaurantDto( x.getRestaurantID(), x.getRestaurantName(), x.getRestaurantLocation(), x.getCousineType(), x.getRestaurantDescription(), x.getRestaurantEdbs(), x.getMenus(), tablesByRestaurants.get(x), getReviewsForRestaurant(x.getRestaurantID()), getReviewsForRestaurant(x.getRestaurantID()).stream().mapToDouble(Reviews::getNumStar).average().orElse(0), getRestaurantImages(x.getRestaurantID()), getMenuImagesByRestaurant(x.getRestaurantID()) )).toList(); return restaurantsList; } }}} Сервисот кој исто како и во хотели користи Dto притоа ги листа сите ресторани кои можат да го опслужат барањето од корисниците. Со тоа што времето од-до се интерпретира како текст, па тие ги средуваме во сервисот. {{{#!java @GetMapping(path = "/restaurant/search") public List searchAvailableRestaurant(@RequestParam(name = "restaurantLocation") String restaurantLocation, @RequestParam(name = "date") @DateTimeFormat(pattern = "yyyy-MM-dd") Date date, @RequestParam(name = "hourFrom") String hourFrom, @RequestParam(name = "hourTo") String hourTo, @RequestParam(name = "numPeople") int noSeats) { return restaurantManager.getTablesByDateAndLocation(restaurantLocation, date, dateFrom, dateTo, noSeats); } }}} Во контролерот се примаат податоци за локација, датум, време од до во форма на текст и број на седишта === Листа превоз [[Image(TransportSearch.png)]] {{{#!java public List getTransportsAvailableByFilters(String fromL, String toL, Date date, int numPassengers) { return em.createQuery("select h from TransportRoute h where h.from = :froml and h.to = :tol and h.freeSpace >= :nump") .setParameter("froml", fromL) .setParameter("tol", toL) .setParameter("nump", numPassengers) .getResultList(); } }}} Ова е кверито со кое се наоѓаат сите слобдни транспорти со одредена дата од-до одредено место и број на патници {{{#!java public List getTransportsAvailableByFilters(String from, String to, Date date, int numPassengers) { List transportAvailable = transportDao.getTransportsAvailableByFilters(from, to, date, numPassengers); Map> transportsByTransporter = transportAvailable.stream().collect(Collectors.groupingBy(x -> x.getParentRoute())); List transportList = transportsByTransporter.keySet().stream().toList().stream() .map(x -> new TransportListingDto( x.getTransportAvailibleId(), x.getFrom(), x.getTo(), x.getDate(), x.getFreeSpace(), x.getTime(), transportsByTransporter.get(x).stream().mapToDouble(y -> y.getPrice()).min().getAsDouble(), x.getRoutes(), x.getTransport(), getReviewsForTransport(x.getTransport().getTransportID()), getReviewsForTransport(x.getTransport().getTransportID()).stream().mapToDouble(Reviews::getNumStar).average().orElse(0) )).collect(Collectors.toList()); return transportList; } }}} Исто како и останатите имаме Dto и ги листаме сите слобдони транспорти со наведените критериуми. Притоа што доколку се внесе некој град кој е попатен во некоја рута се листа и таа со тоа што се зема главната рута на тој град. {{{#!comment Не знам дали вака види }}} {{{#!java @GetMapping(path = "/transport/search") public List searchAvailableTransport(@RequestParam(name = "from") String from, @RequestParam(name = "to") String to, @RequestParam(name = "date") @DateTimeFormat(pattern = "yyyy-MM-dd") Date date, @RequestParam(name = "numPassengers") int numPassengers){ return transportManager.getTransportsAvailableByFilters(from, to, date, numPassengers); } }}} Контролерот кој според влезните критериуми ги листаме сите слобдони транспорти за тие дестинации. == Најавен давател на услуги === Регистра бизнис [[Image(registerBusiness.png)]] ---- [[Image(RegisterBusinessForm.png)]] {{{#!java public void createBusiness(Business business) { em.persist(business); } }}} Се креира нов бизнис во база {{{#!java public void createBusiness(Business business, long userId) { User u = usersManager.findUserByID(userId); business.setUser(u); businessDao.createBusiness(business); } }}} Креирање бизнис во сервисот, каде што се наоѓа корисникот кој сака да го креира {{{#!java @PostMapping(path = "/business/add") public void addBusiness(@RequestBody Business business, @RequestParam(name = "userId") long userId) { businessManager.createBusiness(business, userId); } }}} Контролерот каде се зема цел објект бизнис од Frontend, па се повикува функцијата од сервисот === Додава хотел ---- [[Image(MyResources1.png)]] ---- [[Image(MyResourcesHotel.png)]] ---- [[Image(myResourcesHotel1.png)]] {{{#!java public void createHotel(Hotels hotel) { em.persist(hotel); } }}} Се креира нов хотел во база {{{#!java public void createHotel(Hotels hotels, long userId) { User u = usersManager.findUserByID(userId); hotels.setOwner(u); hotelDao.createHotel(hotels); } }}} Во сервисот се наоѓа корисникот кој сака да отвори хотел, и истиот се сетира за поседител на хотелот {{{#!java @PostMapping(path = "/hotel/add") public void add(@RequestBody Hotels hotel, @RequestParam(name = "userId") long userId) { hotelManager.createHotel(hotel, userId); } }}} Хотелот се зема како цел објект преку frontend и се зема id на user и се повикува функцијата од сервисот ==== Додава соба во хотел [[Image(MyResourcesHotelRooms.png)]] ---- [[Image(HotelRoomAdd.png)]] ---- [[Image(HotelRoom.png)]] {{{#!java public void createRoom(HotelRoom hotelRoom) { em.persist(hotelRoom); } }}} Се крериа нова соба за одреден хотел {{{#!java public void createRoom(Long hotel, String hotelRoomDescription, String hotelRoomName, Boolean kitchenAvailable, Boolean airConditioning, Boolean balcony, double price, int numOfBeds) { Hotel hotel = findHotelByID(hotel); HotelRoom hotelRoom = new HotelRoom(hotel, hotelRoomDescription, hotelRoomName, kitchenAvailable, airConditioning, balcony, price, numOfBeds); hotelDao.createRoom(hotelRoom); } }}} Се креира нова соба во сервисот притоа што се зема id на хотелот на кој ќе се креира новата соба {{{#!java @PostMapping(path = "/hotel/rooms/add") public void addRoom(@RequestBody HotelRoom room, @RequestParam(name = "hotelId") long hotelId) { hotelManager.createRoom(hotelId, room.getHotelRoomDescription(), room.getHotelRoomName(), room.getKitchenAvailable(), room.getAirConditioning(), room.getBalcony(), room.getPrice(), room.getNumOfBeds()); } }}} Во контролерот се зема цел објект HotelRoom и се зема id на хотелот и се повикува функцијата за креирање соба од сервисот ---- [[Image(HotelRoomFreeDates.png)]] {{{#!java public void createRoomAvailible(HotelRoomAvailable hra) { em.persist(hra); } }}} Се креира слободна соба за хотелот во база {{{#!java public void createRoomAvailible(Long id, Date dateFrom, Date dateTo, int numberOfBeds) { HotelRoom hotelRoom = findRoomById(id); HotelRoomAvailable hra = new HotelRoomAvailable(hotelRoom, dateFrom, dateTo, numberOfBeds); hotelDao.createRoomAvailible(hra); } }}} Во сервисот се зема претходно креирана соба и се креира нов објект од тип HotelRoomAVailable кој чува за која соба од кога до кога е слободна {{{#!java @PostMapping(path = "/hotel/rooms/available/{id}/add") public void addRoomAvailible(@RequestBody HotelRoomAvailable hotelRoomAvailable, @PathVariable long id) { hotelManager.createRoomAvailible(id, hotelRoomAvailable.getDateFrom(), hotelRoomAvailable.getDateTo(), hotelRoomAvailable.getNumberOfBeds()); } }}} Во контролерот се повикува функцијата од сервисот за креирање на соба, а прима цел објект од типот преку frontend и id за која соба станува збор ---- [[Image(HotelRoomEdit.png)]] {{{#!java public void editRoomAvailible(long hotelRoomAvailableId, HotelRoom hotelRoom, Date dateFrom, Date dateTo, int numberOfBeds) { HotelRoomAvailable hr = findAvailibleRoomById(hotelRoomAvailableId); hr.setHotelRoom(hotelRoom); hr.setDateFrom(dateFrom); hr.setDateTo(dateTo); hr.setNumberOfBeds(numberOfBeds); hotelDao.createRoomAvailible(hr); } }}} Креирањето и менувањето во база е исто затоа не се наведува, а во сервисот се менуваат сите ново наведени детали за самата соба {{{#!java @PostMapping(path = "/hotel/rooms/available/edit") public void editRoomAvailible(@RequestBody HotelRoomAvailable hotelRoomAvailable) { hotelManager.editRoomAvailible(hotelRoomAvailable.getHotelRoomAvailableId(), hotelRoomAvailable.getHotelRoom(), hotelRoomAvailable.getDateFrom(), hotelRoomAvailable.getDateTo(), hotelRoomAvailable.getNumberOfBeds()); } }}} Во Котнтролерот се зема цел објект за слободната соба и таа се повикува функцијата од сервисот ---- ==== Менаџира резервации [[Image(HotelReservation.png)]] {{{#!java public List findReservationByHotel(Hotels hotel) { List hotelRooms = getRoomsOfHotel(hotel); return em.createQuery("select hr from HotelRoomReservations hr where hr.hotelRoom.hotel = :hotel").setParameter("hotel", hotel).getResultList(); } }}} Се земаат сите резервации за одреден хотел од база {{{#!java public List findVaidReseravtionsByHotel(Long hotelId) { Hotels hotel = findHotelByID(hotelId); List reservations = hotelDao.findReservationByHotel(hotel); return reservations.stream() .map(x -> new HotelReservationDto( x.getUser(), x.getHotelRoom(), x.getDateFrom(), x.getDateTo(), x.getNumberOfBeds() )).toList(); } }}} Исто како и претходно се користи Dto со кој се па се враќаат сите резервации за одреден хотел {{{#!java @GetMapping(path = "/hotel/{id}/reservations/active") public List getActiveReservationsForHotel(@PathVariable Long id) { return hotelManager.findVaidReseravtionsByHotel(id); } }}} Се праќа само id на хотелот и во сервисот се прави логиката. === Менува хотел ---- [[Image(MyResourcesHotelEdit.png)]] {{{#!java public void editHotel(long hotelId, String hotelName, String hotelDescripiton, String hotelLocation, String hotelEDBS, Boolean parking, Boolean petFriendly, Boolean internetAvailable) { Hotels hotel = hotelDao.findHotelByID(hotelId); hotel.setHotelName(hotelName); hotel.setHotelDescripiton(hotelDescripiton); hotel.setHotelLocation(hotelLocation); hotel.setHotelEDBS(hotelEDBS); hotel.setParking(parking); hotel.setPetFriendly(petFriendly); hotel.setInternetAvailable(internetAvailable); hotelDao.editHotel(hotel); } }}} Зачувување во база е исто како и креирање притоа што не го покажуваме, а менувањето на хотел во сервисот ги менува сите потребни детали за истиот {{{#!java @PostMapping(path = "/hotel/edit") public void edit(@RequestBody Hotels hotel) { hotelManager.editHotel(hotel.getHotelId(), hotel.getHotelName(), hotel.getHotelDescripiton(), hotel.getHotelLocation(), hotel.getHotelEDBS(), hotel.getParking(), hotel.getPetFriendly(), hotel.getInternetAvailable()); } }}} Во контролерот се зема цел објект преку frontend и истиот се препраќа во функцијата од сервисот === Додава ресторан ---- [[Image(MyResourcesRestaurant.png)]] ---- [[Image(MyResourcesRestaurantAdd.png)]] {{{#!java public void createRestaurant(Restaurant restaurant) { em.persist(restaurant); } }}} Се креира ресторан во базата {{{#!java public void createRestaurant(Restaurant restaurant, long userId) { User u = usersManager.findUserByID(userId); Restaurant r = new Restaurant(restaurant.getRestaurantName(), restaurant.getRestaurantLocation(), restaurant.getCousineType(), restaurant.getRestaurantDescription(), restaurant.getRestaurantEdbs(), u); restaurantDao.createRestaurant(r); } }}} Во сервисот се наоѓа корисникот кој го креира и се става ресторанот под негово име {{{#!java @PostMapping(path = "/restaurant/add") public void add(@RequestBody Restaurant restaurant, @RequestParam(name = "userId") long userId) { restaurantManager.createRestaurant(restaurant, userId); } }}} Во контролерот се зема ресторанот како цел објект и id на корисникот кој го креира, па се повикува функцијата за креирање ресторан од сервисот === Менува ресторан [[Image(RestaurantEdit.png)]] {{{#!java public void editRestaurant(long restaurantID, String restaurantName, String restaurantLocation, String cousineType, String restaurantDescription, String restaurantEdbs, User restaurantOwner) { Restaurant res = findRestaurantByID(restaurantID); res.setRestaurantName(restaurantName); res.setRestaurantLocation(restaurantLocation); res.setRestaurantEdbs(restaurantEdbs); res.setRestaurantDescription(restaurantDescription); res.setRestaurantOwner(restaurantOwner); res.setCousineType(cousineType); restaurantDao.createRestaurant(res); } }}} Исто е креирање и менување на ресторан во база, затоа не го покажуваме. Во сервисот се зема ресторанот и се менуваат сите негови детали што поседителот ги изменил {{{#!java @PostMapping(path = "/restaurant/edit") public List edit(@RequestBody Restaurant restaurant) { restaurantManager.editRestaurant(restaurant.getRestaurantID(), restaurant.getRestaurantName(), restaurant.getRestaurantLocation(), restaurant.getCousineType(), restaurant.getRestaurantDescription(), restaurant.getRestaurantEdbs(), restaurant.getRestaurantOwner()); return restaurantManager.getRestaurants(); } }}} Во контролерот се зема целиот ресторан како објект од frontend и тој се препраќа во сервисот ==== Додава мени ---- [[Image(RestaurantMenuAdd.png)]] ---- [[Image(RestaurantMenu.png)]] {{{#!java public void addMenuToRestaurant(Menu menu) { em.persist(menu); } }}} Менито се зачувува во база {{{#!java public void addMenuToRestaurant(long restaurantId, Menu menu) { Restaurant r = findRestaurantByID(restaurantId); menu.setRestaurant(r); restaurantDao.addMenuToRestaurant(menu); } }}} Во сервисот се ноѓа ресторанот на кого ќе се додава нова ставка од менито и истото се додава {{{#!java @PostMapping(path = "/restaurant/{id}/menu/add") public void addMenu(@PathVariable(name = "id") long restaurantId, @RequestBody Menu menu) { restaurantManager.addMenuToRestaurant(restaurantId, menu); } }}} Во котрнолерот се зема id на ресторан и цел објект од менито, и истото се препраќа на сервисот да ја прави логиката ---- [[Image(RestaurantMenuEdit.png)]] {{{#!java public void editMenu(long menuId, String name, String ingredients, double price) { Menu menu = findMenuById(menuId); menu.setName(name); menu.setIngredients(ingredients); menu.setPrice(price); menuDao.editMenu(menu); } }}} Повторно дека менување и креирање е исто не се покажува тој дел. Во сервисот се зема менито според id и истото се менува според новите детали кои се внесени {{{#!java @PostMapping(path = "/menu/{id}/edit") public void editMenu(@PathVariable Long id, @RequestParam String name, @RequestParam String ingredients, @RequestParam double price) { menuManager.editMenu(id, name, ingredients, price); } }}} Контролерот прима id од менито и негови нови детали кои ги предава на сервисот да ја доврши логиката ==== Додава маси [[Image(RestaurantTablesAdd.png)]] ---- [[Image(RestaurantTables.png)]] {{{#!java public void saveTable(RestaurantsTable resTable) { em.persist(resTable); } }}} Додавање на нова маса во базата {{{#!java public void createTable(Long restaurantId, int noSeats) { Restaurant r = restaurantDao.findRestaurantByID(restaurantId); RestaurantsTable restaurantsTable = new RestaurantsTable(r, noSeats); restaurantDao.saveTable(restaurantsTable); } }}} Во сервисот се зема ресторанот на кој ќе се додава новата маса, за неа се внесуваат број на седишта {{{#!java @PostMapping(path = "/restaurant/table/{id}/add") public void addTable(@PathVariable Long id, @RequestParam Integer noSeats) { restaurantManager.createTable(id, noSeats); } }}} Се зема id на ресторанот и бројот за колку луѓе е наменета масата, па се повикува функцијата од сервисот за понатамошна логика ---- [[Image(RestaurantTableAvailable.png)]] {{{#!java public void saveTableAvailable(RestaurantsAvailible ra){ em.persist(ra); } }}} Се зачувува слободната маса во база {{{#!java public void createTableAvailable(Long rt, Date hourFrom, Date hourTo, int numTables) { RestaurantsTable rtabl = findTableById(rt); RestaurantsAvailible ra = new RestaurantsAvailible(rtabl, hourFrom, hourTo, numTables); restaurantDao.saveTableAvailable(ra); } }}} Во сервисот се зема масата преку id и се прави нов објект од расположливост на маси и се сетира од кога до кога и колку такви маси има на располагање {{{#!java @PostMapping(path = "/restaurant/table/{id}/available/add") public void addTableAvailable(@RequestBody RestaurantsAvailible restaurantsAvailible, @PathVariable Long id) { restaurantManager.createTableAvailable(id, restaurantsAvailible.getHourFrom(), restaurantsAvailible.getHourTo(), restaurantsAvailible.getNumTables()); } }}} За додавање на расположливост во контролерот се зема објект од RestaurantsAvailable и се зема id од која маса ---- [[Image(RestaurantTableEdit.png)]] {{{#!java public void editTable(Restaurant restaurant, long tableId, int noSeats) { RestaurantsTable resTable = findTableById(tableId); resTable.setRestaurant(restaurant); resTable.setNoSeats(noSeats); restaurantDao.saveTable(resTable); } }}} Се наоѓа масата преку id и на истата се додава се менуваат потребните детали. Зачувувањето во база е исто па не се покажува {{{#!java @PostMapping(path = "/restaurant/table/edit") public void editTable(@RequestBody RestaurantsTable restaurantsTable) { restaurantManager.editTable(restaurantsTable.getRestaurant(), restaurantsTable.getTableId(), restaurantsTable.getNoSeats()); } }}} Се зема цел објект од масата и за истата се пропушта на сервисот за логиката ---- [[Image(ReservationTable.png)]] {{{#!java public List findReservationByRestaurant(Restaurant restaurant) { return em.createQuery("select rr from RestaurantReservations rr where rr.table.restaurant = :rest").setParameter("rest", restaurant).getResultList(); } }}} Се листаат сите резервации кои се резервирани на ресторанот {{{#!java public List findReservationByRestaurant(Long restaurant) { Restaurant r = findRestaurantByID(restaurant); List res = restaurantDao.findReservationByRestaurant(r); return res.stream().map(x -> new RestaurantReservationDto( x.getUser(), x.getTable(), x.getTimeFrom(), x.getTimeTo() )).collect(Collectors.toList()); } }}} Исто како и горе се прави Dto и се ноѓаат сите резервации од конкретниот ресторан и истите се враќаат како резултатна листа {{{#!java @GetMapping(path = "/restaurant/{id}/reservations/active") public List getActiveReservationsForRestaurant(@PathVariable Long id) { return restaurantManager.findReservationByRestaurant(id); } }}} Преку контролерот се земаат податоци за id на на ресторанот и потоа се повикува функцијата од сервисот за резервација. === Додава транспорт [[Image(MyResourcesTransport.png)]] ---- [[Image(MyResourcesTranposrtAdd.png)]] ---- [[Image(MyResourcesTransport1.png)]] {{{#!java public void createTransport(Transport t) { em.persist(t); } }}} Зачувување на траспорт во база {{{#!java public void createTransport(String transportName, String carBrand, String carType, int carManufacturedYear, int noPassengers, int noBags, long EMBG, Long userId, String carPlate) { User u = usersManager.findUserByID(userId); Transport t = new Transport(transportName,carBrand,carType,carManufacturedYear,noPassengers,noBags,EMBG,u,carPlate); transportDao.createTransport(t); } }}} Во сервисот се зема корисникот преку id и притоа се креира нов транпсорт на негово име {{{#!java @PostMapping(path = "/transport/add/{userId}") public void add(@RequestBody Transport transport, @PathVariable Long userId) { transportManager.createTransport(transport.getTransportName(), transport.getCarBrand(), transport.getCarType(), transport.getCarManufacturedYear(), transport.getNoPassengers(), transport.getNoBags(), transport.getEMBG(), userId, transport.getCarPlate()); } }}} Преку контролерот се земаат податоци за id на user и се зема цел објект кој се препраќа во сервисот ---- [[Image(transportEdit.png)]] {{{#!java public void editTransport(long transportID, String transportName, String carBrand, String carType, int carManufacturedYear, int noPassengers, int noBags, long EMBG, User owner, String carPlate) { Transport t=getTransportById(transportID); t.setTransportName(transportName); t.setCarBrand(carBrand); t.setCarType(carType); t.setCarManufacturedYear(carManufacturedYear); t.setNoPassengers(noPassengers); t.setNoBags(noBags); t.setEMBG(EMBG); t.setOwner(owner); t.setCarPlate(carPlate); transportDao.editTransport(t); } }}} Во сервисот се зема транспортот кој се менува и се менуаат неговите детали. Зачувување во база е исто како и во креирање {{{#!java @PostMapping(path = "/transport/edit") public void edit(@RequestBody Transport transport) { transportManager.editTransport(transport.getTransportID(), transport.getTransportName(), transport.getCarBrand(), transport.getCarType(), transport.getCarManufacturedYear(), transport.getNoPassengers(), transport.getNoBags(), transport.getEMBG(), transport.getOwner(), transport.getCarPlate()); } }}} Во контролерот се зема цел објект на транспорт и за истиот се повикува функција од сервис === Менува транспорт (Додава рути) //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// [[Image(RouteAdd.png)]] ---- [[Image(RoutesAddExtended.png)]] ---- [[Image(route.png)]] ---- За правилно справување со формуларот за додавање рути и попатни точки, имплементиравме специјална верзија од претходно опишаниот useFormData Hook. {{{#!javascript const useFormNested = (editData) => { const [formData, setData] = useState({...editData}); const onFormChange = (e) => { console.log(e) const dependantRoutes = e.target.name === 'freeSpace' ? e.target.parentElement.getAttribute('dependantRoutes').split(',') : undefined; console.log(dependantRoutes) let updatedData = { ...formData, [e.target.parentElement.attributes.routeId.value]: { ...formData[e.target.parentElement.attributes.routeId.value], [e.target.name]: e.target.value }, } if (e.target.name === 'freeSpace') dependantRoutes.forEach(x => { updatedData = { ...updatedData, [x]: { ...formData[x], [e.target.name]: e.target.value }, } }) setData(updatedData); console.log(updatedData) }; const setFormData = (newData) => { setData({...newData}); } return { formData, onFormChange, setFormData }; }; export default useFormNested; }}} Оваа функција овозможува при промена во поле кај една рута, одеднаш да се ажурираат сите рути кои директно зависат од неа. На овој начин се обезбедува конзиистентност во бројот на слободни места за секоја од под-рутите. По завршениот внес, се испраќа POST барање кое го пречекува контролерот: {{{#!java @PostMapping(path = "/transport/available/add") public void add(@RequestBody TransportAvailible transportAvailable, @RequestParam(name = "transportId") long transportId) { transportManager.createTransportAvailable(transportAvailable, transportAvailable.getTransport(), transportAvailable.getFrom(), transportAvailable.getTo(), transportAvailable.getDate(), transportAvailable.getFreeSpace(), transportAvailable.getTime(), transportId, transportAvailable.getRoutes()); } }}} А потоа се предава на сервисот каде се формира главната (родител) рутa, се зачувува и потоа се формираат пооделните рути каде иста е назначена и се зачувуваат. {{{#!java @Override public void createTransportAvailable(TransportAvailible transportAvailible, Transport transport, String departureLocation, String arrivalLocation, Date date, Integer noSeats, Date departureHour, Long transportId, Collection trRoutes) { Transport t = getTransportById(transportId); List routes = trRoutes.stream().toList(); routes.get(0).setDeparture(transportAvailible.getDate()); transportAvailible.setTime(routes.get(routes.size() - 1).getArrival()); routes.forEach(x -> x.setParentRoute(transportAvailible)); transportAvailible.setTransport(t); TransportAvailible ta=new TransportAvailible(t,departureLocation,arrivalLocation,date,noSeats,departureHour); transportDao.createTransportAvailable(ta); routes.forEach(x -> { TransportRoute tr = new TransportRoute(ta, x.getFrom(), x.getTo(), x.getPrice(), x.getDeparture(), x.getArrival(), x.getFreeSpace(), x.getOrder()); transportDao.createTransportRoute(tr); }); } }}} {{{#!java }}} {{{#!java @Override @Transactional public void createTransportAvailable(TransportAvailible ta) { em.persist(ta); } }}} // {{{#!java @Override @Transactional public void createTransportRoute(TransportRoute tr) { em.persist(tr); } }}} ==== Менаџира резервации [[Image(RouteReservation.png)]] {{{#!java public List getTransportReservations() { return em.createQuery("from TransportReservation order by reservationID").getResultList(); } }}} Листање на резервациите на одреден транспорт {{{#!java public List findTransportReservationByTransportId(long reservationID) { Transport t = getTransportById(reservationID); return transportDao.findTransportReservationByTransportId(t); } }}} Се листаат сите резервации на одреден транспорт со одреден id {{{#!java @GetMapping(path = "/transport/{id}/reservations/active") public List showTransportReservationsByTransport(@PathVariable Long id) { return transportManager.findTransportReservationByTransportId(id); } }}} Преку контролерот се зема id на траспортот и се предава на котрнолерот == Најавен администратор === Управување со профили [[Image(profiles4.png)]] === Управува со фирми [[Image(profiles5.png)]] == Најавен корисник на услуги === Резеревација ресторан [[Image(RestaurantDetails1-min.png)]] ---- [[Image(RestaurantDetails2.png)]] ---- [[Image(RestaurantReservation.png)]] {{{#!java public void createReservation(RestaurantReservations reservations) { em.persist(reservations); } }}} Се зачувува резервацијата во база {{{#!java public void createReservation(Long userId, Long restaurantTableId, Long restaurantAvailibleId, String hourFrom, String hourTo, Date date) { User u = usersManager.findUserByID(userId); RestaurantsTable restaurantTable = restaurantDao.findTableById(restaurantTableId); RestaurantsAvailible restaurantsAvailible = restaurantDao.findAvailableReservationByID(restaurantAvailibleId); Date dateFrom = date; Date dateTo = Date.from(date.toInstant()); String[] splittedFrom = hourFrom.split(":"); String[] splittedTo = hourTo.split(":"); dateFrom.setHours(Integer.parseInt(splittedFrom[0])); dateFrom.setMinutes(Integer.parseInt(splittedFrom[1])); dateTo.setHours(Integer.parseInt(splittedTo[0])); dateTo.setMinutes(Integer.parseInt(splittedTo[1])); RestaurantReservations reservation = new RestaurantReservations(restaurantTable, dateFrom, dateTo, restaurantTable.getNoSeats(), u); restaurantDao.createReservation(reservation); editTableAvailability(restaurantsAvailible, restaurantTable, dateFrom, dateTo); } }}} Во сервисот се зема id на user, на масата и на слободната маса. Времето од-до повторно се зема како текст и тој се средува тука. Притоа што повторно со тоа што има одреден број на слободни маси со резервација тоа се средува на одреден начин во логиката editTableAvailability {{{#!java public void editTableAvailability(RestaurantsAvailible restaurantsAvailible, RestaurantsTable table, Date dateFrom, Date dateTo) { restaurantsAvailible.setNumTables(restaurantsAvailible.getNumTables() - 1); RestaurantsAvailible ra1 = new RestaurantsAvailible(table, restaurantsAvailible.getHourFrom(), dateFrom, 1); RestaurantsAvailible ra2 = new RestaurantsAvailible(table, dateTo, restaurantsAvailible.getHourTo(), 1); restaurantDao.saveTableAvailable(ra1); restaurantDao.saveTableAvailable(ra2); } }}} Логиката е тоа што се намалува една од x слободни маси и се додаваат 2 нови една од почеток на слободната маса до почеток на резервираната и уште една од крај на резервианата до крај на слободната. {{{#!java @PostMapping(path = "/restaurant/reserve") public void reserveRestaurantTable(@RequestParam(name = "restaurantTableId") Long restaurantTableId, @RequestParam(name = "userId") Long userId, @RequestParam(name = "restaurantAvailibleId") Long restaurantAvailibleId, @RequestParam(name = "hourFrom") String hourFrom, @RequestParam(name = "hourTo") String hourTo, @RequestParam(name = "date") @DateTimeFormat(pattern = "yyyy-MM-dd") Date date) { restaurantManager.createReservation(userId, restaurantTableId, restaurantAvailibleId, hourFrom, hourTo, date); } }}} Во контролерот се примаат потребните информации за да се направи резервација и тоа од кој корисник, на која маса и на која слободна маса. === Резервира транспорт [[Image(TransportDetails.png)]] ---- [[Image(TransportDetails2.png)]] ---- [[Image(TransportReservation.png)]] {{{#!java public void createTransportReservation(TransportReservation transportReservation) { em.persist(transportReservation); } }}} Се зачувува резервацијата во база {{{#!java public void createTransportReservation(Long transportRouteId, Long userId, int noSeats) { TransportRoute transportRoute = transportDao.findTransportRouteById(transportRouteId); User u = usersManager.findUserByID(userId); TransportReservation transportReservation = new TransportReservation(transportRoute, noSeats, u); transportDao.createTransportReservation(transportReservation); } }}} Во во сервисот се зима транпосрт рутата се зима користникот и се прави резервација на транспортот {{{#!java @PostMapping(path = "/transport/reserve") public void reserveTransport(@RequestParam(name = "transportRouteId") Long transportRouteId, @RequestParam(name = "userId") Long userId, @RequestParam(name = "numSeats") int numSeats) { transportManager.createTransportReservation(transportRouteId, userId, numSeats); } }}} Се зимаат потребните информации од контролерот и се препраќа кон сервисот за понатамошна логика со резервациите. === Резервира хотел [[Image(HotelDetails1-min.png)]] ---- [[Image(HotelReservationUser-min.png)]] {{{#!java public void saveReservation(HotelRoomReservations hotelRoomReservations) { em.persist(hotelRoomReservations); } }}} Се зачувува резервацијата во база {{{#!java public void createReservation(Long userId, Long hotelRoomId, Long hotelRoomAvailableId, Date dateFrom, Date dateTo, Integer numberOfBeds) { HotelRoom room = hotelDao.findRoomById(hotelRoomId); User user = usersDao.findUserByID(userId); HotelRoomReservations r = new HotelRoomReservations(user, room, dateFrom, dateTo, numberOfBeds); editRoomAvailibleReservation(hotelRoomAvailableId, hotelRoomId, dateFrom, dateTo, numberOfBeds); hotelDao.createReservation(r); } }}} Во сервисот се зема id на корисникот, id на соба и id на слободната соба. Притоа се наоѓаат сите од нив преку соодветните сервиси и се креира нова резервација од-до одреден датум, притоа се прави логика за кои соби ќе бидат слободни по резервацијата {{{#!java public void editRoomAvailibleReservation(Long HotelRoomAvailableId, Long hotelRoomId, Date from, Date to, int numberOfBeds){ HotelRoomAvailable roomAvailable = hotelDao.findAvailibleRoomById(HotelRoomAvailableId); roomAvailable.setNumberOfBeds(roomAvailable.getNumberOfBeds()-numberOfBeds); hotelDao.createRoomAvailible(roomAvailable); HotelRoom room = hotelDao.findRoomById(hotelRoomId); HotelRoomAvailable hra = new HotelRoomAvailable(room,roomAvailable.getDateFrom(),from,1); hotelDao.createRoomAvailible(hra); HotelRoomAvailable hra1 = new HotelRoomAvailable(room,to,roomAvailable.getDateTo(),1); hotelDao.createRoomAvailible(hra1); } }}} Логиката е во тоа што од сите х соби од таков тип се менуваа во х-1 а се додаваат две нови слободни и тоа една од од почеток на слободната соба до почеток на резервираната и уште една од крај на резервиранта до крај на слободната. {{{#!java @PostMapping(path = "/hotel/reserve") public void reserveHotelRoom(@RequestParam(name = "hotelRoomId")Long hotelRoomId, @RequestParam(name = "userId") Long userId, @RequestParam(name = "hotelRoomAvailableId") Long hotelRoomAvailableId, @RequestParam(name = "numberOfBeds") Integer numberOfBeds, @RequestParam(name = "from") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date from, @RequestParam(name = "to") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date to){ hotelManager.createReservation(userId, hotelRoomId, hotelRoomAvailableId, from, to, numberOfBeds); } }}} Контролерот кој ги прима сите потребни атрибути за да може да се направи резервацијата и тоа од кој корисник за која соба и за кој период. === Откажува услуга ---- [[Image(ReservationsHotel.png)]] {{{#!java public void deleteReservation(HotelRoomReservations hr) { em.remove(hr); } }}} Се брише резервацијата од база {{{#!java @Override public void deleteReservation(long hotelRoomReservedId) { HotelRoomReservations r = findReservationById(hotelRoomReservedId); HotelRoomAvailable hra = new HotelRoomAvailable(r.getHotelRoom(), r.getDateFrom(), r.getDateTo(), r.getNumberOfBeds()); hotelDao.createRoomAvailible(hra); hotelDao.deleteReservation(r); } }}} Во сервисот се зема id на резервацијата се наоѓа и се отстранува и притоа собата што беше зафатена се ослободува {{{#!java @PostMapping("/hotel/{id}/cancel") public void cancelHotelReservation(@PathVariable Long id){ hotelManager.deleteReservation(id); } }}} Преку контролерот се зема id на резервацијата и таа се препраќа во сервисот за да ја изврши логиката. === Внесува оценка [[Image(prev_res-min.png)]] [[Image(review_entry.png)]] По испраќање на формуларот се испраќа POST барање кое се обработува од контролерот {{{#!java @PostMapping(path = "/review/add") public void add(@RequestParam(name = "title") String title, @RequestParam(name = "numStars") Integer numStars, @RequestParam(name = "description") String description, @RequestParam(name = "hotelId", required = false) Long hotelId, @RequestParam(name = "restaurantId", required = false) Long restaurantId, @RequestParam(name = "transportId", required = false) Long transportId, @RequestParam(name = "reservationId") Long reservationId) { reviewManager.createReview(title, numStars, description, hotelId, restaurantId, transportId, reservationId); } }}} По што се повикува соодветна функција од сервисот каде се формира објектот и се испаќа во Dao за да биде зачуван во базата {{{#!java @Override public void createReview(String title, int numStar, String description, Long hotel, Long restaurant, Long transport, Long reservationId) { Hotels h = null; Restaurant r = null; Transport t = null; if(hotel != null) { h = hotelManager.findHotelByID(hotel); hotelManager.setReservationReviewed(reservationId); } if(restaurant != null) { r = restaurantManager.findRestaurantByID(restaurant); restaurantManager.setReservationReviewed(reservationId); } if(transport != null) { t = transportManager.getTransportById(transport); transportManager.setReservationReviewed(reservationId); } Reviews review = new Reviews (title, numStar, description, h, r, t); reviewDao.createReview(review); } }}} // {{{#!java @Override @Transactional public void createReview(Reviews review) { em.persist(review); } }}}