I'm building an app in android that manages users and shifts. Each user can be assigned to multiple shifts and every shift can have many users. Each shift is associated with a single date.
This is how my database is structured in firebase :
{
"shift-assign" : {
"key1" : {
"111" : true,
"222" : true,
"888" : true
},
"key2" : {
"111" : true,
"222" : true
}
},
"shifts" : {
"key1" : {
"date" : "20191202",
"endTime" : "02:00",
"numOfEmps" : 4,
"startTime" : "21:00",
"wage" : 30
},
"key2" : {
"date" : "20191202",
"endTime" : "12:00",
"numOfEmps" : 3,
"startTime" : "08:00",
"wage" : 10
},
"key3" : {
"date" : "20191203",
"endTime" : "06:00",
"numOfEmps" : 3,
"startTime" : "00:00",
"wage" : 30
}
},
"user-assign" : {
"111" : {
"key1" : true,
"key2" : true
},
"222" : {
"key1" : true,
"key2" : true
},
"888" : {
"key1" : true
}
},
"users" : {
"111" : {
"active" : true,
"email" : "111",
"firstName" : "aaa",
"id" : "111",
"lastName" : "aaa",
"password" : "111",
"phone" : "1111111"
},
"222" : {
"active" : true,
"email" : "222",
"firstName" : "bbb",
"id" : "222",
"lastName" : "bbb",
"password" : "222",
"phone" : "4444444"
},
"888" : {
"active" : false,
"email" : "888",
"firstName" : "ccc",
"id" : "888",
"lastName" : "ccc",
"password" : "888",
"phone" : "5555555"
},
"99999" : {
"active" : true,
"firstName" : "Admin",
"id" : "99999",
"password" : "123"
}
}
}
When a user tries to assign themselves to a shift I want to check for a few constraints before actually assigning them to the shift.
I tried using a transaction but the code got messed up because I needed to reference different nodes inside a transaction.
My code now looks like this:
private void AssignUserToShift() {
// Constraint No. 0
// user cannot be assigned to shifts whose date < today
if(dates[0].compareTo(LocalDateTime.now().toLocalDate().format(dtf)) < 0){
Toast.makeText(getApplicationContext(), "Cannot assign to prior dates", Toast.LENGTH_SHORT).show();
return;
}
Map<String, Object> pathsToUpdate = new HashMap<>();
pathsToUpdate.put("shift-assign/" + shiftKey + "/" + userId, true);
pathsToUpdate.put("user-assign/" + userId + "/" + shiftKey, true);
mDatabase.updateChildren(pathsToUpdate, new DatabaseReference.CompletionListener() {
@Override
public void onComplete(@Nullable DatabaseError databaseError, @NonNull DatabaseReference databaseReference) {
if(databaseError == null) {
// add to adapter's list in GUI
AddUserToList();
}else{
Toast.makeText(getApplicationContext(), "Could not update database entries", Toast.LENGTH_SHORT).show();
}
}
});
}
And the opposite method:
private void RemoveUserFromShift() {
mUser user = userList.stream()
.filter(u -> u.getId().equals(userId))
.findAny()
.orElse(null);
if(user == null){
Toast.makeText(UsersAssignedActivity.this, "You are not assigned to this shift.", Toast.LENGTH_SHORT).show();
return;
}
AlertDialog.Builder builder = new AlertDialog.Builder(UsersAssignedActivity.this, R.style.AlertDialogCustom);
builder.setMessage("Do you wish to remove yourself from this shift?");
builder.setCancelable(false);
builder.setPositiveButton("Yes",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int i) {
Map<String, Object> pathsToDelete = new HashMap<>();
pathsToDelete.put("shift-assign/" + shiftKey + "/" + userId, null);
pathsToDelete.put("user-assign/" + userId + "/" + shiftKey, null);
mDatabase.updateChildren(pathsToDelete, new DatabaseReference.CompletionListener() {
@Override
public void onComplete(@Nullable DatabaseError databaseError, @NonNull DatabaseReference databaseReference) {
if(databaseError == null){
// remove from adapter's list in GUI
RemoveUserFromList();
}else{
Toast.makeText(UsersAssignedActivity.this, "Could not delete database entries", Toast.LENGTH_SHORT).show();
}
}
});
}
});
builder.setNegativeButton("No",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int i) {
dialog.cancel();
}
});
AlertDialog alertDialog = builder.create();
alertDialog.show();
}
So I thought I'd use rules instead and check the constraints server side.
I want to write rules that satisfy the following conditions -
User shall not be able to assign to a shift that is full. (its
numOfEmps
equals to the number of children nodes undershift-assign/shiftKey
)User shall not be able to assign to more than half of the shifts in a certain date (if there are 4 shifts associated with the same date and the user is already assigned to one of those shifts, they will only be allowed to assign themselves to one more shift in that date.)
User shall not be able to work more than 4 days in a row. (if the user tries to assign themselves to a shift in a certain date and the user is already assigned to a shift(s) in the previous 4 days, they will not be able to assign themselves to this shift)
Lastly if the user is not already assigned to this shift. I guess this can be checked client-side using a transaction.
How can I write these rules?