From 1627379cc01cff5489ace7ceedc8605a168df54d Mon Sep 17 00:00:00 2001 From: Robert van Dijk Date: Wed, 19 Apr 2023 17:01:08 +0200 Subject: [PATCH 01/22] External product url (#430) * Add external product URL to Event * Add rendering of external URL when there is one * Add rendering of external URL on event page * Fix double text in external product url field * Add validation checking for both products and url in one event * Add validation checking for both products and url in one event in admin panel * Add migration for external_product_url in event table * Update script.min * Remove description from external url event in webshop overview --- .../controller/DashboardEventController.java | 6 ++++ .../wisv/events/core/model/event/Event.java | 15 ++++++++++ .../core/service/event/EventServiceImpl.java | 5 ++++ .../webshop/service/WebshopServiceImpl.java | 2 +- ...16__Add_external_product_url_to_event.java | 29 +++++++++++++++++++ src/main/resources/static/js/events/script.js | 21 ++++++++++++++ .../resources/static/js/events/script.min.js | 2 +- .../templates/admin/events/event.html | 9 ++++++ .../templates/admin/events/view.html | 7 +++++ .../resources/templates/webshop/event.html | 15 +++++++++- .../resources/templates/webshop/index.html | 11 ++++++- .../core/service/EventServiceImplTest.java | 12 ++++++++ 12 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 src/main/java/db/migration/V202304016__Add_external_product_url_to_event.java diff --git a/src/main/java/ch/wisv/events/admin/controller/DashboardEventController.java b/src/main/java/ch/wisv/events/admin/controller/DashboardEventController.java index e43b97ab..c5294d44 100644 --- a/src/main/java/ch/wisv/events/admin/controller/DashboardEventController.java +++ b/src/main/java/ch/wisv/events/admin/controller/DashboardEventController.java @@ -142,6 +142,9 @@ public String create(RedirectAttributes redirect, @ModelAttribute Event event, @ if (file.getSize() > 0) { eventService.addDocumentImage(event, documentService.storeDocument(file)); } + if (event.getExternalProductUrl() != null && event.getExternalProductUrl().length() == 0){ + event.setExternalProductUrl(null); + } eventService.create(event); redirect.addFlashAttribute(FLASH_SUCCESS, "Event " + event.getTitle() + " has been created!"); @@ -204,6 +207,9 @@ public String update( eventService.addDocumentImage(event, documentService.storeDocument(file)); } event.setKey(key); + if (event.getExternalProductUrl() != null && event.getExternalProductUrl().length() == 0){ + event.setExternalProductUrl(null); + } eventService.update(event); redirect.addFlashAttribute(FLASH_SUCCESS, "Event changes saved!"); diff --git a/src/main/java/ch/wisv/events/core/model/event/Event.java b/src/main/java/ch/wisv/events/core/model/event/Event.java index 8bd32beb..759e8786 100644 --- a/src/main/java/ch/wisv/events/core/model/event/Event.java +++ b/src/main/java/ch/wisv/events/core/model/event/Event.java @@ -74,6 +74,11 @@ public class Event { */ private String imageUrl; + /** + * Field externalProductUrl link to external url for registering for the Event. + */ + private String externalProductUrl; + /** * Product that are related to this event and can be sold. OneToMany so one Product can be used by one Event, but * an Event can contain multiple Products. @@ -237,4 +242,14 @@ private double calcProgress(double reserved) { public boolean isSoldOut() { return this.maxSold != null && this.getSold() >= this.maxSold; } + + /** + * Check if the event has an external ticker URL configured + * + * @return boolean + */ + + public boolean hasExternalProductUrl() { + return this.externalProductUrl != null && this.externalProductUrl.length() > 0; + } } diff --git a/src/main/java/ch/wisv/events/core/service/event/EventServiceImpl.java b/src/main/java/ch/wisv/events/core/service/event/EventServiceImpl.java index 850328dc..82d35035 100644 --- a/src/main/java/ch/wisv/events/core/service/event/EventServiceImpl.java +++ b/src/main/java/ch/wisv/events/core/service/event/EventServiceImpl.java @@ -177,6 +177,7 @@ public void update(Event event) throws EventNotFoundException, EventInvalidExcep update.setPublished(event.getPublished()); update.setOrganizedBy(event.getOrganizedBy()); update.setCategories(event.getCategories()); + update.setExternalProductUrl(event.getExternalProductUrl()); if (event.getImageUrl() != null) { update.setImageUrl(event.getImageUrl()); @@ -251,6 +252,10 @@ private void assertIsValidEvent(Event event) throws EventInvalidException { if (event.getProducts().stream().distinct().count() != event.getProducts().size()) { throw new EventInvalidException("It is not possible to add the same product twice or more!"); } + + if (event.getProducts().size() > 0 && event.hasExternalProductUrl()) { + throw new EventInvalidException("It is not possible to use products when using an external product URL."); + } } /** diff --git a/src/main/java/ch/wisv/events/webshop/service/WebshopServiceImpl.java b/src/main/java/ch/wisv/events/webshop/service/WebshopServiceImpl.java index 66cf6f8b..9efc1b90 100644 --- a/src/main/java/ch/wisv/events/webshop/service/WebshopServiceImpl.java +++ b/src/main/java/ch/wisv/events/webshop/service/WebshopServiceImpl.java @@ -42,7 +42,7 @@ public Event filterEventProductNotSalable(Event event) { @Override public List filterEventProductNotSalable(List events) { return events.stream().map(this::filterEventProductNotSalable) - .filter(event -> event.getProducts().size() > 0) + .filter(event -> event.getProducts().size() > 0 || event.hasExternalProductUrl()) .collect(Collectors.toList()); } diff --git a/src/main/java/db/migration/V202304016__Add_external_product_url_to_event.java b/src/main/java/db/migration/V202304016__Add_external_product_url_to_event.java new file mode 100644 index 00000000..7a02fc79 --- /dev/null +++ b/src/main/java/db/migration/V202304016__Add_external_product_url_to_event.java @@ -0,0 +1,29 @@ +package db.migration; + +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; + +import java.sql.Statement; + + +/** + * DB migration which adds an external product URL to an event. + * If it is present, the event will have a button to this URL instead of normal prducts. + */ +public class V202304016__Add_external_product_url_to_event extends BaseJavaMigration { + + /** + * Executes this migration. The execution will automatically take place within a transaction, when the underlying + * database supports it. + * + * @param context of type Context + * @throws Exception when something is wrong + */ + public void migrate(Context context) throws Exception { + try (Statement select = context.getConnection().createStatement()) { + select.execute("ALTER TABLE public.event ADD COLUMN external_product_url varchar(255)"); + select.execute("UPDATE public.event SET external_product_url = NULL"); + } + } + +} diff --git a/src/main/resources/static/js/events/script.js b/src/main/resources/static/js/events/script.js index 97d86750..b12e277f 100644 --- a/src/main/resources/static/js/events/script.js +++ b/src/main/resources/static/js/events/script.js @@ -97,6 +97,21 @@ $(document).ready(function () { } }); + const externalProductUrlField = document.getElementById("externalProductUrl"); + + function validateExternalProductUrlField() { + let products = $("#products"); + let productsSize = products.children().length; + + if (productsSize > 0 && externalProductUrlField.value !== "") { + externalProductUrlField.setCustomValidity("It is not possible to use products when using an external product URL."); + } else { + externalProductUrlField.setCustomValidity(""); + } + } + + externalProductUrlField.addEventListener("input", validateExternalProductUrlField); + $('.remove-product').on('click', function (e) { e.preventDefault(); @@ -118,6 +133,9 @@ $(document).ready(function () { followingProduct.attr('id', 'products' + (i - 1)); followingProduct.attr('name', 'products[' + (i - 1) + ']'); } + + // After changing the amount of products, recheck that there is not both an external product url and products + validateExternalProductUrlField(); }); $("#createNewProductButton").on('click', function (e) { @@ -193,6 +211,9 @@ $(document).ready(function () { $("#noProducts").remove(); initTooltips(); + + // After changing the amount of products, recheck that there is not both an external product url and products + validateExternalProductUrlField(); } function initTooltips() { diff --git a/src/main/resources/static/js/events/script.min.js b/src/main/resources/static/js/events/script.min.js index c1326067..4849c4b1 100644 --- a/src/main/resources/static/js/events/script.min.js +++ b/src/main/resources/static/js/events/script.min.js @@ -1 +1 @@ -var TemplateSelector;function format(t,e){return $.each(e,function(e,a){t=t.replace(new RegExp("\\{"+e+"\\}","g"),a)}),t}!function(t){TemplateSelector={init:function(){TemplateSelector.binds()},binds:function(){t(".event-template-item a").on("click",TemplateSelector.__setEventTemplateValues),t(".product-template-item a").on("click",TemplateSelector.__setProductTemplateValues)},__setEventTemplateValues:function(e){e.preventDefault();var a=t(e.target).parent().data("template");t.each(a,function(e,a){t("#"+e).val(a)}),TemplateSelector.__setCategories(a),TemplateSelector.__setTimes(a)},__setProductTemplateValues:function(e){e.preventDefault();var a=t(e.target).parent().data("template");t.each(a,function(e,a){var r=e.replace(/\b\w/g,function(t){return t.toUpperCase()}),c=t("#product"+r);c.val(a),"chOnly"===e&&!0===a?c.attr("checked","checked"):c.removeAttr("checked"),"reservable"===e&&!0===a?c.attr("checked","checked"):c.removeAttr("checked")})},__setCategories:function(e){t("input[name='categories']").each(function(){t.inArray(t(this).val(),e.categories)>=0?t(this).attr("checked","checked"):t(this).removeAttr("checked")})},__setTimes:function(t){document.querySelector("#start")._flatpickr.setDate(t.startingTime),document.querySelector("#ending")._flatpickr.setDate(t.endingTime)}}}(jQuery),$(document).ready(function(){TemplateSelector.init();var t={enableTime:!0,altInput:!0,altFormat:"F j, Y H:i",dateFormat:"Y-m-dTH:i:S",time_24hr:!0};function e(t,e){var a=$("#products"),r=a.children().length;$("#productsTable").append(format(" {0}",[e,t])),a.append(format('',[r,r,t])),$("#noProducts").remove(),$(function(){$('[data-toggle="tooltip"]').tooltip()})}$("#ending").flatpickr(t),$("#start").flatpickr(t),$("#productSellStart").flatpickr(t),$("#productSellEnd").flatpickr(t),$("#q").autocomplete({serviceUrl:"/api/v1/products/search/unused",onSelect:function(t){e(t.data,t.value),$(this).val(""),$("#addProduct").modal("hide")}}),$(".remove-product").on("click",function(t){t.preventDefault();var e=$("#products"),a=e.children().length,r=$(this).data("product-id"),c=e.find(":input[value='"+r+"']"),o=c.attr("id").replace("products","");c.remove(),$(this).parent().parent().remove();for(var l=o;l=0?t(this).attr("checked","checked"):t(this).removeAttr("checked")}))},__setTimes:function(t){document.querySelector("#start")._flatpickr.setDate(t.startingTime),document.querySelector("#ending")._flatpickr.setDate(t.endingTime)}}}(jQuery),$(document).ready((function(){TemplateSelector.init();var t={enableTime:!0,altInput:!0,altFormat:"F j, Y H:i",dateFormat:"Y-m-dTH:i:S",time_24hr:!0};$("#ending").flatpickr(t),$("#start").flatpickr(t),$("#productSellStart").flatpickr(t),$("#productSellEnd").flatpickr(t),$("#q").autocomplete({serviceUrl:"/api/v1/products/search/unused",onSelect:function(t){r(t.data,t.value),$(this).val(""),$("#addProduct").modal("hide")}});const e=document.getElementById("externalProductUrl");function a(){$("#products").children().length>0&&""!==e.value?e.setCustomValidity("It is not possible to use products when using an external product URL."):e.setCustomValidity("")}function r(t,e){var r=$("#products"),o=r.children().length;$("#productsTable").append(format(" {0}",[e,t])),r.append(format('',[o,o,t])),$("#noProducts").remove(),$((function(){$('[data-toggle="tooltip"]').tooltip()})),a()}e.addEventListener("input",a),$(".remove-product").on("click",(function(t){t.preventDefault();var e=$("#products"),r=e.children().length,o=$(this).data("product-id"),c=e.find(":input[value='"+o+"']"),n=c.attr("id").replace("products","");c.remove(),$(this).parent().parent().remove();for(var l=n;lInformation th:text="${event.getDescription()}" required> +
+ +
diff --git a/src/main/resources/templates/admin/events/view.html b/src/main/resources/templates/admin/events/view.html index e1922738..733c39e8 100644 --- a/src/main/resources/templates/admin/events/view.html +++ b/src/main/resources/templates/admin/events/view.html @@ -68,6 +68,13 @@
Information
readonly>
+
+ +
-
+

@@ -146,6 +146,19 @@

FREE

+
+
+ +
+ +
diff --git a/src/main/resources/templates/webshop/index.html b/src/main/resources/templates/webshop/index.html index 3b970b53..6efcee0c 100644 --- a/src/main/resources/templates/webshop/index.html +++ b/src/main/resources/templates/webshop/index.html @@ -83,6 +83,7 @@
- + diff --git a/src/test/java/ch/wisv/events/core/service/EventServiceImplTest.java b/src/test/java/ch/wisv/events/core/service/EventServiceImplTest.java index bc7a408e..ea5e7f21 100644 --- a/src/test/java/ch/wisv/events/core/service/EventServiceImplTest.java +++ b/src/test/java/ch/wisv/events/core/service/EventServiceImplTest.java @@ -208,6 +208,18 @@ public void testCreateInvalidDoubleProduct() throws EventInvalidException { service.create(this.event); } + @Test + public void testCreateInvalidExternalURLWithProduct() throws EventInvalidException{ + Product product = new Product(); + this.event.addProduct(product); + this.event.setExternalProductUrl("https://example.com"); + + thrown.expect(EventInvalidException.class); + thrown.expectMessage("It is not possible to use products when using an external product URL."); + + service.create(this.event); + } + @Test public void testGetByKey() throws Exception { when(repository.findByKey(this.event.getKey())).thenReturn(Optional.of(this.event)); From 5a262b0aa41cfdbf39cbb38c1b3ab17906d06eee Mon Sep 17 00:00:00 2001 From: KasperVaessen <58064813+KasperVaessen@users.noreply.github.com> Date: Wed, 14 Jun 2023 12:19:02 +0200 Subject: [PATCH 02/22] =?UTF-8?q?Added=20feature=20to=20make=20sure=20user?= =?UTF-8?q?s=20have=20to=20agree=20to=20the=20General=20terms=20a=E2=80=A6?= =?UTF-8?q?=20(#432)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added feature to make sure users have to agree to the General terms and conditions * Edited link to GTC. * removed unnecessary margins from css --- .../ch/wisv/events/core/model/order/OrderProductDto.java | 2 ++ .../webshop/controller/WebshopCheckoutController.java | 7 +++++++ src/main/resources/static/css/wisvch-dashboard.css | 4 ++++ src/main/resources/templates/webshop/index.html | 1 + 4 files changed, 14 insertions(+) diff --git a/src/main/java/ch/wisv/events/core/model/order/OrderProductDto.java b/src/main/java/ch/wisv/events/core/model/order/OrderProductDto.java index 6f4d913c..a41edd3c 100644 --- a/src/main/java/ch/wisv/events/core/model/order/OrderProductDto.java +++ b/src/main/java/ch/wisv/events/core/model/order/OrderProductDto.java @@ -10,11 +10,13 @@ public class OrderProductDto { * Field product should contain Product keys and number of webshop of that product. */ HashMap products; + Boolean agreedGTC; /** * Constructor. */ public OrderProductDto() { this.products = new HashMap<>(); + this.agreedGTC = Boolean.FALSE; } } diff --git a/src/main/java/ch/wisv/events/webshop/controller/WebshopCheckoutController.java b/src/main/java/ch/wisv/events/webshop/controller/WebshopCheckoutController.java index a3e3b86b..f7aec37d 100644 --- a/src/main/java/ch/wisv/events/webshop/controller/WebshopCheckoutController.java +++ b/src/main/java/ch/wisv/events/webshop/controller/WebshopCheckoutController.java @@ -28,6 +28,8 @@ public class WebshopCheckoutController extends WebshopController { /** Error message no products in Order. */ private static final String ERROR_MESSAGE_ORDER_WITHOUT_PRODUCTS = "Shopping basket can not be empty!"; + private static final String ERROR_MESSAGE_ORDER_NOT_AGREED = "You have to agree with the General Terms and Conditions to proceed to checkout."; + /** Username of the user that created this order. */ private static final String USERNAME_ORDER_CREATED = "events-webshop"; @@ -64,6 +66,11 @@ public WebshopCheckoutController( @PostMapping public String checkoutShoppingBasket(RedirectAttributes redirect, @ModelAttribute OrderProductDto orderProductDto) { try { + if (orderProductDto.getAgreedGTC() == Boolean.FALSE) { + redirect.addFlashAttribute(MODEL_ATTR_ERROR, ERROR_MESSAGE_ORDER_NOT_AGREED); + + return REDIRECT_EVENTS_HOME; + } if (orderProductDto.getProducts().isEmpty()) { redirect.addFlashAttribute(MODEL_ATTR_ERROR, ERROR_MESSAGE_ORDER_WITHOUT_PRODUCTS); diff --git a/src/main/resources/static/css/wisvch-dashboard.css b/src/main/resources/static/css/wisvch-dashboard.css index 6c2e3f7a..9914b483 100644 --- a/src/main/resources/static/css/wisvch-dashboard.css +++ b/src/main/resources/static/css/wisvch-dashboard.css @@ -153,6 +153,10 @@ textarea:focus { transition: box-shadow 0.15s linear, -webkit-box-shadow 0.15s linear } +#checkoutShoppingBasket { + float: right; +} + select.form-control { height: 2.4375rem !important; padding: .5rem; diff --git a/src/main/resources/templates/webshop/index.html b/src/main/resources/templates/webshop/index.html index 6efcee0c..e8efe77e 100644 --- a/src/main/resources/templates/webshop/index.html +++ b/src/main/resources/templates/webshop/index.html @@ -209,6 +209,7 @@

Pending tasks

- - + + - - + + - + + - - + + - + + - - + + - + + - + + - + + diff --git a/src/main/resources/templates/sales/scan/event/barcode.html b/src/main/resources/templates/sales/scan/event/barcode.html index b14eb520..9bef9e51 100644 --- a/src/main/resources/templates/sales/scan/event/barcode.html +++ b/src/main/resources/templates/sales/scan/event/barcode.html @@ -27,7 +27,7 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/templates/webshop/overview/index.html b/src/main/resources/templates/webshop/overview/index.html index bd9713e9..b500374b 100644 --- a/src/main/resources/templates/webshop/overview/index.html +++ b/src/main/resources/templates/webshop/overview/index.html @@ -216,6 +216,10 @@
Open tickets