Skip to content

Commit

Permalink
Fix the approve issue.
Browse files Browse the repository at this point in the history
  • Loading branch information
Mahmoud-Emad committed Apr 23, 2024
1 parent f2563c0 commit 57a51f3
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 36 deletions.
65 changes: 62 additions & 3 deletions client/src/components/NotificationDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,27 @@
</v-card>
</v-col>
</v-row>

<v-card-actions class="d-flex justify-end">
<v-btn color="info" variant="flat" @click="closeDialog()">Close</v-btn>
<v-row class="d-flex justify-end mt-3">
<div v-if="couldApprove && vacationStatus == 'pending'">
<v-btn variant="tonal" color="primary" class="ma-1" @click="handleApprove">Approve</v-btn>
<v-btn variant="tonal" color="error" class="ma-1" @click="handleReject">Reject</v-btn>
<v-spacer></v-spacer>
</div>
<v-btn color="info" class="ma-1" variant="flat" @click="closeDialog()">Close</v-btn>
</v-row>
</v-card-actions>
</v-card>
</template>

<script lang="ts">
import { computed, type PropType } from 'vue'
import { computed, ref, type PropType } from 'vue'
import { getStatusColor } from '@/utils'
import type { Api } from '@/types'
import { ApiClientBase } from '@/clients/api/base'
import { useAsyncState } from '@vueuse/core'
import { $api } from '@/clients'
export default {
name: 'NotificationDetails',
Expand All @@ -71,22 +83,69 @@ export default {
type: Array as PropType<any[]>,
required: true
},
vacation: {
type: Object as PropType<Api.Vacation>,
required: true
},
onClose: {
type: Function as PropType<() => void>,
required: true
}
},
setup(props) {
const color = computed(() => getStatusColor(props.status))
const user = ApiClientBase.user
const vacationStatus = ref(props.status)
const closeDialog = () => {
props.onClose()
}
async function handleApprove() {
return useAsyncState($api.vacations.approve.update(props.vacation.id), [], {
onSuccess() {
vacationStatus.value = 'approved'
}
})
}
async function handleReject() {
return useAsyncState($api.vacations.reject.update(props.vacation.id), [], {
onSuccess() {
vacationStatus.value = 'rejected'
}
})
}
const couldApprove = computed(() => {
const vacation = props.vacation
if (user.value && vacation) {
// supervisor could approve vacation
if (
user.value.fullUser.user_type.toLocaleLowerCase() === 'admin' ||
user.value.fullUser.user_type.toLocaleLowerCase() === 'supervisor'
) {
// Could approve if applying user reports to the logged in supervisor
if (
vacation.applying_user.reporting_to &&
vacation.applying_user.reporting_to[0]?.id === user.value.fullUser.id
) {
return true
}
return false
}
}
return false
})
return {
color,
couldApprove,
vacationStatus,
closeDialog,
getStatusColor
getStatusColor,
handleApprove,
handleReject
}
}
}
Expand Down
14 changes: 10 additions & 4 deletions client/src/components/NotificationDetailsDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
:eventId="notification.state.value.event_id"
:sections="getSections(notification.state.value)"
:status="notification.state.value.status"
:vacation="
notification.state.value.type === 'vacations' ? notification.state.value : undefined
"
@close="closeDialog"
/>
</VDialog>
Expand Down Expand Up @@ -86,8 +89,11 @@ export default {
{
title: 'Request Details',
details: [
{ label: 'From Date', value: data.from_date },
{ label: 'End Date', value: data.end_date }
{
label: 'From Date',
value: `${new Date(data.from_date).toDateString()}`
},
{ label: 'End Date', value: `${new Date(data.end_date).toDateString()}` }
]
},
{
Expand All @@ -100,8 +106,8 @@ export default {
{
title: 'Approval User',
details: [
{ label: 'Name', value: data.approval_user.full_name },
{ label: 'Email', value: data.approval_user.email }
{ label: 'Name', value: data.approval_user.full_name || '-' },
{ label: 'Email', value: data.approval_user.email || '-' }
]
}
]
Expand Down
16 changes: 5 additions & 11 deletions client/src/components/cards/vacationCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -196,21 +196,15 @@ export default {
const couldApprove = computed(() => {
if (user.value) {
// only admins or supervisors could approve vacation
// supervisor could approve vacation
if (
user.value.fullUser.user_type === 'Admin' ||
user.value.fullUser.user_type === 'Supervisor'
user.value.fullUser.user_type.toLocaleLowerCase() === 'admin' ||
user.value.fullUser.user_type.toLocaleLowerCase() === 'supervisor'
) {
// Could approve if user signed in is the same user applied for vacation
if (props.vacation.applying_user.id == user.value.fullUser.id) {
return true
}
// Could approve if applying user reports to the supervisor or admin logged in
// and works from the same office
// Could approve if applying user reports to the logged in supervisor
if (
props.vacation.applying_user.reporting_to &&
props.vacation.applying_user.reporting_to[0]?.id === props.vacation.applying_user?.id &&
props.vacation.applying_user.location.name === user.value.fullUser.location.name
props.vacation.applying_user.reporting_to[0]?.id === user.value.fullUser.id
) {
return true
}
Expand Down
5 changes: 4 additions & 1 deletion server/cshr/serializers/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ class BaseUserSerializer(ModelSerializer):
skills = SerializerMethodField()
user_certificates = SerializerMethodField()
location = SerializerMethodField()
reporting_to = SerializerMethodField()

class Meta:
model = User
Expand All @@ -334,6 +335,7 @@ class Meta:
"user_certificates",
"is_active",
"telegram_link",
"reporting_to",
"location"
]

Expand All @@ -350,7 +352,8 @@ def get_user_certificates(self, obj):
training_courses = get_training_courses_for_a_user(obj.id)
return TrainingCoursesSerializer(training_courses, many=True).data


def get_reporting_to(self, obj):
return BasicUserSerializer(obj.reporting_to, many=True).data

class TeamSerializer(ModelSerializer):
"""Class team serilaizer to return user team leaders and team members."""
Expand Down
53 changes: 42 additions & 11 deletions server/cshr/utils/redis_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from components import config
import redis
import json
from datetime import datetime


from cshr.api.response import CustomResponse

Expand All @@ -23,7 +25,9 @@
def set_notification_request_redis(data: Dict) -> bool:
"""this function set requests notifications"""
applying_user = None
if type(data.get("applying_user")) is not int and data.get("applying_user").get("id"):
if type(data.get("applying_user")) is not int and data.get("applying_user").get(
"id"
):
applying_user = data["applying_user"]["id"]
else:
applying_user = data["applying_user"]
Expand Down Expand Up @@ -110,30 +114,56 @@ def notification_commented(data: Dict, user, state: str, event_id: int):


def get_notifications(user: User):
"""this function returns all notifications for certain user"""
keys = redis_instance.keys("user" + str(user.id) + "*")
"""
Retrieve all notifications for a specific user.
Args:
user (User): The user for whom notifications are to be retrieved.
Returns:
list: A list of dictionaries containing notification data.
"""
notifications = []
val = ""
keys = redis_instance.keys("user" + str(user.id) + "*")

for key in keys:
val = redis_instance.hgetall(key)
dval = dict((k.decode("utf8"), v.decode("utf8")) for k, v in val.items())
noti_user_id: int = json.loads(dval.get("user"))["id"]
noti_id: int = int(json.loads(dval.get("event_id")))

# Parse notification data
noti_user_id = json.loads(dval.get("user"))["id"]
noti_id = int(json.loads(dval.get("event_id")))

# Check if the user associated with the notification exists
try:
usernt = User.objects.get(
id=noti_user_id
) # Check if the user is not deleted
usernt = User.objects.get(id=noti_user_id)
dval["user"] = BaseUserSerializer(usernt).data
except User.DoesNotExist:
redis_instance.delete(key)

# Check if the request associated with the notification exists
try:
Requests.objects.get(id=noti_id) # Check if the Request is not deleted
Requests.objects.get(id=noti_id)
except Requests.DoesNotExist:
redis_instance.delete(key)

notifications.append(dval)

return notifications


def sort_notifications_by_created_at(notifications):
# Custom key function to extract timestamp from the notification
def get_timestamp(notification):
created_at_str = notification["created_at"]
# Parse the timestamp string and convert it to datetime object
return datetime.strptime(created_at_str, "%Y-%m-%d | %H:%M")

# Sort the notifications based on the created_at timestamp
sorted_notifications = sorted(notifications, key=get_timestamp, reverse=True)
return sorted_notifications


def ping_redis():
try:
redis_instance.ping()
Expand All @@ -146,11 +176,12 @@ def ping_redis():
def get_redis_conf() -> Dict[str, str]:
return {"host": R_HOST, "port": R_PORT}


def http_ensure_redis_error():
return CustomResponse.bad_request(
message="Connection Refused",
error={
"message": "Redis is not running, please make sure that you run the Redis server on the provided values",
"values": {"host": R_HOST, "port": R_PORT},
},
)
)
5 changes: 2 additions & 3 deletions server/cshr/utils/vacation_balance_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,10 @@ def check_and_update_balance(
if hasattr(v, reason):
curr_balance = getattr(v, reason)
if vacation.status == STATUS_CHOICES.APPROVED:
if delete:
new_value: int= curr_balance + vacation_days
if delete:
new_value: int= curr_balance + vacation_days
return self.update_user_balance(applying_user, reason, new_value)
if curr_balance >= vacation_days:

if vacation.status == STATUS_CHOICES.PENDING:
new_value: int = curr_balance - vacation_days
return self.update_user_balance(applying_user, reason, new_value)
Expand Down
5 changes: 3 additions & 2 deletions server/cshr/views/notifications.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from cshr.utils.redis_functions import get_notifications, http_ensure_redis_error, ping_redis
from cshr.utils.redis_functions import get_notifications, http_ensure_redis_error, ping_redis, sort_notifications_by_created_at
from rest_framework.generics import GenericAPIView, ListAPIView
from cshr.api.permission import UserIsAuthenticated
from rest_framework.response import Response
Expand All @@ -15,4 +15,5 @@ def get(self, request: Request) -> Response:
except:
return http_ensure_redis_error()
res = get_notifications(request.user)
return CustomResponse.success(data=res)
sorted_notifications = sort_notifications_by_created_at(res)
return CustomResponse.success(data=sorted_notifications)
2 changes: 1 addition & 1 deletion server/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@

LANGUAGE_CODE = "en-us"

TIME_ZONE = "UTC"
TIME_ZONE = 'Africa/Cairo'

USE_I18N = True

Expand Down

0 comments on commit 57a51f3

Please sign in to comment.