Changes between Version 4 and Version 5 of ApplicationDevelopment
- Timestamp:
- 09/04/25 23:28:16 (3 days ago)
Legend:
- Unmodified
- Added
- Removed
- Modified
-
ApplicationDevelopment
v4 v5 7 7 || 4 || View and Manage Menu Categories || 8 8 || 5 || Manage Menu Products || 9 || 6 || View and Manage Restaurant Tables||9 || 6 || Employee Clocks in/out || 10 10 || 7 || Create and Manage Customer Reservations || 11 11 || 8 || Create and Manage In-House Orders (Tabs) || … … 24 24 A manager accesses the /api/assignments endpoint to schedule employees for specific shifts. The frontend displays existing assignments and a form for creating new ones. For this, the following controller is responsible: 25 25 {{{ 26 @PostMapping 27 public ResponseEntity<AssignmentDto> createAssignment(@RequestBody CreateAssignmentDto dto, Authentication authentication) { 28 try { 29 String managerEmail = authentication.getName(); 30 Assignment assignment = assignmentService.createAssignment(dto, managerEmail); 31 return ResponseEntity.status(HttpStatus.CREATED).body(AssignmentDto.fromAssignment(assignment)); 32 } catch (SecurityException e) { 33 return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); 34 } 35 } 36 }}} 37 [[Image(shifts.png)]] 26 @PostMapping 27 public ResponseEntity<AssignmentDto> createAssignment(@RequestBody CreateAssignmentDto dto, Authentication authentication) { 28 try { 29 String managerEmail = authentication.getName(); 30 Assignment assignment = assignmentService.createAssignment(dto, managerEmail); 31 return ResponseEntity.status(HttpStatus.CREATED).body(AssignmentDto.fromAssignment(assignment)); 32 } catch (SecurityException e) { 33 return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); 34 } 35 } 36 }}} 37 38 [[Image(shfits.png)]] 38 39 [[Image(assignments.png)]] 39 40 40 The controller extracts the manager's email from the security context and passes the request data to the assignmentService. To ensure data integrity, the service method is transactional.It validates that the user is a manager and that the specified employee and shift exist before creating and saving a new Assignment entity.41 The controller extracts the manager's email from the security context and passes the request data to the assignmentService. It validates that the user is a manager and that the specified employee and shift exist before creating and saving a new Assignment entity. 41 42 42 43 {{{ … … 81 82 }}} 82 83 84 == Employee shift lifecycle 85 86 Clock-In Process 87 88 When an employee begins their shift, they call the /api/assignments/{id}/clockin endpoint. The controller authenticates the user and delegates to the service layer. The system enforces that employees can only manage their own shifts and maintains the logical order of clock-in before clock-out operations. 89 90 [[Image(next-shift.png)]] 91 [[Image(clockout-shift.png)]] 92 {{{ 93 @Override 94 public Assignment clockInShift(Long assignmentId, String employeeEmail, LocalDateTime clockInTime) { 95 Assignment assignment = assignmentRepository.findById(assignmentId) 96 .orElseThrow(() -> new AssignmentNotFoundException( assignmentId)); 97 98 if (!assignment.getEmployee().getEmail().equals(employeeEmail)) { 99 throw new AccessDeniedException("You can only clock in your own shift."); 100 } 101 102 assignment.setClockInTime(clockInTime); 103 return assignmentRepository.save(assignment); 104 } 105 }}} 106 107 {{{ 108 @Override 109 public Assignment clockOutShift(Long assignmentId, String employeeEmail, LocalDateTime clockOutTime) { 110 Assignment assignment = assignmentRepository.findById(assignmentId) 111 .orElseThrow(() -> new AssignmentNotFoundException(assignmentId)); 112 113 if (!assignment.getEmployee().getEmail().equals(employeeEmail)) { 114 throw new AccessDeniedException("You can only clock out your own shift."); 115 } 116 117 if (assignment.getClockInTime() == null) { 118 throw new IllegalStateException("Cannot clock out without clocking in first."); 119 } 120 121 assignment.setClockOutTime(clockOutTime); 122 return assignmentRepository.save(assignment); 123 } 124 }}} 83 125 == Manage Menu Products 84 126 A manager can add new items to the menu via the /api/products/add endpoint. The controller takes a CreateProductDto and passes it to the service layer. 127 85 128 [[Image(product-list.png)]] 86 129 [[Image(create-product.png)]] … … 96 139 {{{ 97 140 @Override 141 @Transactional 98 142 public Product createProduct(CreateProductDto dto) { 99 143 Product productTmp=new Product(); … … 141 185 142 186 == Create and Manage In-House Orders (Tabs) 143 A server (Front Staff)creates a new order for a table by sending a POST request to /api/orders/tab. The controller authenticates the user and delegates the creation logic to the OrderService.187 A server creates a new order for a table by sending a POST request to /api/orders/tab. The controller authenticates the user and delegates the creation logic to the OrderService. 144 188 145 189 {{{ … … 224 268 == Process a Payment for an Order 225 269 When customers are ready to pay, the server initiates the payment process from the order details screen by calling the /api/payments endpoint. 270 226 271 [[Image(payment.png)]] 227 272 228 This action is primarily handled by the PaymentServiceImpl. The createPayment method is annotated with @Transactional and @CheckOnDuty. A critical design choice is the use of a database trigger (payments_mark_order_paid) to update the order's status to PAID. This offloads the responsibility from the application layer to the database, ensuring atomicity. Furthermore, after the payment is successfully saved, the service calls mvRefresher.refreshPaymentsMvAfterCommit() to schedule a refresh of the analytics materialized view, ensuring reports are kept up-to-date.273 This action is primarily handled by the PaymentServiceImpl. The createPayment method is annotated with @Transactional and @CheckOnDuty. We implemented a database trigger (payments_mark_order_paid) to update the order's status to PAID. This offloads the responsibility from the application layer to the database. Furthermore, after the payment is successfully saved, the service calls mvRefresher.refreshPaymentsMvAfterCommit() to schedule a refresh of the analytics materialized view, ensuring reports are kept up-to-date. 229 274 230 275 {{{ … … 241 286 payment.setTipAmount(dto.tipAmount()); 242 287 payment.setPaymentType(dto.paymentType()); 243 payment.setTimestamp(LocalDateTime.now()); // ensure mapped to created_at column288 payment.setTimestamp(LocalDateTime.now()); 244 289 payment.setOrder(order); 245 290 246 291 Payment saved = paymentRepository.save(payment); 247 292 248 // Schedule MV refresh after the transaction commits293 249 294 mvRefresher.refreshPaymentsMvAfterCommit(); 250 295 … … 273 318 274 319 To solve this, we implemented a Materialized View (mv_payments_daily_channel). This view pre-aggregates sales data daily, broken down by order channel (TAB vs. ONLINE). When a user requests the report, the application queries this small, fast view instead of the large transaction tables. 320 275 321 [[Image(analytics-1.png)]] 276 322 [[Image(monthly-revenue-split-orders.png)]] … … 295 341 if (to == null) to = LocalDate.now(); 296 342 297 // pass null channel to fetch ALL channels (JPQL has :channel is null guard)343 298 344 List<PaymentsDailyChannel> rows = mvRepo.findRange(from, to, null); 299 345 300 // group by channel346 301 347 Map<String, List<PaymentsDailyChannel>> byChannel = rows.stream() 302 348 .collect(Collectors.groupingBy(PaymentsDailyChannel::getChannel)); 303 349 304 // build per-channel tables350 305 351 List<ChannelTableDto> channels = byChannel.entrySet().stream() 306 352 .sorted(Map.Entry.comparingByKey()) // ONLINE, TAB, UNKNOWN (alphabetical) … … 333 379 .toList(); 334 380 335 // grand totals381 336 382 long grandOrders = rows.stream() 337 383 .map(PaymentsDailyChannel::getPaidOrdersCnt)