|
16 | 16 | import functools
|
17 | 17 | from datetime import datetime, timedelta
|
18 | 18 | from typing import List
|
| 19 | +import pytz |
19 | 20 |
|
20 | 21 | from sqlalchemy import and_, extract, func, or_
|
21 | 22 |
|
|
42 | 43 | from api.models.work_type import WorkType
|
43 | 44 | from api.services.outcome_configuration import OutcomeConfigurationService
|
44 | 45 | from api.utils import util
|
| 46 | +from api.application_constants import MIN_WORK_START_DATE |
45 | 47 |
|
46 | 48 | from ..utils.roles import Membership
|
47 | 49 | from ..utils.roles import Role as KeycloakRole
|
@@ -295,6 +297,7 @@ def _process_events(
|
295 | 297 | current_work_phase_index = util.find_index_in_array(
|
296 | 298 | all_work_phases, current_work_phase
|
297 | 299 | )
|
| 300 | + cls._validate_dates(event, current_work_phase, all_work_phases) |
298 | 301 | cls._previous_event_acutal_date_rule(
|
299 | 302 | all_work_events, all_work_phases, current_work_phase_index, event, event_old
|
300 | 303 | )
|
@@ -383,6 +386,88 @@ def _process_events(
|
383 | 386 | current_event_index,
|
384 | 387 | )
|
385 | 388 |
|
| 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 | + |
386 | 471 | @classmethod
|
387 | 472 | def _handle_work_phase_for_start_event(
|
388 | 473 | cls,
|
|
0 commit comments