diff --git a/models/order.js b/models/order.js index f95bfc7..a81111f 100644 --- a/models/order.js +++ b/models/order.js @@ -8,7 +8,7 @@ var schema = new Schema({ }, deliveryId: { type: Schema.Types.ObjectId, - ref: "Stock", + ref: "Delivery", required: true, }, order_date: { diff --git a/public/javascripts/admin_orders.js b/public/javascripts/admin_orders.js new file mode 100644 index 0000000..8f52201 --- /dev/null +++ b/public/javascripts/admin_orders.js @@ -0,0 +1,13 @@ +const myModal = new bootstrap.Modal("#confirm-modal"); + +// Display confirmation dialog +async function showConfirm(id) { + const submit = document.getElementById("modal_confirm"); + submit.dataset.submit_id = "storno_" + id; + myModal.show(); +} + +// Submit form from modal to confirm order storno +function submitFromModal(ctx) { + document.getElementById(ctx.dataset.submit_id).submit(); +} diff --git a/routes/orders.js b/routes/orders.js index 5f62ac1..d7a108a 100644 --- a/routes/orders.js +++ b/routes/orders.js @@ -1,10 +1,17 @@ import { Router } from "express"; import moment from "moment"; +import User from "../models/user.js"; +import Product from "../models/product.js"; import Order from "../models/order.js"; +import Delivery from "../models/delivery.js"; +import { sendMail } from "../functions/sendMail.js"; import { ensureAuthenticated } from "../functions/ensureAuthenticated.js"; import { checkKiosk } from "../functions/checkKiosk.js"; +import csrf from "csurf"; import logger from "../functions/logger.js"; var router = Router(); +const csrfProtection = csrf(); +router.use(csrfProtection); moment.locale("cs"); /* GET orders page. */ @@ -30,9 +37,6 @@ router.get("/", ensureAuthenticated, checkKiosk, function (req, res) { }; } - // Order.listIndexes().then((indexes) => { - // console.log(indexes); - // }); if (req.session.alert) { var alert = req.session.alert; delete req.session.alert; @@ -152,6 +156,7 @@ router.get("/", ensureAuthenticated, checkKiosk, function (req, res) { admin: filter, user: req.user, alert: alert, + csrfToken: req.csrfToken(), }); }) .catch((err) => { @@ -163,4 +168,189 @@ router.get("/", ensureAuthenticated, checkKiosk, function (req, res) { }); }); +/* POST admin_orders page - orders page has no POST method. */ +router.post("/", ensureAuthenticated, function (req, res, _next) { + if (!req.user.admin) { + logger.warn( + `server.routes.adminorders.post__User tried to cancel order on admin page without permission.`, + { + metadata: { + result: req.user, + }, + } + ); + res.redirect("/"); + return; + } + if (req.body.name === "storno") { + // TODO: Handle already invoiced orders and orders older than 15 minutes - Possibly replace with FindOneAndDelete + Order.findByIdAndDelete({ + _id: req.body.order, + }) + .populate("buyerId", "email") + .populate({ + path: "deliveryId", + populate: { + path: "productId", + select: ["displayName", "imagePath"], + }, + select: ["productId", "price"], + }) + .then((order) => { + logger.debug( + `server.routes.adminorders.post__Order [${order._id}] with product [${order.deliveryId.productId.displayName}] purchased by user [${order.buyerId.email}] has been deleted.`, + { + metadata: { + object: order, + }, + } + ); + Delivery.findByIdAndUpdate( + { + _id: order.deliveryId._id, + }, + { + $inc: { + amount_left: 1, + }, + } + ) + .then((delivery) => { + logger.debug( + `server.routes.adminorders.post__Returned product's stock amount incremented in delivery [${delivery._id}] by one.`, + { + metadata: { + object: delivery, + }, + } + ); + logger.info( + `server.routes.adminorders.post__User [${req.user.displayName}] succesfully returned product [${order.deliveryId.productId.displayName}] for [${order.deliveryId.price}].`, + { + metadata: { + order: delivery, + }, + } + ); + + const alert = { + type: "success", + message: `Stornovali jste ${order.deliveryId.productId.displayName} za ${order.deliveryId.price} Kč a e-mail byl zaslán zákazníkovi na adresu ${order.buyerId.email}.`, + success: 1, + }; + req.session.alert = alert; + res.redirect("/admin_orders"); + const subject = `Stornování objednávky - ${order.deliveryId.productId.displayName}`; + const mailPreview = `Administrátor ${req.user.displayName} stornoval ${order.deliveryId.productId.displayName} za ${order.deliveryId.price} Kč.`; + sendMail( + order.buyerId.email, + "productReturned", + { + subject, + mailPreview, + orderId: order._id, + productId: order.deliveryId.productId._id, + productName: order.deliveryId.productId.displayName, + productPrice: order.deliveryId.price, + purchaseDate: moment(order.order_date).format("LLLL"), + }, + order.deliveryId.productId.imagePath + ); + return; + }) + .catch((err) => { + logger.error( + "server.routes.adminorders.post__Failed to increment stock in the database, but order has been already deleted!", + { + metadata: { + error: err.message, + order: order, + }, + } + ); + const alert = { + type: "danger", + component: "db", + message: err.message, + danger: 1, + }; + req.session.alert = alert; + res.redirect("/admin_orders"); + const subject = "[SYSTEM ERROR] Chyba při zápisu do databáze!"; + const message = `Potenciálně se nepodařilo zapsat navýšení stavu dodávky do databáze, ale již došlo k odstranění objednávky. Dodávka ID [${order.deliveryId._id}]. Administrátor [${req.user.displayName}] se pokusil zákazníkovi ID [${order.buyerId._id}], e-mail [${order.buyerId.email}] stornovat produkt ID [${order.deliveryId.productId._id}], zobrazované jméno [${order.deliveryId.productId.displayName}] za [${order.deliveryId.price}] Kč. Zkontrolujte konzistenci databáze.`; + sendMail("system@system", "systemMessage", { + subject, + message, + messageTime: moment().toISOString(), + errorMessage: err.message, + }); + return; + }); + + // newOrder + // .save() + // .then((order) => { + + // req.session.alert = alert; + // res.redirect("/shop"); + // if (req.user.sendMailOnEshopPurchase) { + + // } + // return; + // }) + // .catch((err) => { + // }); + // }) + // .catch((err) => { + // logger.error( + // "server.routes.shop.post__Failed to decrement stock amount from the delivery.", + // { + // metadata: { + // error: err.message, + // }, + // } + // ); + // const alert = { + // type: "danger", + // component: "db", + // message: err.message, + // danger: 1, + // }; + // req.session.alert = alert; + // res.redirect("/shop"); + // const subject = "[SYSTEM ERROR] Chyba při zápisu do databáze!"; + // const message = `Potenciálně se nepodařilo snížit skladovou zásobu v dodávce ID [${delivery._id}] a následně vystavit objednávku. Zákazník ID [${req.user._id}], zobrazované jméno [${req.user.displayName}] se pokusil koupit produkt ID [${delivery.productId}], zobrazované jméno [${req.body.display_name}] za [${delivery.price}] Kč. Zkontrolujte konzistenci databáze.`; + // sendMail("system@system", "systemMessage", { + // subject, + // message, + // messageTime: moment().toISOString(), + // errorMessage: err.message, + // }); + // return; + // }); + }) + .catch((err) => { + logger.error( + "server.routes.adminorders.post__Failed delete order from database.", + { + metadata: { + error: err.message, + }, + } + ); + const alert = { + type: "danger", + component: "db", + message: err.message, + danger: 1, + }; + req.session.alert = alert; + res.redirect("/admin_orders"); + return; + }); + } else { + res.status(400).send(); + } +}); + export default router; diff --git a/views/email/productReturned.handlebars b/views/email/productReturned.handlebars new file mode 100644 index 0000000..f07db8e --- /dev/null +++ b/views/email/productReturned.handlebars @@ -0,0 +1,158 @@ +{{> title title="Stornování objednávky" }} +{{! Single column template }} + + + {{! Spacer }} + + + + + + + + + {{! Spacer }} + + + + +
 
+ + + + {{! 1st row - text }} + + {{! Middle column }} + + + + + {{! 2nd row - info and picture }} + + {{! Middle column }} + + + + {{! 3rd row - button }} + + {{! Middle column }} + + +
+ {{! Single column template }} + + + {{! Spacer }} + + + + + + + + + {{! Spacer }} + + + + +
 
+ + + + + {{! Middle column }} + + + +
+

Vážený zákazníku,

+

mrzí nás, že nejste spokojený se zakoupeným produktem {{productName}}.

+

Administrátor stornoval Vaši objednávku. Prosíme Vás, pokud jste tak ještě neučinili, vraťte produkt do Lednice IT a ověřte skladovou zásobu, zda souhlasí se skutečným stavem.

+

Přejeme, ať se Váš další výběr produktu podaří a těsíme se na Vaši další návštěvu.

+
+ +
 
+
+ {{! Two columns template }} + + + {{! Spacer }} + + + + + + + + + {{! Spacer }} + + + + +
 
+ + + + + {{! Left column }} + + + {{! Right column }} + + + +
+ + + + + + + + + + + + + + + + + + + + +
+

Detail objednávky

+
+ Název + + {{productName}} +
+ Datum + + {{purchaseDate}} +
+ Částka + + {{productPrice}} Kč +
+ Číslo + + {{orderId}} +
+
+ Buď jste zrakově postižený a čtečka Vám tento popis předčítá, což vám nepomůže zjistit, jak zakoupený produkt vypadá, nebo jsme udělali chybu a obrázek se nezobrazuje. V obou případech se omlouváme. +
+ +
 
+
+ {{!-- Button table partial --}} + {{> centerButton label="Objednávky" baseUrl=baseUrl path="/orders" }} +
+ +
 
\ No newline at end of file diff --git a/views/partials/confirm_storno_modal.hbs b/views/partials/confirm_storno_modal.hbs new file mode 100644 index 0000000..758db79 --- /dev/null +++ b/views/partials/confirm_storno_modal.hbs @@ -0,0 +1,34 @@ + \ No newline at end of file diff --git a/views/shop/orders.hbs b/views/shop/orders.hbs index a19ff67..7660d2b 100644 --- a/views/shop/orders.hbs +++ b/views/shop/orders.hbs @@ -1,3 +1,6 @@ +{{#unless admin.buyerId}} + {{> confirm_storno_modal }} +{{/unless}}
@@ -34,6 +37,9 @@ Dodavatel Způsob Fakturováno + {{#unless admin.buyerId}} + Akce + {{/unless}} @@ -78,6 +84,16 @@ Ne {{/if}} + {{#unless ../admin.buyerId}} + + +
+ + + +
+ + {{/unless}} {{/each}} @@ -88,4 +104,7 @@ {{> datatables }} - \ No newline at end of file + +{{#unless admin.buyerId}} + +{{/unless}} \ No newline at end of file