Commit 8930f7aa by Tuomas Riihimäki

Refactor and fix user searching with role.

1 parent 2b19d7ba
......@@ -22,6 +22,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import javax.ejb.Local;
......@@ -216,4 +217,6 @@ public interface UserBeanLocal {
* @return Eventusers for all events this user has been associated to.
*/
List<EventUser> findAllEventusers(User user);
Map<Role,List<String>> findUsersRolesWithReason(EventUser user);
}
......@@ -186,7 +186,7 @@ public class UserBean implements UserBeanLocal {
@Override
public boolean setUsersLocale(String locale) {
if(permbean.getCurrentUser().isAnonymous()) {
if (permbean.getCurrentUser().isAnonymous()) {
return false;
}
......@@ -234,6 +234,13 @@ public class UserBean implements UserBeanLocal {
}
@Override
@RolesAllowed(UserPermission.S_VIEW_ALL)
public Map<Role, List<String>> findUsersRolesWithReason(EventUser u) {
return localFindUsersRolesWithReason(u);
}
// private EventUser currentEventuser;
// private ArrayList<Role> currentEventuserRoles;
......@@ -241,6 +248,10 @@ public class UserBean implements UserBeanLocal {
// Käytä Viewien puolelta findUsersRoles joka tarkistaa käyttäjän oikeudet
// ensin.
public Set<Role> localFindUsersRoles(EventUser u) {
return localFindUsersRolesWithReason(u).keySet();
}
public HashMap<Role, List<String>> localFindUsersRolesWithReason(EventUser u) {
// if (currentEventuser != null && u.equals(currentEventuser)) {
// logger.debug("Returnin cached eventuserroles for user {}: {}",
// currentEventuser, currentEventuserRoles);
......@@ -248,26 +259,27 @@ public class UserBean implements UserBeanLocal {
// }
LanEvent event = u.getEvent();
Set<Role> checkedRoles = new HashSet<Role>();
//Set<Role> checkedRoles = new HashSet<Role>();
HashMap<Role, List<String>> checkedRoles = new HashMap<>();
if (u != null) {
addRecursive(checkedRoles, rolefacade.findForUser(u));
addRecursive(checkedRoles, orgRoleFacade.findRolesForUser(u));
addRecursive(checkedRoles, rolefacade.findForUser(u), "DIRECT");
addRecursive(checkedRoles, orgRoleFacade.findRolesForUser(u), "ORG_ROLE");
if (permbean.isLoggedIn()) {
// add roles from events default role.
addRecursive(checkedRoles, event.getDefaultRole());
addRecursive(checkedRoles, event.getDefaultRole(), "EVENT_DEFAULT");
}
if (!u.isAnonymous()) {
// add roles from accountEvents of the user
addRecursive(checkedRoles, productbean.getRolesFromAccountEvents(u));
addRecursive(checkedRoles, productbean.getRolesFromAccountEvents(u), "ACCOUNTEVENT");
for (GroupMembership member : gmfacade.findMemberships(u)) {
if (member != null && member.getPlaceReservation() != null) {
addRecursive(checkedRoles, member.getPlaceReservation().getProvidesRole());
addRecursive(checkedRoles, member.getPlaceReservation().getProvidesRole(), "PLACE");
if (member.getPlaceReservation().getProduct() != null) {
addRecursive(checkedRoles, member.getPlaceReservation().getProduct().getProvides());
addRecursive(checkedRoles, member.getPlaceReservation().getProduct().getProvides(), "PLACE_PRODUCT");
}
}
}
......@@ -283,20 +295,28 @@ public class UserBean implements UserBeanLocal {
return checkedRoles;
}
private void addRecursive(Set<Role> checkedRoles, Collection<Role> roles) {
public void addRecursive(Map<Role, List<String>> checkedRoles, Collection<Role> roles, String source) {
for (Role r : roles) {
addRecursive(checkedRoles, r);
addRecursive(checkedRoles, r, source);
}
}
private void addRecursive(Set<Role> checkedRoles, Role role) {
if (role == null || checkedRoles.contains(role)) {
public void addRecursive(Map<Role, List<String>> checkedRoles, Role role, String source) {
if (role == null) {
return;
}
checkedRoles.add(role);
List<String> reasons = checkedRoles.get(role);
if (reasons == null) {
reasons = new ArrayList<>();
checkedRoles.put(role, reasons);
for (Role r : role.getParents()) {
addRecursive(checkedRoles, r);
addRecursive(checkedRoles, r, source);
}
}
reasons.add(source);
}
......@@ -963,11 +983,11 @@ public class UserBean implements UserBeanLocal {
@Override
public boolean isUserInRole(EventUser user, Integer roleId) {
Set<Role> roles = localFindUsersRoles(user);
HashMap<Role, List<String>> roles = localFindUsersRolesWithReason(user);
logger.info("CHecking user {} roleid {} from roles {}, roles size{}", new Object[]{user, roleId, roles, roles.size()});
addRecursive(roles, eventBean.getCurrentEvent().getDefaultRole());
addRecursive(roles, eventBean.getCurrentEvent().getDefaultRole(), "DEFAULT");
for (Role r : roles) {
for (Role r : roles.keySet()) {
if (roleId.equals(r.getId())) {
logger.info("User {} found in role {}", user, roleId);
return true;
......@@ -1188,7 +1208,6 @@ public class UserBean implements UserBeanLocal {
return eventUserFacade.findForUser(user);
}
@Override
@RolesAllowed(EventPermission.S_MANAGE_EVENT)
public EventUser getUserByAuthcode(String authcode) {
......
......@@ -56,12 +56,14 @@ public class AccountEventFacade extends IntegerPkGenericFacade<AccountEvent> {
CriteriaQuery<Role> cq = cb.createQuery(Role.class);
Root<AccountEvent> root = cq.from(AccountEvent.class);
Path<Role> role = root.get(AccountEvent_.product).get(Product_.provides);
Path<Product> prodPath = root.get(AccountEvent_.product);
Path<Role> role = prodPath.get(Product_.provides);
cq.select(role);
cq.where(
cb.equal(role.get(Role_.event), event),
cb.equal(root.get(AccountEvent_.user), u)
,cb.isEmpty(prodPath.get(Product_.places))
);
// TypedQuery<Role> q =
......
......@@ -18,45 +18,32 @@
*/
package fi.codecrew.moya.facade.callbacks;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import java.util.*;
import javax.persistence.criteria.*;
import fi.codecrew.moya.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import fi.codecrew.moya.model.AccountEvent_;
import fi.codecrew.moya.model.EventUser;
import fi.codecrew.moya.model.EventUser_;
import fi.codecrew.moya.model.GroupMembership_;
import fi.codecrew.moya.model.Place_;
import fi.codecrew.moya.model.Product_;
import fi.codecrew.moya.model.Role;
import fi.codecrew.moya.model.Role_;
import fi.codecrew.moya.utilities.jpa.FacadeCallback;
public class EventUserRolefilter implements FacadeCallback<EventUser> {
private final List<Role> roles;
private static final Logger logger = LoggerFactory.getLogger(EventUserRolefilter.class);
private final LanEvent event;
public EventUserRolefilter(List<Role> filters) {
roles = filters;
roles = Collections.unmodifiableList(filters);
event = roles.get(0).getEvent();
}
private static final Set<Integer> getAllChildren(Collection<Role> roles, Set<Integer> checkedRoles) {
for (Role role : roles)
{
if (checkedRoles == null) {
checkedRoles = new HashSet<>();
}
for (Role role : roles) {
if (role == null || checkedRoles.contains(role.getId())) {
continue;
}
......@@ -66,40 +53,112 @@ public class EventUserRolefilter implements FacadeCallback<EventUser> {
return checkedRoles;
}
private static final Set<Integer> getAllParents(Collection<Role> roles, Set<Integer> checkedRoles) {
if (checkedRoles == null) {
checkedRoles = new HashSet<>();
}
for (Role role : roles) {
if (role == null || checkedRoles.contains(role.getId())) {
continue;
}
checkedRoles.add(role.getId());
getAllParents(role.getParents(), checkedRoles);
}
return checkedRoles;
}
public void exec(CriteriaBuilder cb, CriteriaQuery<?> cq, Path<EventUser> root, List<Predicate> predicates, boolean isFullQuery) {
if (roles != null && !roles.isEmpty())
{
HashSet<Integer> roleids = new HashSet<Integer>();
getAllChildren(roles, roleids);
logger.debug("Requiring roles {}", roleids);
if (roles == null || roles.isEmpty()) {
return;
}
Set<Integer> roleWChildrenIds = getAllChildren(roles, null);
Set<Integer> roleWParentsIds = getAllParents(roles, null);
logger.warn("roles with children: {}, roles with parent {}", roleWChildrenIds, roleWParentsIds);
Path<Integer> rootId = root.get(EventUser_.id);
ArrayList<Predicate> orPreds = new ArrayList<Predicate>();
/**
* Find all users that have a role "forced" directly to the user
* If role has parents, user belongs also to them, so also parents are included
*/
{
Subquery<Integer> subq = cq.subquery(Integer.class);
Root<Role> subroot = subq.from(Role.class);
subq.select(subroot.join(Role_.users).get(EventUser_.id));
subq.where(subroot.get(Role_.id).in(roleids));
// Root<Role> rolePath = subq.correlate(roleRoot);
subq.where(subroot.get(Role_.id).in(roleWChildrenIds));
orPreds.add(rootId.in(subq));
}
/**
* Find all products that provide a given role, or a parent of the role via accountevents
* NOTE! Products that have places, do not provide roles to users
*/
{
Subquery<Integer> subq = cq.subquery(Integer.class);
Root<Role> subroot = subq.from(Role.class);
subq.select(subroot.join(Role_.productsProvide).join(Product_.accountEvents).get(AccountEvent_.user).get(EventUser_.id));
subq.where(subroot.get(Role_.id).in(roleids));
ListJoin<Role, Product> prodsPath = subroot.join(Role_.productsProvide);
subq.select(prodsPath.join(Product_.accountEvents).get(AccountEvent_.user).get(EventUser_.id));
subq.where(
subroot.get(Role_.id).in(roleWChildrenIds),
cb.isEmpty(prodsPath.get(Product_.places))
);
orPreds.add(rootId.in(subq));
}
/**
* Roles provided by products via places user has been given to.
*/
{
Subquery<Integer> subq = cq.subquery(Integer.class);
Root<Role> subroot = subq.from(Role.class);
subq.select(subroot.join(Role_.productsProvide).join(Product_.places).get(Place_.placeReserver).get(GroupMembership_.user).get(EventUser_.id));
subq.where(subroot.get(Role_.id).in(roleids));
subq.where(subroot.get(Role_.id).in(roleWChildrenIds));
orPreds.add(rootId.in(subq));
}
/** Roles provided by places user has been given to.
*/
{
Subquery<Integer> subq = cq.subquery(Integer.class);
Root<Role> subroot = subq.from(Role.class);
subq.select(subroot.join(Role_.placesProvide).get(Place_.placeReserver).get(GroupMembership_.user).get(EventUser_.id));
subq.where(subroot.get(Role_.id).in(roleWChildrenIds));
orPreds.add(rootId.in(subq));
}
predicates.add(cb.or(orPreds.toArray(new Predicate[orPreds.size()])));
/** Find all roles provided by organisation roles
*
*/
{
Subquery<Integer> subq = cq.subquery(Integer.class);
Root<Role> subroot = subq.from(Role.class);
subroot.get(Role_.orgRoles);
ListJoin<OrgRole, User> orgRoleUser = subroot.join(Role_.orgRoles).join(OrgRole_.users);
ListJoin<User, EventUser> euJoin = orgRoleUser.join(User_.eventusers);
subq.select(euJoin.get(EventUser_.id));
subq.where(
cb.equal(euJoin.get(EventUser_.event), event),
subroot.get(Role_.id).in(roleWChildrenIds));
orPreds.add(rootId.in(subq));
}
predicates.add(cb.or(orPreds.toArray(new Predicate[orPreds.size()])));
}
}
......@@ -138,6 +138,18 @@ public class User extends GenericEntity implements IUser {
@OrderBy()
private List<LicenseCode> licenseCodes;
@OneToMany(mappedBy="user", fetch = FetchType.LAZY)
private List<EventUser> eventusers;
private List<EventUser> getEventusers(){
throw new RuntimeException("We never want to fetch this. Only here for metamodel");
}
private void setEventusers(List<EventUser> eventusers){
throw new RuntimeException("We never want to fetch this. Only here for metamodel");
}
@Transient
private static final Logger logger = LoggerFactory.getLogger(User.class);
......
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"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"
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:p="http://primefaces.org/ui">
<h:body>
<ui:composition template="#{sessionHandler.template}">
<f:metadata>
<f:event type="preRenderView"
listener="#{applicationApiView.initApplicationListView}" />
</f:metadata>
<ui:define name="content">
<button></button>
<div style="display:none;" id="createApp">
</div>
<h1>#{i18n['apiapp.edit.applist']}</h1>
<p:dataTable value="#{applicationApiView.applist}" var="app">
<p:column headerText="#{i18n['apiapp.name']}">
<h:outputText value="#{app.name}" />
</p:column>
<p:column headerText="#{i18n['apiapp.description']}">
<h:outputText value="#{app.description}" />
</p:column>
<p:column>
<h:outputText value="#{app.created}">
<f:convertDateTime pattern="#{sessionHandler.datetimeFormat}"
timeZone="#{sessionHandler.timezone}" />
</h:outputText>
</p:column>
<p:column headerText="#{i18n['apiapp.enabled']}">
<h:outputText value="#{app.enabled}" />
</p:column>
</p:dataTable>
</ui:define>
</ui:composition>
</h:body>
</html>
\ No newline at end of file
......@@ -6,9 +6,9 @@
<h:body>
<ui:composition template="#{sessionHandler.template}">
<f:metadata>
<f:viewParam name="userid" value="#{userView.userid}" />
<f:event type="preRenderView" listener="#{userView.initView}" />
<f:event type="preRenderView" listener="#{userView.setCaptureForwardUrl('')}" />
<f:viewParam name="userid" value="#{userView.userid}"/>
<f:event type="preRenderView" listener="#{userView.initView}"/>
<f:event type="preRenderView" listener="#{userView.setCaptureForwardUrl('')}"/>
</f:metadata>
<ui:define name="title">
......@@ -16,27 +16,41 @@
<!-- <users:usertabs tabId="edit" /> -->
</ui:define>
<ui:define name="edittab">
<users:usertabs tabId="edit" />
<users:usertabs tabId="edit"/>
</ui:define>
<ui:define name="content">
<users:edit commitaction="#{userCartView.saveUser()}" commitvalue="#{i18n['user.save']}" />
<users:edit commitaction="#{userCartView.saveUser()}" commitvalue="#{i18n['user.save']}"/>
<div style="display: flex;">
<h:form id="roleform" rendered="#{roleView.canReadRoles}">
<h2>
<h:outputText value="#{i18n['user.roles']}:" />
<h:outputText value="#{i18n['user.roles']}:"/>
</h2>
<h:selectManyCheckbox converter="#{roleConverter}" disabled="#{!roleView.canWriteRoles}" layout="pageDirection" id="roles" value="#{userView.usersRoles}">
<f:selectItems var="roleitem" itemLabel="#{roleitem.name}" value="#{roleDataView.roles}" />
<f:selectItems var="roleitem" itemLabel="#{roleitem.name}" value="#{roleDataView.roles}"/>
</h:selectManyCheckbox>
<div>
<h:message rendered="#{roleView.canReadRoles}" for="roles" />
<p:message rendered="#{roleView.canReadRoles}" for="roles"/>
</div>
<h:commandButton action="#{userView.saveRoles}" value="#{i18n['user.saveRoles']}" />
<p:commandButton ajax="false" action="#{userView.saveRoles}" value="#{i18n['user.saveRoles']}"/>
</h:form>
<div style="margin-left: 4em;">
<h2>All users roles</h2>
<p:dataTable style="max-width: 600px;" var="r" value="#{userView.allUsersRoles}">
<p:column style="width: 30%;"><f:facet name="header"><h:outputText value="#{i18n['role.name']}"/></f:facet><h:outputText value="#{r.key.name}"/></p:column>
<p:column>
<f:facet name="header">
<h:outputText value="#{i18n['role.source']}"/>
</f:facet>
<ul><ui:repeat var="src" value="#{r.value}">
<li>#{i18n['role.source.'+=src]}</li>
</ui:repeat></ul>
</p:column>
</p:dataTable>
</div>
</div>
</ui:define>
</ui:composition>
</h:body>
......
......@@ -21,9 +21,7 @@ package fi.codecrew.moya.web.cdiview.user;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import javax.ejb.EJB;
import javax.enterprise.context.Conversation;
......@@ -231,6 +229,11 @@ public class UserView extends GenericCDIView {
return null;
}
public ArrayList<Map.Entry<Role, List<String>>> getAllUsersRoles(){
return new ArrayList<>(userbean.findUsersRolesWithReason(user).entrySet());
}
public List<Role> getUserSelectableRoles() {
if (userSelectableRoles == null && user != null) {
userSelectableRoles = rolebean.getRoles(getSelectedUser());
......
......@@ -1679,3 +1679,11 @@ user.allergies =Allergies / die
allergies.save =Save allergies
user.eventproperties =
submenu.permissionDenied =
role.source =
role.source.DIRECT =
role.source.ORG_ROLE =
role.source.PLACE =
role.source.ACCOUNTEVENT =
role.source.PLACE_PRODUCT =
role.source.EVENT_DEFAULT =
user.allroles =
\ No newline at end of file
......@@ -1916,3 +1916,11 @@ user.allergies =Allergies
allergies.save =Save allergies
user.eventproperties = Event specific properties
submenu.permissionDenied = Access denied
role.source = Role source
role.source.DIRECT = Forced for user
role.source.ORG_ROLE = Organisation role
role.source.PLACE = Place
role.source.ACCOUNTEVENT = Accountevent (Bought product)
role.source.PLACE_PRODUCT = Product via place
role.source.EVENT_DEFAULT = Event default role
user.allroles = All users roles
\ No newline at end of file
......@@ -598,7 +598,7 @@ incomingflow.giveplace = Merkitse annetuksi
incomingflow.groupmemberships = Konepaikat sek\u00E4 liput
incomingflow.invalidbarcode.message = Piipatulla koodilla ei l\u00F6ytynyt mit\u00E4\u00E4n, voit lis\u00E4t\u00E4 koodin k\u00E4ytt\u00E4j\u00E4\u00E4n
incomingflow.invalidbarcode.title = Virheellinen koodi
incomingflow.markEverythingGiven=Anna kaikki antamattomat liput
incomingflow.markEverythingGiven =Anna kaikki antamattomat liput
incomingflow.multisearch = Monihaku
incomingflow.placesummary = Paikkayhteenveto
incomingflow.printedCard = Kortti
......@@ -917,6 +917,7 @@ page.game.list.header = Insomnia Game
page.game.start.header = Insomnia Game
page.index.header = Etusivu
page.index.pagegroup = etusivu
page.permissionDenied.header = P\u00E4\u00E4sy kielletty
page.place.edit.header = Muokkaa paikkaa
page.place.insertToken.header = Sy\u00F6t\u00E4 paikkakoodi
page.poll.answer.header = Kysely
......@@ -925,7 +926,11 @@ page.poll.start.header = Kysely
page.product.create.pagegroup = yll\u00E4pito
page.product.createBill.header = Osta tuotteita
page.product.createBill.pagegroup = kauppa
page.product.edit.pagegroup = admin
page.product.list.pagegroup = admin
page.product.validateBillProducts.header = Tilaus luotu
page.role.create.pagegroup = admin
page.role.edit.pagegroup = admin
page.svm.error.header = Verkkomaksu ep\u00E4onnistui
page.svm.failure.header = Verkkomaksuvirhe
page.svm.notification.header = Maksutapahtuman rekister\u00F6inti
......@@ -1860,46 +1865,54 @@ voting.create.voteEnd = \u00C4\u00E4nestys kiinni
voting.create.voteStart = \u00C4\u00E4nestys auki
yes = Kyll\u00E4
page.product.shopClosed.header=Kauppa on kiinni!
page.product.shopClosed.notOpenYet=Tapahtuman lipunmyynti ei ole viel\u00E4 alkanut, Kokeile uudelleen my\u00F6hemmin.
page.product.shopClosed.alreadyClosed=Kauppa on suljettu, tervetuloa uudelleen tuleviin tapahtumiin.
queuemgmt.inDatabase=Arvo tietokannassa
queuemgmt.biggestFirst=Isoin ryhm\u00E4 ensin
queuemgmt.biggestIsFirst=Isompi ryhm\u00E4 nousee automaattisesti jonossa edemm\u00E4ksi.
reservequeue.reservingTimeIsUpAlert=Sinulle varattu varausauka on kulunut loppuun! Palaa takaisin 'varaa paikkasi' -sivulle jatkaaksesi.
mapView.reserveTimeLeft=Aikaa varata paikkasi
queuemgmt.queueEnabled=Varausjono ON k\u00E4yt\u00F6ss\u00E4
queuemgmt.queueDisabled=K\u00E4ytt\u00E4j\u00E4jono EI OLE k\u00E4yt\u00F6ss\u00E4 (ota k\u00E4yttoon tapahtuman tiedot -sivulta)
product.dependency.add=Lis\u00E4\u00E4 tuoteriippuvuus
product.dependency.create=Luo uusi
product.dependency.supporter=Tuote josta on riippuvuus
product.dependency.type=Riippuvuuden tyyppi
product.dependency.priority=Prioriteetti
product.dependency.delete=Poista
product.dependency.multiplier=Kerroin
product.dependency.MAX_SUPPORTED_COUNT=Tuotteita voi ostaa enint\u00E4\u00E4n saman m\u00E4\u00E4r\u00E4n
placemove.alreadyTaken=Paikkojen siirto peruuntui koska paikka {0} oli jo varattu.
placegroupview.moveUsersPlaces=Siirr\u00E4 k\u00E4ytt\u00E4j\u00E4n paikkoja
submenu.neomap.moveplaces=Vaihda paikkoja
mapEdit.removeSelectedPlaces=Poista paikat
page.product.shopClosed.header =Kauppa on kiinni!
page.product.shopClosed.notOpenYet =Tapahtuman lipunmyynti ei ole viel\u00E4 alkanut, Kokeile uudelleen my\u00F6hemmin.
page.product.shopClosed.alreadyClosed =Kauppa on suljettu, tervetuloa uudelleen tuleviin tapahtumiin.
queuemgmt.inDatabase =Arvo tietokannassa
queuemgmt.biggestFirst =Isoin ryhm\u00E4 ensin
queuemgmt.biggestIsFirst =Isompi ryhm\u00E4 nousee automaattisesti jonossa edemm\u00E4ksi.
reservequeue.reservingTimeIsUpAlert =Sinulle varattu varausauka on kulunut loppuun! Palaa takaisin 'varaa paikkasi' -sivulle jatkaaksesi.
mapView.reserveTimeLeft =Aikaa varata paikkasi
queuemgmt.queueEnabled =Varausjono ON k\u00E4yt\u00F6ss\u00E4
queuemgmt.queueDisabled =K\u00E4ytt\u00E4j\u00E4jono EI OLE k\u00E4yt\u00F6ss\u00E4 (ota k\u00E4yttoon tapahtuman tiedot -sivulta)
product.dependency.add =Lis\u00E4\u00E4 tuoteriippuvuus
product.dependency.create =Luo uusi
product.dependency.supporter =Tuote josta on riippuvuus
product.dependency.type =Riippuvuuden tyyppi
product.dependency.priority =Prioriteetti
product.dependency.delete =Poista
product.dependency.multiplier =Kerroin
product.dependency.MAX_SUPPORTED_COUNT =Tuotteita voi ostaa enint\u00E4\u00E4n saman m\u00E4\u00E4r\u00E4n
placemove.alreadyTaken =Paikkojen siirto peruuntui koska paikka {0} oli jo varattu.
placegroupview.moveUsersPlaces =Siirr\u00E4 k\u00E4ytt\u00E4j\u00E4n paikkoja
submenu.neomap.moveplaces =Vaihda paikkoja
mapEdit.removeSelectedPlaces =Poista paikat
bortalApplication.map.MOVE_PLACES = Paikkojen itsepalvelusiirto
bill.list.header = Tilaukset
placemove.noMovablePlaces = Ei siirrett\u00E4vi\u00E4 paikkoja
deliver=Toimita
placegroupview.noProductsToDeliver=Ei toimitettavia tuotteita
placegroupview.productName=Tuotteen nimi
incomingflow.giveAccountEvent=Merkitse annetuksi
incomingflow.ungiveAccountEvent=Ei olekkaan annettu
incomingflow.deliverableProducts=Toimitettavat tuotteet
placegroupview.accountEventDescription=Kuvaus
incomingflow.ungivenProducts=K\u00E4ytt\u00E4j\u00E4ll\u00E4 on toimittamattomia tuotteita
placegroupview.count=Kpl
user.allergies=Allergiat / ruokavaliot
allergies.save=Tallenna allergiat
deliver =Toimita
placegroupview.noProductsToDeliver =Ei toimitettavia tuotteita
placegroupview.productName =Tuotteen nimi
incomingflow.giveAccountEvent =Merkitse annetuksi
incomingflow.ungiveAccountEvent =Ei olekkaan annettu
incomingflow.deliverableProducts =Toimitettavat tuotteet
placegroupview.accountEventDescription =Kuvaus
incomingflow.ungivenProducts =K\u00E4ytt\u00E4j\u00E4ll\u00E4 on toimittamattomia tuotteita
placegroupview.count =Kpl
user.allergies =Allergiat / ruokavaliot
allergies.save =Tallenna allergiat
user.eventproperties = Tapahtumakohtaiset tiedot
submenu.permissionDenied = Pääsy kielletty
\ No newline at end of file
submenu.permissionDenied = P\u00E4\u00E4sy kielletty
role.source = Roolin lhde
role.source.DIRECT = Pakotettu k\u00E4ytt\u00E4j\u00E4lle
role.source.ORG_ROLE = Organisaatiorooli
role.source.PLACE = Paikka
role.source.ACCOUNTEVENT = Tilitapahtuma (Ostettu tuote)
role.source.PLACE_PRODUCT = Tuote paikan kautta
role.source.EVENT_DEFAULT = Tapahtuman oletusrooli
user.allroles = Kaikki k\u00E4yttäj\u00E4n roolit
\ No newline at end of file
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!