Commit 60ba90b9 by Tuomas Riihimäki

Unpaid bill slot reservation percent

Add ability to limit the number of slots unpaid bill can reserve from places
This should mitigate the problem that one user can reserve all places for X number
of minutes by creating a huge bill and not paying it

Also add default Long value to LanEventPropertyKey
1 parent 1d11e26a
...@@ -214,7 +214,7 @@ public class EventBean implements EventBeanLocal { ...@@ -214,7 +214,7 @@ public class EventBean implements EventBeanLocal {
LanEventProperty retProp = eventPropertyFacade.find(getCurrentEvent(), property); LanEventProperty retProp = eventPropertyFacade.find(getCurrentEvent(), property);
long ret = 0; long ret = 0;
if (retProp == null) { if (retProp == null) {
ret = Long.parseLong(property.getDefaultvalue()); ret = property.getDefaultLong();
} else { } else {
ret = retProp.getLongValue(); ret = retProp.getLongValue();
} }
...@@ -242,7 +242,7 @@ public class EventBean implements EventBeanLocal { ...@@ -242,7 +242,7 @@ public class EventBean implements EventBeanLocal {
if (retProp == null) { if (retProp == null) {
String def = property.getDefaultvalue(); String def = property.getDefaultvalue();
if(def != null && !def.trim().isEmpty() && !def.trim().equals("0")) if(def != null && !def.trim().isEmpty() && def.trim().equals("1"))
ret = true; ret = true;
} else { } else {
......
...@@ -55,10 +55,13 @@ import fi.codecrew.moya.facade.PlaceSlotFacade; ...@@ -55,10 +55,13 @@ import fi.codecrew.moya.facade.PlaceSlotFacade;
import fi.codecrew.moya.facade.ProductFacade; import fi.codecrew.moya.facade.ProductFacade;
import fi.codecrew.moya.facade.UserFacade; import fi.codecrew.moya.facade.UserFacade;
import fi.codecrew.moya.model.AccountEvent; import fi.codecrew.moya.model.AccountEvent;
import fi.codecrew.moya.model.Bill;
import fi.codecrew.moya.model.Discount; import fi.codecrew.moya.model.Discount;
import fi.codecrew.moya.model.EventUser; import fi.codecrew.moya.model.EventUser;
import fi.codecrew.moya.model.InventoryEvent; import fi.codecrew.moya.model.InventoryEvent;
import fi.codecrew.moya.model.LanEvent; import fi.codecrew.moya.model.LanEvent;
import fi.codecrew.moya.model.LanEventPropertyKey;
import fi.codecrew.moya.model.PlaceSlot;
import fi.codecrew.moya.model.Product; import fi.codecrew.moya.model.Product;
import fi.codecrew.moya.model.ProductFlag; import fi.codecrew.moya.model.ProductFlag;
import fi.codecrew.moya.model.ProductLimitation; import fi.codecrew.moya.model.ProductLimitation;
...@@ -207,26 +210,81 @@ public class ProductBean implements ProductBeanLocal { ...@@ -207,26 +210,81 @@ public class ProductBean implements ProductBeanLocal {
HashMap<Integer, BigDecimal> ret = new HashMap<>(); HashMap<Integer, BigDecimal> ret = new HashMap<>();
for (Entry<Integer, BigDecimal> pc : prodCounts.entrySet()) for (Entry<Integer, BigDecimal> pc : prodCounts.entrySet())
{ {
Product prod = productFacade.find(pc.getKey()); Product prod = productFacade.find(pc.getKey());
// if this is a place product and not a prepaid credit product and places are not created on the fly
// get the available buyable places from database and check the number of unused places
if (!prod.getProductFlags().contains(ProductFlag.PREPAID_CREDIT)
&& !prod.getProductFlags().contains(ProductFlag.CREATE_NEW_PLACE_WHEN_BOUGHT)
&& prod.getPlaces() != null
&& !prod.getPlaces().isEmpty()) {
Long selectableCount = placeFacade.countSelectable(prod);
int unusedSlotcount = getUnusedSlotcount(prod);
int freeCount = selectableCount.intValue() - unusedSlotcount;
// logger.info("Prodlimit selectable {}, unused {}, free {}, prod {}", selectableCount, unusedSlots, freeCount, prod);
ret.put(prod.getId(), BigDecimal.valueOf(freeCount));
} else {
BigDecimal lim = getPrivateProductLimit(prod, user, prodCounts, userroles); BigDecimal lim = getPrivateProductLimit(prod, user, prodCounts, userroles);
ret.put(prod.getId(), lim); ret.put(prod.getId(), lim);
// logger.info("Added product limit {} to {}", lim, prod); // logger.info("Added product limit {} to {}", lim, prod);
if (!prod.getProductFlags().contains(ProductFlag.PREPAID_CREDIT) && !prod.getProductFlags().contains(ProductFlag.CREATE_NEW_PLACE_WHEN_BOUGHT)) }
}
return ret;
}
/**
* How many unused slots there are for the product.
*
* @param prod
* @return
*/
private int getUnusedSlotcount(Product prod)
{ {
if (prod.getPlaces() != null && !prod.getPlaces().isEmpty()) { // How many percent of places can be reserved from shop by one unpaid bill
long unpaidSlotpercent = eventbean.getPropertyLong(LanEventPropertyKey.RESERVE_UNPAID_SLOT_PERCENT);
Long selectableCount = placeFacade.countSelectable(prod); int unusedSlotcount = 0;
Long unusedSlots = slotfacade.findUnusedSlotsCount(prod, false);
int freeCount = selectableCount.intValue() - unusedSlots.intValue();
logger.info("Prodlimit selectable {}, unused {}, free {}, prod {}", selectableCount, unusedSlots, freeCount, prod); // If unpaid slot percent is 0 or 100, calculate directly from db.
ret.put(prod.getId(), BigDecimal.valueOf(freeCount)); if (unpaidSlotpercent < 1 || unpaidSlotpercent > 99) {
unusedSlotcount = slotfacade.findUnusedSlotsCount(prod, false).intValue();
} else {
// Calculate only 'unpaidSlotpercent' of total places to be reserver from one unpaid bill.
// This is to try to reduce the possibility of abuse by creating a 1000 place bill
// and obviously never intending to pay them.
final List<PlaceSlot> unusedSlots = slotfacade.findUnusedSlots(prod, false);
final Map<Integer, Integer> unpaidBills = new HashMap<>();
int prodPlacecount = prod.getPlaces().size();
for (PlaceSlot s : unusedSlots) {
// If bill s paid (or there is no bill), calculate all slots
if (s.getBill() == null || s.getBill().isPaid()) {
++unusedSlotcount;
} else {
// If bill is paid, calculate only 'unpaidSlotpercent'% of slots
Integer billcnt = unpaidBills.get(s.getBill().getId());
if (billcnt == null) {
billcnt = 0;
}
if (billcnt * 100 / prodPlacecount < unpaidSlotpercent) {
++unusedSlotcount;
} }
unpaidBills.put(s.getBill().getId(), ++billcnt);
} }
} }
return ret; }
return unusedSlotcount;
} }
@Override @Override
...@@ -420,7 +478,7 @@ public class ProductBean implements ProductBeanLocal { ...@@ -420,7 +478,7 @@ public class ProductBean implements ProductBeanLocal {
public AccountEvent createAccountEvent(Product product, BigDecimal overriddenUnitPrice, BigDecimal quantity, EventUser user) { public AccountEvent createAccountEvent(Product product, BigDecimal overriddenUnitPrice, BigDecimal quantity, EventUser user) {
user = eventUserFacade.reload(user); user = eventUserFacade.reload(user);
AccountEvent ret = productPBean.createAccountEvent(product, overriddenUnitPrice , quantity, user, Calendar.getInstance(), null); AccountEvent ret = productPBean.createAccountEvent(product, overriddenUnitPrice, quantity, user, Calendar.getInstance(), null);
cardTemplateBean.checkPrintedCard(user); cardTemplateBean.checkPrintedCard(user);
return ret; return ret;
} }
...@@ -565,13 +623,12 @@ public class ProductBean implements ProductBeanLocal { ...@@ -565,13 +623,12 @@ public class ProductBean implements ProductBeanLocal {
List<Product> allProducts = productFacade.findAll(); List<Product> allProducts = productFacade.findAll();
List<Product> ticketProducts = new ArrayList<>(); List<Product> ticketProducts = new ArrayList<>();
for(Product product : allProducts) { for (Product product : allProducts) {
if(product.getProductFlags().contains(ProductFlag.RESERVE_PLACE_WHEN_BOUGHT)) { if (product.getProductFlags().contains(ProductFlag.RESERVE_PLACE_WHEN_BOUGHT)) {
ticketProducts.add(product); ticketProducts.add(product);
} else if(product.getPlaces() != null && product.getPlaces().size() > 0) { } else if (product.getPlaces() != null && product.getPlaces().size() > 0) {
ticketProducts.add(product); ticketProducts.add(product);
} }
...@@ -579,7 +636,6 @@ public class ProductBean implements ProductBeanLocal { ...@@ -579,7 +636,6 @@ public class ProductBean implements ProductBeanLocal {
return ticketProducts; return ticketProducts;
// TODO: fix this facade and use it // TODO: fix this facade and use it
//return placeFacade.getPlaceProducts(); //return placeFacade.getPlaceProducts();
......
...@@ -113,11 +113,22 @@ public class PlaceSlotFacade extends IntegerPkGenericFacade<PlaceSlot> { ...@@ -113,11 +113,22 @@ public class PlaceSlotFacade extends IntegerPkGenericFacade<PlaceSlot> {
return getEm().createQuery(q).getResultList(); return getEm().createQuery(q).getResultList();
} }
public Long findUnusedSlotsCount(Product prod, boolean paidOnly) { public List<PlaceSlot> findUnusedSlots(Product prod, boolean paidOnly) {
CriteriaBuilder cb = getEm().getCriteriaBuilder(); CriteriaBuilder cb = getEm().getCriteriaBuilder();
CriteriaQuery<Long> q = cb.createQuery(Long.class); CriteriaQuery<PlaceSlot> q = cb.createQuery(PlaceSlot.class);
Root<PlaceSlot> root = q.from(PlaceSlot.class); Root<PlaceSlot> root = q.from(PlaceSlot.class);
q.select(cb.count(root)); q.select(root);
List<Predicate> preds = getUnusedSlotPredicates(cb, root, prod, paidOnly);
q.where(preds.toArray(new Predicate[preds.size()]));
return getEm().createQuery(q).getResultList();
}
private static List<Predicate> getUnusedSlotPredicates(CriteriaBuilder cb, Root<PlaceSlot> root, Product prod, boolean paidOnly)
{
Path<Bill> bill = root.get(PlaceSlot_.bill); Path<Bill> bill = root.get(PlaceSlot_.bill);
List<Predicate> preds = new ArrayList<>(); List<Predicate> preds = new ArrayList<>();
...@@ -138,9 +149,18 @@ public class PlaceSlotFacade extends IntegerPkGenericFacade<PlaceSlot> { ...@@ -138,9 +149,18 @@ public class PlaceSlotFacade extends IntegerPkGenericFacade<PlaceSlot> {
// Check that slot is not used // Check that slot is not used
preds.add(cb.isNull(root.get(PlaceSlot_.place))); preds.add(cb.isNull(root.get(PlaceSlot_.place)));
preds.add(cb.isNull(root.get(PlaceSlot_.used))); preds.add(cb.isNull(root.get(PlaceSlot_.used)));
return preds;
q.where(preds.toArray(new Predicate[preds.size()])); }
public Long findUnusedSlotsCount(Product prod, boolean paidOnly) {
CriteriaBuilder cb = getEm().getCriteriaBuilder();
CriteriaQuery<Long> q = cb.createQuery(Long.class);
Root<PlaceSlot> root = q.from(PlaceSlot.class);
q.select(cb.count(root));
List<Predicate> preds = getUnusedSlotPredicates(cb, root, prod, paidOnly);
q.where(preds.toArray(new Predicate[preds.size()]));
Long count = super.getSingleNullableResult(getEm().createQuery(q)); Long count = super.getSingleNullableResult(getEm().createQuery(q));
return count; return count;
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
package fi.codecrew.moya.model; package fi.codecrew.moya.model;
public enum LanEventPropertyKey { public enum LanEventPropertyKey {
EVENT_LOGO(Type.DATA, null), EVENT_LOGO(Type.DATA),
INVITEMAIL_SUBJECT(Type.TEXT, "Invitation to Moya Online Youth Accumulator"), INVITEMAIL_SUBJECT(Type.TEXT, "Invitation to Moya Online Youth Accumulator"),
INVITEMAIL_CONTENT(Type.TEXT, "You have been invited to an event by {1}.\n\nYou can register to intranet at: {0}."), INVITEMAIL_CONTENT(Type.TEXT, "You have been invited to an event by {1}.\n\nYou can register to intranet at: {0}."),
BILL_PAID_MAIL_SUBJECT(Type.TEXT, "[{0}] Lasku merkitty maksetuksi"), BILL_PAID_MAIL_SUBJECT(Type.TEXT, "[{0}] Lasku merkitty maksetuksi"),
...@@ -29,25 +29,34 @@ public enum LanEventPropertyKey { ...@@ -29,25 +29,34 @@ public enum LanEventPropertyKey {
ADMIN_MAIL(Type.TEXT, "moya@codecrew.fi"), ADMIN_MAIL(Type.TEXT, "moya@codecrew.fi"),
EVENT_LAYOUT(Type.TEXT, "primelayout"), EVENT_LAYOUT(Type.TEXT, "primelayout"),
SHOP_DEFAULT_CASH(Type.BOOL, null), SHOP_DEFAULT_CASH(Type.BOOL),
PLACECODE_FROM_USER(Type.BOOL, "1"), PLACECODE_FROM_USER(Type.BOOL, "1"),
PLACECODE_PRINT_ONLY_OWN(Type.BOOL, null), PLACECODE_PRINT_ONLY_OWN(Type.BOOL),
CHECK_BILL_STATS_PERMISSION(Type.BOOL, null), CHECK_BILL_STATS_PERMISSION(Type.BOOL),
GATHER_OTHER_BILL_INFO(Type.BOOL, null), GATHER_OTHER_BILL_INFO(Type.BOOL),
GATHER_SHIRT_SIZE(Type.BOOL, null), GATHER_SHIRT_SIZE(Type.BOOL),
ALLOW_BILLING(Type.BOOL, null), ALLOW_BILLING(Type.BOOL),
BILL_EXPIRE_HOURS(Type.LONG, "1"), BILL_EXPIRE_HOURS(Type.LONG, 1l),
TEMPLATE_PROPERTY1(Type.TEXT, null), TEMPLATE_PROPERTY1(Type.TEXT),
TEMPLATE_PROPERTY2(Type.TEXT, null), TEMPLATE_PROPERTY2(Type.TEXT),
TEMPLATE_PROPERTY3(Type.TEXT, null), TEMPLATE_PROPERTY3(Type.TEXT),
TEMPLATE_PROPERTY4(Type.TEXT, null), TEMPLATE_PROPERTY4(Type.TEXT),
TEMPLATE_PROPERTY5(Type.TEXT, null), TEMPLATE_PROPERTY5(Type.TEXT),
INVITE_ONLY_EVENT(Type.BOOL, null), INVITE_ONLY_EVENT(Type.BOOL),
USE_ETICKET(Type.BOOL, "1"), USE_ETICKET(Type.BOOL, "1"),
ETICKETMAIL_SUBJECT(Type.TEXT, "Your etickets to Moya Online Youth Accumulator"), ETICKETMAIL_SUBJECT(Type.TEXT, "Your etickets to Moya Online Youth Accumulator"),
ETICKETMAIL_CONTENT(Type.TEXT, "Hello {1},\n\nYou can find your etickets to an event from: {0}"), ETICKETMAIL_CONTENT(Type.TEXT, "Hello {1},\n\nYou can find your etickets to an event from: {0}"),
MAP_QUEUE(Type.BOOL, null), /**
DISABLE_PHOTO_ON_KIOSK(Type.BOOL, null), * Should users be put to queue when reserving places from map, ie. only a
* limited number of people can reserve places at once.
*/
MAP_QUEUE(Type.BOOL),
DISABLE_PHOTO_ON_KIOSK(Type.BOOL),
/**
* How many percent of the available places can be locked by one unpaid and
* unexpired bill
*/
RESERVE_UNPAID_SLOT_PERCENT(Type.LONG, 10l),
; ;
...@@ -57,6 +66,7 @@ public enum LanEventPropertyKey { ...@@ -57,6 +66,7 @@ public enum LanEventPropertyKey {
private final String defaultvalue; private final String defaultvalue;
private final Type type; private final Type type;
private final long defaultLong;
public boolean isText() { public boolean isText() {
return Type.TEXT.equals(type); return Type.TEXT.equals(type);
...@@ -78,10 +88,25 @@ public enum LanEventPropertyKey { ...@@ -78,10 +88,25 @@ public enum LanEventPropertyKey {
return Type.LONG.equals(type); return Type.LONG.equals(type);
} }
private LanEventPropertyKey(Type t)
{
this.type = t;
defaultvalue = null;
defaultLong = 0;
}
private LanEventPropertyKey(Type t, String def) private LanEventPropertyKey(Type t, String def)
{ {
this.type = t; this.type = t;
defaultvalue = def; defaultvalue = def;
defaultLong = 0;
}
private LanEventPropertyKey(Type t, Long def)
{
this.type = t;
defaultvalue = null;
defaultLong = def;
} }
public String getDefaultvalue() { public String getDefaultvalue() {
...@@ -91,4 +116,8 @@ public enum LanEventPropertyKey { ...@@ -91,4 +116,8 @@ public enum LanEventPropertyKey {
public Type getType() { public Type getType() {
return type; return type;
} }
public Long getDefaultLong() {
return defaultLong;
}
} }
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!