Changes between Version 4 and Version 5 of ApplicationDevelopment


Ignore:
Timestamp:
09/04/25 23:28:16 (3 days ago)
Author:
226030
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • ApplicationDevelopment

    v4 v5  
    77|| 4 || View and Manage Menu Categories ||
    88|| 5 || Manage Menu Products ||
    9 || 6 || View and Manage Restaurant Tables ||
     9|| 6 || Employee Clocks in/out ||
    1010|| 7 || Create and Manage Customer Reservations ||
    1111|| 8 || Create and Manage In-House Orders (Tabs) ||
     
    2424A 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:
    2525{{{
    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)]]
    3839[[Image(assignments.png)]]
    3940
    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.
     41The 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.
    4142
    4243{{{
     
    8182}}}
    8283
     84== Employee shift lifecycle
     85
     86Clock-In Process
     87
     88When 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}}}
    83125== Manage Menu Products
    84126A 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
    85128[[Image(product-list.png)]]
    86129[[Image(create-product.png)]]
     
    96139{{{
    97140    @Override
     141    @Transactional
    98142    public Product createProduct(CreateProductDto dto) {
    99143        Product productTmp=new Product();
     
    141185
    142186== 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.
     187A 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.
    144188
    145189{{{
     
    224268== Process a Payment for an Order
    225269When customers are ready to pay, the server initiates the payment process from the order details screen by calling the /api/payments endpoint.
     270
    226271[[Image(payment.png)]]
    227272
    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.
     273This 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.
    229274
    230275{{{
     
    241286        payment.setTipAmount(dto.tipAmount());
    242287        payment.setPaymentType(dto.paymentType());
    243         payment.setTimestamp(LocalDateTime.now()); // ensure mapped to created_at column
     288        payment.setTimestamp(LocalDateTime.now());
    244289        payment.setOrder(order);
    245290
    246291        Payment saved = paymentRepository.save(payment);
    247292
    248         // Schedule MV refresh after the transaction commits
     293       
    249294        mvRefresher.refreshPaymentsMvAfterCommit();
    250295
     
    273318
    274319To 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
    275321[[Image(analytics-1.png)]]
    276322[[Image(monthly-revenue-split-orders.png)]]
     
    295341        if (to == null)   to   = LocalDate.now();
    296342
    297         // pass null channel to fetch ALL channels (JPQL has :channel is null guard)
     343       
    298344        List<PaymentsDailyChannel> rows = mvRepo.findRange(from, to, null);
    299345
    300         // group by channel
     346       
    301347        Map<String, List<PaymentsDailyChannel>> byChannel = rows.stream()
    302348                .collect(Collectors.groupingBy(PaymentsDailyChannel::getChannel));
    303349
    304         // build per-channel tables
     350       
    305351        List<ChannelTableDto> channels = byChannel.entrySet().stream()
    306352                .sorted(Map.Entry.comparingByKey()) // ONLINE, TAB, UNKNOWN (alphabetical)
     
    333379                .toList();
    334380
    335         // grand totals
     381       
    336382        long grandOrders = rows.stream()
    337383                .map(PaymentsDailyChannel::getPaidOrdersCnt)