Commit 39f25ee8 by Tuukka Kivilahti

Merge branch 'releaseplace' into 'master'

Updates

Look at the commits

See merge request !178
2 parents f607b9ae b4ee141e
Showing with 351 additions and 132 deletions
...@@ -18,18 +18,64 @@ ...@@ -18,18 +18,64 @@
*/ */
package fi.codecrew.moya.beans; package fi.codecrew.moya.beans;
import java.io.Serializable;
import java.util.List;
import java.util.Map; import java.util.Map;
import javax.ejb.Local; import javax.ejb.Local;
import fi.codecrew.moya.beans.StatisticsBeanLocal.ProductSlotcountMover;
import fi.codecrew.moya.model.Product;
@Local @Local
public interface StatisticsBeanLocal { public interface StatisticsBeanLocal {
public Long getGroupMembershipsEnteredEvent(); public Long getGroupMembershipsEnteredEvent();
public Long getCardDeliveredCount(); public Long getCardDeliveredCount();
public Long getGroupMembershipsTotalCount(); public Long getGroupMembershipsTotalCount();
public Map<Long, Long> getHourlyIncomingStatistics(long startingFromMillis, int hourCount); public Map<Long, Long> getHourlyIncomingStatistics(long startingFromMillis, int hourCount);
public static class ProductSlotcountMover implements Serializable {
/**
*
*/
private static final long serialVersionUID = -7759706761198824766L;
private final Product product;
private Long totalSlots;
private Long unusedSlots;
public ProductSlotcountMover(Product p) {
this.product = p;
}
public Long getTotalSlots() {
return totalSlots;
}
public void setTotalSlots(Long totalSlots) {
this.totalSlots = totalSlots;
}
public Long getUnusedSlots() {
return unusedSlots;
}
public void setUnusedSlots(Long unusedSlots) {
this.unusedSlots = unusedSlots;
}
public Product getProduct() {
return product;
}
}
List<ProductSlotcountMover> getUnusedSlots(boolean onlyPaid);
} }
...@@ -18,6 +18,9 @@ ...@@ -18,6 +18,9 @@
*/ */
package fi.codecrew.moya.beans; package fi.codecrew.moya.beans;
import java.util.List;
import java.util.Map;
import javax.ejb.Local; import javax.ejb.Local;
import fi.codecrew.moya.util.SvmReturnType; import fi.codecrew.moya.util.SvmReturnType;
...@@ -33,4 +36,9 @@ public interface VerkkomaksutFiBeanLocal { ...@@ -33,4 +36,9 @@ public interface VerkkomaksutFiBeanLocal {
boolean validateReturn(SvmReturnType type, String orderNumber, String timestamp, String paid, String method, String authcode); boolean validateReturn(SvmReturnType type, String orderNumber, String timestamp, String paid, String method, String authcode);
String getMerchantId();
Map<Integer, String> getAuthcodeForBills(List<Bill> bills);
} }
...@@ -288,6 +288,7 @@ public class MenuBean implements MenuBeanLocal { ...@@ -288,6 +288,7 @@ public class MenuBean implements MenuBeanLocal {
mapnavi.addPage(menuitemfacade.findOrCreate("/map/list"), MapPermission.MANAGE_MAPS); mapnavi.addPage(menuitemfacade.findOrCreate("/map/list"), MapPermission.MANAGE_MAPS);
mapnavi.addPage(menuitemfacade.findOrCreate("/map/create"), MapPermission.MANAGE_MAPS); mapnavi.addPage(menuitemfacade.findOrCreate("/map/create"), MapPermission.MANAGE_MAPS);
mapnavi.addPage(menuitemfacade.findOrCreate("/map/edit"), null).setVisible(false); mapnavi.addPage(menuitemfacade.findOrCreate("/map/edit"), null).setVisible(false);
mapnavi.addPage(menuitemfacade.findOrCreate("/neomap/quemgmt"), null).setVisible(false);
// event // event
MenuNavigation adminevent = adminmenu.addPage(null, null); MenuNavigation adminevent = adminmenu.addPage(null, null);
......
...@@ -618,6 +618,7 @@ public class PlaceBean implements PlaceBeanLocal { ...@@ -618,6 +618,7 @@ public class PlaceBean implements PlaceBeanLocal {
// remove also slot from place // remove also slot from place
if (slot != null) { if (slot != null) {
slot.setPlace(null); slot.setPlace(null);
slot.setUsed(null);
place.setReserverSlot(null); place.setReserverSlot(null);
} }
return place; return place;
......
...@@ -217,9 +217,9 @@ public class ProductBean implements ProductBeanLocal { ...@@ -217,9 +217,9 @@ public class ProductBean implements ProductBeanLocal {
if (prod.getPlaces() != null && !prod.getPlaces().isEmpty()) { if (prod.getPlaces() != null && !prod.getPlaces().isEmpty()) {
Long selectableCount = placeFacade.countSelectable(prod); Long selectableCount = placeFacade.countSelectable(prod);
Long unusedSlots = slotfacade.findUnusedSlotsCount(prod); Long unusedSlots = slotfacade.findUnusedSlotsCount(prod, false);
int freeCount = selectableCount.intValue() - unusedSlots.intValue(); int freeCount = selectableCount.intValue() - unusedSlots.intValue();
logger.info("Prodlimit selectable {}, unused {}, free {}, prod {}", selectableCount, unusedSlots, freeCount, prod); logger.info("Prodlimit selectable {}, unused {}, free {}, prod {}", selectableCount, unusedSlots, freeCount, prod);
ret.put(prod.getId(), BigDecimal.valueOf(freeCount)); ret.put(prod.getId(), BigDecimal.valueOf(freeCount));
} }
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
*/ */
package fi.codecrew.moya.beans; package fi.codecrew.moya.beans;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -32,8 +33,10 @@ import org.slf4j.LoggerFactory; ...@@ -32,8 +33,10 @@ import org.slf4j.LoggerFactory;
import fi.codecrew.moya.enums.apps.EventPermission; import fi.codecrew.moya.enums.apps.EventPermission;
import fi.codecrew.moya.facade.GroupMembershipFacade; import fi.codecrew.moya.facade.GroupMembershipFacade;
import fi.codecrew.moya.facade.PlaceSlotFacade;
import fi.codecrew.moya.facade.PrintedCardFacade; import fi.codecrew.moya.facade.PrintedCardFacade;
import fi.codecrew.moya.model.GroupMembership; import fi.codecrew.moya.model.GroupMembership;
import fi.codecrew.moya.model.Product;
/** /**
* Session Bean implementation class FoodWaveBean * Session Bean implementation class FoodWaveBean
...@@ -43,14 +46,18 @@ import fi.codecrew.moya.model.GroupMembership; ...@@ -43,14 +46,18 @@ import fi.codecrew.moya.model.GroupMembership;
public class StatisticsBean implements StatisticsBeanLocal { public class StatisticsBean implements StatisticsBeanLocal {
private static final Logger logger = LoggerFactory.getLogger(StatisticsBean.class); private static final Logger logger = LoggerFactory.getLogger(StatisticsBean.class);
@EJB @EJB
GroupMembershipFacade groupMembershipFacade; GroupMembershipFacade groupMembershipFacade;
@EJB @EJB
PrintedCardFacade printedCardFacade; PrintedCardFacade printedCardFacade;
@EJB
private PlaceSlotFacade psfacade;
@EJB
private ProductBeanLocal prodbean;
@Override @Override
@RolesAllowed(EventPermission.S_VIEW_STATISTICS) @RolesAllowed(EventPermission.S_VIEW_STATISTICS)
...@@ -72,38 +79,44 @@ public class StatisticsBean implements StatisticsBeanLocal { ...@@ -72,38 +79,44 @@ public class StatisticsBean implements StatisticsBeanLocal {
@Override @Override
public Map<Long, Long> getHourlyIncomingStatistics(long startingFromMillis, int hourCount) { public Map<Long, Long> getHourlyIncomingStatistics(long startingFromMillis, int hourCount) {
List<GroupMembership> groupMemberships = groupMembershipFacade.findAllEnteredBetween(startingFromMillis, (startingFromMillis + ((long) hourCount)*60l*60l*1000l)); List<GroupMembership> groupMemberships = groupMembershipFacade.findAllEnteredBetween(startingFromMillis, (startingFromMillis + ((long) hourCount) * 60l * 60l * 1000l));
HashMap<Long, Long> retMap = new HashMap<>(); HashMap<Long, Long> retMap = new HashMap<>();
long currentTimestamp = startingFromMillis; long currentTimestamp = startingFromMillis;
long hour = (60l*60l*1000l); long hour = (60l * 60l * 1000l);
long hourEntered = 0; long hourEntered = 0;
for(GroupMembership gm : groupMemberships) { for (GroupMembership gm : groupMemberships) {
// find the hour this one belongs to (sometime we need to skip empty hours) // find the hour this one belongs to (sometime we need to skip empty hours)
while(gm.getEnteredEvent().getTimeInMillis() > currentTimestamp+hour) { while (gm.getEnteredEvent().getTimeInMillis() > currentTimestamp + hour) {
retMap.put(currentTimestamp, hourEntered); retMap.put(currentTimestamp, hourEntered);
hourEntered = 0; hourEntered = 0;
currentTimestamp += hour; currentTimestamp += hour;
} }
hourEntered++; hourEntered++;
} }
return retMap; return retMap;
} }
}
@Override
public List<ProductSlotcountMover> getUnusedSlots(boolean onlyPaid) {
List<ProductSlotcountMover> ret = new ArrayList<>();
for (Product p : prodbean.getProducts()) {
logger.info("Products for stats {}", p);
ProductSlotcountMover m = new ProductSlotcountMover(p);
m.setUnusedSlots(psfacade.findUnusedSlotsCount(p, onlyPaid));
m.setTotalSlots(psfacade.totalSlotcount(p, onlyPaid));
if (m.getTotalSlots() > 0 && m.getUnusedSlots() > 0) {
ret.add(m);
}
}
return ret;
}
}
...@@ -27,6 +27,9 @@ import java.math.BigDecimal; ...@@ -27,6 +27,9 @@ import java.math.BigDecimal;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.security.DeclareRoles; import javax.annotation.security.DeclareRoles;
import javax.annotation.security.RolesAllowed; import javax.annotation.security.RolesAllowed;
...@@ -63,6 +66,7 @@ import fi.codecrew.moya.beanutil.DecimalXMLAdapter; ...@@ -63,6 +66,7 @@ import fi.codecrew.moya.beanutil.DecimalXMLAdapter;
import fi.codecrew.moya.clientutils.BortalLocalContextHolder; import fi.codecrew.moya.clientutils.BortalLocalContextHolder;
import fi.codecrew.moya.enums.apps.BillPermission; import fi.codecrew.moya.enums.apps.BillPermission;
import fi.codecrew.moya.facade.BillFacade; import fi.codecrew.moya.facade.BillFacade;
import fi.codecrew.moya.facade.LanEventPrivatePropertyFacade;
import fi.codecrew.moya.model.Bill; import fi.codecrew.moya.model.Bill;
import fi.codecrew.moya.model.LanEventPrivateProperty; import fi.codecrew.moya.model.LanEventPrivateProperty;
import fi.codecrew.moya.model.LanEventPrivatePropertyKey; import fi.codecrew.moya.model.LanEventPrivatePropertyKey;
...@@ -77,7 +81,7 @@ import fi.codecrew.moya.verkkomaksutfi.PaymentEntry; ...@@ -77,7 +81,7 @@ import fi.codecrew.moya.verkkomaksutfi.PaymentEntry;
*/ */
@Stateless @Stateless
@LocalBean @LocalBean
@DeclareRoles({ BillPermission.S_CREATE_VERKKOMAKSU }) @DeclareRoles({ BillPermission.S_CREATE_VERKKOMAKSU, BillPermission.S_WRITE_ALL })
public class VerkkomaksutFiBean implements VerkkomaksutFiBeanLocal { public class VerkkomaksutFiBean implements VerkkomaksutFiBeanLocal {
private static final Logger logger = LoggerFactory.getLogger(VerkkomaksutFiBean.class); private static final Logger logger = LoggerFactory.getLogger(VerkkomaksutFiBean.class);
...@@ -343,4 +347,31 @@ public class VerkkomaksutFiBean implements VerkkomaksutFiBeanLocal { ...@@ -343,4 +347,31 @@ public class VerkkomaksutFiBean implements VerkkomaksutFiBeanLocal {
logger.warn(msg, params); logger.warn(msg, params);
} }
@EJB
private LanEventPrivatePropertyFacade eventPrivatePropertyFacade;
@Override
public String getMerchantId() {
String ret = null;
if (isSvmEnabled() && permbean.hasPermission(BillPermission.WRITE_ALL)) {
LanEventPrivateProperty e = eventPrivatePropertyFacade.getPropertyForEvent(LanEventPrivatePropertyKey.VERKKOMAKSU_MERCHANT_ID);
if (e != null && e.getTextvalue() != null && !e.getTextvalue().isEmpty())
ret = e.getTextvalue();
}
return ret;
}
@Override
public Map<Integer, String> getAuthcodeForBills(List<Bill> bills) {
Map<Integer, String> ret = new HashMap<>();
if (isSvmEnabled() && permbean.hasPermission(BillPermission.WRITE_ALL)) {
String merchantId = eventPrivatePropertyFacade.getPropertyForEvent(LanEventPrivatePropertyKey.VERKKOMAKSU_MERCHANT_ID).getTextvalue();
String merchantPassword = eventPrivatePropertyFacade.getPropertyForEvent(LanEventPrivatePropertyKey.VERKKOMAKSU_MERCHANT_PASSWORD).getTextvalue();
for (Bill b : bills) {
ret.put(b.getId(),PasswordFunctions.calculateMd5("&", merchantPassword, merchantId, b.getId().toString()).toUpperCase());
}
}
return ret;
}
} }
...@@ -206,7 +206,8 @@ public class PlaceFacade extends IntegerPkGenericFacade<Place> { ...@@ -206,7 +206,8 @@ public class PlaceFacade extends IntegerPkGenericFacade<Place> {
cb.equal(root.get(Place_.product), product), cb.equal(root.get(Place_.product), product),
cb.isNull(root.get(Place_.releaseTime)), cb.isNull(root.get(Place_.releaseTime)),
cb.isNull(root.get(Place_.group)), cb.isNull(root.get(Place_.group)),
cb.isFalse(root.get(Place_.disabled)) cb.isFalse(root.get(Place_.disabled)),
cb.isTrue(root.get(Place_.buyable))
); );
return super.getSingleNullableResult(getEm().createQuery(cq)); return super.getSingleNullableResult(getEm().createQuery(cq));
} }
......
...@@ -107,41 +107,65 @@ public class PlaceSlotFacade extends IntegerPkGenericFacade<PlaceSlot> { ...@@ -107,41 +107,65 @@ public class PlaceSlotFacade extends IntegerPkGenericFacade<PlaceSlot> {
CriteriaBuilder cb = getEm().getCriteriaBuilder(); CriteriaBuilder cb = getEm().getCriteriaBuilder();
CriteriaQuery<PlaceSlot> q = cb.createQuery(PlaceSlot.class); CriteriaQuery<PlaceSlot> q = cb.createQuery(PlaceSlot.class);
Root<PlaceSlot> root = q.from(PlaceSlot.class); Root<PlaceSlot> root = q.from(PlaceSlot.class);
q.where(cb.equal(root.get(PlaceSlot_.bill).get(Bill_.user), user)); Path<Bill> bill = root.get(PlaceSlot_.bill);
q.where(cb.equal(bill.get(Bill_.user), user),
cb.isNotNull(bill.get(Bill_.paidDate)));
return getEm().createQuery(q).getResultList(); return getEm().createQuery(q).getResultList();
} }
public Long findUnusedSlotsCount(Product prod) { public Long findUnusedSlotsCount(Product prod, boolean paidOnly) {
CriteriaBuilder cb = getEm().getCriteriaBuilder(); CriteriaBuilder cb = getEm().getCriteriaBuilder();
CriteriaQuery<Long> q = cb.createQuery(Long.class); CriteriaQuery<Long> q = cb.createQuery(Long.class);
Root<PlaceSlot> root = q.from(PlaceSlot.class); Root<PlaceSlot> root = q.from(PlaceSlot.class);
q.select(cb.count(root)); q.select(cb.count(root));
Path<Bill> bill = root.get(PlaceSlot_.bill); Path<Bill> bill = root.get(PlaceSlot_.bill);
Path<Calendar> billexp = bill.get(Bill_.expires);
q.where(cb.equal(root.get(PlaceSlot_.product), prod), List<Predicate> preds = new ArrayList<>();
cb.or(cb.isNull(billexp), preds.add(cb.equal(root.get(PlaceSlot_.product), prod));
cb.greaterThan(billexp, Calendar.getInstance())
), if (paidOnly) {
cb.isNull(root.get(PlaceSlot_.place)), // Only if bill is paid
cb.isNull(root.get(PlaceSlot_.used)) preds.add(cb.isNotNull(bill.get(Bill_.paidDate)));
); } else {
// If expire is null or has not passed, count it
Path<Calendar> billexp = bill.get(Bill_.expires);
preds.add(cb.or(
cb.isNull(billexp),
cb.greaterThan(billexp, Calendar.getInstance())
));
}
// Check that slot is not used
preds.add(cb.isNull(root.get(PlaceSlot_.place)));
preds.add(cb.isNull(root.get(PlaceSlot_.used)));
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;
} }
public Long totalSlotcount(Product prod) { public Long totalSlotcount(Product prod, boolean paidOnly) {
CriteriaBuilder cb = getEm().getCriteriaBuilder(); CriteriaBuilder cb = getEm().getCriteriaBuilder();
CriteriaQuery<Long> q = cb.createQuery(Long.class); CriteriaQuery<Long> q = cb.createQuery(Long.class);
Root<PlaceSlot> root = q.from(PlaceSlot.class); Root<PlaceSlot> root = q.from(PlaceSlot.class);
q.select(cb.count(root)); q.select(cb.count(root));
Path<Bill> bill = root.get(PlaceSlot_.bill); Path<Bill> bill = root.get(PlaceSlot_.bill);
Path<Calendar> billexp = bill.get(Bill_.expires);
q.where(cb.equal(root.get(PlaceSlot_.product), prod), List<Predicate> preds = new ArrayList<>();
cb.or(cb.isNull(billexp), preds.add(cb.equal(root.get(PlaceSlot_.product), prod));
cb.greaterThan(billexp, Calendar.getInstance()) if (paidOnly) {
) preds.add(cb.isNotNull(bill.get(Bill_.paidDate)));
); } else {
Path<Calendar> billexp = bill.get(Bill_.expires);
preds.add(cb.or(
cb.isNull(billexp),
cb.greaterThan(billexp, Calendar.getInstance())
));
}
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;
......
...@@ -13,8 +13,9 @@ import org.slf4j.LoggerFactory; ...@@ -13,8 +13,9 @@ import org.slf4j.LoggerFactory;
public enum SystemProperty { public enum SystemProperty {
MOYA_IRC_SERVER, MOYA_IRC_SERVER,
MOYA_IRC_CHANNEL("#moya-debug") MOYA_IRC_CHANNEL("#moya-debug"),
MOYA_IRC_SERVERPASS(),
; ;
private final String defaultValue; private final String defaultValue;
...@@ -62,4 +63,10 @@ public enum SystemProperty { ...@@ -62,4 +63,10 @@ public enum SystemProperty {
} }
return intval; return intval;
} }
public String getValueOrNull() {
String val = System.getProperty(this.name());
return val;
}
} }
...@@ -15,6 +15,10 @@ ...@@ -15,6 +15,10 @@
<h:form id="billform"> <h:form id="billform">
<p:panelGrid columns="2"> <p:panelGrid columns="2">
<h:outputLabel for="billid" value="#{i18n['bill.id']}:" />
<h:inputText id="billid" value="#{billEditView.bill.id}" />
<h:outputLabel for="paidDate" value="#{i18n['bill.paidDate']}:" /> <h:outputLabel for="paidDate" value="#{i18n['bill.paidDate']}:" />
<h:inputText id="paidDate" value="#{billEditView.bill.paidDate}"> <h:inputText id="paidDate" value="#{billEditView.bill.paidDate}">
<f:convertDateTime pattern="#{sessionHandler.datetimeFormat}" timeZone="#{sessionHandler.timezone}" /> <f:convertDateTime pattern="#{sessionHandler.datetimeFormat}" timeZone="#{sessionHandler.timezone}" />
......
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
<h2>Currently reserving</h2> <h2>Currently reserving</h2>
<p:dataTable var="u" value="#{queueManageView.userReserving}"> <p:dataTable var="u" value="#{queueManageView.userReserving}">
<p:column> <p:column>
<h:outputText value="#{u.user.user.nick}" /> <h:outputText value="#{u.user.user.login}" />
</p:column> </p:column>
<p:column headerText="Created"> <p:column headerText="Created">
<h:outputText value="#{u.created}"> <h:outputText value="#{u.created}">
...@@ -64,9 +64,11 @@ ...@@ -64,9 +64,11 @@
</p:dataTable> </p:dataTable>
<h2>In queue</h2> <h2>In queue</h2>
Queue size: #{queueManageView.userQueue.size()}<br/>
<p:dataTable var="u" value="#{queueManageView.userQueue}"> <p:dataTable var="u" value="#{queueManageView.userQueue}">
<p:column> <p:column>
<h:outputText value="#{u.user.user.nick}" /> <h:outputText value="#{u.user.user.login}" />
</p:column> </p:column>
<p:column headerText="Created"> <p:column headerText="Created">
<h:outputText value="#{u.created}"> <h:outputText value="#{u.created}">
......
<!DOCTYPE html <!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:users="http://java.sun.com/jsf/composite/cditools/user"
xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core" xmlns:p="http://primefaces.org/ui">
xmlns:h="http://java.sun.com/jsf/html"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:users="http://java.sun.com/jsf/composite/cditools/user"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:p="http://primefaces.org/ui">
<h:body> <h:body>
<ui:composition template="#{sessionHandler.template}"> <ui:composition template="#{sessionHandler.template}">
<ui:param name="thispage" value="page.place.mygroups" /> <ui:param name="thispage" value="page.place.mygroups" />
...@@ -25,10 +20,8 @@ ...@@ -25,10 +20,8 @@
</ui:define> </ui:define>
<ui:define name="content"> <ui:define name="content">
<h:outputText rendered="#{empty placeGroupView.groupMemberships}" <h:outputText rendered="#{empty placeGroupView.groupMemberships}" value="#{i18n['placegroupview.noMemberships']}" />
value="#{i18n['placegroupview.noMemberships']}" /> <h:form rendered="#{!empty placeGroupView.groupMemberships}" id="placelistform">
<h:form rendered="#{!empty placeGroupView.groupMemberships}"
id="placelistform">
<h:dataTable value="#{placeGroupView.groupMemberships}" var="member"> <h:dataTable value="#{placeGroupView.groupMemberships}" var="member">
<h:column> <h:column>
...@@ -41,34 +34,26 @@ ...@@ -41,34 +34,26 @@
<f:facet name="header"> <f:facet name="header">
<h:outputText value="#{i18n['placegroupview.token']}" /> <h:outputText value="#{i18n['placegroupview.token']}" />
</f:facet> </f:facet>
<h:outputText rendered="#{empty member.user}" <h:outputText rendered="#{empty member.user}" value="#{member.inviteToken}" />
value="#{member.inviteToken}" /> <h:outputText rendered="#{!empty member.user}" value="#{member.user.firstnames} #{member.user.lastname} (#{member.user.nick})" />
<h:outputText rendered="#{!empty member.user}"
value="#{member.user.firstnames} #{member.user.lastname} (#{member.user.nick})" />
</h:column> </h:column>
<h:column> <h:column>
<f:facet name="header"> <f:facet name="header">
<h:outputText value="#{i18n['placegroupview.groupCreator']}" /> <h:outputText value="#{i18n['placegroupview.groupCreator']}" />
</f:facet> </f:facet>
<h:outputText <h:outputText value="#{member.placeGroup.creator.firstnames} #{member.placeGroup.creator.lastname} (#{member.placeGroup.creator.nick})" />
value="#{member.placeGroup.creator.firstnames} #{member.placeGroup.creator.lastname} (#{member.placeGroup.creator.nick})" />
</h:column> </h:column>
<h:column> <h:column>
<h:commandButton <h:commandButton rendered="#{placeGroupView.canModifyCurrent and placeGroupView.currentMemberUserNotNull}" action="#{placeGroupView.releasePlace()}" value="#{i18n['placegroupview.releasePlace']}" />
rendered="#{placeGroupView.canModifyCurrent and placeGroupView.currentMemberUserNotNull}"
action="#{placeGroupView.releasePlace()}"
value="#{i18n['placegroupview.releasePlace']}" />
</h:column> </h:column>
</h:dataTable> </h:dataTable>
</h:form> </h:form>
<p> <p>
<input type="button" <input type="button" onclick="location.replace('#{request.contextPath}/PlaceGroupPdf?eventuserId=#{placeGroupView.user.id}');" value="#{i18n['placegroup.printPdf']}" />
onclick="location.replace('#{request.contextPath}/PlaceGroupPdf?eventuserId=#{placeGroupView.user.id}');"
value="#{i18n['placegroup.printPdf']}" />
</p> </p>
<h2>#{i18n['placetoken.pageHeader']}</h2> <h2>#{i18n['placetoken.pageHeader']}</h2>
...@@ -76,32 +61,30 @@ ...@@ -76,32 +61,30 @@
<h:form id="placeTokenForm"> <h:form id="placeTokenForm">
<h:outputLabel value="#{i18n['placetoken.token']}:" /> <h:outputLabel value="#{i18n['placetoken.token']}:" />
<h:inputText value="#{tokenView.token}" /> <h:inputText value="#{tokenView.token}" />
<h:commandButton id="commitbtn" action="#{tokenView.saveToken()}" <h:commandButton id="commitbtn" action="#{tokenView.saveToken()}" value="#{i18n['placetoken.commit']}" />
value="#{i18n['placetoken.commit']}" />
</h:form> </h:form>
<h2>Place slots</h2> <h2>Place slots</h2>
<p:dataTable var="slot" value="#{placeGroupView.placeslots}"> <p:dataTable var="slot" value="#{placeGroupView.placeslots}">
<p:column headerText="#{i18n['placeslot.id']}">
<h:outputText value="#{slot.id}" />
</p:column>
<p:column headerText="#{i18n['placeslot.state']}"> <p:column headerText="#{i18n['placeslot.state']}">
<h:outputText <h:outputText value="#{slot.bill.expired ? i18n['placeslot.state.expired'] : ( slot.bill.paid ? i18n['placeslot.state.paid'] : i18n['placeslot.state.notPaid'])}" />
value="#{slot.bill.expired ? i18n['placeslot.state.expired'] : ( slot.bill.paid ? i18n['placeslot.state.paid'] : i18n['placeslot.state.notPaid'])}" />
</p:column> </p:column>
<p:column headerText="#{i18n['placeslot.product']}"> <p:column headerText="#{i18n['placeslot.product']}">
<h:outputText value="#{slot.product.name}" /> <h:outputText value="#{slot.product.name}" />
</p:column> </p:column>
<p:column headerText="#{i18n['placeslot.place']}"> <p:column headerText="#{i18n['placeslot.place']}">
<h:outputText rendered="#{!empty slot.place}" <h:outputText rendered="#{!empty slot.place}" value="#{slot.place.name}" />
value="#{slot.place.name}" />
</p:column> </p:column>
<p:column headerText="#{i18n['placeslot.used']}"> <p:column headerText="#{i18n['placeslot.used']}">
<h:outputText value="#{slot.used}"> <h:outputText value="#{slot.used}">
<f:convertDateTime timeZone="#{sessionHandler.timezone}" <f:convertDateTime timeZone="#{sessionHandler.timezone}" pattern="#{sessionHandler.shortDatetimeFormat}" />
pattern="#{sessionHandler.shortDatetimeFormat}" />
</h:outputText> </h:outputText>
</p:column> </p:column>
<p:column headerText="#{i18n['placeslot.bill']}"> <p:column headerText="#{i18n['placeslot.bill']}">
<h:link outcome="/bill/showBill" <h:link outcome="/bill/showBill" value="#{i18n['bill.billNumber']}: #{slot.bill.id}">
value="#{i18n['bill.billNumber']}: #{slot.bill.id}">
<f:param name="billid" value="#{slot.bill.id}" /> <f:param name="billid" value="#{slot.bill.id}" />
</h:link> </h:link>
</p:column> </p:column>
......
<!DOCTYPE html <!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:products="http://java.sun.com/jsf/composite/tools/products" xmlns:foodwave="http://java.sun.com/jsf/composite/tools" xmlns:p="http://primefaces.org/ui">
xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:products="http://java.sun.com/jsf/composite/tools/products"
xmlns:foodwave="http://java.sun.com/jsf/composite/tools"
xmlns:p="http://primefaces.org/ui"
>
<h:body> <h:body>
<ui:composition template="#{sessionHandler.template}"> <ui:composition template="#{sessionHandler.template}">
<ui:define name="content"> <ui:define name="content">
Sisällä: <p:outputLabel value="#{basicStatisticsView.groupMembershipsEnteredCount} / #{basicStatisticsView.groupMembershipsTotalCount}" /><br /> Sisällä: <h:outputText value="#{basicStatisticsView.groupMembershipsEnteredCount} / #{basicStatisticsView.groupMembershipsTotalCount}" />
Korttei: <p:outputLabel value="#{basicStatisticsView.cardDeliveredCount}" /> <br />
Korttei: <h:outputText value="#{basicStatisticsView.cardDeliveredCount}" />
<br />
<h2>Laskutettuja paikkaslotteja</h2>
<p:dataTable var="slot" value="#{basicStatisticsView.unusedBilledSlots}">
<p:column headerText="Tuote">
<h:outputText value="#{slot.product.name}" />
</p:column>
<p:column headerText="Paikkaslotteja yhteensä">
<h:outputText value="#{slot.totalSlots}" />
</p:column>
<p:column headerText="Käyttämättömiä slotteja">
<h:outputText value="#{slot.unusedSlots}" />
</p:column>
</p:dataTable>
<h2>Maksettuja paikkaslotteja</h2>
<p:dataTable var="slot" value="#{basicStatisticsView.unusedPaidSlots}">
<p:column headerText="Tuote">
<h:outputText value="#{slot.product.name}" />
</p:column>
<p:column headerText="Paikkaslotteja yhteensä">
<h:outputText value="#{slot.totalSlots}" />
</p:column>
<p:column headerText="Käyttämättömiä slotteja">
<h:outputText value="#{slot.unusedSlots}" />
</p:column>
</p:dataTable>
</ui:define> </ui:define>
</ui:composition> </ui:composition>
</h:body> </h:body>
......
...@@ -130,6 +130,16 @@ ...@@ -130,6 +130,16 @@
<h:outputText rendered="#{bill.paidDate == null}" <h:outputText rendered="#{bill.paidDate == null}"
value="#{i18n['bill.isNotPaid']}" /> value="#{i18n['bill.isNotPaid']}" />
</p:column> </p:column>
<p:column rendered="#{billListView.canWriteBill and not empty billListView.paytrailMerchantId}">
<form target="_blank" action="https://payment.paytrail.com/check-payment" method="post">
<input name="MERCHANT_ID" type="hidden" value="#{billListView.paytrailMerchantId}" />
<input name="ORDER_NUMBER" type="hidden" value="#{bill.id}" />
<input name="AUTHCODE" type="hidden" value="#{billListView.getAuthcodeForBill(bill.id)}" />
<input name="VERSION" type="hidden" value="2" />
<input name="submit" type="submit" value="Check payment state" />
</form>
</p:column>
<p:rowExpansion rendered="#{billListView.bills.rowCount lt 20}"> <p:rowExpansion rendered="#{billListView.bills.rowCount lt 20}">
<p:dataList value="#{bill.billLines}" var="line"> <p:dataList value="#{bill.billLines}" var="line">
......
...@@ -32,6 +32,11 @@ ...@@ -32,6 +32,11 @@
<f:param name="mapid" value="#{map.id}" /> <f:param name="mapid" value="#{map.id}" />
</h:link> </h:link>
</h:column> </h:column>
<h:column>
<h:link value="#{i18n['map.showQueue']}" outcome="/neomap/quemgmt">
<f:param name="mapId" value="#{map.id}" />
</h:link>
</h:column>
</h:dataTable> </h:dataTable>
</h:form> </h:form>
......
...@@ -100,7 +100,7 @@ public class IrcServlet extends HttpServlet { ...@@ -100,7 +100,7 @@ public class IrcServlet extends HttpServlet {
{ {
logger.info("Starting IRC client with server {}", ircserver); logger.info("Starting IRC client with server {}", ircserver);
IrcBot bot = new IrcBot(ircserver, SystemProperty.MOYA_IRC_CHANNEL.getValueOrDefault(), "moya-bot"); IrcBot bot = new IrcBot(ircserver, 6667, SystemProperty.MOYA_IRC_SERVERPASS.getValueOrNull() ,SystemProperty.MOYA_IRC_CHANNEL.getValueOrDefault(), "moya-bot");
botbean.add(bot); botbean.add(bot);
bots.add(bot); bots.add(bot);
bot.start(); bot.start();
...@@ -123,6 +123,11 @@ public class IrcServlet extends HttpServlet { ...@@ -123,6 +123,11 @@ public class IrcServlet extends HttpServlet {
*/ */
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
for (IrcBot b : bots) { for (IrcBot b : bots) {
try {
b.stop();
} catch (Throwable t) {
logger.info("Error stopping bot", t);
}
b.start(); b.start();
} }
......
...@@ -20,6 +20,8 @@ package fi.codecrew.moya.web.cdiview.shop; ...@@ -20,6 +20,8 @@ package fi.codecrew.moya.web.cdiview.shop;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.List;
import java.util.Map;
import javax.ejb.EJB; import javax.ejb.EJB;
import javax.enterprise.context.ConversationScoped; import javax.enterprise.context.ConversationScoped;
...@@ -29,6 +31,7 @@ import javax.inject.Named; ...@@ -29,6 +31,7 @@ import javax.inject.Named;
import javax.management.RuntimeErrorException; import javax.management.RuntimeErrorException;
import fi.codecrew.moya.beans.BillBeanLocal; import fi.codecrew.moya.beans.BillBeanLocal;
import fi.codecrew.moya.beans.VerkkomaksutFiBeanLocal;
import fi.codecrew.moya.bortal.views.BillSummary; import fi.codecrew.moya.bortal.views.BillSummary;
import fi.codecrew.moya.enums.apps.BillPermission; import fi.codecrew.moya.enums.apps.BillPermission;
import fi.codecrew.moya.exceptions.BillException; import fi.codecrew.moya.exceptions.BillException;
...@@ -70,14 +73,30 @@ public class BillListView extends GenericCDIView { ...@@ -70,14 +73,30 @@ public class BillListView extends GenericCDIView {
return null; return null;
} }
@EJB
private VerkkomaksutFiBeanLocal paytrailBean;
private String paytrailMerchantId;
private Map<Integer, String> paytrailAuthcodes;
public void initAllBills() { public void initAllBills() {
if (super.requirePermissions(BillPermission.READ_ALL)) { if (super.requirePermissions(BillPermission.READ_ALL)) {
beginConversation(); beginConversation();
bills = new ListDataModel<Bill>(billbean.findAll()); List<Bill> billList = billbean.findAll();
bills = new ListDataModel<Bill>(billList);
writeBill = permbean.hasPermission(BillPermission.WRITE_ALL); writeBill = permbean.hasPermission(BillPermission.WRITE_ALL);
if (paytrailBean.isSvmEnabled()) {
setPaytrailMerchantId(paytrailBean.getMerchantId());
paytrailAuthcodes = paytrailBean.getAuthcodeForBills(billList);
}
} }
} }
public String getAuthcodeForBill(Integer id) {
return paytrailAuthcodes.get(id);
}
public void initSummaryView() public void initSummaryView()
{ {
if (super.requirePermissions(BillPermission.READ_ALL)) { if (super.requirePermissions(BillPermission.READ_ALL)) {
...@@ -143,4 +162,12 @@ public class BillListView extends GenericCDIView { ...@@ -143,4 +162,12 @@ public class BillListView extends GenericCDIView {
this.showPayButtons = showPayButtons; this.showPayButtons = showPayButtons;
} }
public String getPaytrailMerchantId() {
return paytrailMerchantId;
}
public void setPaytrailMerchantId(String paytrailMerchantId) {
this.paytrailMerchantId = paytrailMerchantId;
}
} }
...@@ -43,28 +43,27 @@ public class ProductShopItemHelper extends GenericCDIView { ...@@ -43,28 +43,27 @@ public class ProductShopItemHelper extends GenericCDIView {
@EJB @EJB
private DiscountBeanLocal discountBean; private DiscountBeanLocal discountBean;
public void updateProductShopItemCount(ProductShopItem item) { public void updateProductShopItemCount(ProductShopItem item) {
// Discounts or overridden price, you cannot get both // Discounts or overridden price, you cannot get both
if(item.isOverrideUnitPrice()) { if (item.isOverrideUnitPrice()) {
item.setInternalPrice(item.getOverriddenUnitPrice().multiply(item.getCount())); item.setInternalPrice(item.getOverriddenUnitPrice().multiply(item.getCount()));
} else { } else {
item.setInternalPrice(item.getProduct().getPrice().abs().multiply(item.getCount())); item.setInternalPrice(item.getProduct().getPrice().abs().multiply(item.getCount()));
item.setInternalDiscounts(discountBean.getActiveDiscountsByProduct(item.getProduct(), item.getCount(), Calendar.getInstance(), item.getUser())); item.setInternalDiscounts(discountBean.getActiveDiscountsByProduct(item.getProduct(), item.getCount(), Calendar.getInstance(), item.getUser()));
item.setInternalDiscountValues(new HashMap<Integer, BigDecimal>()); item.setInternalDiscountValues(new HashMap<Integer, BigDecimal>());
for (Discount d : item.getDiscounts())
{
BigDecimal newprice = item.getPrice().multiply(d.getPercentage());
item.getInternalDiscountValues().put(d.getId(), item.getPrice().subtract(newprice));
item.setInternalPrice(newprice);
}
}
for (Discount d : item.getDiscounts())
{
BigDecimal newprice = item.getPrice().multiply(d.getPercentage());
item.getInternalDiscountValues().put(d.getId(), item.getPrice().subtract(newprice));
item.setInternalPrice(newprice);
}
}
} }
public void setProductShopItemCount(ProductShopItem item, BigDecimal count) { public void setProductShopItemCount(ProductShopItem item, BigDecimal count) {
...@@ -73,24 +72,27 @@ public class ProductShopItemHelper extends GenericCDIView { ...@@ -73,24 +72,27 @@ public class ProductShopItemHelper extends GenericCDIView {
count = BigDecimal.ZERO; count = BigDecimal.ZERO;
} }
item.setCount(count); item.setCount(count);
updateProductShopItemCount(item); updateProductShopItemCount(item);
} }
public boolean updateProductShopItemLimit(ProductShopItem item, BigDecimal limitValue) { public boolean updateProductShopItemLimit(ProductShopItem item, BigDecimal limitValue) {
BigDecimal productsLeft = null;
if (limitValue != null && limitValue.compareTo(BigDecimal.ZERO) < 0) if (limitValue != null) {
{ productsLeft = limitValue.subtract(item.getCount());
this.setProductShopItemCount(item, item.getCount().add(limitValue)); if (productsLeft.compareTo(BigDecimal.ZERO) < 0) {
this.setProductShopItemCount(item, limitValue);
if (item.getCount().compareTo(BigDecimal.ZERO) < 0) { if (item.getCount().compareTo(BigDecimal.ZERO) < 0) {
this.setProductShopItemCount(item, BigDecimal.ZERO); this.setProductShopItemCount(item, BigDecimal.ZERO);
}
item.setLimit(BigDecimal.ZERO);
return true;
} }
item.setLimit(BigDecimal.ZERO);
return true;
} }
item.setLimit(limitValue); item.setLimit(productsLeft);
return false; return false;
} }
} }
...@@ -18,6 +18,8 @@ ...@@ -18,6 +18,8 @@
*/ */
package fi.codecrew.moya.web.reports; package fi.codecrew.moya.web.reports;
import java.util.List;
import javax.ejb.EJB; import javax.ejb.EJB;
import javax.enterprise.context.ConversationScoped; import javax.enterprise.context.ConversationScoped;
import javax.enterprise.context.RequestScoped; import javax.enterprise.context.RequestScoped;
...@@ -29,6 +31,7 @@ import javax.inject.Named; ...@@ -29,6 +31,7 @@ import javax.inject.Named;
import fi.codecrew.moya.beans.EventBeanLocal; import fi.codecrew.moya.beans.EventBeanLocal;
import fi.codecrew.moya.beans.LectureBeanLocal; import fi.codecrew.moya.beans.LectureBeanLocal;
import fi.codecrew.moya.beans.StatisticsBeanLocal; import fi.codecrew.moya.beans.StatisticsBeanLocal;
import fi.codecrew.moya.beans.StatisticsBeanLocal.ProductSlotcountMover;
import fi.codecrew.moya.enums.apps.LecturePermission; import fi.codecrew.moya.enums.apps.LecturePermission;
import fi.codecrew.moya.model.Lecture; import fi.codecrew.moya.model.Lecture;
import fi.codecrew.moya.model.LectureGroup; import fi.codecrew.moya.model.LectureGroup;
...@@ -43,20 +46,36 @@ public class BasicStatisticsView extends GenericCDIView { ...@@ -43,20 +46,36 @@ public class BasicStatisticsView extends GenericCDIView {
@EJB @EJB
private StatisticsBeanLocal statisticsBean; private StatisticsBeanLocal statisticsBean;
private List<ProductSlotcountMover> billedslots;
private List<ProductSlotcountMover> paidslots;
public Long getGroupMembershipsEnteredCount() { public Long getGroupMembershipsEnteredCount() {
return statisticsBean.getGroupMembershipsEnteredEvent(); return statisticsBean.getGroupMembershipsEnteredEvent();
} }
public Long getCardDeliveredCount() { public Long getCardDeliveredCount() {
return statisticsBean.getCardDeliveredCount(); return statisticsBean.getCardDeliveredCount();
} }
public Long getGroupMembershipsTotalCount() { public Long getGroupMembershipsTotalCount() {
return statisticsBean.getGroupMembershipsTotalCount(); return statisticsBean.getGroupMembershipsTotalCount();
} }
public List<ProductSlotcountMover> getUnusedBilledSlots()
{
if (billedslots == null) {
billedslots = statisticsBean.getUnusedSlots(false);
}
return billedslots;
}
public List<ProductSlotcountMover> getUnusedPaidSlots()
{
if (paidslots == null) {
paidslots = statisticsBean.getUnusedSlots(true);
}
return paidslots;
}
} }
...@@ -75,6 +75,7 @@ bill.cancel = Cancel bill ...@@ -75,6 +75,7 @@ bill.cancel = Cancel bill
bill.deliveryTerms = Delivery terms bill.deliveryTerms = Delivery terms
bill.edit = edit bill.edit = edit
bill.expires = Expires bill.expires = Expires
bill.id = ID
bill.isExpired = Bill is expired bill.isExpired = Bill is expired
bill.isNoPaid = Not paid bill.isNoPaid = Not paid
bill.isNotPaid = Not paid bill.isNotPaid = Not paid
...@@ -695,6 +696,7 @@ map.namebase = Semicolon separated table prefixes ...@@ -695,6 +696,7 @@ map.namebase = Semicolon separated table prefixes
map.oneRowTable = One row tables map.oneRowTable = One row tables
map.placesInRow = Places in row map.placesInRow = Places in row
map.product = Place product map.product = Place product
map.showQueue = Show maps user queue
map.startX = Place start X-coordinate map.startX = Place start X-coordinate
map.startY = Place start Y-coordinate\n map.startY = Place start Y-coordinate\n
map.submitMap = Send map image map.submitMap = Send map image
...@@ -951,6 +953,7 @@ placegroupview.reservationProduct = Ticket ...@@ -951,6 +953,7 @@ placegroupview.reservationProduct = Ticket
placegroupview.token = Placecode / user placegroupview.token = Placecode / user
placeslot.bill = Bill placeslot.bill = Bill
placeslot.id = ID
placeslot.place = Place placeslot.place = Place
placeslot.product = Slot product placeslot.product = Slot product
placeslot.showBill = Show bill placeslot.showBill = Show bill
......
...@@ -75,6 +75,7 @@ bill.cancel = Peruuta lasku ...@@ -75,6 +75,7 @@ bill.cancel = Peruuta lasku
bill.deliveryTerms = Toimitusehdot bill.deliveryTerms = Toimitusehdot
bill.edit = Muokkaa bill.edit = Muokkaa
bill.expires = Vanhentuu bill.expires = Vanhentuu
bill.id = ID
bill.isExpired = Lasku on vanhentunut bill.isExpired = Lasku on vanhentunut
bill.isNoPaid = Maksamatta bill.isNoPaid = Maksamatta
bill.isNotPaid = Maksamatta bill.isNotPaid = Maksamatta
...@@ -704,6 +705,7 @@ map.namebase = Puolipisteell\u00E4 erotetut p\u00F6yt\u00E4-etuliitteet ...@@ -704,6 +705,7 @@ map.namebase = Puolipisteell\u00E4 erotetut p\u00F6yt\u00E4-etuliitteet
map.oneRowTable = Yhden rivin p\u00F6yd\u00E4t map.oneRowTable = Yhden rivin p\u00F6yd\u00E4t
map.placesInRow = Paikkoja riviss\u00E4 map.placesInRow = Paikkoja riviss\u00E4
map.product = Paikkatuote map.product = Paikkatuote
map.showQueue = N\u00E4yt\u00E4 kartan k\u00E4ytt\u00E4j\u00E4jono
map.startX = P\u00F6yd\u00E4n X-aloituskoord. map.startX = P\u00F6yd\u00E4n X-aloituskoord.
map.startY = P\u00F6yd\u00E4n Y-aloituskoord. map.startY = P\u00F6yd\u00E4n Y-aloituskoord.
map.submitMap = L\u00E4het\u00E4 karttapohja map.submitMap = L\u00E4het\u00E4 karttapohja
...@@ -934,6 +936,7 @@ placegroupview.reservationProduct = Lippu ...@@ -934,6 +936,7 @@ placegroupview.reservationProduct = Lippu
placegroupview.token = Paikkakoodi / k\u00E4ytt\u00E4j\u00E4 placegroupview.token = Paikkakoodi / k\u00E4ytt\u00E4j\u00E4
placeslot.bill = Lasku placeslot.bill = Lasku
placeslot.id = ID
placeslot.place = Paikka placeslot.place = Paikka
placeslot.product = Tuote placeslot.product = Tuote
placeslot.showBill = N\u00E4yt\u00E4 lasku placeslot.showBill = N\u00E4yt\u00E4 lasku
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!