Skip to content

Commit 4c5d15d

Browse files
committed
fix: fixed bug with enablement service
1 parent 3c29c64 commit 4c5d15d

File tree

9 files changed

+221
-147
lines changed

9 files changed

+221
-147
lines changed

ichub-backend/controllers/fastapi/routers/sharing_handler.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,15 @@
2525
from services.sharing_service import SharingService
2626
from models.services.sharing_management import (
2727
SharedPartBase,
28-
ShareCatalogPart
28+
ShareCatalogPart,
29+
SharedPartner
2930
)
30-
31+
from typing import Optional, List
3132
router = APIRouter(prefix="/share", tags=["Sharing Functionality"])
32-
part_sharing_shortcut_service = SharingService()
33+
part_sharing_service = SharingService()
3334

3435
@router.post("/catalog-part", response_model=SharedPartBase)
35-
async def twin_management_create_part_sharing_shortcut(catalog_part_to_share: ShareCatalogPart) -> SharedPartBase:
36-
return part_sharing_shortcut_service.share_catalog_part(
36+
async def share_catalog_part(catalog_part_to_share: ShareCatalogPart) -> SharedPartBase:
37+
return part_sharing_service.share_catalog_part(
3738
catalog_part_to_share=catalog_part_to_share
38-
)
39+
)

ichub-backend/controllers/fastapi/routers/twin_management.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ async def twin_management_get_catalog_part_twins(include_data_exchange_agreement
4141

4242
@router.get("/catalog-part-twin/{global_id}", response_model=List[CatalogPartTwinDetailsRead])
4343
async def twin_management_get_catalog_part_twin(global_id: UUID) -> List[CatalogPartTwinDetailsRead]:
44-
return twin_management_service.get_catalog_part_twin_details(global_id)
44+
return twin_management_service.get_catalog_part_twin_details_id(global_id)
4545

4646
@router.get("/catalog-part-twin/{manufacturerId}/{manufacturerPartId}", response_model=List[CatalogPartTwinDetailsRead])
4747
async def twin_management_get_catalog_part_twin_from_manufacturer(manufacturerId: str, manufacturerPartId: str) -> List[CatalogPartTwinDetailsRead]:
@@ -50,17 +50,3 @@ async def twin_management_get_catalog_part_twin_from_manufacturer(manufacturerId
5050
@router.post("/catalog-part-twin", response_model=TwinRead)
5151
async def twin_management_create_catalog_part_twin(catalog_part_twin_create: CatalogPartTwinCreate) -> TwinRead:
5252
return twin_management_service.create_catalog_part_twin(catalog_part_twin_create)
53-
54-
@router.post("/catalog-part-twin/share", responses={
55-
201: {"description": "Catalog part twin shared successfully"},
56-
204: {"description": "Catalog part twin already shared"}
57-
})
58-
async def twin_management_share_catalog_part_twin(catalog_part_twin_share: CatalogPartTwinShare):
59-
if twin_management_service.create_catalog_part_twin_share(catalog_part_twin_share):
60-
return JSONResponse(status_code=201, content={"description":"Catalog part twin shared successfully"})
61-
else:
62-
return JSONResponse(status_code=204, content={"description":"Catalog part twin already shared"})
63-
64-
@router.post("/twin-aspect", response_model=TwinAspectRead)
65-
async def twin_management_create_twin_aspect(twin_aspect_create: TwinAspectCreate) -> TwinAspectRead:
66-
return twin_management_service.create_twin_aspect(twin_aspect_create)

ichub-backend/managers/enablement_services/dtr_manager.py

Lines changed: 148 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@
4343
from tools.aspect_id_tools import extract_aspect_id_name_from_urn_camelcase
4444
from urllib.parse import urljoin
4545

46-
4746
import json
47+
import logging
48+
logger = logging.getLogger(__name__)
4849

4950
class DTRManager:
5051
def __init__(
@@ -81,89 +82,174 @@ def get_dtr_url(base_dtr_url: str = '', uri: str = '', api_path: str = '') -> st
8182
full_url = urljoin(base_plus_uri.rstrip('/') + '/', api_path.lstrip('/'))
8283
return full_url
8384

85+
def _reference_from_bpn_list(self, bpn_list, fallback_id=None):
86+
keys = []
87+
if bpn_list:
88+
keys = [
89+
ReferenceKey(type=ReferenceKeyTypes.GLOBAL_REFERENCE, value=bpn)
90+
for bpn in bpn_list
91+
]
92+
elif fallback_id:
93+
keys = [ReferenceKey(type=ReferenceKeyTypes.GLOBAL_REFERENCE, value=fallback_id)]
94+
return Reference(
95+
type=ReferenceTypes.EXTERNAL_REFERENCE,
96+
keys=keys,
97+
)
98+
99+
def _add_or_update_asset_id(self, name, value, bpn_list, fallback_id=None):
100+
ref = self._reference_from_bpn_list(bpn_list, fallback_id=fallback_id)
101+
return SpecificAssetId(name=name, value=value, externalSubjectId=ref)
102+
def create_or_update_shell_descriptor(self,
103+
aas_id: UUID,
104+
global_id: UUID,
105+
manufacturer_id: str,
106+
manufacturer_part_id: str,
107+
customer_part_ids: Dict[str, str] | None,
108+
part_category: str,
109+
digital_twin_type: str,
110+
) -> ShellDescriptor:
111+
"""
112+
Registers or updates a twin in the DTR.
113+
"""
114+
try:
115+
existing_shell = self.aas_service.get_asset_administration_shell_descriptor_by_id(aas_id.urn)
116+
logger.info(f"Shell with ID {aas_id} already exists and will be updated.")
117+
specific_asset_ids = existing_shell.specificAssetIds or []
118+
existing_keys = {(id.name, id.value) for id in specific_asset_ids}
119+
except Exception:
120+
existing_shell = None
121+
specific_asset_ids = []
122+
existing_keys = set()
123+
124+
bpn_list = list(customer_part_ids.values()) if customer_part_ids else []
125+
126+
if manufacturer_id and ("manufacturerId", manufacturer_id) not in existing_keys:
127+
specific_asset_ids.append(self._add_or_update_asset_id("manufacturerId", manufacturer_id, bpn_list, fallback_id=manufacturer_id))
128+
129+
if digital_twin_type and ("digitalTwinType", digital_twin_type) not in existing_keys:
130+
specific_asset_ids.append(self._add_or_update_asset_id("digitalTwinType", digital_twin_type, bpn_list, fallback_id=manufacturer_id))
131+
132+
if manufacturer_part_id and ("manufacturerPartId", manufacturer_part_id) not in existing_keys:
133+
specific_asset_ids.append(self._add_or_update_asset_id("manufacturerPartId", manufacturer_part_id, bpn_list, fallback_id=manufacturer_id))
134+
135+
if customer_part_ids:
136+
for customer_part_id, bpn in customer_part_ids.items():
137+
if not customer_part_id:
138+
continue
139+
key = ("customerPartId", customer_part_id)
140+
if key not in existing_keys:
141+
specific_customer_part_asset_id = SpecificAssetId(
142+
name="customerPartId",
143+
value=customer_part_id,
144+
externalSubjectId=self._reference_from_bpn_list([bpn]),
145+
)
146+
specific_asset_ids.append(specific_customer_part_asset_id)
147+
148+
shell = ShellDescriptor(
149+
id=aas_id.urn,
150+
globalAssetId=global_id.urn,
151+
specificAssetIds=specific_asset_ids,
152+
)
153+
154+
if existing_shell:
155+
res = self.aas_service.update_asset_administration_shell_descriptor(shell)
156+
else:
157+
res = self.aas_service.create_asset_administration_shell_descriptor(shell)
158+
159+
if isinstance(res, Result):
160+
raise Exception("Error creating or updating shell descriptor", res.to_json_string())
161+
162+
return res
163+
164+
84165
def create_shell_descriptor(
85166
self,
86167
aas_id: UUID,
87168
global_id: UUID,
88169
manufacturer_id: str,
89170
manufacturer_part_id: str,
90-
customer_part_ids: Dict[str, str],
171+
customer_part_ids: Dict[str, str] | None,
91172
part_category: str,
92173
digital_twin_type: str,
93174
) -> ShellDescriptor:
94175
"""
95176
Registers a twin in the DTR.
96177
"""
97-
# List with specific asset ids that
98-
# are required by the Industry Core KIT
99178
specific_asset_ids = []
100-
# Adds the customerPartId of the customers from which we have
101-
# this information
102-
for customer_part_id, bpn in customer_part_ids.items():
103-
# This value is optional, so if we don't have it, we skip it
104-
if not customer_part_id:
105-
continue
106-
specific_customer_part_asset_id = SpecificAssetId(
107-
name="customerPartId",
108-
value=customer_part_id,
179+
# Prepare BPN list from customer_part_ids, if present
180+
bpn_list = list(customer_part_ids.values()) if customer_part_ids else []
181+
182+
# manufacturerId
183+
if manufacturer_id:
184+
ref_keys = (
185+
[ReferenceKey(type=ReferenceKeyTypes.GLOBAL_REFERENCE, value=bpn) for bpn in bpn_list]
186+
if bpn_list else
187+
[ReferenceKey(type=ReferenceKeyTypes.GLOBAL_REFERENCE, value=manufacturer_id)]
188+
)
189+
specific_manufacturer_asset_id = SpecificAssetId(
190+
name="manufacturerId",
191+
value=manufacturer_id,
109192
externalSubjectId=Reference(
110193
type=ReferenceTypes.EXTERNAL_REFERENCE,
111-
keys=[
112-
ReferenceKey(
113-
type=ReferenceKeyTypes.GLOBAL_REFERENCE, value=bpn
114-
),
115-
],
194+
keys=ref_keys,
116195
),
117196
) # type: ignore
118-
specific_asset_ids.append(specific_customer_part_asset_id)
197+
specific_asset_ids.append(specific_manufacturer_asset_id)
119198

120-
# Adds the manufacturerId that we have assigned to the part
121-
# The visibility of this id is restricted to partners we have
122-
# a contract with, so we use set their BPN
123-
specific_manufacturer_asset_id = SpecificAssetId(
124-
name="manufacturerId",
125-
value=manufacturer_id,
126-
externalSubjectId=Reference(
127-
type=ReferenceTypes.EXTERNAL_REFERENCE,
128-
keys=[
129-
ReferenceKey(type=ReferenceKeyTypes.GLOBAL_REFERENCE, value=bpn)
130-
for bpn in customer_part_ids.values()
131-
],
132-
),
133-
) # type: ignore
134-
specific_asset_ids.append(specific_manufacturer_asset_id)
199+
# digitalTwinType
200+
if digital_twin_type:
201+
ref_keys = (
202+
[ReferenceKey(type=ReferenceKeyTypes.GLOBAL_REFERENCE, value=bpn) for bpn in bpn_list]
203+
if bpn_list else
204+
[ReferenceKey(type=ReferenceKeyTypes.GLOBAL_REFERENCE, value=manufacturer_id)]
205+
)
206+
digital_twin_asset_id = SpecificAssetId(
207+
name="digitalTwinType",
208+
value=digital_twin_type,
209+
externalSubjectId=Reference(
210+
type=ReferenceTypes.EXTERNAL_REFERENCE,
211+
keys=ref_keys,
212+
),
213+
) # type: ignore
214+
specific_asset_ids.append(digital_twin_asset_id)
135215

136-
# Is added to allow data consumers to search for all digital twins of a particular type
137-
digital_twin_asset_id = SpecificAssetId(
138-
name="digitalTwinType",
139-
value=digital_twin_type,
140-
externalSubjectId=Reference(
141-
type=ReferenceTypes.EXTERNAL_REFERENCE,
142-
keys=[
143-
ReferenceKey(type=ReferenceKeyTypes.GLOBAL_REFERENCE, value=bpn)
144-
for bpn in customer_part_ids.values()
145-
],
146-
),
147-
) # type: ignore
148-
specific_asset_ids.append(digital_twin_asset_id)
216+
# manufacturerPartId
217+
if manufacturer_part_id:
218+
ref_keys = (
219+
[ReferenceKey(type=ReferenceKeyTypes.GLOBAL_REFERENCE, value=bpn) for bpn in bpn_list]
220+
if bpn_list else
221+
[ReferenceKey(type=ReferenceKeyTypes.GLOBAL_REFERENCE, value=manufacturer_id)]
222+
)
223+
specific_manufacturer_part_asset_id = SpecificAssetId(
224+
name="manufacturerPartId",
225+
value=manufacturer_part_id,
226+
externalSubjectId=Reference(
227+
type=ReferenceTypes.EXTERNAL_REFERENCE,
228+
keys=ref_keys,
229+
),
230+
) # type: ignore
231+
specific_asset_ids.append(specific_manufacturer_part_asset_id)
149232

150-
# The ID of the type/catalog part from the manufacturer
151-
# visble to everyone
152-
specific_manufacturer_part_asset_id = SpecificAssetId(
153-
name="manufacturerPartId",
154-
value=manufacturer_part_id,
155-
externalSubjectId=Reference(
156-
type=ReferenceTypes.EXTERNAL_REFERENCE,
157-
keys=[
158-
ReferenceKey(
159-
type=ReferenceKeyTypes.GLOBAL_REFERENCE, value="PUBLIC_READABLE"
233+
# customerPartId(s)
234+
if customer_part_ids is not None and customer_part_ids != {}:
235+
for customer_part_id, bpn in customer_part_ids.items():
236+
if not customer_part_id:
237+
continue
238+
ref_keys = (
239+
[ReferenceKey(type=ReferenceKeyTypes.GLOBAL_REFERENCE, value=bpn)]
240+
if bpn else
241+
([ReferenceKey(type=ReferenceKeyTypes.GLOBAL_REFERENCE, value=manufacturer_id)] if manufacturer_id else [])
242+
)
243+
specific_customer_part_asset_id = SpecificAssetId(
244+
name="customerPartId",
245+
value=customer_part_id,
246+
externalSubjectId=Reference(
247+
type=ReferenceTypes.EXTERNAL_REFERENCE,
248+
keys=ref_keys,
160249
),
161-
],
162-
),
163-
) # type: ignore
164-
specific_asset_ids.append(specific_manufacturer_part_asset_id)
250+
) # type: ignore
251+
specific_asset_ids.append(specific_customer_part_asset_id)
165252

166-
# ids need to be saved as urn
167253
shell = ShellDescriptor(
168254
id=aas_id.urn,
169255
globalAssetId=global_id.urn,

ichub-backend/models/services/sharing_management.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,6 @@ class SharedPartBase(BaseModel):
4747
shared_at: datetime = Field(alias="sharedAt", description="The date and time when the catalog part was shared.")
4848
twin: Optional[CatalogPartTwinDetailsRead] = Field(alias="twin", description="The digital twin created for part that was shared.", default=None)
4949

50+
51+
class SharedPartner(SharingBase):
52+
name: str = Field(description="The unique name of the business partner.")

ichub-backend/services/sharing_service.py

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@
2828
from managers.metadata_database.manager import RepositoryManagerFactory, RepositoryManager
2929
from models.services.twin_management import CatalogPartTwinCreate, CatalogPartTwinShare, TwinAspectCreate, CatalogPartTwinDetailsRead, TwinAspectRead
3030
from models.metadata_database.models import BusinessPartner, DataExchangeAgreement, EnablementServiceStack, CatalogPart, Twin, PartnerCatalogPart
31-
from models.services.sharing_management import SharedPartBase, ShareCatalogPart
31+
from models.services.sharing_management import SharedPartBase, ShareCatalogPart, SharedPartner
3232
from models.services.partner_management import BusinessPartnerRead
3333
from typing import Dict, Optional, List, Any, Tuple
3434

35+
from uuid import uuid4
3536
from managers.config.log_manager import LoggingManager
3637

3738
logger = LoggingManager.get_logger(__name__)
@@ -45,14 +46,17 @@ def __init__(self):
4546
self.submodel_document_generator = SubmodelDocumentGenerator()
4647
self.twin_management_service = TwinManagementService()
4748

49+
def get_shared_partners(self, manufacturerId:str, manufacturerPartId:str) -> List[SharedPartner]:
50+
pass
51+
4852
def share_catalog_part(self, catalog_part_to_share: ShareCatalogPart) -> SharedPartBase:
4953
shared_at = datetime.now(timezone.utc)
5054
with RepositoryManagerFactory.create() as repo:
5155
# Step 1: Retrieve the catalog part from the repository
5256
db_catalog_part = self._get_catalog_part(repo, catalog_part_to_share)
5357
# Step 2: Get or create the enablement service stack for the manufacturer
5458
## Note: this is not used at the moment
55-
db_enablement_service_stack = self._get_or_create_enablement_service_stack(repo, catalog_part_to_share)
59+
db_enablement_service_stack = self.twin_management_service.get_or_create_enablement_stack(repo, catalog_part_to_share.manufacturer_id)
5660
# Step 3: Get or create the business partner entity
5761
db_business_partner = self._get_or_create_business_partner(repo, catalog_part_to_share)
5862
# Step 4: Get or create the data exchange agreement for the business partner
@@ -70,7 +74,7 @@ def share_catalog_part(self, catalog_part_to_share: ShareCatalogPart) -> SharedP
7074
businessPartnerNumber=catalog_part_to_share.business_partner_number,
7175
customerPartIds=db_partner_catalog_parts,
7276
sharedAt=shared_at,
73-
twin=self.twin_management_service.get_catalog_part_twin_details(db_twin.global_id)
77+
twin=self.twin_management_service.get_catalog_part_twin_details_id(global_id=db_twin.global_id)
7478
)
7579

7680
def _get_catalog_part(self, repo: RepositoryManager, catalog_part_to_share: ShareCatalogPart) -> CatalogPart:
@@ -89,21 +93,6 @@ def _get_catalog_part(self, repo: RepositoryManager, catalog_part_to_share: Shar
8993
db_catalog_part, _ = db_catalog_parts[0]
9094
return db_catalog_part
9195

92-
def _get_or_create_enablement_service_stack(self, repo: RepositoryManager, catalog_part_to_share: ShareCatalogPart) -> EnablementServiceStack:
93-
"""
94-
Retrieve or create an EnablementServiceStack for the given manufacturer ID.
95-
"""
96-
db_enablement_service_stacks = repo.enablement_service_stack_repository.find_by_legal_entity_bpnl(catalog_part_to_share.manufacturer_id)
97-
if not db_enablement_service_stacks:
98-
db_legal_entity = repo.legal_entity_repository.get_by_bpnl(catalog_part_to_share.manufacturer_id)
99-
db_enablement_service_stack = repo.enablement_service_stack_repository.create(
100-
EnablementServiceStack(name='EDC/DTR Default', legal_entity_id=db_legal_entity.id))
101-
repo.commit()
102-
repo.refresh(db_enablement_service_stack)
103-
else:
104-
db_enablement_service_stack = db_enablement_service_stacks[0]
105-
return db_enablement_service_stack
106-
10796
def _get_or_create_business_partner(self, repo: RepositoryManager, catalog_part_to_share: ShareCatalogPart) -> BusinessPartner:
10897
"""
10998
Retrieve or create a BusinessPartner for the given business partner number.
@@ -221,4 +210,4 @@ def _create_part_twin_aspect(self, db_twin: Twin, db_catalog_part: CatalogPart,
221210
globalId=db_twin.global_id,
222211
semanticId=SEM_ID_PART_TYPE_INFORMATION_V1,
223212
payload=payload
224-
))
213+
), manufacturer_id=catalog_part_to_share.manufacturer_id)

0 commit comments

Comments
 (0)