diff --git a/.all-contributorsrc b/.all-contributorsrc index 93c3ea7..5878996 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -108,6 +108,33 @@ "bug", "review" ] + }, + { + "login": "SaibalCts23", + "name": "SaibalCts23", + "avatar_url": "https://avatars.githubusercontent.com/u/153187590?v=4", + "profile": "https://github.com/SaibalCts23", + "contributions": [ + "code" + ] + }, + { + "login": "AdrishOfHogwarts", + "name": "Adrish Bose", + "avatar_url": "https://avatars.githubusercontent.com/u/152976845?v=4", + "profile": "https://github.com/AdrishOfHogwarts", + "contributions": [ + "code" + ] + }, + { + "login": "AnkitaGhosh2000", + "name": "AnkitaGhosh2000", + "avatar_url": "https://avatars.githubusercontent.com/u/152983487?v=4", + "profile": "https://github.com/AnkitaGhosh2000", + "contributions": [ + "code" + ] } ], "commitType": "docs", diff --git a/.gitignore b/.gitignore index eeee1b0..5dc8b2c 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,9 @@ target/ # NetBeans nbproject/ nb-configuration.xml -server/*.war \ No newline at end of file +server/*.war + +## Eclipse +.settings/ +.classpath +.project \ No newline at end of file diff --git a/README.md b/README.md index c8e74c2..9c1541b 100644 --- a/README.md +++ b/README.md @@ -79,17 +79,13 @@ docker-compose up -d
All Contributors

Imgbot

Automated code reviews
+
AnkitaGhosh2000
+ + +
SaibalCts23
+
Adrish Bose
- - - - - Add your contributions - - - - diff --git a/database/setup.sql b/database/setup.sql index d891489..972c405 100644 --- a/database/setup.sql +++ b/database/setup.sql @@ -32,7 +32,7 @@ USE `saledock`; -- CREATE TABLE `category` ( - `id` bigint(20) NOT NULL, + `id` int(20) NOT NULL, `created_at` datetime(6) DEFAULT NULL, `name` varchar(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; @@ -53,7 +53,7 @@ INSERT INTO `category` (`id`, `created_at`, `name`) VALUES -- CREATE TABLE `customer` ( - `id` bigint(20) NOT NULL, + `id` int(20) NOT NULL, `address` varchar(255) NOT NULL, `code` varchar(255) NOT NULL, `created_at` datetime(6) DEFAULT NULL, @@ -78,7 +78,7 @@ INSERT INTO `customer` (`id`, `address`, `code`, `created_at`, `email`, `name`, -- CREATE TABLE `employee` ( - `id` bigint(20) NOT NULL, + `id` int(20) NOT NULL, `code` varchar(255) NOT NULL, `created_at` datetime(6) DEFAULT NULL, `email` varchar(255) NOT NULL, @@ -106,15 +106,15 @@ INSERT INTO `employee` (`id`, `code`, `created_at`, `email`, `name`, `password`, -- CREATE TABLE `order` ( - `id` bigint(20) NOT NULL, + `id` int(20) NOT NULL, `code` varchar(255) NOT NULL, `created_at` datetime(6) DEFAULT NULL, `note` longtext DEFAULT NULL, `status` enum('PENDING','SHIPPING','DONE','CANCELLED') NOT NULL, `total` double NOT NULL, `updated_at` datetime(6) DEFAULT NULL, - `customer_id` bigint(20) NOT NULL, - `employee_id` bigint(20) NOT NULL + `customer_id` int(20) NOT NULL, + `employee_id` int(20) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- @@ -133,8 +133,8 @@ INSERT INTO `order` (`id`, `code`, `created_at`, `note`, `status`, `total`, `upd CREATE TABLE `order_detail` ( `quantity` int(11) NOT NULL, - `order_id` bigint(20) NOT NULL, - `product_id` bigint(20) NOT NULL + `order_id` int(20) NOT NULL, + `product_id` int(20) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- @@ -155,7 +155,7 @@ INSERT INTO `order_detail` (`quantity`, `order_id`, `product_id`) VALUES -- CREATE TABLE `product` ( - `id` bigint(20) NOT NULL, + `id` int(20) NOT NULL, `code` varchar(255) NOT NULL, `created_at` datetime(6) DEFAULT NULL, `description` longtext DEFAULT NULL, @@ -163,7 +163,7 @@ CREATE TABLE `product` ( `name` varchar(255) NOT NULL, `price` double NOT NULL, `updated_at` datetime(6) DEFAULT NULL, - `category_id` bigint(20) NOT NULL + `category_id` int(20) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -- @@ -238,31 +238,31 @@ ALTER TABLE `product` -- AUTO_INCREMENT for table `category` -- ALTER TABLE `category` - MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4; + MODIFY `id` int(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4; -- -- AUTO_INCREMENT for table `customer` -- ALTER TABLE `customer` - MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4; + MODIFY `id` int(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4; -- -- AUTO_INCREMENT for table `employee` -- ALTER TABLE `employee` - MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7; + MODIFY `id` int(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7; -- -- AUTO_INCREMENT for table `order` -- ALTER TABLE `order` - MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3; + MODIFY `id` int(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3; -- -- AUTO_INCREMENT for table `product` -- ALTER TABLE `product` - MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=10; + MODIFY `id` int(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=10; -- -- Constraints for dumped tables diff --git a/get_version.sh b/get_version.sh index 493eb99..e4e9e95 100644 --- a/get_version.sh +++ b/get_version.sh @@ -1,7 +1,7 @@ #!/bin/bash major_version=0 -minor_version=4 +minor_version=5 path_version=0 echo "$major_version.$minor_version.$path_version" diff --git a/pom.xml b/pom.xml index 147e1a2..7e88071 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ io.hardingadonis saledock - 0.4.0 + 0.5.0 war Sale Dock - v${project.version} @@ -75,7 +75,11 @@ json-simple 1.1.1 - + + com.sun.mail + jakarta.mail + 2.0.1 + diff --git a/src/main/java/io/hardingadonis/saledock/controller/management/customer/CustomerDetailServlet.java b/src/main/java/io/hardingadonis/saledock/controller/management/customer/CustomerDetailServlet.java index c4cb198..91fc2a2 100644 --- a/src/main/java/io/hardingadonis/saledock/controller/management/customer/CustomerDetailServlet.java +++ b/src/main/java/io/hardingadonis/saledock/controller/management/customer/CustomerDetailServlet.java @@ -21,10 +21,10 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) String id = request.getParameter("id"); if (id == null) { - response.sendError(404); + response.sendError(404); return; } - + Integer id_customer = Integer.valueOf(id); Optional customer = Singleton.customerDAO.getByID(id_customer); diff --git a/src/main/java/io/hardingadonis/saledock/controller/management/customer/CustomerServlet.java b/src/main/java/io/hardingadonis/saledock/controller/management/customer/CustomerServlet.java index 29eb948..993a339 100644 --- a/src/main/java/io/hardingadonis/saledock/controller/management/customer/CustomerServlet.java +++ b/src/main/java/io/hardingadonis/saledock/controller/management/customer/CustomerServlet.java @@ -18,6 +18,8 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) response.setContentType("text/html; charset=UTF-8"); List customers = Singleton.customerDAO.getAll(); + Integer customerCount = Singleton.customerDAO.count(); + request.setAttribute("customerCount", customerCount); request.setAttribute("customers", customers); request.setAttribute("page", "customer"); diff --git a/src/main/java/io/hardingadonis/saledock/controller/management/order/AddOrderServlet.java b/src/main/java/io/hardingadonis/saledock/controller/management/order/AddOrderServlet.java index 814fe02..0c4e463 100644 --- a/src/main/java/io/hardingadonis/saledock/controller/management/order/AddOrderServlet.java +++ b/src/main/java/io/hardingadonis/saledock/controller/management/order/AddOrderServlet.java @@ -1,9 +1,11 @@ package io.hardingadonis.saledock.controller.management.order; -import java.io.*; +import io.hardingadonis.saledock.model.*; +import io.hardingadonis.saledock.utils.*; import jakarta.servlet.*; import jakarta.servlet.annotation.*; import jakarta.servlet.http.*; +import java.io.*; @WebServlet(name = "AddOrderServlet", urlPatterns = {"/add-order"}) public class AddOrderServlet extends HttpServlet { @@ -11,10 +13,53 @@ public class AddOrderServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.setCharacterEncoding("UTF-8"); + response.setContentType("text/html; charset=UTF-8"); + + request.setAttribute("page", "order"); + + RequestDispatcher requestDispatcher = request.getRequestDispatcher("/view/jsp/management/order/add-order.jsp"); + requestDispatcher.forward(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String customerID = request.getParameter("customerID"); + String productID = request.getParameter("productID"); + String quantity = request.getParameter("quantity"); + + if (customerID == null || productID == null || quantity == null) { + response.sendError(404, "Please provide Customer ID, Product ID and Quantity"); + return; + } + + Integer id_customer = Integer.valueOf(customerID); + Integer id_product = Integer.valueOf(productID); + + Customer customer = Singleton.customerDAO.getByID(id_customer).orElse(null); + Product product = Singleton.productDAO.getByID(id_product).orElse(null); + HttpSession session = request.getSession(); + Employee employee = (Employee) session.getAttribute("employee"); + + // Check if an employee is logged in + if (employee == null) { + response.sendRedirect(request.getContextPath() + "/login?message=notLoggedIn"); + return; + } + + if (customer == null || product == null) { + response.sendError(404, "Please provide correct Customer ID and Product ID"); + return; + } + + Order order = new Order(); + order.setCustomer(customer); + order.setEmployee(employee); + + order.addProduct(product, Integer.valueOf(quantity)); + + Singleton.orderDAO.save(order); + response.sendRedirect(request.getContextPath() + "/order"); } } diff --git a/src/main/java/io/hardingadonis/saledock/controller/management/order/DetailOrderServlet.java b/src/main/java/io/hardingadonis/saledock/controller/management/order/DetailOrderServlet.java deleted file mode 100644 index ff8a5db..0000000 --- a/src/main/java/io/hardingadonis/saledock/controller/management/order/DetailOrderServlet.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.hardingadonis.saledock.controller.management.order; - -import java.io.*; -import jakarta.servlet.*; -import jakarta.servlet.annotation.*; -import jakarta.servlet.http.*; - -@WebServlet(name = "DetailOrderServlet", urlPatterns = {"/order-detail"}) -public class DetailOrderServlet extends HttpServlet { - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - } - - @Override - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - } -} diff --git a/src/main/java/io/hardingadonis/saledock/controller/management/order/OrderDetailServlet.java b/src/main/java/io/hardingadonis/saledock/controller/management/order/OrderDetailServlet.java new file mode 100644 index 0000000..591cebd --- /dev/null +++ b/src/main/java/io/hardingadonis/saledock/controller/management/order/OrderDetailServlet.java @@ -0,0 +1,46 @@ +package io.hardingadonis.saledock.controller.management.order; + +import io.hardingadonis.saledock.model.*; +import io.hardingadonis.saledock.utils.*; +import java.io.*; +import jakarta.servlet.*; +import jakarta.servlet.annotation.*; +import jakarta.servlet.http.*; +import java.util.Optional; + +@WebServlet(name = "OrderDetailServlet", urlPatterns = {"/order-detail"}) +public class OrderDetailServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + request.setCharacterEncoding("UTF-8"); + response.setContentType("text/html; charset=UTF-8"); + + request.setAttribute("page", "order"); + + String id = request.getParameter("id"); + if (id == null) { + response.sendError(404); + return; + } + + Integer id_order = Integer.parseInt(id); + Optional order = Singleton.orderDAO.getByID(id_order); + + if (order.isPresent()) { + var ord = order.get(); + request.setAttribute("ord", ord); + + request.getRequestDispatcher("/view/jsp/management/order/order-detail.jsp").forward(request, response); + } else { + response.sendRedirect(request.getContextPath() + "/order"); + } + + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + } +} diff --git a/src/main/java/io/hardingadonis/saledock/controller/management/order/OrderServlet.java b/src/main/java/io/hardingadonis/saledock/controller/management/order/OrderServlet.java index 7931080..5ed284f 100644 --- a/src/main/java/io/hardingadonis/saledock/controller/management/order/OrderServlet.java +++ b/src/main/java/io/hardingadonis/saledock/controller/management/order/OrderServlet.java @@ -1,9 +1,12 @@ package io.hardingadonis.saledock.controller.management.order; -import java.io.*; +import io.hardingadonis.saledock.model.*; +import io.hardingadonis.saledock.utils.*; import jakarta.servlet.*; import jakarta.servlet.annotation.*; import jakarta.servlet.http.*; +import java.io.*; +import java.util.*; @WebServlet(name = "OrderServlet", urlPatterns = {"/order"}) public class OrderServlet extends HttpServlet { @@ -11,6 +14,14 @@ public class OrderServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.setCharacterEncoding("UTF-8"); + response.setContentType("text/html; charset=UTF-8"); + + List orders = Singleton.orderDAO.getAll(); + request.setAttribute("orders", orders); + request.setAttribute("page", "order"); + + request.getRequestDispatcher("/view/jsp/management/order/order.jsp").forward(request, response); } @Override diff --git a/src/main/java/io/hardingadonis/saledock/controller/management/product/AddProductServlet.java b/src/main/java/io/hardingadonis/saledock/controller/management/product/AddProductServlet.java index b1f4317..57860a3 100644 --- a/src/main/java/io/hardingadonis/saledock/controller/management/product/AddProductServlet.java +++ b/src/main/java/io/hardingadonis/saledock/controller/management/product/AddProductServlet.java @@ -1,9 +1,22 @@ package io.hardingadonis.saledock.controller.management.product; +import io.hardingadonis.saledock.model.*; +import io.hardingadonis.saledock.utils.*; import java.io.*; import jakarta.servlet.*; import jakarta.servlet.annotation.*; import jakarta.servlet.http.*; +import java.util.*; + +import io.hardingadonis.saledock.model.Category; +import io.hardingadonis.saledock.model.Product; +import io.hardingadonis.saledock.utils.Singleton; +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; @WebServlet(name = "AddProductServlet", urlPatterns = {"/add-product"}) public class AddProductServlet extends HttpServlet { @@ -11,10 +24,57 @@ public class AddProductServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.setCharacterEncoding("UTF-8"); + response.setContentType("text/html; charset=UTF-8"); + + request.setAttribute("page", "product"); + + List cat = Singleton.categoryDAO.getAll(); + + request.setAttribute("categories", cat); + System.out.println(cat); + + RequestDispatcher requestDispatcher = request.getRequestDispatcher("/view/jsp/management/product/add-product.jsp"); + requestDispatcher.forward(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String codePro = request.getParameter("codeP"); + String namePro = request.getParameter("nameP"); + + int catPro = Integer.parseInt(request.getParameter("categoryP")); + Optional cat = Singleton.categoryDAO.getByID(catPro); + + double pricePro = Double.parseDouble(request.getParameter("priceP")); + String desPro = request.getParameter("descriptionP"); + +// try { +// List items = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(request); +// for (FileItem item : items) { +// if (!item.isFormField() && item.getFieldName().equals("imageUpload")) { +// // item is the file (and not a field) +// InputStream fileContent = item.getInputStream(); +// // assuming you have a connection (conn) to your database +// PreparedStatement stmt = conn.prepareStatement("INSERT INTO images (content) VALUES (?)"); +// stmt.setBlob(1, fileContent); +// stmt.executeUpdate(); +// break; // we only process one image, so break the loop after the first file +// } +// } +// } catch (Exception e) { +// throw new ServletException("Cannot parse multipart request.", e); +// } + Product p = new Product(); + p.setCode(codePro.toUpperCase()); + p.setName(namePro); + p.setCategory(cat.get()); + p.setPrice(pricePro); + p.setDescription(desPro); + Singleton.productDAO.save(p); + + response.sendRedirect("product"); } + } diff --git a/src/main/java/io/hardingadonis/saledock/controller/management/product/ProductDetailServlet.java b/src/main/java/io/hardingadonis/saledock/controller/management/product/ProductDetailServlet.java index b488d58..3fc85e0 100644 --- a/src/main/java/io/hardingadonis/saledock/controller/management/product/ProductDetailServlet.java +++ b/src/main/java/io/hardingadonis/saledock/controller/management/product/ProductDetailServlet.java @@ -24,6 +24,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) return; } + Integer idP = Integer.valueOf(pId); Optional product = Singleton.productDAO.getByID(idP); diff --git a/src/main/java/io/hardingadonis/saledock/controller/management/product/ProductServlet.java b/src/main/java/io/hardingadonis/saledock/controller/management/product/ProductServlet.java index 9be5c97..3cfff48 100644 --- a/src/main/java/io/hardingadonis/saledock/controller/management/product/ProductServlet.java +++ b/src/main/java/io/hardingadonis/saledock/controller/management/product/ProductServlet.java @@ -1,9 +1,12 @@ package io.hardingadonis.saledock.controller.management.product; -import java.io.*; +import io.hardingadonis.saledock.model.*; +import io.hardingadonis.saledock.utils.*; import jakarta.servlet.*; import jakarta.servlet.annotation.*; import jakarta.servlet.http.*; +import java.io.*; +import java.util.*; @WebServlet(name = "ProductServlet", urlPatterns = {"/product"}) public class ProductServlet extends HttpServlet { @@ -11,10 +14,21 @@ public class ProductServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.setCharacterEncoding("UTF-8"); + response.setContentType("text/html; charset=UTF-8"); + + List products = Singleton.productDAO.getAll(); + Integer productCount = Singleton.productDAO.count(); + request.setAttribute("productCount", productCount); + request.setAttribute("products", products); + request.setAttribute("page", "product"); + + request.getRequestDispatcher("/view/jsp/management/product/product.jsp").forward(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + } } diff --git a/src/main/java/io/hardingadonis/saledock/controller/others/login/ForgotPasswordServlet.java b/src/main/java/io/hardingadonis/saledock/controller/others/login/ForgotPasswordServlet.java index 1c21f66..ac4e4a3 100644 --- a/src/main/java/io/hardingadonis/saledock/controller/others/login/ForgotPasswordServlet.java +++ b/src/main/java/io/hardingadonis/saledock/controller/others/login/ForgotPasswordServlet.java @@ -1,20 +1,97 @@ package io.hardingadonis.saledock.controller.others.login; -import java.io.*; +import io.hardingadonis.saledock.model.*; +import io.hardingadonis.saledock.utils.*; import jakarta.servlet.*; import jakarta.servlet.annotation.*; import jakarta.servlet.http.*; +import java.io.*; +import java.util.*; -@WebServlet(name = "ForgotPasswordServlet", urlPatterns = {"/forgot-password"}) +@WebServlet(name = "ForgotPasswordServlet", urlPatterns = { "/forgot-password" }) public class ForgotPasswordServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + response.setContentType("text/html;charset=UTF-8"); + request.getRequestDispatcher("/view/jsp/others/login/forgot-password.jsp").forward(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String action = request.getParameter("action"); + + switch (action) { + case "submitEmail" -> checkEmail(request, response); + case "submitOtp" -> checkOtp(request, response); + case "newPassword" -> newPassword(request, response); + default -> response.sendRedirect("./login"); + } + } + + private void checkEmail(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String email = request.getParameter("forgot-email").trim(); + + if (email == null || email.isEmpty()) { + request.setAttribute("message", "fail"); + request.getRequestDispatcher("/view/jsp/others/login/forgot-password.jsp").forward(request, response); + } else { + Optional employee = Singleton.employeeDAO.getByEmail(email); + + if (employee.isPresent()) { + request.setAttribute("message", "success"); + String newOtp = OtpUltil.generateRandomOTP(); + SendEmailUtil.sendGetOTPMessage(email, "New OTP", newOtp); + SessionUtil.getInstance().putValue(request, "otp", newOtp); + SessionUtil.getInstance().putValue(request, "email", email); + request.getRequestDispatcher("/view/jsp/others/login/otp.jsp").forward(request, response); + } else { + request.setAttribute("message", "emailNotExist"); + this.doGet(request, response); + } + } + } + + private void checkOtp(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String otpInput = request.getParameter("otp-input").trim(); + String otpStored = (String) SessionUtil.getInstance().getValue(request, "otp"); + + if (otpInput != null && otpStored != null && otpInput.equals(otpStored)) { + SessionUtil.getInstance().removeValue(request, "otp"); + request.getRequestDispatcher("/view/jsp/others/login/new-password.jsp").forward(request, response); + } else { + request.setAttribute("message", "fail"); + request.getRequestDispatcher("/view/jsp/others/login/otp.jsp").forward(request, response); + } + } + + private void newPassword(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String email = (String) SessionUtil.getInstance().getValue(request, "email"); + + String newPassword = request.getParameter("new-password"); + String confirmPassword = request.getParameter("confirm-password"); + + if (newPassword == null || confirmPassword == null) { + request.setAttribute("message", "fail"); + } + + if (newPassword != null && confirmPassword != null) { + if (newPassword.equals(confirmPassword)) { + Employee employee = Singleton.employeeDAO.getByEmail(email).get(); + employee.setHashedPassword(Hash.MD5(newPassword)); + Singleton.employeeDAO.save(employee); + SessionUtil.getInstance().removeValue(request, "email"); + response.sendRedirect("./login?message=success"); + } else { + request.setAttribute("message", "notCorrect"); + request.getRequestDispatcher("/view/jsp/others/login/newPassword.jsp").forward(request, response); + } + } else { + request.setAttribute("message", "fail"); + request.getRequestDispatcher("/view/jsp/others/login/newPassword.jsp").forward(request, response); + } } } diff --git a/src/main/java/io/hardingadonis/saledock/dao/ICustomerDAO.java b/src/main/java/io/hardingadonis/saledock/dao/ICustomerDAO.java index 9656bb8..0f82e9c 100644 --- a/src/main/java/io/hardingadonis/saledock/dao/ICustomerDAO.java +++ b/src/main/java/io/hardingadonis/saledock/dao/ICustomerDAO.java @@ -2,7 +2,7 @@ import io.hardingadonis.saledock.model.*; -public interface ICustomerDAO extends IDAO { - +public interface ICustomerDAO extends IDAO, IPagination { + public String getTop10(Integer duration); } diff --git a/src/main/java/io/hardingadonis/saledock/dao/IDAO.java b/src/main/java/io/hardingadonis/saledock/dao/IDAO.java index 79f8783..fb37990 100644 --- a/src/main/java/io/hardingadonis/saledock/dao/IDAO.java +++ b/src/main/java/io/hardingadonis/saledock/dao/IDAO.java @@ -10,7 +10,7 @@ public interface IDAO { public List getAll(); - default Long count() { - return 0L; + default Integer count() { + return 0; } } diff --git a/src/main/java/io/hardingadonis/saledock/dao/IEmployeeDAO.java b/src/main/java/io/hardingadonis/saledock/dao/IEmployeeDAO.java index d9713f4..1691623 100644 --- a/src/main/java/io/hardingadonis/saledock/dao/IEmployeeDAO.java +++ b/src/main/java/io/hardingadonis/saledock/dao/IEmployeeDAO.java @@ -6,4 +6,6 @@ public interface IEmployeeDAO extends IDAO { public Optional getByCode(String code); + + public Optional getByEmail(String email); } diff --git a/src/main/java/io/hardingadonis/saledock/dao/IOrderDAO.java b/src/main/java/io/hardingadonis/saledock/dao/IOrderDAO.java index 7c8258e..31e2270 100644 --- a/src/main/java/io/hardingadonis/saledock/dao/IOrderDAO.java +++ b/src/main/java/io/hardingadonis/saledock/dao/IOrderDAO.java @@ -2,13 +2,13 @@ import io.hardingadonis.saledock.model.*; -public interface IOrderDAO extends IDAO { +public interface IOrderDAO extends IDAO, IPagination { - default Long countOrderInProcess() { - return 0L; + default Integer countOrderInProcess() { + return 0; } public String statisticBySold(Integer duration); - + public String statisticByStatus(Integer duration); } diff --git a/src/main/java/io/hardingadonis/saledock/dao/IPagination.java b/src/main/java/io/hardingadonis/saledock/dao/IPagination.java new file mode 100644 index 0000000..00cb4d8 --- /dev/null +++ b/src/main/java/io/hardingadonis/saledock/dao/IPagination.java @@ -0,0 +1,10 @@ +package io.hardingadonis.saledock.dao; + +import java.util.*; + +public interface IPagination { + + public List pagination(Integer offset, Integer limit); + + public Integer totalPages(Integer limit); +} diff --git a/src/main/java/io/hardingadonis/saledock/dao/IProductDAO.java b/src/main/java/io/hardingadonis/saledock/dao/IProductDAO.java index cb52d12..a522385 100644 --- a/src/main/java/io/hardingadonis/saledock/dao/IProductDAO.java +++ b/src/main/java/io/hardingadonis/saledock/dao/IProductDAO.java @@ -2,7 +2,7 @@ import io.hardingadonis.saledock.model.*; -public interface IProductDAO extends IDAO { +public interface IProductDAO extends IDAO, IPagination { public String getTop10(Integer duration); } diff --git a/src/main/java/io/hardingadonis/saledock/dao/impl/CustomerDAOImpl.java b/src/main/java/io/hardingadonis/saledock/dao/impl/CustomerDAOImpl.java index ccd3cec..8f603a3 100644 --- a/src/main/java/io/hardingadonis/saledock/dao/impl/CustomerDAOImpl.java +++ b/src/main/java/io/hardingadonis/saledock/dao/impl/CustomerDAOImpl.java @@ -6,6 +6,7 @@ import java.sql.*; import java.util.*; import org.hibernate.*; +import org.hibernate.query.*; import org.json.simple.*; public class CustomerDAOImpl implements ICustomerDAO { @@ -42,8 +43,8 @@ public List getAll() { } @Override - public Long count() { - Long count = 0L; + public Integer count() { + Integer count = 0; try { Connection conn = Singleton.dbContext.getConnection(); @@ -53,7 +54,7 @@ public Long count() { ResultSet rs = smt.executeQuery(); if (rs.next()) { - count = rs.getLong(1); + count = rs.getInt(1); } Singleton.dbContext.closeConnection(conn); @@ -95,8 +96,22 @@ public String getTop10(Integer duration) { System.err.println(ex.getMessage()); } - System.out.println(json.toJSONString()); - return json.toJSONString(); } + + @Override + public List pagination(Integer offset, Integer limit) { + try (Session session = sessionFactory.openSession()) { + Query query = session.createQuery("FROM Customer", Customer.class); + query.setFirstResult(offset); + query.setMaxResults(limit); + + return query.getResultList(); + } + } + + @Override + public Integer totalPages(Integer limit) { + return (int) Math.ceil((double) this.count() / limit); + } } diff --git a/src/main/java/io/hardingadonis/saledock/dao/impl/EmployeeDAOImpl.java b/src/main/java/io/hardingadonis/saledock/dao/impl/EmployeeDAOImpl.java index 673a495..c49221e 100644 --- a/src/main/java/io/hardingadonis/saledock/dao/impl/EmployeeDAOImpl.java +++ b/src/main/java/io/hardingadonis/saledock/dao/impl/EmployeeDAOImpl.java @@ -46,4 +46,12 @@ public Optional getByCode(String code) { return Optional.ofNullable(session.createQuery(hql, Employee.class).setParameter("code", code).uniqueResult()); } } + + @Override + public Optional getByEmail(String email) { + try (Session session = sessionFactory.openSession()) { + String hql = "FROM Employee WHERE email = :email"; + return Optional.ofNullable(session.createQuery(hql, Employee.class).setParameter("email", email).uniqueResult()); + } + } } diff --git a/src/main/java/io/hardingadonis/saledock/dao/impl/OrderDAOImpl.java b/src/main/java/io/hardingadonis/saledock/dao/impl/OrderDAOImpl.java index 8bafc2c..46ded41 100644 --- a/src/main/java/io/hardingadonis/saledock/dao/impl/OrderDAOImpl.java +++ b/src/main/java/io/hardingadonis/saledock/dao/impl/OrderDAOImpl.java @@ -6,6 +6,7 @@ import java.sql.*; import java.util.*; import org.hibernate.*; +import org.hibernate.query.Query; import org.json.simple.*; public class OrderDAOImpl implements IOrderDAO { @@ -42,8 +43,8 @@ public List getAll() { } @Override - public Long count() { - Long count = 0L; + public Integer count() { + Integer count = 0; try { Connection conn = Singleton.dbContext.getConnection(); @@ -53,7 +54,7 @@ public Long count() { ResultSet rs = smt.executeQuery(); if (rs.next()) { - count = rs.getLong(1); + count = rs.getInt(1); } Singleton.dbContext.closeConnection(conn); @@ -65,8 +66,8 @@ public Long count() { } @Override - public Long countOrderInProcess() { - Long count = 0L; + public Integer countOrderInProcess() { + Integer count = 0; try { Connection conn = Singleton.dbContext.getConnection(); @@ -76,7 +77,7 @@ public Long countOrderInProcess() { ResultSet rs = smt.executeQuery(); if (rs.next()) { - count = rs.getLong(1); + count = rs.getInt(1); } Singleton.dbContext.closeConnection(conn); @@ -154,4 +155,20 @@ public String statisticByStatus(Integer duration) { return json.toJSONString(); } + + @Override + public List pagination(Integer offset, Integer limit) { + try (Session session = sessionFactory.openSession()) { + Query query = session.createQuery("FROM Order", Order.class); + query.setFirstResult(offset); + query.setMaxResults(limit); + + return query.getResultList(); + } + } + + @Override + public Integer totalPages(Integer limit) { + return (int) Math.ceil((double) this.count() / limit); + } } diff --git a/src/main/java/io/hardingadonis/saledock/dao/impl/ProductDAOImpl.java b/src/main/java/io/hardingadonis/saledock/dao/impl/ProductDAOImpl.java index aad163c..db8989d 100644 --- a/src/main/java/io/hardingadonis/saledock/dao/impl/ProductDAOImpl.java +++ b/src/main/java/io/hardingadonis/saledock/dao/impl/ProductDAOImpl.java @@ -6,6 +6,7 @@ import java.sql.*; import java.util.*; import org.hibernate.*; +import org.hibernate.query.*; import org.json.simple.*; public class ProductDAOImpl implements IProductDAO { @@ -42,8 +43,8 @@ public List getAll() { } @Override - public Long count() { - Long count = 0L; + public Integer count() { + Integer count = 0; try { Connection conn = Singleton.dbContext.getConnection(); @@ -53,7 +54,7 @@ public Long count() { ResultSet rs = smt.executeQuery(); if (rs.next()) { - count = rs.getLong(1); + count = rs.getInt(1); } Singleton.dbContext.closeConnection(conn); @@ -95,8 +96,22 @@ public String getTop10(Integer duration) { System.err.println(ex.getMessage()); } - System.out.println(json.toJSONString()); - return json.toJSONString(); } + + @Override + public List pagination(Integer offset, Integer limit) { + try (Session session = sessionFactory.openSession()) { + Query query = session.createQuery("FROM Product", Product.class); + query.setFirstResult(offset); + query.setMaxResults(limit); + + return query.getResultList(); + } + } + + @Override + public Integer totalPages(Integer limit) { + return (int) Math.ceil((double) this.count() / limit); + } } diff --git a/src/main/java/io/hardingadonis/saledock/model/Category.java b/src/main/java/io/hardingadonis/saledock/model/Category.java index 242d0de..bf96c47 100644 --- a/src/main/java/io/hardingadonis/saledock/model/Category.java +++ b/src/main/java/io/hardingadonis/saledock/model/Category.java @@ -16,7 +16,7 @@ public class Category { @Id @Column(name = "`id`") @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long ID; + private Integer ID; @Column(name = "`name`", nullable = false) private String name; diff --git a/src/main/java/io/hardingadonis/saledock/model/Customer.java b/src/main/java/io/hardingadonis/saledock/model/Customer.java index f8cbeed..1a2b29b 100644 --- a/src/main/java/io/hardingadonis/saledock/model/Customer.java +++ b/src/main/java/io/hardingadonis/saledock/model/Customer.java @@ -16,7 +16,7 @@ public class Customer { @Id @Column(name = "`id`") @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long ID; + private Integer ID; @Column(name = "`code`", unique = true, nullable = false, insertable = true, updatable = false) private String code; diff --git a/src/main/java/io/hardingadonis/saledock/model/Employee.java b/src/main/java/io/hardingadonis/saledock/model/Employee.java index 8205f2a..f3426e9 100644 --- a/src/main/java/io/hardingadonis/saledock/model/Employee.java +++ b/src/main/java/io/hardingadonis/saledock/model/Employee.java @@ -16,7 +16,7 @@ public class Employee { @Id @Column(name = "`id`") @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long ID; + private Integer ID; @Column(name = "`code`", unique = true, nullable = false, insertable = true, updatable = false) private String code; diff --git a/src/main/java/io/hardingadonis/saledock/model/Order.java b/src/main/java/io/hardingadonis/saledock/model/Order.java index ec5ecf8..2bfeb04 100644 --- a/src/main/java/io/hardingadonis/saledock/model/Order.java +++ b/src/main/java/io/hardingadonis/saledock/model/Order.java @@ -1,6 +1,7 @@ package io.hardingadonis.saledock.model; import jakarta.persistence.*; +import java.text.*; import java.time.*; import java.util.*; import lombok.*; @@ -24,7 +25,7 @@ public static enum Status { @Id @Column(name = "`id`") @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long ID; + private Integer ID; @Column(name = "`code`", unique = true, nullable = false, insertable = true, updatable = false) private String code; @@ -86,4 +87,9 @@ private static String generateRandomOrderCode() { return uuid.toString().toUpperCase().replace("-", "").substring(0, 15); } + + public String getTotalToString() { + DecimalFormat decimalFormat = new DecimalFormat("#,###.#"); + return decimalFormat.format(this.total); + } } diff --git a/src/main/java/io/hardingadonis/saledock/model/OrderDetail.java b/src/main/java/io/hardingadonis/saledock/model/OrderDetail.java index 7b081d2..2c26b2d 100644 --- a/src/main/java/io/hardingadonis/saledock/model/OrderDetail.java +++ b/src/main/java/io/hardingadonis/saledock/model/OrderDetail.java @@ -22,10 +22,10 @@ public class OrderDetail { public static class ID implements Serializable { @Column(name = "`order_id`") - private Long orderID; + private Integer orderID; @Column(name = "`product_id`") - private Long productID; + private Integer productID; } @EmbeddedId diff --git a/src/main/java/io/hardingadonis/saledock/model/Product.java b/src/main/java/io/hardingadonis/saledock/model/Product.java index 11e9e1a..3981e85 100644 --- a/src/main/java/io/hardingadonis/saledock/model/Product.java +++ b/src/main/java/io/hardingadonis/saledock/model/Product.java @@ -16,7 +16,7 @@ public class Product { @Id @Column(name = "`id`") @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long ID; + private Integer ID; @Column(name = "`code`", unique = true, nullable = false, insertable = true, updatable = false) private String code; diff --git a/src/main/java/io/hardingadonis/saledock/utils/OtpUltil.java b/src/main/java/io/hardingadonis/saledock/utils/OtpUltil.java new file mode 100644 index 0000000..16c3fdb --- /dev/null +++ b/src/main/java/io/hardingadonis/saledock/utils/OtpUltil.java @@ -0,0 +1,15 @@ +package io.hardingadonis.saledock.utils; + +public class OtpUltil { + + public static String generateRandomOTP() { + int otpLength = 6; + String allowedDigits = "0123456789"; + StringBuilder otp = new StringBuilder(); + for (int i = 0; i < otpLength; i++) { + int index = (int) (Math.random() * allowedDigits.length()); + otp.append(allowedDigits.charAt(index)); + } + return otp.toString(); + } +} diff --git a/src/main/java/io/hardingadonis/saledock/utils/SendEmailUtil.java b/src/main/java/io/hardingadonis/saledock/utils/SendEmailUtil.java new file mode 100644 index 0000000..bbbfaa6 --- /dev/null +++ b/src/main/java/io/hardingadonis/saledock/utils/SendEmailUtil.java @@ -0,0 +1,81 @@ +package io.hardingadonis.saledock.utils; + +import jakarta.mail.*; +import jakarta.mail.internet.*; + +import java.util.*; +import java.util.logging.*; + +public class SendEmailUtil { + + private static void sendEmail(String to, String title, String textMessage) { + + //properties : Khai báo thuộc tính + Properties props = new Properties(); + props.put("mail.smtp.host", "smtp.gmail.com"); //smtp host + props.put("mail.smtp.port", "587"); // TSL 587, SSL 465 + props.put("mail.smtp.auth", "true"); + props.put("mail.smtp.starttls.enable", "true"); + + ResourceBundle resourceBundle = ResourceBundle.getBundle("email"); + final String from = resourceBundle.getString("email"); + final String password = resourceBundle.getString("password"); + + Session session = Session.getInstance(props, new Authenticator() { + + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(from, password); + } + + }); + + new Thread(() -> { + try { + Message message = prepareMessage(session, from, to, title, textMessage); + Transport.send(message); + System.out.println("Send email success!"); + } catch (MessagingException ex) { + System.out.println("Send email fail!"); + Logger.getLogger(SendEmailUtil.class.getName()).log(Level.SEVERE, null, ex); + } + }).start(); + } + + public static Message prepareMessage(Session session, String from, String recipient, String title, String textMessage) { + + try { + Message message = new MimeMessage(session); + message.addHeader("Content-type", "text/HTML; charset = UTF-8"); + message.setFrom(new InternetAddress(from)); + message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipient, false)); + message.setReplyTo(InternetAddress.parse(from, false)); + message.setSubject(title); + message.setSentDate(new Date()); + message.setContent(textMessage, "text/html; charset=UTF-8"); + return message; + + } catch (MessagingException exception) { + Logger.getLogger(SendEmailUtil.class.getName()).log(Level.SEVERE, null, exception); + } + return null; + } + + public static void sendGetOTPMessage(String to, String title, String otp) { + String message = "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " Get OTP\n" + + "\n" + + "\n" + + "

Welcome to Sale Dock

\n" + + "

Your OTP to change your account password is: " + otp + "

\n" + + "

Please usse this OTP to change your old password.

\n" + + " \n" + + "\n" + + ""; + sendEmail(to, title, message); + } +} diff --git a/src/main/resources/config.mysql.properties b/src/main/resources/config.mysql.properties index 0ae7593..6eee35a 100644 --- a/src/main/resources/config.mysql.properties +++ b/src/main/resources/config.mysql.properties @@ -1,4 +1,4 @@ -config.jdbc.url=localhost +config.jdbc.url=database config.jdbc.database_name=saledock config.jdbc.user=root config.jdbc.password= \ No newline at end of file diff --git a/src/main/resources/email.properties b/src/main/resources/email.properties new file mode 100644 index 0000000..04d6da6 --- /dev/null +++ b/src/main/resources/email.properties @@ -0,0 +1,2 @@ +email = hadonis.contact@gmail.com +password = tkdowaokxzfakpif \ No newline at end of file diff --git a/src/main/resources/hibernate.cfg.xml b/src/main/resources/hibernate.cfg.xml index 1f441f1..48e5fbd 100644 --- a/src/main/resources/hibernate.cfg.xml +++ b/src/main/resources/hibernate.cfg.xml @@ -4,7 +4,7 @@ com.mysql.cj.jdbc.Driver - jdbc:mysql://localhost:3306/saledock?createDatabaseIfNotExist=true + jdbc:mysql://database:3306/saledock?createDatabaseIfNotExist=true root diff --git a/src/main/webapp/view/assets/images/icons/order.png b/src/main/webapp/view/assets/images/icons/order.png new file mode 100644 index 0000000..f86ea5d Binary files /dev/null and b/src/main/webapp/view/assets/images/icons/order.png differ diff --git a/src/main/webapp/view/assets/images/icons/product.png b/src/main/webapp/view/assets/images/icons/product.png new file mode 100644 index 0000000..ea4d5b9 Binary files /dev/null and b/src/main/webapp/view/assets/images/icons/product.png differ diff --git a/src/main/webapp/view/assets/js/management/order/order-detail.js b/src/main/webapp/view/assets/js/management/order/order-detail.js new file mode 100644 index 0000000..7ea3f7b --- /dev/null +++ b/src/main/webapp/view/assets/js/management/order/order-detail.js @@ -0,0 +1,25 @@ +//Ngày tạo +window.onload = function () { + var input = document.getElementById('createAt'); + var date = new Date(input.value); + input.value = date.toLocaleString(); +}; + + +const prices = document.getElementsByClassName("price"); + +for (let price of prices) { + const amount = parseFloat(price.textContent || price.value); + + price.textContent = formatCurrencyVND(amount); + price.value = formatCurrencyVND(amount); +} + +function formatCurrencyVND(amount) { + const formatter = new Intl.NumberFormat('vi-VN', { + style: 'currency', + currency: 'VND' + }); + + return formatter.format(amount); +} diff --git a/src/main/webapp/view/assets/js/validate/validator.js b/src/main/webapp/view/assets/js/validate/validator.js index 07098c6..44cd0f6 100644 --- a/src/main/webapp/view/assets/js/validate/validator.js +++ b/src/main/webapp/view/assets/js/validate/validator.js @@ -153,8 +153,26 @@ Validator.isEmail = function (selector, message) { return { selector: selector, test: function (value) { - var regex = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/; - return regex.test(value) ? undefined : message || 'This field must be email!'; + // Regular expression for a strict email validation + var regex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; + + if (!regex.test(value)) { + return message || 'This field must be a valid email address!'; + } + + // Additional checks for email address + var parts = value.split('@'); + var domain = parts[1]; + + // Check for valid domain + if (domain) { + var domainParts = domain.split('.'); + if (domainParts.length < 2 || domainParts[domainParts.length - 1].length < 2) { + return message || 'Invalid email address - invalid domain!'; + } + } + + return undefined; // Email is valid } }; }; diff --git a/src/main/webapp/view/common/_nav.jsp b/src/main/webapp/view/common/_nav.jsp index 10040fa..631de89 100644 --- a/src/main/webapp/view/common/_nav.jsp +++ b/src/main/webapp/view/common/_nav.jsp @@ -15,7 +15,7 @@ diff --git a/src/main/webapp/view/jsp/management/customer/customer.jsp b/src/main/webapp/view/jsp/management/customer/customer.jsp index 29b899a..06a9469 100644 --- a/src/main/webapp/view/jsp/management/customer/customer.jsp +++ b/src/main/webapp/view/jsp/management/customer/customer.jsp @@ -25,7 +25,7 @@

Khách hàng

-

Quản lý khách hàng

Thêm khách hàng +

Quản lý khách hàng

Thêm khách hàng
@@ -41,10 +41,14 @@ - ${customer.name} + ${customer.name} ${customer.code} ${customer.email} - + + + + @@ -53,7 +57,7 @@
-

Hiển thị 1 trên 1 khách hàng

+

Hiển thị <%= request.getAttribute("customerCount") %> trên <%= request.getAttribute("customerCount") %> khách hàng

+
+
+
+ + + + + + + + + + + + + diff --git a/src/main/webapp/view/jsp/others/login/login.jsp b/src/main/webapp/view/jsp/others/login/login.jsp index cbec068..d576fb5 100644 --- a/src/main/webapp/view/jsp/others/login/login.jsp +++ b/src/main/webapp/view/jsp/others/login/login.jsp @@ -27,6 +27,11 @@
+ + + + + +
@@ -58,7 +68,7 @@

- + diff --git a/src/main/webapp/view/jsp/others/login/new-password.jsp b/src/main/webapp/view/jsp/others/login/new-password.jsp new file mode 100644 index 0000000..12901e6 --- /dev/null +++ b/src/main/webapp/view/jsp/others/login/new-password.jsp @@ -0,0 +1,95 @@ +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> + + + + + + + + Quên mật khẩu - Sale Dock + + + + + + + +
+
+
+
+
+
+
+
+ + + + + + + + + + + +
+

Quên mật khẩu?

+

Tạo mật khẩu mới!

+
+
+ +
+ +

${sessionScope.email}

+
+
+ + +
+
+ + +
+ +
+
+
+
+
+
+
+
+
+ + + + + + + + + + diff --git a/src/main/webapp/view/jsp/others/login/otp.jsp b/src/main/webapp/view/jsp/others/login/otp.jsp new file mode 100644 index 0000000..8431d03 --- /dev/null +++ b/src/main/webapp/view/jsp/others/login/otp.jsp @@ -0,0 +1,75 @@ +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> + + + + + + + + Quên mật khẩu - Sale Dock + + + + + + + +
+
+
+
+
+
+
+
+ + + + + + + + +
+

Quên mật khẩu?

+

Nhập OTP để thay đổi mật khẩu!

+
+
+
+ + + +
+ +
+
+
+
+
+
+
+
+
+ + + + + + + + + +