Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/src/clients/api/vacations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class VacationsApi extends ApiClientBase {
return userBalance
}

async updateUsersBalance(data: { user_ids: number[], balance: Api.BalanceVacation }) {
async updateUsersBalance(data: { user_ids: number[], balance: Api.BalanceVacation, year?: number }) {
ApiClientBase.assertUser()
return await this.unwrap(() => this.$http.put<Api.Returns.Balances>(this.getUrl(`/balance/users`), data), {
transform: (d) => d.results
Expand Down
47 changes: 41 additions & 6 deletions client/src/components/dashboard/SetUserVacationBalance.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<!-- Tooltip Content -->
<template #default>
<v-card class="pa-2">
<h3 class="pa-2">Vacation Balances</h3>
<h3 class="pa-2">Vacation Balances ({{ selectedYear }})</h3>
<v-card v-if="slotProps.item">
<div class="reason mb-2" v-for="(type, index) in defaultBalanceValues" :key="index">
<div class="d-flex">
Expand Down Expand Up @@ -67,11 +67,18 @@
</template>
</v-autocomplete>
</v-col>
<v-col cols="12">
<!-- Year Selection -->
<v-select v-model="selectedYear" :items="availableYears" label="Year" :rules="requiredRules"
:disabled="isLoading" hint="Select the year for which you want to view/update the balance" persistent-hint>
</v-select>
</v-col>
<v-col cols="12">
<v-alert type="info">
The form below allows you to set vacation balances for users. You can select multiple users and then update
the
balance as the base balance for all selected users.
The form below allows you to set vacation balances for users. You can select the year and multiple users,
then
update
the balance as the base balance for all selected users for the specified year.
</v-alert>

<v-row class="mt-1">
Expand Down Expand Up @@ -116,18 +123,24 @@
const user = ApiClientBase.user
const officeUsers = ref<any[]>([])
const selectedUsers = ref<Api.User[]>([])
const page = ref(0)

Check warning on line 126 in client/src/components/dashboard/SetUserVacationBalance.vue

View workflow job for this annotation

GitHub Actions / Run linters (18.x)

'page' is assigned a value but never used
const count = ref(0)

Check warning on line 127 in client/src/components/dashboard/SetUserVacationBalance.vue

View workflow job for this annotation

GitHub Actions / Run linters (18.x)

'count' is assigned a value but never used
const balances: Ref<{ [userId: number]: Api.BalanceVacation }> = ref({})
const isLoading = ref(false)

// Year selection - dynamically generate last 5 years
const currentYear = new Date().getFullYear()
const availableYears = Array.from({ length: 5 }, (_, i) => currentYear - i)
const selectedYear = ref(currentYear)

// Computed Properties
const selectedSomeUsers = computed(() => selectedUsers.value.length > 0)
const selectedAllUsers = computed(() => selectedUsers.value.length === officeUsers.value.length)
const balanceState = useAsyncState(async () => {
const balance_result = await $api.vacations.updateUsersBalance(
{
user_ids: selectedUsers.value.map(user => user.id),
year: selectedYear.value,
balance: {
total_days: {
annual: +totalBalanceFields.value.find((i) => i.key === 'annual')!.value,
Expand All @@ -149,6 +162,8 @@
}
)

// Clear old balances and update with new ones for the selected year
balances.value = {}
for (let i = 0; i < balance_result!.length; i++) {
balances.value[balance_result![i].user!.id] = balance_result![i]
}
Expand All @@ -169,9 +184,9 @@
]


// Fetch and Cache User Balance
// Fetch and Cache User Balance for selected year
const fetchUserBalance = async (userId: number) => {
const result = await $api.vacations.getUserBalance(userId)
const result = await $api.vacations.getUserBalance(userId, { year: selectedYear.value })
Reflect.set(balances.value, userId, result)
}

Expand Down Expand Up @@ -290,6 +305,24 @@
}
}, { deep: true })

// Watch for year changes to refetch balances
watch(selectedYear, async () => {
// Clear cached balances when year changes
balances.value = {}

// Refetch balances for all selected users with new year
for (const user of selectedUsers.value) {
await fetchUserBalance(user.id)
}

// Update form values
if (selectedUsers.value.length == 1) {
updateBalanceFormValues({ user: selectedUsers.value[0] })
} else {
updateBalanceFormValues({ reset: true })
}
})

const updateBalanceFormValues = (options?: { reset?: boolean, user?: Api.User }) => {
if (options && options.reset) {
for (let i = 0; i < totalBalanceFields.value.length; i++) {
Expand Down Expand Up @@ -341,6 +374,8 @@
Reflect,
isLoading,
submitBalance,
availableYears,
selectedYear,

fetchUserBalance,
getUserById,
Expand Down
2 changes: 2 additions & 0 deletions server/cshr/serializers/vacations.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
FloatField,
ReadOnlyField,
CharField,
IntegerField,
)
from cshr.models.vacations import (
UserVacationBalance,
Expand Down Expand Up @@ -145,6 +146,7 @@ class BalanceObjectSerializer(Serializer):
class UpdateVacationBalanceSerializer(Serializer):
user_ids = ListField()
balance = BalanceObjectSerializer()
year = IntegerField(required=False)


class UserVacationBalanceSerializer(ModelSerializer):
Expand Down
25 changes: 24 additions & 1 deletion server/cshr/views/vacations.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,12 +275,35 @@ def get(self, request: Request, user_id: str) -> Response:
Use this endpoint to get user balance
### Parameters
- `user_id`: The id of the user
- `year`: Optional year parameter (defaults to current year)
"""

user = get_user_by_id(user_id)
if user is None:
return CustomResponse.not_found(message="User not found", status_code=404)

year = request.query_params.get("year")
if year:
try:
year = int(year)
except ValueError:
return CustomResponse.bad_request(message="Invalid year parameter")

# Get or create balance for specific year
user_balance, _ = UserVacationBalance.objects.get_or_create(
user=user,
year=year,
defaults={
"total_days": VacationBalanceModel.objects.create(),
"remaining_days": VacationBalanceModel.objects.create(compensation=0, paternity=0, maternity=0),
"transferred_days": None,
},
)
return CustomResponse.success(
message="Success found balance.",
data=self.serializer_class(user_balance).data,
)

balance_calculator = VacationBalanceCalculator(
applying_user=user, vacation=None
)
Expand All @@ -305,7 +328,7 @@ def put(self, request: Request):

user_ids = serializer.validated_data.get("user_ids", [])
balance = serializer.validated_data.get("balance", {})
current_year = datetime.now().year
current_year = serializer.validated_data.get("year", datetime.now().year)

users = self.get_users_by_ids(user_ids)
if not isinstance(users, list): # If not a list, it's a response (error case)
Expand Down
Loading