Coverage for rfpy/api/endpoints/network.py: 100%
101 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-24 10:52 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-24 10:52 +0000
1"""
2Define a network of relationships between organisations
3"""
5from enum import Enum
6from rfpy.model.exc import BusinessRuleViolation
7from rfpy.model.humans import OrganisationType
8from typing import List
10from sqlalchemy.orm import Session
11from sqlalchemy.orm.exc import NoResultFound
13from rfpy.api import validate, fetch
14from rfpy.auth import perms
15from rfpy.suxint import http
16from rfpy.model import User, Edge, RelationshipType
18from rfpy.web import serial
21@http
22def get_network(session: Session, user: User) -> List[serial.NetworkRelationship]:
23 """
24 Get an array of all the Network Relationships described by your organisation
25 """
26 q = fetch.edges_for_org_query(session, user.org_id)
27 return [serial.NetworkRelationship.model_validate(r) for r in q]
30@http
31def delete_network(session: Session, user: User):
32 """
33 Delete all relationships defined for this network.
34 Relationship Types are not deleted.
35 """
36 validate.check(user, perms.MANAGE_ORGANISATION, target_org=user.organisation)
37 rids = {rt.id for rt in user.organisation.relationship_types}
38 session.query(Edge).filter(Edge.relationship_id.in_(rids)).delete(
39 synchronize_session=False
40 )
43@http
44def get_reltypes(session: Session, user: User) -> List[serial.RelationshipType]:
45 """
46 Get an array of Relationship Types for your organisation
47 """
48 return [
49 serial.RelationshipType(id=rt.id, name=rt.name, description=rt.description)
50 for rt in user.organisation.relationship_types
51 ]
54@http
55def post_reltype(
56 session: Session, user: User, reltype_doc: serial.RelationshipType
57) -> serial.Id:
58 """
59 Create a new Relationship Type for your organisation
61 A RelationshipType must be defined for your organisation before a Relationship can
62 be defined between two 3rd party organisations.
63 Multiple RelationshipTypes can be defined for your organisation in order to model different
64 types of inter-organisation relationships - e.g. 'Partner', 'Downstream Supplier',
65 'Regulator', etc.
67 @permission MANAGE_ORGANISATION
68 """
69 validate.check(user, perms.MANAGE_ORGANISATION, target_org=user.organisation)
70 rt = RelationshipType(**reltype_doc.model_dump())
71 user.organisation.relationship_types.append(rt)
72 session.flush()
73 return serial.Id(id=rt.id)
76@http
77def put_reltype(
78 session: Session, user: User, reltype_id, reltype_doc: serial.RelationshipType
79) -> serial.Id:
80 """
81 Update the name or description for the Relationship Type with the given ID
82 """
83 validate.check(user, perms.MANAGE_ORGANISATION, target_org=user.organisation)
84 rt = user.organisation.relationship_types.filter(
85 RelationshipType.id == reltype_id
86 ).one()
87 rt.name = reltype_doc.name
88 rt.description = reltype_doc.description
90 return serial.Id(id=rt.id)
93@http
94def delete_reltype(session: Session, user: User, reltype_id: int) -> serial.Id:
95 """
96 Delete the Relationship Type with the given ID together with all
97 relationships of that type. Underlying Organisations are not deleted.
98 """
99 validate.check(user, perms.MANAGE_ORGANISATION, target_org=user.organisation)
100 rt = user.organisation.relationship_types.filter(
101 RelationshipType.id == reltype_id
102 ).one()
103 session.delete(rt)
104 return serial.Id(id=rt.id)
107@http
108def post_relationship(
109 session: Session, user: User, relationship_doc: serial.Relationship
110) -> None:
111 """
112 Create a new Relationship between two organisations
113 """
114 validate.check(user, perms.MANAGE_ORGANISATION, target_org=user.organisation)
115 reltype_id = relationship_doc.reltype_id
116 rt = user.organisation.relationship_types.filter(
117 RelationshipType.id == reltype_id
118 ).one()
119 from_org = fetch.organisation(session, relationship_doc.from_org_id)
120 to_org = fetch.organisation(session, relationship_doc.to_org_id)
121 edge = Edge(to_org=to_org, from_org=from_org, relationship_type=rt)
122 session.add(edge)
125@http
126def delete_relationship(
127 session: Session, user: User, relationship_doc: serial.Relationship
128) -> None:
129 """
130 Delete a new Relationship between two organisations
131 """
132 validate.check(user, perms.MANAGE_ORGANISATION, target_org=user.organisation)
133 reltype_id = relationship_doc.reltype_id
134 rt = user.organisation.relationship_types.filter(
135 RelationshipType.id == reltype_id
136 ).one()
137 from_org = fetch.organisation(session, relationship_doc.from_org_id)
138 to_org = fetch.organisation(session, relationship_doc.to_org_id)
139 edge = (
140 session.query(Edge)
141 .filter_by(to_org_id=to_org.id, from_org_id=from_org.id, relationship_id=rt.id)
142 .one()
143 )
144 session.delete(edge)
147class StandardRelTypes(str, Enum):
148 CONSULTING_ENGAGEMENT = "Consults For"
149 CONSULTING_PARTNERSHIP = "Partners With"
150 SUPPLIES = "Supplies"
151 VENDOR_EVALUATION = "Evaluates"
154@http
155def post_network_project(
156 session: Session, user: User, project_id: int
157) -> List[serial.NetworkRelationship]:
158 """
159 Generate a network of relationships between vendors and participants for the project ID
160 provided. Creates default Relationship Types for standard RFP project relationships.
161 """
163 validate.check(user, perms.MANAGE_ORGANISATION, target_org=user.organisation)
164 if user.organisation.type != OrganisationType.CONSULTANT:
165 raise BusinessRuleViolation(
166 "Action only permitted for Consultant Organisations"
167 )
168 project = fetch.project(session, project_id=project_id)
169 validate.check(user, action=perms.PROJECT_ACCESS, project=project)
170 org = user.organisation
172 rt_lookup = dict()
173 for SRT in StandardRelTypes:
174 try:
175 rel = org.relationship_types.filter_by(name=SRT.value).one()
176 except NoResultFound:
177 rel = RelationshipType(org_id=org.id, name=SRT.value)
178 session.add(rel)
179 rt_lookup[SRT] = rel
181 # Populate Edge objects in the Session to facilitate merge() operations later
182 fetch.edges_for_org_query(session, user.org_id).all()
184 for participant in project.participants:
185 if participant.organisation is org:
186 continue
187 if participant.organisation.is_consultant:
188 rel = rt_lookup[StandardRelTypes.CONSULTING_PARTNERSHIP]
189 else:
190 rel = rt_lookup[StandardRelTypes.CONSULTING_ENGAGEMENT]
191 edge = Edge(
192 from_org_id=org.id, to_org_id=participant.org_id, relationship_id=rel.id
193 )
194 session.merge(edge)
196 respondent_id_set = {i.respondent_id for i in project.issues}
198 for respondent_id in respondent_id_set:
199 if respondent_id is None or respondent_id == org.id:
200 continue
202 buyer_id = project.org_id # Owner org assumed to be the buyer
204 rel = rt_lookup[StandardRelTypes.SUPPLIES]
205 session.merge(
206 Edge(from_org_id=respondent_id, to_org_id=buyer_id, relationship_id=rel.id)
207 )
209 evaluates_rel = rt_lookup[StandardRelTypes.VENDOR_EVALUATION]
210 session.merge(
211 Edge(
212 from_org_id=org.id,
213 to_org_id=respondent_id,
214 relationship_id=evaluates_rel.id,
215 )
216 )
218 return get_network(session, user)