Ejemplo n.º 1
0
def learning_units_search(request, search_type):
    service_course_search = search_type == SERVICE_COURSES_SEARCH
    borrowed_course_search = search_type == BORROWED_COURSE

    form = LearningUnitYearForm(
        request.GET or None,
        service_course_search=service_course_search,
        borrowed_course_search=borrowed_course_search,
        initial={'academic_year_id': starting_academic_year()}
    )
    found_learning_units = LearningUnitYear.objects.none()
    try:
        if form.is_valid():
            found_learning_units = form.get_activity_learning_units()
            check_if_display_message(request, found_learning_units)

    except TooManyResultsException:
        display_error_messages(request, 'too_many_results')

    if request.POST.get('xls_status') == "xls":
        return create_xls(request.user, found_learning_units, _get_filter(form, search_type))

    if request.POST.get('xls_status') == "xls_comparison":
        return create_xls_comparison(
            request.user,
            found_learning_units,
            _get_filter(form, search_type),
            request.POST.get('comparison_year')
        )

    if request.POST.get('xls_status') == "xls_with_parameters":
        return create_xls_with_parameters(
            request.user,
            found_learning_units,
            _get_filter(form, search_type),
            {
                WITH_GRP: request.POST.get('with_grp') == 'true',
                WITH_ATTRIBUTIONS: request.POST.get('with_attributions') == 'true'
            }
        )

    form_comparison = SelectComparisonYears(academic_year=get_academic_year_of_reference(found_learning_units))

    context = {
        'form': form,
        'academic_years': get_last_academic_years(),
        'container_types': learning_container_year_types.LEARNING_CONTAINER_YEAR_TYPES,
        'types': learning_unit_year_subtypes.LEARNING_UNIT_YEAR_SUBTYPES,
        'learning_units_count': len(found_learning_units)
        if isinstance(found_learning_units, list) else
        found_learning_units.count(),
        'current_academic_year': starting_academic_year(),
        'experimental_phase': True,
        'search_type': search_type,
        'is_faculty_manager': request.user.person.is_faculty_manager,
        'form_comparison': form_comparison,
        'page_obj': paginate_queryset(found_learning_units, request.GET),
    }

    return render(request, "learning_units.html", context)
Ejemplo n.º 2
0
def _get_data(request):
    person = mdl.person.find_by_user(request.user)
    tutor = mdl.tutor.find_by_person(person)
    programs = mdl.program_manager.find_by_person(person).prefetch_related(
        Prefetch(
            'education_group__educationgroupyear_set',
            queryset=EducationGroupYear.objects.filter(
                academic_year=starting_academic_year()).select_related(
                    'academic_year'),
            to_attr='current_egy')).order_by(
                'education_group__educationgroupyear__acronym').distinct()

    return {
        'person':
        person,
        'addresses':
        mdl.person_address.find_by_person(person),
        'tutor':
        tutor,
        'attributions':
        mdl_attr.attribution.search(tutor=tutor) if tutor else None,
        'programs':
        programs,
        'supported_languages':
        settings.LANGUAGES,
        'default_language':
        settings.LANGUAGE_CODE,
        'summary_submission_opened':
        base.business.learning_unit.is_summary_submission_opened()
    }
Ejemplo n.º 3
0
def form_part5_edit(request, msg=None):
    mandate = assistant_mandate.find_mandate_by_assistant_for_academic_year(
        academic_assistant.find_by_person(request.user.person),
        academic_year.starting_academic_year())
    assistant = mandate.assistant
    form = AssistantFormPart5(initial={
        'faculty_representation': mandate.faculty_representation,
        'institute_representation': mandate.institute_representation,
        'sector_representation': mandate.sector_representation,
        'governing_body_representation': mandate.governing_body_representation,
        'corsci_representation': mandate.corsci_representation,
        'students_service': mandate.students_service,
        'infrastructure_mgmt_service': mandate.infrastructure_mgmt_service,
        'events_organisation_service': mandate.events_organisation_service,
        'publishing_field_service': mandate.publishing_field_service,
        'scientific_jury_service': mandate.scientific_jury_service,
        'formations': mandate.formations
    },
                              prefix='mand')
    return render(request, "assistant_form_part5.html", {
        'assistant': assistant,
        'mandate': mandate,
        'msg': msg,
        'form': form
    })
Ejemplo n.º 4
0
    def test_learning_units_summary_list(self, mock_render):
        request_factory = RequestFactory()

        now = datetime.datetime.now()

        EntityVersionFactory(entity=self.an_entity,
                             start_date=now,
                             end_date=datetime.datetime(now.year + 1, 9, 15),
                             entity_type='INSTITUTE')

        request = request_factory.get(
            self.url, data={'academic_year_id': starting_academic_year().id})
        request.user = self.faculty_user

        lu = self._create_learning_unit_year_for_entity(self.an_entity)
        person_lu = PersonFactory()
        tutor_lu_1 = TutorFactory(person=person_lu)
        self.attribution_lu = AttributionFactory(learning_unit_year=lu,
                                                 tutor=tutor_lu_1,
                                                 summary_responsible=True)
        self._create_entity_calendar(self.an_entity)
        self.client.force_login(self.faculty_user)

        learning_units_summary_list(request)

        self.assertTrue(mock_render.called)
        request, template, context = mock_render.call_args[0]
        self.assertEqual(template, 'learning_units.html')
        self.assertEqual(context['search_type'], SUMMARY_LIST)
        self.assertEqual(len(context['learning_units_with_errors']), 1)
        self.assertTrue(context['is_faculty_manager'])
Ejemplo n.º 5
0
    def test_update_with_parent_when_existing_group_element_year(
            self, mock_find_authorized_types):
        parent = EducationGroupYearFactory(
            academic_year=self.expected_educ_group_year.academic_year,
            education_group__end_year=None)

        entity_version = MainEntityVersionFactory()
        initial_educ_group_year = MiniTrainingFactory(
            management_entity=entity_version.entity,
            academic_year=self.expected_educ_group_year.academic_year,
            education_group__start_year=starting_academic_year())

        GroupElementYearFactory(parent=parent,
                                child_branch=initial_educ_group_year)
        initial_count = GroupElementYear.objects.all().count()

        form = self.form_class(data=self.post_data,
                               instance=initial_educ_group_year,
                               parent=parent,
                               user=self.user)
        self.assertTrue(form.is_valid(), form.errors)
        updated_education_group_year = form.save()

        # Assert existing GroupElementYear is reused.
        self.assertEqual(initial_count, GroupElementYear.objects.all().count())
        self._assert_all_fields_correctly_saved(updated_education_group_year)
        self.assertTrue(form.forms[ModelForm].fields["academic_year"].disabled)
Ejemplo n.º 6
0
def reviewer_action(request):
    reviewers_formset = formset_factory(ReviewersFormset)(request.POST,
                                                          request.FILES)
    if reviewers_formset.is_valid():
        for reviewer_form in reviewers_formset:
            action = reviewer_form.cleaned_data.get('action')
            if action == 'DELETE':
                reviewer_delete(request, reviewer_form.cleaned_data.get('id'))
            elif action == 'REPLACE':
                year = academic_year.starting_academic_year().year
                reviewer_id = reviewer_form.cleaned_data.get('id')
                this_reviewer = reviewer.find_by_id(reviewer_id)
                entity = entity_version.get_last_version(this_reviewer.entity)
                form = ReviewerReplacementForm(initial={
                    'person': this_reviewer.person,
                    'id': this_reviewer.id
                },
                                               prefix="rev",
                                               instance=this_reviewer)
                return render(
                    request, "manager_replace_reviewer.html", {
                        'reviewer': this_reviewer,
                        'entity': entity,
                        'year': year,
                        'form': form
                    })
    return HttpResponseRedirect(reverse('reviewers_list'))
Ejemplo n.º 7
0
    def test_learning_units_summary_list(self):
        now = datetime.datetime.now()

        EntityVersionFactory(entity=self.an_entity,
                             start_date=now,
                             end_date=datetime.datetime(now.year + 1, 9, 15),
                             entity_type='INSTITUTE')

        lu = self._create_learning_unit_year_for_entity(
            self.an_entity, "LBIR1100")
        person_lu = PersonFactory()
        tutor_lu_1 = TutorFactory(person=person_lu)
        self.attribution_lu = AttributionFactory(learning_unit_year=lu,
                                                 tutor=tutor_lu_1,
                                                 summary_responsible=True)
        self._create_entity_calendar(self.an_entity)
        self.client.force_login(self.faculty_user)

        response = self.client.get(
            self.url, data={'academic_year_id': starting_academic_year().id})
        self.assertTemplateUsed(response, 'learning_units.html')
        context = response.context
        self.assertEqual(context['search_type'], SUMMARY_LIST)
        self.assertEqual(context['learning_units_count'], 1)
        self.assertTrue(context['is_faculty_manager'])
Ejemplo n.º 8
0
    def __init__(self, instance, person=None):
        """
        The instance must be a training in the current academic year with an end year greater than
        the next academic year.

        During the initialization, we'll also check if the current instance has a content to postpone.
        """
        if not isinstance(instance, EducationGroupYear):
            raise NotPostponeError(
                _('You are not allowed to copy the content of this kind of education group.'
                  ))

        self.instance = instance
        self.current_year = starting_academic_year()
        self.next_academic_year = self.instance.academic_year.next()

        self.check_instance(person)

        self.result = []
        self.warnings = []
        self.instance_n1 = self.get_instance_n1(self.instance)

        self.postponed_luy = []
        self.postponed_options = {}
        self.postponed_finalities = []

        self.number_links_created = 0
        self.number_elements_created = 0
Ejemplo n.º 9
0
    def __init__(self, data=None, queryset=None, *, request=None, prefix=None):
        translated_text_qs = TranslatedText.objects.filter(
            entity=LEARNING_UNIT_YEAR,
            text_label__label__in=CMS_LABEL_PEDAGOGY,
            changed__isnull=False,
            reference=OuterRef('pk')).order_by("-changed")

        queryset = LearningUnitYear.objects.all().annotate(
            full_title=Case(
                When(Q(learning_container_year__common_title__isnull=True)
                     | Q(learning_container_year__common_title__exact=''),
                     then='specific_title'),
                When(Q(specific_title__isnull=True)
                     | Q(specific_title__exact=''),
                     then='learning_container_year__common_title'),
                default=Concat('learning_container_year__common_title',
                               Value(' - '), 'specific_title'),
                output_field=CharField(),
            ),
            last_translated_text_changed=Subquery(
                translated_text_qs.values('changed')[:1]),
        )
        super(LearningUnitDescriptionFicheFilter, self).__init__(
            data=data,
            queryset=queryset,
            request=request,
            prefix=prefix,
        )
        self.form.fields['academic_year'].initial = starting_academic_year()
        self.form.fields['with_entity_subordinated'].initial = True
Ejemplo n.º 10
0
 def test_current_academic_year(self):
     current_academic_yr = academic_year.current_academic_year()
     starting_academic_yr = academic_year.starting_academic_year()
     if starting_academic_yr != current_academic_yr:
         self.assertEqual(current_academic_yr.year, now.year - 1)
     else:
         self.assertEqual(current_academic_yr.year, now.year)
Ejemplo n.º 11
0
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.queryset = self.get_queryset()
        self.form.fields["academic_year"].initial = starting_academic_year()

        if self.data.get('country'):
            self._init_dropdown_list()
Ejemplo n.º 12
0
def reviewer_add(request):
    year = academic_year.starting_academic_year().year
    if request.POST:
        form = ReviewerForm(data=request.POST)
        this_person = request.POST.get('person_id')
        if form.is_valid() and this_person:
            this_person = person.find_by_id(this_person)
            new_reviewer = form.save(commit=False)
            if reviewer.find_by_entity_and_role(new_reviewer.entity,
                                                new_reviewer.role):
                msg = _(
                    "A reviewer having the same role for this entity already exists"
                )
                form.add_error(None, msg)
            if not form.has_error(field=NON_FIELD_ERRORS):
                new_reviewer.person = this_person
                new_reviewer.save()
                return redirect('reviewers_list')
        else:
            msg = _(
                "Please enter the last name and first name of the person you are looking for and select the "
                "corresponding choice in the drop-down list")
            form.add_error(None, msg)
    else:
        form = ReviewerForm(initial={'year': year})
    return render(request, "manager_add_reviewer.html", {
        'form': form,
        'year': year
    })
Ejemplo n.º 13
0
def save_message_history(request, type):
    message = Message.objects.create(
        sender=manager.Manager.objects.get(person=request.user.person),
        date=timezone.now(),
        type=type,
        academic_year=academic_year.starting_academic_year())
    message.save()
Ejemplo n.º 14
0
 def get_context_data(self, **kwargs):
     context = super(StructuresListView, self).get_context_data(**kwargs)
     context['year'] = academic_year.starting_academic_year().year
     context['current_reviewer'] = self.reviewers[0]
     context['entity'] = entity_version.get_last_version(context['current_reviewer'].entity)
     context['is_supervisor'] = is_supervisor(self.request.user.person)
     return context
Ejemplo n.º 15
0
def is_academic_year_in_range_to_create_partim(learning_unit_year, person):
    current_acy = starting_academic_year()
    luy_acy = learning_unit_year.academic_year
    max_range = MAX_ACADEMIC_YEAR_FACULTY if person.is_faculty_manager(
    ) else MAX_ACADEMIC_YEAR_CENTRAL

    return current_acy.year <= luy_acy.year <= current_acy.year + max_range
Ejemplo n.º 16
0
    def can_update_by_faculty_manager(self):
        if not self.learning_container_year:
            return False

        starting_year = starting_academic_year().year
        year = self.academic_year.year
        return starting_year <= year <= starting_year + MAX_ACADEMIC_YEAR_FACULTY
Ejemplo n.º 17
0
    def test_learning_units_summary_list_by_client_xls_empty(self):
        # Generate data
        now = datetime.datetime.now()
        EntityVersionFactory(entity=self.an_entity,
                             start_date=now,
                             end_date=datetime.datetime(now.year+1, 9, 15),
                             entity_type='INSTITUTE')

        luy = self._create_learning_unit_year_for_entity(self.an_entity, "LBIR1100")
        self._create_entity_calendar(self.an_entity)

        TeachingMaterialFactory(learning_unit_year=luy, title="Magic wand", mandatory=False)
        TeachingMaterialFactory(learning_unit_year=luy, title="Broomsticks", mandatory=False)

        luy_without_mandatory_teaching_material = self._create_learning_unit_year_for_entity(self.an_entity, "LBIR1101")
        TeachingMaterialFactory(learning_unit_year=luy_without_mandatory_teaching_material, title="cauldron", mandatory=False)

        # Test the view
        self.client.force_login(self.faculty_user)
        response = self.client.get(self.url, data={
            'academic_year_id': starting_academic_year().id,
            'xls_status': 'xls_teaching_material'
        })

        # OK, the server will stay in the page
        self.assertEqual(response.status_code, 200)

        # A warning message should be generated
        messages = list(get_messages(response.wsgi_request))
        self.assertEqual(len(messages), 1)
        self.assertEqual(str(messages[0]), _("the list to generate is empty.").capitalize())
Ejemplo n.º 18
0
 def _is_faculty_manager_eligible(self):
     if self.education_group_year.academic_year.year < starting_academic_year(
     ).year:
         raise PermissionDenied(
             _("The faculty manager cannot modify general information which are lower than N"
               ))
     return True
Ejemplo n.º 19
0
def add_reviewer_for_structure(request):
    current_entity = entity.find_by_id(request.POST.get("entity"))
    year = academic_year.starting_academic_year().year
    current_reviewer = reviewer_eligible_to_delegate(
        reviewer.find_by_person(request.user.person),
        current_entity
    )
    if not current_reviewer:
        return redirect('assistants_home')

    form = ReviewerDelegationForm(data=request.POST)
    if form.is_valid() and request.POST.get('person_id'):
        new_reviewer = form.save(commit=False)
        new_reviewer.person = person.find_by_id(request.POST.get('person_id'))
        new_reviewer.save()
        send_message(
            person=new_reviewer.person,
            html_template_ref='assistant_reviewers_startup_html',
            txt_template_ref='assistant_reviewers_startup_txt'
        )
        return redirect('reviewer_delegation')

    role = current_reviewer.role + '_ASSISTANT'
    form = ReviewerDelegationForm(initial={'entity': current_entity, 'year': year, 'role': role})
    return render(request, "reviewer_add_reviewer.html", {
        'form': form,
        'year': year,
        'entity': current_entity,
        'reviewer': current_reviewer
    })
Ejemplo n.º 20
0
def export_declined_mandates(request):
    current_academic_year = academic_year.starting_academic_year()
    return render(
        request, 'export_declined_mandates.html', {
            'form':
            ExportDeclineMandateForm(
                initial={'academic_year': current_academic_year})
        })
Ejemplo n.º 21
0
def count_by_proposition(proposition):
    starting_academic_year = academic_year.starting_academic_year()
    return Dissertation.objects.filter(proposition_dissertation=proposition) \
        .filter(active=True) \
        .filter(education_group_year__academic_year=starting_academic_year) \
        .exclude(status=dissertation_status.DRAFT) \
        .exclude(status=dissertation_status.DIR_KO) \
        .count()
Ejemplo n.º 22
0
 def test_starting_equalto_current(self):
     start_date = timezone.now() - datetime.timedelta(days=5)
     end_date = start_date + datetime.timedelta(days=220)
     academic_yr = AcademicYearFactory(year=start_date.year,
                                       start_date=start_date,
                                       end_date=end_date)
     starting_academic_year = academic_year.starting_academic_year()
     self.assertEqual(starting_academic_year.year, academic_yr.year)
Ejemplo n.º 23
0
def count_by_proposition(prop_dissert):
    current_academic_year = academic_year.starting_academic_year()
    return Dissertation.objects.filter(proposition_dissertation=prop_dissert)\
        .filter(active=True)\
        .filter(offer_year_start__academic_year=current_academic_year)\
        .exclude(status='DRAFT') \
        .exclude(status='DIR_KO') \
        .count()
Ejemplo n.º 24
0
    def _restrict_academic_years_choice_for_proposal_creation_suppression(
            self, proposal_type):
        if proposal_type in (ProposalType.CREATION.name,
                             ProposalType.SUPPRESSION):
            if self.person.is_faculty_manager and proposal_type == ProposalType.CREATION.name:
                starting_academic_year = academic_year.starting_academic_year(
                ).next()
            else:
                starting_academic_year = academic_year.starting_academic_year()

            end_year_range = MAX_ACADEMIC_YEAR_CENTRAL - 1 if self.person.is_faculty_manager \
                else MAX_ACADEMIC_YEAR_CENTRAL

            self.fields[
                "academic_year"].queryset = academic_year.find_academic_years(
                    start_year=starting_academic_year.year,
                    end_year=starting_academic_year.year + end_year_range)
Ejemplo n.º 25
0
 def setUpTestData(cls):
     AcademicYearFactory(year=(now.year - 1),
                         start_date=datetime.datetime(now.year - 1, now.month, 15),
                         end_date=datetime.datetime(now.year, now.month, 28))
     AcademicYearFactory(year=now.year,
                         start_date=datetime.datetime(now.year, now.month, 1),
                         end_date=datetime.datetime(now.year + 1, now.month, 28))
     cls.current_academic_yr = academic_year.current_academic_year()
     cls.starting_academic_yr = academic_year.starting_academic_year()
Ejemplo n.º 26
0
 def get_initial(self):
     if self.request.session.get('selected_academic_year'):
         selected_academic_year = academic_year.find_academic_year_by_id(
             self.request.session.get('selected_academic_year'))
     else:
         selected_academic_year = academic_year.starting_academic_year()
         self.request.session[
             'selected_academic_year'] = selected_academic_year.id
     return {'academic_year': selected_academic_year}
Ejemplo n.º 27
0
def find_by_entity_and_reference(entity_id, reference, academic_year=None):
    try:
        return EntityCalendar.objects.filter(
            entity_id=entity_id,
            academic_calendar__academic_year=academic_year or starting_academic_year(),
            academic_calendar__reference=reference
        ).select_related('entity', 'academic_calendar__academic_year').get()
    except ObjectDoesNotExist:
        return None
Ejemplo n.º 28
0
    def _restrict_academic_years_choice(self):
        starting_academic_year = academic_year.starting_academic_year()
        end_year_range = MAX_ACADEMIC_YEAR_FACULTY if self.person.is_faculty_manager(
        ) else MAX_ACADEMIC_YEAR_CENTRAL

        self.fields[
            "academic_year"].queryset = academic_year.find_academic_years(
                start_year=starting_academic_year.year,
                end_year=starting_academic_year.year + end_year_range)
Ejemplo n.º 29
0
 def test_find_academic_years(self):
     today = datetime.date.today()
     academic_yrs = academic_year.find_academic_years(start_date=today, end_date=today)
     current_academic_yr = academic_year.current_academic_year()
     starting_academic_yr = academic_year.starting_academic_year()
     nb_of_academic_yrs = academic_yrs.count()
     if starting_academic_yr != current_academic_yr:
         self.assertEqual(nb_of_academic_yrs, 2)
     else:
         self.assertEqual(nb_of_academic_yrs, 1)
Ejemplo n.º 30
0
def _is_person_eligible_to_edit_proposal_based_on_state(proposal, person):
    if person.is_central_manager():
        return True
    if proposal.state != ProposalState.FACULTY.name:
        return False
    if (proposal.type == ProposalType.MODIFICATION.name
            and proposal.learning_unit_year.academic_year.year !=
            starting_academic_year().year + 1):
        return False
    return True
Ejemplo n.º 31
0
    def _restrict_academic_years_choice(self, postposal, proposal_type):
        if postposal:
            starting_academic_year = academic_year.starting_academic_year()
            end_year_range = MAX_ACADEMIC_YEAR_FACULTY if self.person.is_faculty_manager \
                else MAX_ACADEMIC_YEAR_CENTRAL

            self.fields["academic_year"].queryset = AcademicYear.objects.min_max_years(
                starting_academic_year.year, starting_academic_year.year + end_year_range
            )
        else:
            self._restrict_academic_years_choice_for_proposal_creation_suppression(proposal_type)
Ejemplo n.º 32
0
    def _restrict_academic_years_choice_for_proposal_creation_suppression(self, proposal_type):
        if proposal_type in (ProposalType.CREATION.name, ProposalType.SUPPRESSION):
            if self.person.is_faculty_manager and proposal_type == ProposalType.CREATION.name:
                starting_academic_year = academic_year.current_academic_year().next()
            else:
                starting_academic_year = academic_year.starting_academic_year()

            end_year_range = MAX_ACADEMIC_YEAR_CENTRAL - 1 if self.person.is_faculty_manager \
                else MAX_ACADEMIC_YEAR_CENTRAL

            self.fields["academic_year"].queryset = academic_year.find_academic_years(
                start_year=starting_academic_year.year,
                end_year=starting_academic_year.year + end_year_range
            )
Ejemplo n.º 33
0
def _is_person_eligible_to_edit_proposal_based_on_state(proposal, person, raise_exception=False):
    if person.is_central_manager:
        return True
    if proposal.state != ProposalState.FACULTY.name:
        can_raise_exception(
            raise_exception,
            False,
            MSG_NOT_PROPOSAL_STATE_FACULTY
        )
        return False
    if proposal.type == ProposalType.MODIFICATION.name and \
       proposal.learning_unit_year.academic_year.year != starting_academic_year().year + 1:
        can_raise_exception(
            raise_exception,
            False,
            MSG_PROPOSAL_IS_ON_AN_OTHER_YEAR
        )
        return False
    return True
Ejemplo n.º 34
0
    def __init__(self, instance):
        """
        The instance must be a training in the current academic year with an end year greater than
        the next academic year.

        During the initialization, we'll also check if the current instance has a content to postpone.
        """
        if not isinstance(instance, EducationGroupYear):
            raise NotPostponeError(_('You are not allowed to copy the content of this kind of education group.'))

        self.instance = instance
        self.current_year = starting_academic_year()
        self.next_academic_year = self.current_year.next()

        self.check_instance()

        self.result = []
        self.warnings = []
        self.instance_n1 = self.get_instance_n1(self.instance)

        self.postponed_luy = []
        self.postponed_options = {}
        self.postponed_finalities = []
Ejemplo n.º 35
0
 def __init__(self, *args, **kwargs):
     super().__init__(*args, **kwargs)
     self.fields['academic_year'].initial = academic_year.starting_academic_year()
Ejemplo n.º 36
0
def is_academic_year_in_range_to_create_partim(learning_unit_year, person, raise_exception=False):
    current_acy = starting_academic_year()
    luy_acy = learning_unit_year.academic_year
    max_range = MAX_ACADEMIC_YEAR_FACULTY if person.is_faculty_manager else MAX_ACADEMIC_YEAR_CENTRAL

    return current_acy.year <= luy_acy.year <= current_acy.year + max_range