79461601

Date: 2025-02-23 17:02:57
Score: 7.5 🚩
Natty:
Report link

Thank you so much for your help. More details are given below:

  1. No JavaScript Console Errors
  1. Data Attributes Appear in the Rendered HTML
  1. base.html Layout

base.html :

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:fragment="layout(content)"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head th:fragment="title">
    <meta charset="UTF-8">
    <title>StokSense</title>

    <!-- Bootstrap CSS -->
    <link rel="stylesheet"
          th:href="@{https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css}" />
    <!-- Bootstrap Icons -->
    <link rel="stylesheet"
          href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css" />
    <!-- Custom CSS -->
    <link rel="stylesheet" th:href="@{/css/custom.css}" />

    <style>
        html, body {
            height: 100%;
            margin: 0;
            padding: 0;
        }
        body {
            display: flex;
            flex-direction: column;
        }
        main {
            flex: 1;
        }
    </style>
</head>

<body
        th:with="
    currentUser=
      ${(#authentication != null and #authentication.name != 'anonymousUser')
        ? @userService.findByEmail(#authentication.name)
        : null}
  "
        th:classappend="${currentUser != null and currentUser.theme=='dark'} ? ' dark-mode' : ''"
>

<!-- NAVBAR -->
<header th:fragment="header">
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <div class="container-fluid">
            <!-- Logo -->
            <a class="navbar-brand" th:href="@{/}">
                <i class="bi bi-box-seam me-1"></i> StokSense
            </a>
            <button class="navbar-toggler" type="button"
                    data-bs-toggle="collapse"
                    data-bs-target="#navbarNav"
                    aria-controls="navbarNav"
                    aria-expanded="false"
                    aria-label="Menu">
                <span class="navbar-toggler-icon"></span>
            </button>

            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav me-auto">
                    <!-- Ürünler -->
                    <li class="nav-item" sec:authorize="isAuthenticated()">
                        <a class="nav-link" th:text="#{menu.products}" th:href="@{/products}"></a>
                    </li>
                    <!-- Kategoriler -->
                    <li class="nav-item" sec:authorize="isAuthenticated()">
                        <a class="nav-link" th:text="#{menu.categories}" th:href="@{/categories}"></a>
                    </li>
                    <!-- Satın Alma -->
                    <li class="nav-item" sec:authorize="isAuthenticated()">
                        <a class="nav-link" th:text="#{menu.purchases}" th:href="@{/purchases}"></a>
                    </li>
                    <!-- Tedarikçiler -->
                    <li class="nav-item" sec:authorize="isAuthenticated()">
                        <a class="nav-link" th:text="#{menu.suppliers}" th:href="@{/suppliers}"></a>
                    </li>
                    <!-- Müşteriler -->
                    <li class="nav-item" sec:authorize="isAuthenticated()">
                        <a class="nav-link" th:text="#{menu.customers}" th:href="@{/customers}"></a>
                    </li>
                    <!-- Satış -->
                    <li class="nav-item" sec:authorize="isAuthenticated()">
                        <a class="nav-link" th:text="#{menu.sales}" th:href="@{/sales}"></a>
                    </li>
                    <!-- Raporlar -->
                    <li class="nav-item" sec:authorize="isAuthenticated()">
                        <a class="nav-link" th:text="#{menu.reports}" th:href="@{/reports}"></a>
                    </li>
                    <!-- Admin Panel -->
                    <li class="nav-item" sec:authorize="hasRole('ADMIN')">
                        <a class="nav-link" th:text="#{menu.adminPanel}" th:href="@{/admin/index}"></a>
                    </li>
                    <!-- Forecast -->
                    <li class="nav-item" sec:authorize="hasRole('ADMIN') or hasRole('PREMIUM')">
                        <a class="nav-link" th:text="#{menu.forecast}" th:href="@{/premium/forecast}"></a>
                    </li>
                    <!-- Kullanıcı Panelim -->
                    <li class="nav-item" sec:authorize="hasAnyRole('STANDARD','PREMIUM')">
                        <a class="nav-link" th:text="#{menu.dashboard}" th:href="@{/user/dashboard}"></a>
                    </li>
                    <!-- Plan Yükselt -->
                    <li class="nav-item" sec:authorize="hasRole('STANDARD')">
                        <a class="nav-link" th:text="#{menu.planUpgrade}" th:href="@{/user/plans}"></a>
                    </li>
                </ul>

                <!-- Sağ kısım (Giriş / Profil) -->
                <ul class="navbar-nav ms-auto">
                    <!-- Giriş -->
                    <li class="nav-item" sec:authorize="!isAuthenticated()">
                        <a class="nav-link" th:href="@{/login}" th:text="#{menu.login}"></a>
                    </li>
                    <!-- Kayıt -->
                    <li class="nav-item" sec:authorize="!isAuthenticated()">
                        <a class="nav-link" th:href="@{/register}" th:text="#{menu.register}"></a>
                    </li>

                    <!-- Dil Seçimi (icon ekledik) -->
                    <li class="nav-item dropdown">
                        <a class="nav-link dropdown-toggle"
                           href="#"
                           role="button"
                           data-bs-toggle="dropdown"
                           aria-expanded="false">
                            <i class="bi bi-translate me-1"></i>
                            Dil Seçenekleri
                        </a>
                        <ul class="dropdown-menu dropdown-menu-end">
                            <li>
                                <a class="dropdown-item" href="?lang=tr">Türkçe</a>
                            </li>
                            <li>
                                <a class="dropdown-item" href="?lang=en">English</a>
                            </li>
                        </ul>
                    </li>

                    <!-- Profil Dropdown (giriş yapmış) -->
                    <li class="nav-item dropdown" sec:authorize="isAuthenticated()">
                        <a class="nav-link dropdown-toggle" href="#"
                           role="button" data-bs-toggle="dropdown"
                           aria-expanded="false"
                           th:text="#{menu.profile}"></a>
                        <ul class="dropdown-menu dropdown-menu-end">
                            <li>
                                <a class="dropdown-item" th:href="@{/profile}">
                                    <i class="bi bi-gear-wide-connected me-1"></i>
                                    [[#{menu.profile}]]
                                </a>
                            </li>
                            <li>
                                <form th:action="@{/logout}" method="post" class="m-0">
                                    <button class="dropdown-item" type="submit">
                                        <i class="bi bi-box-arrow-right me-1"></i>
                                        [[#{menu.logout}]]
                                    </button>
                                </form>
                            </li>
                        </ul>
                    </li>
                </ul>

            </div>
        </div>
    </nav>
</header>

<main>

    <div th:replace="${content}"></div>
</main>

<!-- FOOTER -->
<footer class="bg-light text-center text-lg-start mt-5" th:fragment="footer">
    <div class="text-center p-3">
        [[#{footer.copyright}]]
    </div>
</footer>

<!-- Bootstrap JS -->
<script th:src="@{https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js}"></script>

</body>
</html>

4. Order Form Template

order-form.html :

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Ön Sipariş Oluştur - Naaaa</title>

</head>
<body th:replace="~{base :: layout(~{::content})}">


<div th:fragment="content" class="container mt-4">
    <h2>Ön Sipariş Oluştur</h2>

    <!-- Basit form (Spring MVC) -> method="post" -->
    <form th:action="@{/orders/save}"
          th:object="${order}"
          method="post"
          class="needs-validation"
          novalidate>

        <!-- Sipariş ID (hidden) -->
        <input type="hidden" th:field="*{id}" />

        <!-- Müşteri Seçimi -->
        <div class="mb-3">
            <label for="customerSelect" class="form-label">Müşteri Seçiniz</label>
            <select id="customerSelect"
                    th:field="*{customer.id}"
                    class="form-select"
                    required>
                <option value="" disabled selected>Seçiniz</option>
                <option th:each="c : ${customers}"
                        th:value="${c.id}"
                        th:text="${c.name}">
                </option>
            </select>
            <div class="invalid-feedback">Lütfen bir müşteri seçiniz.</div>
        </div>

        <!-- Ürün Seçimi -->
        <div class="mb-3">
            <label for="productSelect" class="form-label">Ürün Seçiniz</label>
            <select id="productSelect"
                    th:field="*{product.id}"
                    class="form-select"
                    required>
                <option value="" disabled selected>Seçiniz</option>
                <!--
                  data-price: Her ürünün "price" değerini HTML5 data attribute olarak tutuyoruz.
                  Seçilen üründe bu attribute'u JS ile okuyup "Net Birim Fiyat" alanına set edeceğiz.
                -->
                <option th:each="p : ${products}"
                        th:value="${p.id}"
                        th:attr="data-price=${p.price}"
                        th:text="${p.name}">
                </option>
            </select>
            <div class="invalid-feedback">Lütfen bir ürün seçiniz.</div>
        </div>

        <!-- Net Birim Fiyat -->
        <div class="mb-3">
            <label for="netUnitPrice" class="form-label">Net Birim Fiyat</label>
            <input type="number"
                   step="0.01"
                   th:field="*{netUnitPrice}"
                   class="form-control"
                   id="netUnitPrice"
                   placeholder="The price of the selected product will appear automatically"
                   required />
            <div class="invalid-feedback">Net birim fiyat alanı zorunlu.</div>
        </div>


        <div class="mb-3">
            <label for="taxRate" class="form-label">Vergi Oranı</label>
            <input type="number"
                   step="0.01"
                   th:field="*{taxRate}"
                   class="form-control"
                   id="taxRate"
                   placeholder="0.18 => %18"
                   required />
            <div class="invalid-feedback">Lütfen geçerli bir vergi oranı giriniz.</div>
        </div>

        <!-- Miktar -->
        <div class="mb-3">
            <label for="quantity" class="form-label">Miktar (Adet)</label>
            <input type="number"
                   th:field="*{quantity}"
                   class="form-control"
                   id="quantity"
                   value="1"
                   min="1"
                   required />
            <div class="invalid-feedback">En az 1 adet olmalıdır.</div>
        </div>

      
        <button type="submit" class="btn btn-primary">Kaydet</button>
        <a th:href="@{/orders/my}" class="btn btn-secondary">İptal</a>
    </form>
</div> 

<!-- JS (Bootstrap ve script) -->
<script th:src="@{https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js}"></script>
<script>
    
    (function () {
        'use strict';
        const forms = document.querySelectorAll('.needs-validation');
        Array.from(forms).forEach(form => {
            form.addEventListener('submit', function (event) {
                if (!form.checkValidity()) {
                    event.preventDefault();
                    event.stopPropagation();
                }
                form.classList.add('was-validated');
            }, false);
        });
    })();

    
    document.addEventListener('DOMContentLoaded', function() {
        const productSelect = document.getElementById('productSelect');
        const netPriceInput = document.getElementById('netUnitPrice');

        productSelect.addEventListener('change', function() {
           
            const selectedOption = productSelect.options[productSelect.selectedIndex];
           
            const price = selectedOption.getAttribute('data-price');
            
            if (price) {
                netPriceInput.value = price;
            }
        });
    });
</script>
</body>
</html>

5. Behavior Observed

6. Browser / Tools

Maybe you want to see it;

OrderController.java :

package com.stoksense.controller;

import com.stoksense.model.*;
import com.stoksense.service.*;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.List;


@Controller
@RequestMapping("/orders")
public class OrderController {

    private final OrderService orderService;
    private final ProductService productService;
    private final CustomerService customerService;
    private final UserService userService;
    private final SaleService saleService;

    public OrderController(OrderService orderService,
                           ProductService productService,
                           CustomerService customerService,
                           UserService userService,
                           SaleService saleService) {
        this.orderService = orderService;
        this.productService = productService;
        this.customerService = customerService;
        this.userService = userService;
        this.saleService = saleService;
    }


    @GetMapping("/my")
    public String myOrders(Authentication auth, Model model) {
        User user = userService.findByEmail(auth.getName());
        if (user == null) {
            return "redirect:/login?notAllowed";
        }
        // Customer
        Customer cust = customerService.findByEmail(user.getEmail());
        if (cust == null) {
            model.addAttribute("orders", List.of());
            return "orders/my-orders";
        }
        // siparişleri al
        List<Order> orders = orderService.getOrdersByCustomer(cust);
        model.addAttribute("orders", orders);
        return "orders/my-orders";
    }


    @GetMapping("/add")
    public String showAddOrderForm(Model model, Authentication auth) {
        Order order = new Order();
        order.setOrderDate(LocalDateTime.now());
        order.setStatus(OrderStatus.PENDING);

        // Giriş yapan user => Customer
        User user = userService.findByEmail(auth.getName());
        if (user != null) {
            Customer c = customerService.findByEmail(user.getEmail());
            if (c != null) {
                order.setCustomer(c);
            }
          
            List<Product> userProducts = productService.getAllProductsByEmail(user.getEmail());
            model.addAttribute("products", userProducts);
        } else {
            model.addAttribute("products", List.of());
        }

        
        model.addAttribute("customers", customerService.getAllCustomers());
        model.addAttribute("order", order);

        return "orders/order-form"; // templates/orders/order-form.html
    }


    @PostMapping("/save")
    public String saveOrder(@ModelAttribute("order") Order order,
                            Authentication auth) {

        User user = userService.findByEmail(auth.getName());
        if (user == null) {
            return "redirect:/login?notAllowed";
        }

        // Müşteri atanmamışsa
        if (order.getCustomer() == null || order.getCustomer().getId() == null) {
            Customer c = customerService.findByEmail(user.getEmail());
            if (c == null) {
                return "redirect:/orders/my?noCustomer";
            }
            order.setCustomer(c);
        }

        // Ürün stok kontrol & stok düş
        if (order.getProduct() != null && order.getProduct().getId() != null) {
            Product p = productService.getProductById(order.getProduct().getId()).orElse(null);
            if (p == null) {
                return "redirect:/orders/my?productNotFound";
            }
            if (p.getStockQuantity() < order.getQuantity()) {
                return "redirect:/orders/my?insufficientStock";
            }
            p.setStockQuantity(p.getStockQuantity() - order.getQuantity());
            productService.saveProduct(p);

            // netUnitPrice boşsa => product.price
            if (order.getNetUnitPrice() == null || order.getNetUnitPrice() == 0) {
                order.setNetUnitPrice(p.getPrice());
            }
        }

        // totalAmount
        double net = (order.getNetUnitPrice() != null) ? order.getNetUnitPrice() : 0;
        double tax = (order.getTaxRate() != null) ? order.getTaxRate() : 0;
        int qty = (order.getQuantity() != null) ? order.getQuantity() : 1;
        double total = (net * qty) * (1 + tax);
        order.setTotalAmount(total);

        // Durum => PENDING
        if (order.getStatus() == null) {
            order.setStatus(OrderStatus.PENDING);
        }

        orderService.save(order);
        return "redirect:/orders/my?created";
    }


    @GetMapping("/complete/{id}")
    public String completeOrder(@PathVariable("id") Long id, Authentication auth) {
        Order order = orderService.getOrderById(id);
        if (order == null) {
            return "redirect:/orders/my?notfound";
        }
        User user = userService.findByEmail(auth.getName());
        if (user == null) {
            return "redirect:/login?error";
        }
        Customer cust = customerService.findByEmail(user.getEmail());
        if (cust == null || !order.getCustomer().getId().equals(cust.getId())) {
            return "redirect:/orders/my?unauthorized";
        }

        if (order.getStatus() != OrderStatus.CONFIRMED) {
            order.setStatus(OrderStatus.CONFIRMED);
            orderService.save(order);

            // Satışa dönüştür
            saleService.createSaleFromOrder(order);
        }

        return "redirect:/orders/my?completed";
    }

}

ProductController.java :

package com.stoksense.controller;

import com.stoksense.model.Product;
import com.stoksense.service.CategoryService;
import com.stoksense.service.ProductService;
import com.stoksense.service.UserService;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.List;


@Controller
@RequestMapping("/products")
public class ProductController {

    private final ProductService productService;
    private final UserService userService;
    private final CategoryService categoryService;

    public ProductController(ProductService productService,
                             UserService userService,
                             CategoryService categoryService) {
        this.productService = productService;
        this.userService = userService;
        this.categoryService = categoryService;
    }

    @GetMapping
    public String listProducts(@RequestParam(value = "search", required = false) String search,
                               Model model,
                               Authentication authentication) {
        // Eskisi gibi ürün liste mantığı
        if (authentication == null) {
            model.addAttribute("products", List.of());
            return "product-list";
        }
        String email = authentication.getName();
        var products = productService.getAllProductsByEmail(email);

        if (search != null && !search.trim().isEmpty()) {
            String lower = search.trim().toLowerCase();
            products = products.stream()
                    .filter(p -> p.getName() != null &&
                            p.getName().toLowerCase().contains(lower))
                    .toList();
            model.addAttribute("search", search);
        }

        long totalUserProducts = productService.countAllProductsByEmail(email);
        long totalStock = products.stream().mapToLong(Product::getStockQuantity).sum();
        double averagePrice = 0.0;
        if (!products.isEmpty()) {
            double sumPrices = products.stream()
                    .mapToDouble(p -> (p.getPrice() != null ? p.getPrice() : 0.0))
                    .sum();
            averagePrice = sumPrices / products.size();
        }

        model.addAttribute("products", products);
        model.addAttribute("totalUserProducts", totalUserProducts);
        model.addAttribute("totalStock", totalStock);
        model.addAttribute("averagePrice", averagePrice);

        return "product-list";
    }

    /**
     * GET /products/add
     * Yeni ürün ekleme formu (Kategorileri de modele ekleyelim).
     */
    @GetMapping("/add")
    public String showAddProductForm(Model model, Authentication authentication) {
        model.addAttribute("product", new Product());

        // Kullanıcının kategorilerini bul
        if (authentication != null) {
            var user = userService.findByEmail(authentication.getName());
            if (user != null) {
                // Tüm kategoriler
                var categories = categoryService.getAllCategoriesByEmail(user.getEmail());
                model.addAttribute("categories", categories);
            } else {
                model.addAttribute("categories", List.of());
            }
        } else {
            model.addAttribute("categories", List.of());
        }

        return "product-form";
    }

    @PostMapping("/save")
    public String saveProduct(@ModelAttribute("product") Product product,
                              Authentication authentication) {

        if (authentication != null) {
            var user = userService.findByEmail(authentication.getName());
            product.setOwner(user);
        }

        productService.saveProduct(product);
        return "redirect:/products?success";
    }

    /**
     * GET /products/edit/{id}
     * Düzenleme formu (Kategori listesi de ekleyelim).
     */
    @GetMapping("/edit/{id}")
    public String showEditProductForm(@PathVariable("id") Long id,
                                      Model model,
                                      Authentication authentication) {
        var opt = productService.getProductById(id);
        if (opt.isEmpty()) {
            return "redirect:/products?notfound";
        }
        Product product = opt.get();
        model.addAttribute("product", product);

        // Kullanıcının kategorilerini bul
        if (authentication != null) {
            var user = userService.findByEmail(authentication.getName());
            if (user != null) {
                var categories = categoryService.getAllCategoriesByEmail(user.getEmail());
                model.addAttribute("categories", categories);
            } else {
                model.addAttribute("categories", List.of());
            }
        } else {
            model.addAttribute("categories", List.of());
        }

        return "product-form";
    }

    @GetMapping("/delete/{id}")
    public String deleteProduct(@PathVariable("id") Long id) {
        productService.deleteProduct(id);
        return "redirect:/products?deleted";
    }

    @GetMapping("/bulk-update")
    public String showBulkUpdateForm(Model model, Authentication authentication) {
        if (authentication == null) {
            model.addAttribute("products", List.of());
            return "bulk-update";
        }
        var user = userService.findByEmail(authentication.getName());
        var products = productService.getAllProductsByEmail(user.getEmail());
        model.addAttribute("products", products);
        return "bulk-update";
    }

    @PostMapping("/bulk-update")
    public String processBulkUpdate(@RequestParam("productIds") List<Long> productIds,
                                    @RequestParam("newStock") List<Integer> newStockValues) {
        for (int i = 0; i < productIds.size(); i++) {
            productService.updateStockQuantity(productIds.get(i), newStockValues.get(i));
        }
        return "redirect:/products?bulkUpdated";
    }
}

Thank you very much in advance for your help, if there is anything else you would like to see, please let me know.

Reasons:
  • Blacklisted phrase (0.5): Thank you
  • Blacklisted phrase (1): no luck
  • RegEx Blacklisted phrase (2.5): please let me know
  • RegEx Blacklisted phrase (3): Thank you very much in advance
  • Long answer (-1):
  • Has code block (-0.5):
  • Contains question mark (0.5):
  • Self-answer (0.5):
  • Low reputation (1):
Posted by: Rosicky