Skip to content

Commit 41d6a31

Browse files
authored
Min/Max anticipated and actual date checking for events (bcgov#1814)
* min/max start date checking for events * event service data validation and rf minor fies * changes to start date validation in events
1 parent 0884d57 commit 41d6a31

File tree

4 files changed

+164
-1
lines changed

4 files changed

+164
-1
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"""simple_title_in_work_history
2+
3+
Revision ID: 5b390f888c3f
4+
Revises: 73868272c32c
5+
Create Date: 2024-02-06 22:38:55.052178
6+
7+
"""
8+
9+
from alembic import op
10+
import sqlalchemy as sa
11+
12+
13+
# revision identifiers, used by Alembic.
14+
revision = "5b390f888c3f"
15+
down_revision = "73868272c32c"
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# ### commands auto generated by Alembic - please adjust! ###
22+
23+
with op.batch_alter_table("indigenous_works_history", schema=None) as batch_op:
24+
batch_op.alter_column(
25+
"indigenous_consultation_level_id",
26+
existing_type=sa.INTEGER(),
27+
nullable=False,
28+
autoincrement=False,
29+
)
30+
31+
with op.batch_alter_table("works_history", schema=None) as batch_op:
32+
batch_op.alter_column(
33+
"simple_title",
34+
existing_type=sa.TEXT(),
35+
type_=sa.String(),
36+
existing_nullable=True,
37+
autoincrement=False,
38+
)
39+
40+
# ### end Alembic commands ###
41+
42+
43+
def downgrade():
44+
# ### commands auto generated by Alembic - please adjust! ###
45+
with op.batch_alter_table("works_history", schema=None) as batch_op:
46+
batch_op.alter_column(
47+
"simple_title",
48+
existing_type=sa.String(),
49+
type_=sa.TEXT(),
50+
existing_nullable=True,
51+
autoincrement=False,
52+
)
53+
54+
with op.batch_alter_table("indigenous_works_history", schema=None) as batch_op:
55+
batch_op.alter_column(
56+
"indigenous_consultation_level_id",
57+
existing_type=sa.INTEGER(),
58+
nullable=True,
59+
autoincrement=False,
60+
)
61+
62+
# ### end Alembic commands ###
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright © 2019 Province of British Columbia
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""This module holds the application wide constants"""
15+
16+
MIN_WORK_START_DATE = "1995-06-30"

epictrack-api/src/api/reports/resource_forecast_report.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ def _fetch_data(self, report_date: datetime):
225225
WorkType.name.label("ea_type"),
226226
WorkType.report_title.label("ea_type_label"),
227227
WorkType.sort_order.label("ea_type_sort_order"),
228-
PhaseCode.name.label("project_phase"),
228+
WorkPhase.name.label("project_phase"),
229229
EAAct.name.label("ea_act"),
230230
FederalInvolvement.name.label("iaac"),
231231
SubType.short_name.label("sub_type"),

epictrack-api/src/api/services/event.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import functools
1717
from datetime import datetime, timedelta
1818
from typing import List
19+
import pytz
1920

2021
from sqlalchemy import and_, extract, func, or_
2122

@@ -42,6 +43,7 @@
4243
from api.models.work_type import WorkType
4344
from api.services.outcome_configuration import OutcomeConfigurationService
4445
from api.utils import util
46+
from api.application_constants import MIN_WORK_START_DATE
4547

4648
from ..utils.roles import Membership
4749
from ..utils.roles import Role as KeycloakRole
@@ -295,6 +297,7 @@ def _process_events(
295297
current_work_phase_index = util.find_index_in_array(
296298
all_work_phases, current_work_phase
297299
)
300+
cls._validate_dates(event, current_work_phase, all_work_phases)
298301
cls._previous_event_acutal_date_rule(
299302
all_work_events, all_work_phases, current_work_phase_index, event, event_old
300303
)
@@ -383,6 +386,88 @@ def _process_events(
383386
current_event_index,
384387
)
385388

389+
@classmethod
390+
def _validate_dates(
391+
cls, event: Event, current_work_phase: WorkPhase, all_work_phases: [WorkPhase]
392+
):
393+
"""Perform date validations for the min and max dates for events"""
394+
if event.actual_date:
395+
actual_min_date = cls._find_actual_date_min(
396+
event, current_work_phase, all_work_phases
397+
)
398+
actual_max_date = cls._find_actual_date_max(current_work_phase)
399+
if (
400+
event.actual_date < actual_min_date
401+
or event.actual_date > actual_max_date
402+
):
403+
raise UnprocessableEntityError(
404+
f"Actual date should be between {actual_min_date} and {actual_max_date}"
405+
)
406+
if not event.actual_date:
407+
anticipated_min_date = cls._find_anticipated_date_min(
408+
event, current_work_phase, all_work_phases
409+
)
410+
if event.anticipated_date < anticipated_min_date:
411+
raise UnprocessableEntityError(
412+
f"Anticipdated date should be greater than {anticipated_min_date}"
413+
)
414+
415+
@classmethod
416+
def _find_anticipated_date_min(
417+
cls, event: Event, current_work_phase: WorkPhase, all_work_phases: [WorkPhase]
418+
):
419+
"""Return the min date of anticipated date"""
420+
anticipated_date_min = (
421+
datetime.strptime(MIN_WORK_START_DATE, "%Y-%m-%d").replace(tzinfo=pytz.utc)
422+
if cls._is_start_event(event)
423+
and cls._is_start_phase(current_work_phase, all_work_phases)
424+
else current_work_phase.work.start_date
425+
)
426+
return anticipated_date_min
427+
428+
@classmethod
429+
def _find_actual_date_min(
430+
cls, event: Event, current_work_phase: WorkPhase, all_work_phases: [WorkPhase]
431+
):
432+
"""Return the min date of actual date"""
433+
actual_date_min = (
434+
datetime.strptime(MIN_WORK_START_DATE, "%Y-%m-%d").replace(tzinfo=pytz.utc)
435+
if cls._is_start_event(event)
436+
and cls._is_start_phase(current_work_phase, all_work_phases)
437+
else current_work_phase.start_date
438+
)
439+
return actual_date_min
440+
441+
@classmethod
442+
def _find_actual_date_max(cls, current_work_phase: WorkPhase):
443+
"""Return the max date of actual date"""
444+
date_diff_days = (
445+
(current_work_phase.end_date - current_work_phase.start_date).days
446+
if current_work_phase.legislated
447+
else 0
448+
)
449+
actual_date_max = (
450+
current_work_phase.start_date + timedelta(days=date_diff_days)
451+
if current_work_phase.legislated
452+
else datetime.utcnow().replace(tzinfo=pytz.utc)
453+
)
454+
return actual_date_max
455+
456+
@classmethod
457+
def _is_start_event(cls, event):
458+
"""Return true if the given event is start event"""
459+
return (
460+
event.event_configuration.event_position.value
461+
== EventPositionEnum.START.value
462+
)
463+
464+
@classmethod
465+
def _is_start_phase(
466+
cls, current_work_phase: WorkPhase, all_work_phases: [WorkPhase]
467+
):
468+
"""Return true if the current phase is start phase"""
469+
return all_work_phases[0].id == current_work_phase.id
470+
386471
@classmethod
387472
def _handle_work_phase_for_start_event(
388473
cls,

0 commit comments

Comments
 (0)