From 88f009bcf8c5a22dbabb214a6324b5e292050acf Mon Sep 17 00:00:00 2001
From: srosse <stephane.rosse@frentix.com>
Date: Fri, 13 Nov 2020 12:06:30 +0100
Subject: [PATCH] OO-5059: special message if the transaction is pending after
 payment

---
 .../ui/RepositoryEntryRuntimeController.java  |  4 +-
 .../resource/accesscontrol/ACService.java     |  2 +
 .../manager/ACFrontendManager.java            |  6 ++
 .../accesscontrol/manager/ACOrderDAO.java     | 25 +++++++
 .../ui/AccessRefusedController.java           | 22 ++++--
 .../ui/_content/access_pending.html           |  4 ++
 .../ui/_content/access_refused.html           | 14 +---
 .../ui/_i18n/LocalStrings_de.properties       |  1 +
 .../ui/_i18n/LocalStrings_en.properties       |  1 +
 .../accesscontrol/ACFrontendManagerTest.java  |  1 +
 .../accesscontrol/ACOrderManagerTest.java     | 67 +++++++++++++++++++
 11 files changed, 127 insertions(+), 20 deletions(-)
 create mode 100644 src/main/java/org/olat/resource/accesscontrol/ui/_content/access_pending.html

diff --git a/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java b/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java
index 8489cd9e867..eba6ef1491a 100644
--- a/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java
+++ b/src/main/java/org/olat/repository/ui/RepositoryEntryRuntimeController.java
@@ -1032,7 +1032,7 @@ public class RepositoryEntryRuntimeController extends MainLayoutBasicController
 	}
 	
 	private void accessRefused(UserRequest ureq) {
-		Controller ctrl = new AccessRefusedController(ureq, getWindowControl());
+		Controller ctrl = new AccessRefusedController(ureq, getWindowControl(), re);
 		listenTo(ctrl);
 		toolbarPanel.rootController(re.getDisplayname(), ctrl);
 	}
@@ -1157,7 +1157,7 @@ public class RepositoryEntryRuntimeController extends MainLayoutBasicController
 				userCourseInfoMgr.updateUserCourseInformations(re.getOlatResource(), getIdentity());
 			}
 		} else {
-			runtimeController = new AccessRefusedController(ureq, getWindowControl());
+			runtimeController = new AccessRefusedController(ureq, getWindowControl(), re);
 			listenTo(runtimeController);
 			toolbarPanel.rootController(re.getDisplayname(), runtimeController);
 		}
diff --git a/src/main/java/org/olat/resource/accesscontrol/ACService.java b/src/main/java/org/olat/resource/accesscontrol/ACService.java
index c5aecfe9968..17852eb2266 100644
--- a/src/main/java/org/olat/resource/accesscontrol/ACService.java
+++ b/src/main/java/org/olat/resource/accesscontrol/ACService.java
@@ -74,6 +74,8 @@ public interface ACService {
 	 * @return
 	 */
 	public AccessResult isAccessible(RepositoryEntry entry, Identity forId, Boolean knowMember, boolean allowNonInteractiveAccess);
+	
+	public boolean isAccessToResourcePending(OLATResource resource, IdentityRef identity);
 
 
 	public Offer createOffer(OLATResource resource, String resourceName);
diff --git a/src/main/java/org/olat/resource/accesscontrol/manager/ACFrontendManager.java b/src/main/java/org/olat/resource/accesscontrol/manager/ACFrontendManager.java
index 265bc0ae40d..27a06e9a3a0 100644
--- a/src/main/java/org/olat/resource/accesscontrol/manager/ACFrontendManager.java
+++ b/src/main/java/org/olat/resource/accesscontrol/manager/ACFrontendManager.java
@@ -481,6 +481,12 @@ public class ACFrontendManager implements ACService, UserDataExportable {
 		}
 	}
 
+	@Override
+	public boolean isAccessToResourcePending(OLATResource resource, IdentityRef identity) {	
+		List<Order> orders = orderManager.findPendingOrders(resource, identity);
+		return !orders.isEmpty();
+	}
+
 	@Override
 	public boolean allowAccesToResource(final Identity identity, final Offer offer) {
 		//check if offer is ok: key is stupid but further check as date, validity...
diff --git a/src/main/java/org/olat/resource/accesscontrol/manager/ACOrderDAO.java b/src/main/java/org/olat/resource/accesscontrol/manager/ACOrderDAO.java
index 22c170c338f..2e150a94d6b 100644
--- a/src/main/java/org/olat/resource/accesscontrol/manager/ACOrderDAO.java
+++ b/src/main/java/org/olat/resource/accesscontrol/manager/ACOrderDAO.java
@@ -34,6 +34,7 @@ import org.olat.basesecurity.IdentityRef;
 import org.olat.core.commons.persistence.DB;
 import org.olat.core.commons.persistence.NativeQueryBuilder;
 import org.olat.core.commons.persistence.PersistenceHelper;
+import org.olat.core.commons.persistence.QueryBuilder;
 import org.olat.core.commons.persistence.SortKey;
 import org.olat.core.id.Identity;
 import org.olat.resource.OLATResource;
@@ -44,6 +45,7 @@ import org.olat.resource.accesscontrol.OrderLine;
 import org.olat.resource.accesscontrol.OrderPart;
 import org.olat.resource.accesscontrol.OrderStatus;
 import org.olat.resource.accesscontrol.model.AccessMethod;
+import org.olat.resource.accesscontrol.model.AccessTransactionStatus;
 import org.olat.resource.accesscontrol.model.OrderImpl;
 import org.olat.resource.accesscontrol.model.OrderLineImpl;
 import org.olat.resource.accesscontrol.model.OrderPartImpl;
@@ -253,6 +255,29 @@ public class ACOrderDAO {
 		Object rawOrders = query.getSingleResult();
 		return rawOrders instanceof Number ? ((Number)rawOrders).intValue() : 0;
 	}
+	
+	public List<Order> findPendingOrders(OLATResource resource, IdentityRef delivery) {
+		QueryBuilder sb = new QueryBuilder();
+		sb.append("select distinct o from ").append(OrderImpl.class.getName()).append(" o")
+		  .append(" inner join o.parts orderPart")
+		  .append(" inner join orderPart.lines orderLine")
+		  .append(" inner join orderLine.offer offer")
+		  .append(" where o.delivery.key=:deliveryKey and offer.resource.key=:resourceKey")
+		  .append(" and o.orderStatus=:status")
+		  .append(" and exists (select trx.key from actransaction as trx")
+		  .append("   where trx.order.key=o.key and trx.statusStr ").in(AccessTransactionStatus.PENDING)
+		  .append(" ) and not exists (select successTrx.key from actransaction as successTrx")
+		  .append("   where successTrx.order.key=o.key and successTrx.statusStr ")
+		  	.in(AccessTransactionStatus.SUCCESS, AccessTransactionStatus.ERROR, AccessTransactionStatus.CANCELED)
+		  .append(" )");
+
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Order.class)
+				.setParameter("resourceKey", resource.getKey())
+				.setParameter("deliveryKey", delivery.getKey())
+				.setParameter("status", OrderStatus.PREPAYMENT.name())
+				.getResultList();
+	}
 
 	/**
 	 * The method is optimized for our settings: 1 order -> 1 order part -> 1 order line
diff --git a/src/main/java/org/olat/resource/accesscontrol/ui/AccessRefusedController.java b/src/main/java/org/olat/resource/accesscontrol/ui/AccessRefusedController.java
index d1ff4fb5baf..e683057df6d 100644
--- a/src/main/java/org/olat/resource/accesscontrol/ui/AccessRefusedController.java
+++ b/src/main/java/org/olat/resource/accesscontrol/ui/AccessRefusedController.java
@@ -21,12 +21,13 @@ package org.olat.resource.accesscontrol.ui;
 
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
-import org.olat.core.gui.components.panel.SimpleStackedPanel;
-import org.olat.core.gui.components.panel.StackedPanel;
 import org.olat.core.gui.components.velocity.VelocityContainer;
 import org.olat.core.gui.control.Event;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.controller.BasicController;
+import org.olat.repository.RepositoryEntry;
+import org.olat.resource.accesscontrol.ACService;
+import org.springframework.beans.factory.annotation.Autowired;
 
 /**
  * 
@@ -35,14 +36,23 @@ import org.olat.core.gui.control.controller.BasicController;
  *
  */
 public class AccessRefusedController extends BasicController {
+	
+	@Autowired
+	private ACService acService;
 
 	public AccessRefusedController(UserRequest ureq, WindowControl wControl) {
+		this(ureq, wControl, null);
+	}
+	
+	public AccessRefusedController(UserRequest ureq, WindowControl wControl, RepositoryEntry entry) {
 		super(ureq, wControl);
 		
-		VelocityContainer mainVC = createVelocityContainer("access_refused");
-		StackedPanel contentP = new SimpleStackedPanel("");
-		contentP.setContent(mainVC);
-		putInitialPanel(contentP);
+		boolean pending = entry != null && acService
+				.isAccessToResourcePending(entry.getOlatResource(), getIdentity());
+		
+		String template = pending ? "access_pending" : "access_refused";
+		VelocityContainer mainVC = createVelocityContainer(template);
+		putInitialPanel(mainVC);
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/resource/accesscontrol/ui/_content/access_pending.html b/src/main/java/org/olat/resource/accesscontrol/ui/_content/access_pending.html
new file mode 100644
index 00000000000..a0e29b49dfa
--- /dev/null
+++ b/src/main/java/org/olat/resource/accesscontrol/ui/_content/access_pending.html
@@ -0,0 +1,4 @@
+<div class="o_access_method_list" role="alert">
+	<h3><i class="o_icon o_icon-fw o_ac_order_status_pending_icon"> </i> $r.translate("access.title")</h3>
+	<p class="o_warning clearfix">$r.translate("access.pending.desc")</p>
+</div>
diff --git a/src/main/java/org/olat/resource/accesscontrol/ui/_content/access_refused.html b/src/main/java/org/olat/resource/accesscontrol/ui/_content/access_refused.html
index a237f835cb6..12e2ef1ab43 100644
--- a/src/main/java/org/olat/resource/accesscontrol/ui/_content/access_refused.html
+++ b/src/main/java/org/olat/resource/accesscontrol/ui/_content/access_refused.html
@@ -1,14 +1,4 @@
-<div class="o_access_method_list">
-	<h3><i class="o_icon o_icon-fw o_icon_booking"> </i> $r.translate("access.title")</h3>
+<div class="o_access_method_list" role="alert">
+	<h3><i class="o_icon o_icon-fw o_icon_warning"> </i> $r.translate("access.title")</h3>
 	<p class="o_warning clearfix">$r.translate("access.refused.desc")</p>
 </div>
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/java/org/olat/resource/accesscontrol/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/resource/accesscontrol/ui/_i18n/LocalStrings_de.properties
index fc7a16214a3..ddc95bb5ab1 100644
--- a/src/main/java/org/olat/resource/accesscontrol/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/resource/accesscontrol/ui/_i18n/LocalStrings_de.properties
@@ -15,6 +15,7 @@ access.free.desc=Diese Ressource ist f\u00FCr Sie frei verf\u00FCgbar. W\u00E4hl
 access.free.title=Freie Ressource
 access.info.price.noVat={0}
 access.info.price.vat={0} (inkl. {1}% MwSt)
+access.pending.desc=Ihre Buchung dieser Ressource ist zur Zeit schwebend. Bitte kontaktieren Sie den Besitzer dieser Ressource wenn es zu lange dauert.
 access.refused.desc=Diese Ressource kann zur Zeit nicht gebucht werden. Bitte kontaktieren Sie den Besitzer dieser Ressource.
 access.title=Ressource buchen
 access.token.desc=Diese Ressource ist mit einem Zugangscode gesch\u00FCtzt. Geben Sie einen Zugangscode ein und w\u00E4hlen Sie die Schaltfl\u00E4che "$\:access.button" um sich f\u00FCr den Zugang zu registrieren. Den Zugangscode erhalten Sie von dem Betreiber dieser Ressource.
diff --git a/src/main/java/org/olat/resource/accesscontrol/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/resource/accesscontrol/ui/_i18n/LocalStrings_en.properties
index 3d63ea907d7..dc555c457f4 100644
--- a/src/main/java/org/olat/resource/accesscontrol/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/resource/accesscontrol/ui/_i18n/LocalStrings_en.properties
@@ -15,6 +15,7 @@ access.free.desc=This resource is freely available for you. Press the button "$\
 access.free.title=Free resource
 access.info.price.noVat={0}
 access.info.price.vat={0} (incl. {1}% VAT)
+access.pending.desc=The booking of this resource is currently pending. If the situation doesn't evolve, please contact the owner of this resource.
 access.refused.desc=This resource is currently not available for booking. Please contact the owner of this resource.
 access.title=Book resource
 access.token.desc=This resource is protected with an access code. Enter the access code and press the button "$\:access.button" to register for access. The access code can be obtained from the operator of this resource.
diff --git a/src/test/java/org/olat/resource/accesscontrol/ACFrontendManagerTest.java b/src/test/java/org/olat/resource/accesscontrol/ACFrontendManagerTest.java
index ce082acd0c7..f9ca761e1ad 100644
--- a/src/test/java/org/olat/resource/accesscontrol/ACFrontendManagerTest.java
+++ b/src/test/java/org/olat/resource/accesscontrol/ACFrontendManagerTest.java
@@ -398,6 +398,7 @@ public class ACFrontendManagerTest extends OlatTestCase {
 		CodeHelper.printMilliSecondTime(start, "One click");
 	}
 	
+	
 	@Test
 	public void testStandardMethods() {
 		Identity ident = JunitTestHelper.createAndPersistIdentityAsRndUser("ac-method-mgr");
diff --git a/src/test/java/org/olat/resource/accesscontrol/ACOrderManagerTest.java b/src/test/java/org/olat/resource/accesscontrol/ACOrderManagerTest.java
index fdafd681897..ccb7afe4e86 100644
--- a/src/test/java/org/olat/resource/accesscontrol/ACOrderManagerTest.java
+++ b/src/test/java/org/olat/resource/accesscontrol/ACOrderManagerTest.java
@@ -45,8 +45,10 @@ import org.olat.resource.accesscontrol.manager.ACOfferDAO;
 import org.olat.resource.accesscontrol.manager.ACOrderDAO;
 import org.olat.resource.accesscontrol.manager.ACTransactionDAO;
 import org.olat.resource.accesscontrol.model.AccessMethod;
+import org.olat.resource.accesscontrol.model.AccessTransactionImpl;
 import org.olat.resource.accesscontrol.model.AccessTransactionStatus;
 import org.olat.resource.accesscontrol.model.FreeAccessMethod;
+import org.olat.resource.accesscontrol.model.OrderImpl;
 import org.olat.resource.accesscontrol.model.PriceImpl;
 import org.olat.resource.accesscontrol.model.RawOrderItem;
 import org.olat.resource.accesscontrol.model.TokenAccessMethod;
@@ -511,6 +513,71 @@ public class ACOrderManagerTest extends OlatTestCase {
 		assertNotNull(retrievedOrder1);
 	}
 	
+	@Test
+	public void findPendingOrders() {
+		//create some offers to buy
+		Identity id = JunitTestHelper.createAndPersistIdentityAsRndUser("pending-");
+		OLATResource randomOres = createResource();
+		Offer offer = acService.createOffer(randomOres, "Test pending resource 1");
+		offer = acService.save(offer);
+		dbInstance.commitAndCloseSession();
+		
+		//create a link offer to method
+		List<AccessMethod> methods = acMethodManager.getAvailableMethodsByType(TokenAccessMethod.class);
+		assertNotNull(methods);
+		assertEquals(1, methods.size());
+		AccessMethod method = methods.get(0);
+		OfferAccess access = acMethodManager.createOfferAccess(offer, method);
+		acMethodManager.save(access);
+		dbInstance.commitAndCloseSession();
+		
+		//save an order
+		Order order = acOrderManager.saveOneClick(id, access);
+		((OrderImpl)order).setOrderStatus(OrderStatus.NEW);
+		order = acOrderManager.save(order);
+		dbInstance.commitAndCloseSession();
+		
+		List<Order> newOrders = acOrderManager.findPendingOrders(randomOres, id);
+		Assert.assertTrue(newOrders.isEmpty());
+
+		// add a transaction in new status
+		OrderPart part = order.getParts().get(0);
+		AccessTransaction accessTransaction = acTransactionManager.createTransaction(order, part, method);
+		((AccessTransactionImpl)accessTransaction).setStatus(AccessTransactionStatus.NEW);
+		acTransactionManager.save(accessTransaction);
+		dbInstance.commitAndCloseSession();
+		
+		List<Order> reallyNewOrders = acOrderManager.findPendingOrders(randomOres, id);
+		Assert.assertTrue(reallyNewOrders.isEmpty());
+		
+		// add a transaction pending
+		order = acOrderManager.loadOrderByKey(order.getKey());
+		((OrderImpl)order).setOrderStatus(OrderStatus.PREPAYMENT);
+		order = acOrderManager.save(order);
+		part = order.getParts().get(0);
+		AccessTransaction pendingAccessTransaction = acTransactionManager.createTransaction(order, part, method);
+		((AccessTransactionImpl)pendingAccessTransaction).setStatus(AccessTransactionStatus.PENDING);
+		acTransactionManager.save(pendingAccessTransaction);
+		dbInstance.commitAndCloseSession();
+		
+		List<Order> pendingOrders = acOrderManager.findPendingOrders(randomOres, id);
+		Assert.assertFalse(pendingOrders.isEmpty());
+		Assert.assertEquals(order, pendingOrders.get(0));
+		
+		// add a transaction success
+		order = acOrderManager.loadOrderByKey(order.getKey());
+		((OrderImpl)order).setOrderStatus(OrderStatus.PREPAYMENT);
+		order = acOrderManager.save(order);
+		part = order.getParts().get(0);
+		AccessTransaction successAccessTransaction = acTransactionManager.createTransaction(order, part, method);
+		((AccessTransactionImpl)successAccessTransaction).setStatus(AccessTransactionStatus.SUCCESS);
+		acTransactionManager.save(successAccessTransaction);
+		dbInstance.commitAndCloseSession();
+		
+		List<Order> successOrders = acOrderManager.findPendingOrders(randomOres, id);
+		Assert.assertTrue(successOrders.isEmpty());
+	}
+	
 	private OLATResource createResource() {
 		//create a repository entry
 		OLATResourceable resourceable = new TypedResourceable(UUID.randomUUID().toString().replace("-", ""));
-- 
GitLab