Coverage for middle_layer/allocate/domain_layer/entities/tac_member.py: 74.42%
43 statements
« prev ^ index » next coverage.py v7.10.5, created at 2026-04-13 06:13 +0000
« prev ^ index » next coverage.py v7.10.5, created at 2026-04-13 06:13 +0000
1# Copyright 2024 Associated Universities, Inc.
2#
3# This file is part of Telescope Time Allocation Tools (TTAT).
4#
5# TTAT is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# any later version.
9#
10# TTAT is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with TTAT. If not, see <https://www.gnu.org/licenses/>.
18from sqlalchemy import ForeignKey
19from sqlalchemy.orm import Mapped, mapped_column, relationship
21from common.domain_layer import JSON_OBJECT
22from common.domain_layer.entities.base import Base
23from solicit.domain_layer.entities.solicitation import Solicitation
26class TACMember(Base):
27 __tablename__ = "tac_members"
28 """A group of people who are tasked with collectively making recommendations to the NRAO/GBO regarding time
29 allocation and scheduling priorities of proposals submitted to the Observatory each semester, weighing science
30 merit, technical feasibility, and programmatic concerns
31 """
33 tac_member_id: Mapped[int] = mapped_column(primary_key=True)
34 solicitation_id: Mapped[int] = mapped_column(ForeignKey("solicitations.solicitation_id", ondelete="CASCADE"))
35 solicitation: Mapped[Solicitation] = relationship(Solicitation, back_populates="tac_members")
36 user_id: Mapped[int] = mapped_column(nullable=False)
37 first_name: Mapped[str] = mapped_column(nullable=False)
38 last_name: Mapped[str] = mapped_column(nullable=False)
39 is_chair: Mapped[bool] = mapped_column(nullable=False, server_default="False")
40 is_active: Mapped[bool] = mapped_column(nullable=False, server_default="False")
42 def __init__(
43 self,
44 solicitation: Solicitation,
45 user_id: int,
46 first_name: str,
47 last_name: str,
48 is_chair: bool = False,
49 is_active: bool = False,
50 ):
51 # Validate solicitation
52 if not solicitation or not isinstance(solicitation, Solicitation):
53 raise ValueError("A valid Solicitation object is required")
54 if not hasattr(solicitation, "solicitation_id") or not solicitation.solicitation_id:
55 raise ValueError("Solicitation must have a valid solicitation_id")
57 # Validate user_id
58 if not isinstance(user_id, int):
59 raise ValueError(f"User ID must be an integer, got {type(user_id).__name__}")
60 if user_id <= 0:
61 raise ValueError(f"User ID must be a positive integer, got {user_id}")
63 # Validate first_name
64 if not isinstance(first_name, str):
65 raise ValueError(f"First name must be a string, got {type(first_name).__name__}")
66 if not first_name.strip():
67 raise ValueError("First name cannot be empty")
69 # Validate last_name
70 if not isinstance(last_name, str):
71 raise ValueError(f"Last name must be a string, got {type(last_name).__name__}")
72 if not last_name.strip():
73 raise ValueError("Last name cannot be empty")
75 # Validate boolean fields
76 if not isinstance(is_chair, bool):
77 raise ValueError(f"is_chair must be a boolean, got {type(is_chair).__name__}")
78 if not isinstance(is_active, bool):
79 raise ValueError(f"is_active must be a boolean, got {type(is_active).__name__}")
81 # All validations passed, proceed with initialization
82 super().__init__(
83 solicitation=solicitation,
84 solicitation_id=solicitation.solicitation_id,
85 user_id=user_id,
86 first_name=first_name.strip(),
87 last_name=last_name.strip(),
88 is_chair=is_chair,
89 is_active=is_active,
90 )
92 # Ensure the relationship is properly established
93 if not hasattr(solicitation, "tac_members"):
94 raise ValueError("Solicitation object is missing the tac_members attribute")
95 solicitation.tac_members.append(self)
97 def __eq__(self, other: object) -> bool:
98 """Use Solicitation.solicitation_id to avoid RecursionError"""
99 return isinstance(other, TACMember) and (
100 self.tac_member_id,
101 self.solicitation_id,
102 self.user_id,
103 self.first_name,
104 self.last_name,
105 self.is_chair,
106 self.is_active,
107 ) == (
108 other.tac_member_id,
109 other.solicitation_id,
110 other.user_id,
111 other.first_name,
112 other.last_name,
113 other.is_chair,
114 other.is_active,
115 )