From 657b72eb7a1e6d5a78e6c2513fea6e7be223a0ef Mon Sep 17 00:00:00 2001 From: Stan <1939656+GDay@users.noreply.github.com> Date: Wed, 31 Dec 2025 05:32:39 +0100 Subject: [PATCH] few updates --- back/admin/people/forms.py | 52 ++++++++++--------- .../people/templates/colleague_update.html | 1 + back/admin/people/templates/colleagues.html | 18 ++++--- .../people/templates/new_hire_detail.html | 10 ++-- back/admin/people/views/colleagues.py | 9 +--- back/admin/people/views/new_hires.py | 41 ++++++++++++--- back/admin/settings/forms.py | 4 +- back/admin/settings/views.py | 1 + back/back/templatetags/general.py | 9 ++-- back/new_hire/views.py | 3 +- back/users/models.py | 13 ----- back/users/utils.py | 14 +++++ 12 files changed, 106 insertions(+), 69 deletions(-) diff --git a/back/admin/people/forms.py b/back/admin/people/forms.py index 077ef2e0..5c61ab76 100644 --- a/back/admin/people/forms.py +++ b/back/admin/people/forms.py @@ -14,9 +14,14 @@ MultiSelectField, UploadField, ) -from misc.mixins import FilterDepartmentsFieldByUserMixin from organization.models import Organization -from users.models import User +from users.models import DepartmentRole, User +from users.selectors import get_available_roles_for_user + + +class RoleModelMultipleChoiceField(forms.ModelMultipleChoiceField): + def label_from_instance(self, obj): + return obj.department.name + " // " + obj.name class NewHireAddForm(forms.ModelForm): @@ -25,7 +30,13 @@ class NewHireAddForm(forms.ModelForm): to_field_name="id", initial=Sequence.onboarding.filter(auto_add=True), required=False, - label=_("Sequences"), + label=_("Additional sequences"), + ) + roles = RoleModelMultipleChoiceField( + queryset=DepartmentRole.objects.none(), + to_field_name="id", + required=False, + label=_("Roles"), ) buddy = forms.ModelChoiceField( queryset=get_user_model().managers_and_admins_or_slack_users.all(), @@ -41,7 +52,7 @@ class NewHireAddForm(forms.ModelForm): widget=forms.DateInput(attrs={"type": "date"}, format=("%Y-%m-%d")), ) - def __init__(self, *args, **kwargs): + def __init__(self, user, *args, **kwargs): super().__init__(*args, **kwargs) self.fields["buddy"].required = False self.fields["manager"].required = False @@ -49,6 +60,10 @@ def __init__(self, *args, **kwargs): self.fields["language"].initial = Organization.object.get().language self.fields["timezone"].initial = Organization.object.get().timezone self.fields["start_day"].initial = timezone.now().date() + self.fields["roles"].queryset = get_available_roles_for_user( + user=user + ).order_by("department") + print(self.fields["roles"].queryset) self.helper = FormHelper() layout = Layout( Div( @@ -93,7 +108,7 @@ def __init__(self, *args, **kwargs): css_class="row", ), Div( - Div(Field("departments"), css_class="col-6"), + Div(Field("roles"), css_class="col-6"), Div( UploadField( "profile_image", @@ -140,7 +155,7 @@ class Meta: "language", "buddy", "manager", - "departments", + "roles", "profile_image", ) @@ -189,6 +204,10 @@ def __init__(self, *args, **kwargs): Div( Field("last_name"), Field("phone"), + UploadField( + "profile_image", + extra_context={"file": self.instance.profile_image}, + ), Field("start_day"), css_class="col-6", ), @@ -198,17 +217,6 @@ def __init__(self, *args, **kwargs): Div(Field("message"), css_class="col-12"), css_class="row", ), - Div( - Div(Field("departments"), css_class="col-6"), - Div( - UploadField( - "profile_image", - extra_context={"file": self.instance.profile_image}, - ), - css_class="col-6", - ), - css_class="row", - ), Div( Div( Field("timezone"), @@ -228,19 +236,17 @@ class Meta: "last_name", "position", "email", - "phone", "start_day", "message", "timezone", "language", "buddy", "manager", - "departments", "profile_image", ) -class ColleagueUpdateForm(FilterDepartmentsFieldByUserMixin, forms.ModelForm): +class ColleagueUpdateForm(forms.ModelForm): birthday = forms.DateField( widget=forms.DateInput(attrs={"type": "date"}, format=("%Y-%m-%d")), required=False, @@ -259,7 +265,6 @@ def __init__(self, *args, **kwargs): Div( Div(Field("email"), css_class="col-12"), Div(Field("position"), css_class="col-12"), - Div(Field("departments"), css_class="col-12"), Div(Field("phone"), css_class="col-12"), Div(Field("birthday"), css_class="col-12"), Div(Field("message"), css_class="col-12"), @@ -281,7 +286,6 @@ class Meta: "first_name", "last_name", "position", - "departments", "birthday", "email", "phone", @@ -295,7 +299,7 @@ class Meta: ) -class ColleagueCreateForm(FilterDepartmentsFieldByUserMixin, forms.ModelForm): +class ColleagueCreateForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() @@ -311,7 +315,6 @@ def __init__(self, *args, **kwargs): Div( Div(Field("email"), css_class="col-12"), Div(Field("position"), css_class="col-12"), - Div(Field("departments"), css_class="col-12"), Div(Field("phone"), css_class="col-12"), Div(Field("message"), css_class="col-12"), Div(Field("facebook"), css_class="col-12"), @@ -332,7 +335,6 @@ class Meta: "first_name", "last_name", "position", - "departments", "email", "phone", "message", diff --git a/back/admin/people/templates/colleague_update.html b/back/admin/people/templates/colleague_update.html index ce666372..adc8c5b2 100644 --- a/back/admin/people/templates/colleague_update.html +++ b/back/admin/people/templates/colleague_update.html @@ -3,6 +3,7 @@ {% load crispy_forms_tags %} {% block actions %} +{% translate "Progress" %} {% if object.termination_date is None %} {% translate "Terminate" %} {% else %} diff --git a/back/admin/people/templates/colleagues.html b/back/admin/people/templates/colleagues.html index 8e19d313..e546212f 100644 --- a/back/admin/people/templates/colleagues.html +++ b/back/admin/people/templates/colleagues.html @@ -33,7 +33,7 @@ {% translate "Name" %} {% translate "Position" %} - {% translate "Departments" %} + {% translate "Has access to departments" %} @@ -47,11 +47,17 @@ {{ colleague.position }} - {% for department in colleague.departments.all %} - {{ department }} - {% empty %} - {% trans "Not in a department" %} - {% endfor %} + {% if colleague.is_admin %} + {% trans "All" %} + {% elif colleague.is_manager %} + {% for department in colleague.departments.all %} + {{ department }} + {% empty %} + {% trans "All" %} + {% endfor %} + {% else %} + User has no admin/manager access + {% endif %} {% if slack_active %} diff --git a/back/admin/people/templates/new_hire_detail.html b/back/admin/people/templates/new_hire_detail.html index 3d4aa693..a3e8c5d1 100644 --- a/back/admin/people/templates/new_hire_detail.html +++ b/back/admin/people/templates/new_hire_detail.html @@ -19,7 +19,9 @@ {% endif %} -{% include "_new_hire_menu.html" %} +{% if object.is_new_hire %} + {% include "_new_hire_menu.html" %} +{% endif %}
@@ -29,7 +31,7 @@

{% translate "What's up next..." %}

    {# only show preboarding if before first day #} - {% if object.workday == 0 and object.preboarding.all|length %} + {% if object.workday == 0 and object.preboarding.all|length and object.is_new_hire %}
  • @@ -61,7 +63,7 @@

    {% translate "Before first day" %}

    {% endif %} {% for condition in conditions %} {% show_highlighted_date_card conditions forloop.counter0 object.start_day as show_start %} - {% if show_start %} + {% if show_start and object.is_new_hire %}
  • @@ -83,7 +85,7 @@

    {% blocktranslate with start_day=object.start_day %}{{ st
    {% if condition.condition_type == ConditionType.AFTER or condition.condition_type == ConditionType.BEFORE %} -

    {{ condition|new_hire_trigger_date:object }} {% trans "at" %} {{ condition.time }}

    +

    {{ condition|new_hire_trigger_date:condition_start_date_map }} {% trans "at" %} {{ condition.time }}

    {% elif condition.condition_type == ConditionType.ADMIN_TASK %}

    {% trans "When these admin tasks are completed:" %} diff --git a/back/admin/people/views/colleagues.py b/back/admin/people/views/colleagues.py index 7d15663a..1debff65 100644 --- a/back/admin/people/views/colleagues.py +++ b/back/admin/people/views/colleagues.py @@ -40,7 +40,6 @@ from admin.resources.selectors import get_resource_templates_for_user from admin.sequences.models import Condition, Sequence from api.permissions import AdminPermission -from misc.mixins import FormWithUserContextMixin from organization.models import Organization, WelcomeMessage from slack_bot.utils import Slack, actions, button, paragraph from users.emails import email_new_admin_cred @@ -89,9 +88,7 @@ def get_context_data(self, **kwargs): return context -class ColleagueCreateView( - AdminOrManagerPermMixin, FormWithUserContextMixin, SuccessMessageMixin, CreateView -): +class ColleagueCreateView(AdminOrManagerPermMixin, SuccessMessageMixin, CreateView): template_name = "colleague_create.html" model = get_user_model() form_class = ColleagueCreateForm @@ -109,9 +106,7 @@ def get_context_data(self, **kwargs): return context -class ColleagueUpdateView( - AdminOrManagerPermMixin, FormWithUserContextMixin, SuccessMessageMixin, UpdateView -): +class ColleagueUpdateView(AdminOrManagerPermMixin, SuccessMessageMixin, UpdateView): template_name = "colleague_update.html" model = get_user_model() form_class = ColleagueUpdateForm diff --git a/back/admin/people/views/new_hires.py b/back/admin/people/views/new_hires.py index f89a70a7..20537e93 100644 --- a/back/admin/people/views/new_hires.py +++ b/back/admin/people/views/new_hires.py @@ -43,7 +43,13 @@ send_reminder_email, ) from users.mixins import AdminOrManagerPermMixin -from users.models import NewHireWelcomeMessage, PreboardingUser, ResourceUser, ToDoUser +from users.models import ( + NewHireWelcomeMessage, + PreboardingUser, + ResourceUser, + ToDoUser, + UserCondition, +) class NewHireListView(AdminOrManagerPermMixin, ListView): @@ -68,6 +74,11 @@ class NewHireAddView(AdminOrManagerPermMixin, SuccessMessageMixin, CreateView): success_message = _("New hire has been created") success_url = reverse_lazy("people:new_hires") + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs["user"] = self.request.user + return kwargs + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["title"] = _("Add new hire") @@ -82,8 +93,17 @@ def form_valid(self, form): new_hire = form.save() - # Add sequences to new hire - new_hire.add_sequences(sequences) + # additional sequences, then add the ones from the roles + sequence_pks = list(sequences.values_list("pk", flat=True)) + + for role in form.cleaned_data["roles"]: + role.users.add(new_hire) + sequence_pks += list( + role.sequences.all().values_list("pk", flat=True) + ) + list(role.department.sequences.all().values_list("pk", flat=True)) + + sequences = Sequence.objects.filter(id__in=sequence_pks) + new_hire.add_sequences(Sequence.objects.filter(id__in=sequence_pks)) # Send credentials email if the user was created after their start day org = Organization.object.get() @@ -313,24 +333,31 @@ class NewHireSequenceView(AdminOrManagerPermMixin, DetailView): template_name = "new_hire_detail.html" def get_queryset(self): - return get_new_hires_for_user(user=self.request.user) + return get_colleagues_for_user(user=self.request.user) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) new_hire = context["object"] context["title"] = new_hire.full_name - context["subtitle"] = _("new hire") + context["subtitle"] = _("new hire") if new_hire.is_new_hire else _("employee") conditions = new_hire.conditions.prefetched() + user_conditions = UserCondition.objects.filter( + condition__in=conditions + ).values_list("condition", "role_start_date") + u_con = {} + for u, start_date in user_conditions: + u_con[u] = start_date + context["condition_start_date_map"] = u_con # condition items context["conditions"] = ( ( conditions.filter( - condition_type=2, days__lte=new_hire.days_before_starting() + condition_type=2 # , days__lte=new_hire.days_before_starting() ) | conditions.filter( - condition_type=Condition.Type.AFTER, days__gte=new_hire.workday() + condition_type=Condition.Type.AFTER # , days__gte=new_hire.workday() ) | conditions.filter(condition_type=Condition.Type.TODO) | conditions.filter(condition_type=Condition.Type.ADMIN_TASK) diff --git a/back/admin/settings/forms.py b/back/admin/settings/forms.py index 4cd1dcbd..c82959a0 100644 --- a/back/admin/settings/forms.py +++ b/back/admin/settings/forms.py @@ -171,9 +171,7 @@ def __init__(self, *args, **kwargs): class Meta: model = get_user_model() - fields = [ - "role", - ] + fields = ["role", "departments"] class WelcomeMessagesUpdateForm(forms.ModelForm): diff --git a/back/admin/settings/views.py b/back/admin/settings/views.py index 3363a2b1..d03bd32a 100644 --- a/back/admin/settings/views.py +++ b/back/admin/settings/views.py @@ -111,6 +111,7 @@ def form_valid(self, form): # Change user if user already exists user = user.first() user.role = form.cleaned_data["role"] + user.departments = form.cleaned_data["departments"] user.save() else: user = form.save() diff --git a/back/back/templatetags/general.py b/back/back/templatetags/general.py index e8a4faa9..b4264ad4 100644 --- a/back/back/templatetags/general.py +++ b/back/back/templatetags/general.py @@ -7,6 +7,7 @@ from admin.sequences.models import Condition from organization.models import File +from users.utils import workday_to_datetime register = template.Library() @@ -56,15 +57,17 @@ def personalize(text, user): @register.filter(name="new_hire_trigger_date") -def new_hire_trigger_date(condition, new_hire): +def new_hire_trigger_date(condition, condition_start_date_map): """ Shows the actual date that the condition will trigger """ if condition.condition_type == Condition.Type.BEFORE: - return new_hire.start_day - timedelta(days=condition.days) + return condition_start_date_map[condition.id] - timedelta(days=condition.days) else: - return new_hire.workday_to_datetime(condition.days) + return workday_to_datetime( + condition.days, condition_start_date_map[condition.id] + ) @register.filter(name="offboarding_trigger_date") diff --git a/back/new_hire/views.py b/back/new_hire/views.py index d2dd01d2..57cba56a 100644 --- a/back/new_hire/views.py +++ b/back/new_hire/views.py @@ -24,6 +24,7 @@ ToDoUser, User, ) +from users.utils import workday_to_datetime from .forms import QuestionsForm @@ -80,7 +81,7 @@ def get_context_data(self, **kwargs): # Convert days to date object for obj in items_by_date: - obj["date"] = self.request.user.workday_to_datetime( + obj["date"] = workday_to_datetime( obj["day"], start_day=obj["role_start_date"] ) diff --git a/back/users/models.py b/back/users/models.py index 8c9a528a..5982eb17 100644 --- a/back/users/models.py +++ b/back/users/models.py @@ -489,19 +489,6 @@ def workday(self, start_day=None): return amount_of_workdays - def workday_to_datetime(self, workdays, start_day=None): - if start_day is None: - start_day = self.start_day - if workdays == 0: - return None - - start = 1 - while start != workdays: - start_day += timedelta(days=1) - if start_day.weekday() not in [5, 6]: - start += 1 - return start_day - def offboarding_workday_to_date(self, workdays): # Converts the workday (before the end date) to the actual date on which it # triggers. This will skip any weekends. diff --git a/back/users/utils.py b/back/users/utils.py index 461fb333..37e4e402 100644 --- a/back/users/utils.py +++ b/back/users/utils.py @@ -1,3 +1,5 @@ +from datetime import timedelta + from django.utils.translation import gettext_lazy as _ @@ -32,3 +34,15 @@ def parse_array_to_string(words): if len(words) == 1: return words[0] return ", ".join(words[:-1]) + " " + _("and") + " " + words[-1] + + +def workday_to_datetime(workdays, start_day): + if workdays == 0: + return None + + start = 1 + while start != workdays: + start_day += timedelta(days=1) + if start_day.weekday() not in [5, 6]: + start += 1 + return start_day