Repository: eclipse-tractusx/industry-core-hub
Pull request #156: feat: Added models for metadata database and service layer
Author: login: sm29105
Created: 2025-04-23T12:20:44Z | Merged: 2025-04-29T10:07:23Z

<!-- 
Thanks for your contribution! 
Please follow the instructions on your PRs title and description.
aligned title description: '(feat|fix|chore|doc): _description of introduced change_'
Important: Contributing Guidelines can be found here: https://eclipse-tractusx.github.io/docs/oss/how-to-contribute
Info: <!- text comments ->  will be hidden from the rendered preview of your PR.
-->

## Description
This contains sqlmodel based model classes for the metadata database as well as pydantic based model classes for the service layer (API layer).

<!-- Please tag the related issue `Fixes or Updates #issue_number`, if applicable. -->

## Pre-review checks

Please ensure to do as many of the following checks as possible, before asking for committer review:

- [x] DEPENDENCIES are up-to-date. [Dash license tool](https://github.com/eclipse/dash-licenses). Committers can open IP issues for restricted libs.
- [x] [Copyright and license header](https://eclipse-tractusx.github.io/docs/release/trg-7/trg-7-02) are present on all affected files



Diff:
diff --git a/ichub-backend/models/metadata_database/__init__.py b/ichub-backend/models/metadata_database/__init__.py
new file mode 100644
index 000000000..30070fa6d
--- /dev/null
+++ b/ichub-backend/models/metadata_database/__init__.py
@@ -0,0 +1,43 @@
+#################################################################################
+# Eclipse Tractus-X - Industry Core Hub Backend
+#
+# Copyright (c) 2025 DRÄXLMAIER Group
+# (represented by Lisa Dräxlmaier GmbH)
+# Copyright (c) 2025 Contributors to the Eclipse Foundation
+#
+# See the NOTICE file(s) distributed with this work for additional
+# information regarding copyright ownership.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Apache License, Version 2.0 which is available at
+# https://www.apache.org/licenses/LICENSE-2.0.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied. See the
+# License for the specific language govern in permissions and limitations
+# under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#################################################################################
+
+from .models import (
+    LegalEntity,
+    BusinessPartner,
+    Twin,
+    CatalogPart,
+    PartnerCatalogPart,
+    SerializedPart,
+    JISPart,
+    Batch,
+    BatchBusinessPartner,
+    DataExchangeAgreement,
+    DataExchangeContract,
+    EnablementServiceStack,
+    TwinAspect,
+    TwinAspectRegistration,
+    TwinExchange,
+    TwinRegistration,
+)
+
diff --git a/ichub-backend/models/metadata_database/models.py b/ichub-backend/models/metadata_database/models.py
new file mode 100644
index 000000000..481ae17fe
--- /dev/null
+++ b/ichub-backend/models/metadata_database/models.py
@@ -0,0 +1,254 @@
+#################################################################################
+# Eclipse Tractus-X - Industry Core Hub Backend
+#
+# Copyright (c) 2025 DRÄXLMAIER Group
+# (represented by Lisa Dräxlmaier GmbH)
+# Copyright (c) 2025 Contributors to the Eclipse Foundation
+#
+# See the NOTICE file(s) distributed with this work for additional
+# information regarding copyright ownership.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Apache License, Version 2.0 which is available at
+# https://www.apache.org/licenses/LICENSE-2.0.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied. See the
+# License for the specific language govern in permissions and limitations
+# under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#################################################################################
+
+from typing import Any, Dict, List, Optional
+from uuid import UUID
+from datetime import datetime
+from sqlmodel import Field, SQLModel, Relationship
+from sqlalchemy import Column, JSON, UniqueConstraint
+
+class LegalEntity(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    bpnl: str = Field(index=True, unique=True, description="The BPNL of the legal entity.")
+
+    # Relationships
+    catalog_parts: List["CatalogPart"] = Relationship(back_populates="legal_entity")
+    enablement_service_stacks: List["EnablementServiceStack"] = Relationship(back_populates="legal_entity")
+
+    __tablename__ = "legal_entity"
+
+
+class BusinessPartner(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    name: str = Field(index=True, unique=True, description="The name of the business partner.")
+    bpnl: str = Field(index=True, unique=True, description="The BPNL of the business partner.")
+
+    # Relationships
+    partner_catalog_parts: List["PartnerCatalogPart"] = Relationship(back_populates="business_partner")
+    data_exchange_agreements: List["DataExchangeAgreement"] = Relationship(back_populates="business_partner")
+
+    __tablename__ = "business_partner"
+
+
+class Twin(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    global_id: UUID = Field(default_factory=UUID, unique=True, description="The global ID (aka. Catena-X ID) of the twin.")
+    aas_id: UUID = Field(default_factory=UUID, unique=True, description="The AAS ID of the twin.")
+    created_date: datetime = Field(default_factory=datetime.utcnow, description="The creation date of the twin.")
+    modified_date: datetime = Field(default_factory=datetime.utcnow, description="The last modification date of the twin.")
+    asset_class: Optional[str] = Field(default=None, description="The asset class of the twin.")
+    additional_context: Optional[str] = Field(default=None, description="Additional context for the twin.")
+
+    # Relationships
+    catalog_parts: List["CatalogPart"] = Relationship(back_populates="twin")
+    serialized_parts: List["SerializedPart"] = Relationship(back_populates="twin")
+    jis_parts: List["JISPart"] = Relationship(back_populates="twin")
+    twin_aspects: List["TwinAspect"] = Relationship(back_populates="twin")
+    twin_exchanges: List["TwinExchange"] = Relationship(back_populates="twin")
+    twin_registrations: List["TwinRegistration"] = Relationship(back_populates="twin")
+
+    __tablename__ = "twin"
+
+
+class CatalogPart(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    manufacturer_part_id: str = Field(index=True, unique=True, description="The manufacturer part ID.")
+    legal_entity_id: int = Field(foreign_key="legal_entity.id", description="The ID of the associated legal entity.")
+    twin_id: Optional[int] = Field(foreign_key="twin.id", description="The ID of the associated twin.")
+    category: Optional[str] = Field(default=None, description="The category of the catalog part.")
+    bpns: Optional[str] = Field(default=None, description="The optional site information (BPNS) of the catalog part.")
+
+    # Relationships
+    legal_entity: LegalEntity = Relationship(back_populates="catalog_parts")
+    twin: Optional[Twin] = Relationship(back_populates="catalog_parts")
+    partner_catalog_parts: List["PartnerCatalogPart"] = Relationship(back_populates="catalog_part")
+    batches: List["Batch"] = Relationship(back_populates="catalog_part")
+
+    __tablename__ = "catalog_part"
+
+
+class PartnerCatalogPart(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    business_partner_id: int = Field(foreign_key="business_partner.id", description="The ID of the associated business partner.")
+    catalog_part_id: int = Field(foreign_key="catalog_part.id", description="The ID of the associated catalog part.")
+    customer_part_id: str = Field(default="", description="The customer part ID.")
+
+    # Relationships
+    business_partner: BusinessPartner = Relationship(back_populates="partner_catalog_parts")
+    catalog_part: CatalogPart = Relationship(back_populates="partner_catalog_parts")
+    serialized_parts: List["SerializedPart"] = Relationship(back_populates="partner_catalog_part")
+    jis_parts: List["JISPart"] = Relationship(back_populates="partner_catalog_part")
+
+    # Composite Unique Constraint
+    __table_args__ = (
+        UniqueConstraint("business_partner_id", "catalog_part_id", name="uq_partner_catalog_part"),
+    )
+
+    __tablename__ = "partner_catalog_part"
+
+
+class SerializedPart(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    partner_catalog_part_id: int = Field(foreign_key="partner_catalog_part.id", description="The ID of the associated partner catalog part.")
+    part_instance_id: str = Field(index=True, unique=True, description="The part instance ID.")
+    van: Optional[str] = Field(default=None, description="The optional VAN (Vehicle Assembly Number).")
+    twin_id: int = Field(foreign_key="twin.id", description="The ID of the associated twin.")
+
+    # Relationships
+    partner_catalog_part: PartnerCatalogPart = Relationship(back_populates="serialized_parts")
+    twin: Twin = Relationship(back_populates="serialized_parts")
+
+    __tablename__ = "serialized_part"
+
+
+class JISPart(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    partner_catalog_part_id: int = Field(foreign_key="partner_catalog_part.id", description="The ID of the associated partner catalog part.")
+    jis_number: str = Field(index=True, unique=True, description="The JIS number.")
+    parent_order_number: Optional[str] = Field(default=None, description="The parent order number.")
+    jis_call_date: Optional[datetime] = Field(default=None, description="The JIS call date.")
+    twin_id: int = Field(foreign_key="twin.id", description="The ID of the associated twin.")
+
+    # Relationships
+    partner_catalog_part: PartnerCatalogPart = Relationship(back_populates="jis_parts")
+    twin: Twin = Relationship(back_populates="jis_parts")
+
+    __tablename__ = "jis_part"
+
+class Batch(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    batch_id: str = Field(index=True, unique=True, description="The batch ID.")
+    catalog_part_id: int = Field(foreign_key="catalog_part.id", description="The ID of the associated catalog part.")
+
+    # Relationships
+    catalog_part: CatalogPart = Relationship(back_populates="batches")
+    batch_business_partners: List["BatchBusinessPartner"] = Relationship(back_populates="batch")
+
+    # Composite Unique Constraint
+    __table_args__ = (
+        UniqueConstraint("catalog_part_id", "batch_id", name="uq_catalog_batch_id"),
+    )
+
+    __tablename__ = "batch"
+
+class BatchBusinessPartner(SQLModel, table=True):
+    batch_id: str = Field(foreign_key="batch.id", description="The batch ID.", primary_key=True)
+    business_partner_id: int = Field(foreign_key="business_partner.id", description="The ID of the associated business partner.", primary_key=True)
+
+    # Relationships
+    business_partner: BusinessPartner = Relationship()
+    batch: Batch = Relationship(back_populates="batch_business_partners")
+
+    __tablename__ = "batch_business_partner"
+
+class DataExchangeAgreement(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    name: str = Field(index=True, unique=True, description="The name of the data exchange agreement.")
+    business_partner_id: int = Field(foreign_key="business_partner.id", description="The ID of the associated business partner.")
+
+    # Relationships
+    business_partner: BusinessPartner = Relationship(back_populates="data_exchange_agreements")
+    data_exchange_contracts: List["DataExchangeContract"] = Relationship(back_populates="data_exchange_agreement")
+    twin_exchanges: List["TwinExchange"] = Relationship(back_populates="data_exchange_agreement")
+
+    __tablename__ = "data_exchange_agreement"
+
+
+class DataExchangeContract(SQLModel, table=True):
+    data_exchange_agreement_id: int = Field(foreign_key="data_exchange_agreement.id", primary_key=True, description="The ID of the associated data exchange agreement.")
+    semantic_id: str = Field(primary_key=True, description="The semantic ID of the contract.")
+    edc_usage_policy_id: str = Field(description="The EDC usage policy ID.")
+
+    # Relationships
+    data_exchange_agreement: DataExchangeAgreement = Relationship(back_populates="data_exchange_contracts")
+
+    __tablename__ = "data_exchange_contract"
+
+
+class EnablementServiceStack(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    name: str = Field(index=True, unique=True, description="The name of the enablement service stack.")
+    connection_settings: Optional[Dict[str, Any]] = Field(
+        sa_column=Column(JSON),  # Specify JSON column type
+        description="Connection settings stored as JSON"
+    )
+    legal_entity_id: int = Field(foreign_key="legal_entity.id", description="The ID of the associated legal entity.")
+
+    # Relationships
+    legal_entity: LegalEntity = Relationship(back_populates="enablement_service_stacks")
+    twin_aspect_registrations: List["TwinAspectRegistration"] = Relationship(back_populates="enablement_service_stack")
+    twin_registrations: List["TwinRegistration"] = Relationship(back_populates="enablement_service_stack")
+
+    __tablename__ = "enablement_service_stack"
+
+
+class TwinAspect(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    submodel_id: UUID = Field(default_factory=UUID, unique=True, description="The submodel ID.")
+    semantic_id: str = Field(description="The semantic ID.")
+    twin_id: int = Field(foreign_key="twin.id", description="The ID of the associated twin.")
+
+    # Relationships
+    twin: Twin = Relationship(back_populates="twin_aspects")
+    twin_aspect_registrations: List["TwinAspectRegistration"] = Relationship(back_populates="twin_aspect")
+
+    __tablename__ = "twin_aspect"
+
+
+class TwinAspectRegistration(SQLModel, table=True):
+    twin_aspect_id: int = Field(foreign_key="twin_aspect.id", primary_key=True, description="The ID of the associated twin aspect.")
+    enablement_service_stack_id: int = Field(foreign_key="enablement_service_stack.id", primary_key=True, description="The ID of the associated enablement service stack.")
+    status: int = Field(default=0, description="The status of the registration.")
+    registration_mode: int = Field(default=0, description="The registration mode.")
+    created_date: datetime = Field(default_factory=datetime.utcnow, description="The creation date of the registration.")
+    modified_date: datetime = Field(default_factory=datetime.utcnow, description="The last modification date of the registration.")
+
+    # Relationships
+    twin_aspect: TwinAspect = Relationship(back_populates="twin_aspect_registrations")
+    enablement_service_stack: EnablementServiceStack = Relationship(back_populates="twin_aspect_registrations")
+
+    __tablename__ = "twin_aspect_registration"
+
+
+class TwinExchange(SQLModel, table=True):
+    twin_id: int = Field(foreign_key="twin.id", primary_key=True, description="The ID of the associated twin.")
+    data_exchange_agreement_id: int = Field(foreign_key="data_exchange_agreement.id", primary_key=True, description="The ID of the associated data exchange agreement.")
+
+    # Relationships
+    twin: Twin = Relationship(back_populates="twin_exchanges")
+    data_exchange_agreement: DataExchangeAgreement = Relationship(back_populates="twin_exchanges")
+
+    __tablename__ = "twin_exchange"
+
+
+class TwinRegistration(SQLModel, table=True):
+    twin_id: int = Field(foreign_key="twin.id", primary_key=True, description="The ID of the associated twin.")
+    enablement_service_stack_id: int = Field(foreign_key="enablement_service_stack.id", primary_key=True, description="The ID of the associated enablement service stack.")
+    dtr_registered: bool = Field(default=False, description="Whether the twin is registered in the DTR.")
+
+    # Relationships
+    twin: Twin = Relationship(back_populates="twin_registrations")
+    enablement_service_stack: EnablementServiceStack = Relationship(back_populates="twin_registrations")
+
+    __tablename__ = "twin_registration"
\ No newline at end of file
diff --git a/ichub-backend/models/services/__init__.py b/ichub-backend/models/services/__init__.py
new file mode 100644
index 000000000..781a0126e
--- /dev/null
+++ b/ichub-backend/models/services/__init__.py
@@ -0,0 +1,68 @@
+#################################################################################
+# Eclipse Tractus-X - Industry Core Hub Backend
+#
+# Copyright (c) 2025 DRÄXLMAIER Group
+# (represented by Lisa Dräxlmaier GmbH)
+# Copyright (c) 2025 Contributors to the Eclipse Foundation
+#
+# See the NOTICE file(s) distributed with this work for additional
+# information regarding copyright ownership.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Apache License, Version 2.0 which is available at
+# https://www.apache.org/licenses/LICENSE-2.0.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied. See the
+# License for the specific language govern in permissions and limitations
+# under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#################################################################################
+
+from .part_management import (
+    CatalogPartRead,
+    CatalogPartCreate,
+    CatalogPartDelete,
+    CatalogPartQuery,
+    PartnerCatalogPartCreate,
+    PartnerCatalogPartDelete,
+    BatchRead,
+    BatchCreate,
+    BatchDelete,
+    BatchQuery,
+    SerializedPartRead,
+    SerializedPartCreate,
+    SerializedPartDelete,
+    SerializedPartQuery,
+    JISPartRead,
+    JISPartCreate,
+    JISPartDelete,
+    JISPartQuery
+)
+
+from .partner_management import (
+    BusinessPartner,
+    DataExchangeContractRead,
+    DataExchangeContractCreate,
+    DataExchangeAgreementCreate,
+    DataExchangeAgreementRead
+)
+
+from .twin_management import (
+    TwinAspectRegistrationStatus,
+    TwinsAspectRegistrationMode,
+    TwinAspectRegistration,
+    TwinAspectRead,
+    TwinAspectCreate,
+    TwinRead,
+    TwinCreateBase,
+    TwinDetailsRead,
+    CatalogPartTwinCreate,
+    BatchTwinCreate,
+    JISPartTwinCreate,
+    SerializedPartTwinCreate
+)
+
diff --git a/ichub-backend/models/services/part_management.py b/ichub-backend/models/services/part_management.py
new file mode 100644
index 000000000..ee7b85aad
--- /dev/null
+++ b/ichub-backend/models/services/part_management.py
@@ -0,0 +1,114 @@
+#################################################################################
+# Eclipse Tractus-X - Industry Core Hub Backend
+#
+# Copyright (c) 2025 DRÄXLMAIER Group
+# (represented by Lisa Dräxlmaier GmbH)
+# Copyright (c) 2025 Contributors to the Eclipse Foundation
+#
+# See the NOTICE file(s) distributed with this work for additional
+# information regarding copyright ownership.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Apache License, Version 2.0 which is available at
+# https://www.apache.org/licenses/LICENSE-2.0.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied. See the
+# License for the specific language govern in permissions and limitations
+# under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#################################################################################
+
+from datetime import datetime
+from typing import Dict, Optional, List
+
+from pydantic import BaseModel, Field
+
+from models.services.partner_management import BusinessPartner
+
+class CatalogPartBase(BaseModel):
+    manufacturer_id: str = Field(alias="manufacturerId", description="The BPNL (manufactuer ID) of the part to register.")
+    manufacturer_part_id: str = Field(alias="manufacturerPartId", description="The manufacturer part ID of the part.")
+
+class PartnerCatalogPartBase(BaseModel):
+    customer_part_id: str = Field(alias="customerPartId", description="The customer part ID for partner specific mapping of the catalog part.")
+    business_partner_name: str = Field(alias="businessPartnerName", description="The unique name of the business partner to map the catalog part to.")
+
+class CatalogPartRead(CatalogPartBase):
+    customer_part_ids: Optional[Dict[str, BusinessPartner]] = Field(alias="customerPartIds", description="The list of customer part IDs mapped to the respective Business Partners.", default={})
+
+class CatalogPartCreate(CatalogPartBase):
+    customer_part_ids: Optional[List[PartnerCatalogPartBase]] = Field(alias="customerPartIds", description="An optional list of customer part IDs to business partner name mappings.", default=[])
+
+class CatalogPartDelete(CatalogPartBase):
+    pass
+
+class CatalogPartQuery(BaseModel):
+    manufacturer_id: Optional[str] = Field(alias="manufacturerId", description="The BPNL (manufactuer ID) of the part to register.", default=None)
+    manufacturer_part_id: Optional[str] = Field(alias="manufacturerPartId", description="The manufacturer part ID of the part.", default=None)
+
+class PartnerCatalogPartCreate(CatalogPartBase, PartnerCatalogPartBase):
+    pass
+
+class PartnerCatalogPartDelete(PartnerCatalogPartCreate):
+    pass
+
+class BatchBase(BaseModel):
+    batch_id: str = Field(alias="batchId", description="The batch ID of the part.")
+
+class BatchRead(CatalogPartRead, BatchBase):
+    pass
+
+class BatchCreate(CatalogPartCreate, BatchBase):  
+    pass
+
+class BatchDelete(CatalogPartDelete, BatchBase):
+    pass
+
+class BatchQuery(CatalogPartQuery):
+    batch_id: Optional[str] = Field(alias="batchId", description="The batch ID of the part.", default=None)
+
+class SerializedPartBase(CatalogPartBase):
+    part_instance_id: str = Field(alias="partInstanceId", description="The part instance ID of the serialized part.")
+    customer_part_id: str = Field(alias="customerPartId", description="The customer part ID of the part.")
+
+class SerializedPartRead(SerializedPartBase):
+    van: Optional[str] = Field(description="The optional VAN (Vehicle Assembly Number) of the serialized part.", default=None)
+    business_partner: BusinessPartner = Field(alias="businessPartner", description="The business partner to whom the part is being offered.")
+
+class SerializedPartCreate(SerializedPartBase):
+    van: Optional[str] = Field(description="The optional VAN (Vehicle Assembly Number) of the serialized part.", default=None)
+    business_partner_name: str = Field(alias="businessPartnerName", description="The unique name of the business partner to map the catalog part to.")
+
+class SerializedPartDelete(SerializedPartBase):
+    business_partner_name: str = Field(alias="businessPartnerName", description="The unique name of the business partner to map the catalog part to.")
+
+class SerializedPartQuery(CatalogPartQuery):
+    part_instance_id: Optional[str] = Field(alias="partInstanceId", description="The part instance ID of the serialized part.", default=None)
+    van: Optional[str] = Field(description="The optional VAN (Vehicle Assembly Number) of the serialized part.", default=None)
+
+class JISPartBase(CatalogPartBase):
+    jis_number: str = Field(alias="jisNumber", description="The JIS number of the JIS part.")
+    customer_part_id: str = Field(alias="customerPartId", description="The customer part ID of the part.")
+
+class JISPartRead(JISPartBase):
+    parent_order_number: Optional[str] = Field(alias="parentOrderNumber", description="The parent order number of the JIS part.", default=None)
+    jis_call_date: Optional[datetime] = Field(alias="jisCallDate", description="The JIS call date of the JIS part.", default=None)
+    business_partner: BusinessPartner = Field(alias="businessPartner", description="The business partner to whom the part is being offered.")
+
+class JISPartCreate(JISPartBase):
+    parent_order_number: Optional[str] = Field(description="The parent order number of the JIS part.", default=None)
+    jis_call_date: Optional[datetime] = Field(description="The JIS call date of the JIS part.", default=None)
+    business_partner_name: str = Field(alias="businessPartnerName", description="The unique name of the business partner to map the catalog part to.")
+
+class JISPartDelete(JISPartBase):
+    business_partner_name: str = Field(alias="businessPartnerName", description="The unique name of the business partner to map the catalog part to.")
+
+class JISPartQuery(CatalogPartQuery):
+    jis_number: Optional[str] = Field(alias="jisNumber", description="The JIS number of the JIS part.", default=None)
+    parent_order_number: Optional[str] = Field(alias="parentOrderNumber", description="The parent order number of the JIS part.", default=None)
+    jis_call_date_min: Optional[datetime] = Field(alias="jisCallDate", description="The minimal JIS call date of the JIS part.", default=None)
+    jis_call_date_max: Optional[datetime] = Field(alias="jisCallDate", description="The maximal JIS call date of the JIS part.", default=None)
\ No newline at end of file
diff --git a/ichub-backend/models/services/partner_management.py b/ichub-backend/models/services/partner_management.py
new file mode 100644
index 000000000..b7e484002
--- /dev/null
+++ b/ichub-backend/models/services/partner_management.py
@@ -0,0 +1,70 @@
+#################################################################################
+# Eclipse Tractus-X - Industry Core Hub Backend
+#
+# Copyright (c) 2025 DRÄXLMAIER Group
+# (represented by Lisa Dräxlmaier GmbH)
+# Copyright (c) 2025 Contributors to the Eclipse Foundation
+#
+# See the NOTICE file(s) distributed with this work for additional
+# information regarding copyright ownership.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Apache License, Version 2.0 which is available at
+# https://www.apache.org/licenses/LICENSE-2.0.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied. See the
+# License for the specific language govern in permissions and limitations
+# under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#################################################################################
+
+from typing import List
+from pydantic import BaseModel, Field, field_validator
+
+def validate_bpnl(bpnl: str, field_name: str) -> str:
+    """Validates the BPNL format."""
+    if len(bpnl) != 16 or not bpnl.isalnum():
+        raise ValueError(f"{field_name} must be exactly 16 alphanumeric characters.")
+    if bpnl[0:3] != "BPNL":
+        raise ValueError(f"{field_name} must start with 'BPNL'.")
+    
+    return bpnl
+
+class BusinessPartner(BaseModel):
+    """Represents a partner with a validated name field."""
+    
+    name: str = Field(description="The unique name of the business partner.")
+    bpnl: str = Field(description="The Catena-X Business Partner Number (BPNL) of the business partner.")
+
+    @field_validator("bpnl")
+    @staticmethod
+    def validate_bpnl(bpnl: str) -> str:
+        """Validates the BPNL format."""
+        return validate_bpnl(bpnl, "bpnl")
+    
+class DataExchangeContractRead(BaseModel):
+    """Represents document type specific contract terms belonging to a data exchange agreement."""
+
+    semantic_id: str = Field(alias="semanticId", description="The semantic ID of the data exchange contract applies to.")
+    edc_usage_policy_id: str = Field(alias="edcUsagePolicyId", description="The ID of the EDC usage policy for the background generated EDC contract negotion")
+
+class DataExchangeAgreementRead(BaseModel):
+    """Represents a data exchange agreement between two business partners."""
+
+    business_partner: BusinessPartner = Field(alias="businessPartner", description="The business partner to whom the data exchange agreement applies.")
+    name: str = Field(description="The unique name of the data exchange agreement with the given business partner.")
+    contracts: List[DataExchangeContractRead] = Field(description="The list of data exchange contracts that are part of the agreement.")
+
+class DataExchangeAgreementCreate(BaseModel):
+    business_partner_name: str = Field(alias="businessPartnerName", description="The unique name of the business partner to whom the data exchange contract applies.")
+    data_exchange_agreement_name: str = Field(alias="dataExchangeAgreementName", description="The unique name of the data exchange agreement with the given business partner.")
+
+class DataExchangeContractCreate(DataExchangeAgreementCreate):
+    """Represents document type specific contract terms belonging to a data exchange agreement."""
+
+    semantic_id: str = Field(alias="semanticId", description="The semantic ID of the data exchange contract applies to.")
+    edc_usage_policy_id: str = Field(alias="edcUsagePolicyId", description="The ID of the EDC usage policy for the background generated EDC contract negotion")
\ No newline at end of file
diff --git a/ichub-backend/models/services/twin_management.py b/ichub-backend/models/services/twin_management.py
new file mode 100644
index 000000000..61cd00982
--- /dev/null
+++ b/ichub-backend/models/services/twin_management.py
@@ -0,0 +1,110 @@
+#################################################################################
+# Eclipse Tractus-X - Industry Core Hub Backend
+#
+# Copyright (c) 2025 DRÄXLMAIER Group
+# (represented by Lisa Dräxlmaier GmbH)
+# Copyright (c) 2025 Contributors to the Eclipse Foundation
+#
+# See the NOTICE file(s) distributed with this work for additional
+# information regarding copyright ownership.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Apache License, Version 2.0 which is available at
+# https://www.apache.org/licenses/LICENSE-2.0.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied. See the
+# License for the specific language govern in permissions and limitations
+# under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#################################################################################
+
+from datetime import datetime
+import enum
+from uuid import UUID
+from typing import Dict, Optional, Any
+from pydantic import BaseModel, Field
+
+from models.services.part_management import CatalogPartCreate, BatchCreate, SerializedPartCreate, JISPartCreate
+
+class TwinAspectRegistrationStatus(enum.Enum):
+    """An enumeration of potential status values when a twin aspect is registered within the system"""
+
+    PLANNED = 0
+    """An aspect is just planned to be registered but not yet actually done"""
+
+    STORED = 1
+    """The apect document is stored within the submodel service"""
+
+    EDC_REGISTERED = 2
+    """In addition to being stored within the submodel service an aspect is also registered within the Eclipse Dataspace Connector as an asset (if necessary)"""
+
+    DTR_REGISTERED = 3
+    """The aspect is store in all related systems: submodel service, Eclipse Dataspace Connector and Digital Twin Registry"""
+
+class TwinsAspectRegistrationMode(enum.Enum):
+    """An enumeration of potential possibilities how to provide aspects as assets wihtin the Eclipse Dataspace Connector"""
+    
+    PLANNED = 0
+    """An aspect is just planned to be registered but not yet actually done"""
+
+    SINGLE = 1
+    """A extra asset has been generated within the Eclipse Dataspace Connector for the aspect document"""
+
+    DISPATCHED = 2
+    """No extra asset has been generated within the Eclipse Dataspace Connector for the aspect document - instead there is a bundle asset that points to a dispatching service"""
+
+
+class TwinAspectRegistration(BaseModel):
+    """Represents the registration of a twin aspect within an enablement service stack."""
+
+    enablement_service_stack_name: str = Field(alias="enablementServiceStackName", description="The name of the enablement service stack where the twin aspect is registered.")
+    status: TwinAspectRegistrationStatus = Field(description="The current status of the aspect registration process.")
+    mode: TwinsAspectRegistrationMode = Field(description="The current mode of the aspect registration process.")
+    created_date: datetime = Field(alias="createdDate", description="The date when the aspect was initially registered.")
+    modified_date: datetime = Field(alias="modifiedDate", description="The date when the registration status information last changed.")
+
+class TwinAspectRead(BaseModel):
+    semantic_id: str = Field(alias="semanticId", description="The semantic ID of the aspect determining the structure of the associated payload data.")
+    submodel_id: UUID = Field(alias="submodelId", description="The ID of the submodel descriptor within the DTR shell descriptor for the associated twin.")
+    registrations: Optional[Dict[str, TwinAspectRegistration]] = Field(description="A map of registration information for the aspect in different enablement service stacks. The key is the name of the enablement service stack.", default={})
+
+class TwinAspectCreate(BaseModel):
+    catenax_id: UUID = Field(alias="catenaXId", description="The Catena-X ID / global ID of the digital twin to which the new aspect belongs.")
+    semantic_id: str = Field(alias="semanticId", description="The semantic ID of the new aspect determining the structure of the associated payload data.")
+    submodel_id: Optional[UUID] = Field(alias="submodelId", description="The optional ID of the submodel descriptor within the DTR shell descriptor for the associated twin. If not specified, a new UUID will be created automatically.") 
+    enablement_service_stack_name: str = Field(alias="enablementServiceStackName", description="The name of the enablement service stack where the twin aspect should be registered.")
+    payload: Dict[str, Any] = Field(description="The payload data of the new aspect. This is a JSON object that contains the actual data of the aspect. The structure of this object is determined by the semantic ID of the aspect.")
+
+class TwinRead(BaseModel):
+    """Represents a digital twin within the Digital Twin Registry."""
+
+    catenax_id: UUID = Field(alias="catenaXId", description="The Catena-X ID / global ID of the digital twin.")
+    dtr_aas_id: UUID = Field(alias="dtrAasId", description="The shell descriptor ID ('AAS ID') of the digital twin in the Digital Twin Registry.") 
+    created_date: datetime = Field(alias="createdDate", description="The date when the digital twin was created.")
+    modified_date: datetime = Field(alias="modifiedDate", description="The date when the digital twin was last modified.")
+
+class TwinCreateBase(BaseModel):
+    """Represents a digital twin to be created within the Digital Twin Registry."""
+
+    catenax_id: Optional[UUID] = Field(alias="catenaXId", description="Optionally the Catena-X ID / global ID of the digital twin to create. If not specified, a new UUID will be created automatically.", default=None)
+    dtr_aas_id: Optional[UUID] = Field(alias="dtrAasId", description="Optionally the shell descriptor ID ('AAS ID') of the digital twin in the Digital Twin Registry. If not specified, a new UUID will be created automatically.", default=None)
+
+class TwinDetailsRead(TwinRead):
+    additional_context: Optional[Dict[str, Any]] = Field(alias="additionalContext", description="Additional context information about the digital twin. This can include various metadata or properties associated with the twin. Intended for handling twins by third party apps.")
+    aspects: Optional[Dict[str, TwinAspectRead]] = Field(description="A map of aspect information for the digital twin. The key is the semantic ID of the aspect. The value is a TwinAspectRead object containing details about the aspect.", default={})
+
+class CatalogPartTwinCreate(CatalogPartCreate, TwinCreateBase):
+    pass
+
+class BatchTwinCreate(BatchCreate, TwinCreateBase):
+    pass
+
+class JISPartTwinCreate(JISPartCreate, TwinCreateBase):
+    pass
+
+class SerializedPartTwinCreate(SerializedPartCreate, TwinCreateBase):
+    pass


Reviews:
user: login: CDiezRodriguez
body: Awesome work @sm29105!
Just one small request: could you add the corresponding models to the `__init__.py` file?
Also, I changed the `UniqueConstraint` because the previous method raised an error on my local environment.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T15:35:15Z
user: login: CDiezRodriguez
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T08:53:32Z

Review comments:
diff_hunk: @@ -0,0 +1,23 @@
+#################################################################################
+# Eclipse Tractus-X - Industry Core Hub Backend
+#
+# Copyright (c) 2025 DRÄXLMAIER Group
+# (represented by Lisa Dräxlmaier GmbH)
+# Copyright (c) 2025 Contributors to the Eclipse Foundation
+#
+# See the NOTICE file(s) distributed with this work for additional
+# information regarding copyright ownership.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Apache License, Version 2.0 which is available at
+# https://www.apache.org/licenses/LICENSE-2.0.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied. See the
+# License for the specific language govern in permissions and limitations
+# under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#################################################################################
path: ichub-backend/models/metadata_database/__init__.py
user: login: CDiezRodriguez
body: ```suggestion
#################################################################################

from .models import (
    LegalEntity,
    BusinessPartner,
    Twin,
    CatalogPart,
    PartnerCatalogPart,
    SerializedPart,
    JISPart,
    Batch,
    BatchBusinessPartner,
    DataExchangeAgreement,
    DataExchangeContract,
    EnablementServiceStack,
    TwinAspect,
    TwinAspectRegistration,
    TwinExchange,
    TwinRegistration,
)

```
created_at: 2025-04-28T14:08:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,254 @@
+#################################################################################
+# Eclipse Tractus-X - Industry Core Hub Backend
+#
+# Copyright (c) 2025 DRÄXLMAIER Group
+# (represented by Lisa Dräxlmaier GmbH)
+# Copyright (c) 2025 Contributors to the Eclipse Foundation
+#
+# See the NOTICE file(s) distributed with this work for additional
+# information regarding copyright ownership.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Apache License, Version 2.0 which is available at
+# https://www.apache.org/licenses/LICENSE-2.0.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied. See the
+# License for the specific language govern in permissions and limitations
+# under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#################################################################################
+
+from typing import Any, Dict, List, Optional
+from uuid import UUID
+from datetime import datetime
+from sqlmodel import Field, SQLModel, Relationship
+from sqlalchemy import Column, JSON
+
+class LegalEntity(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    bpnl: str = Field(index=True, unique=True, description="The BPNL of the legal entity.")
+
+    # Relationships
+    catalog_parts: List["CatalogPart"] = Relationship(back_populates="legal_entity")
+    enablement_service_stacks: List["EnablementServiceStack"] = Relationship(back_populates="legal_entity")
+
+    __tablename__ = "legal_entity"
+
+
+class BusinessPartner(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    name: str = Field(index=True, unique=True, description="The name of the business partner.")
+    bpnl: str = Field(index=True, unique=True, description="The BPNL of the business partner.")
+
+    # Relationships
+    partner_catalog_parts: List["PartnerCatalogPart"] = Relationship(back_populates="business_partner")
+    data_exchange_agreements: List["DataExchangeAgreement"] = Relationship(back_populates="business_partner")
+
+    __tablename__ = "business_partner"
+
+
+class Twin(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    global_id: UUID = Field(default_factory=UUID, unique=True, description="The global ID (aka. Catena-X ID) of the twin.")
+    aas_id: UUID = Field(default_factory=UUID, unique=True, description="The AAS ID of the twin.")
+    created_date: datetime = Field(default_factory=datetime.utcnow, description="The creation date of the twin.")
+    modified_date: datetime = Field(default_factory=datetime.utcnow, description="The last modification date of the twin.")
+    asset_class: Optional[str] = Field(default=None, description="The asset class of the twin.")
+    additional_context: Optional[str] = Field(default=None, description="Additional context for the twin.")
+
+    # Relationships
+    catalog_parts: List["CatalogPart"] = Relationship(back_populates="twin")
+    serialized_parts: List["SerializedPart"] = Relationship(back_populates="twin")
+    jis_parts: List["JISPart"] = Relationship(back_populates="twin")
+    twin_aspects: List["TwinAspect"] = Relationship(back_populates="twin")
+    twin_exchanges: List["TwinExchange"] = Relationship(back_populates="twin")
+    twin_registrations: List["TwinRegistration"] = Relationship(back_populates="twin")
+
+    __tablename__ = "twin"
+
+
+class CatalogPart(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    manufacturer_part_id: str = Field(index=True, unique=True, description="The manufacturer part ID.")
+    legal_entity_id: int = Field(foreign_key="legal_entity.id", description="The ID of the associated legal entity.")
+    twin_id: Optional[int] = Field(foreign_key="twin.id", description="The ID of the associated twin.")
+    category: Optional[str] = Field(default=None, description="The category of the catalog part.")
+    bpns: Optional[str] = Field(default=None, description="The optional site information (BPNS) of the catalog part.")
+
+    # Relationships
+    legal_entity: LegalEntity = Relationship(back_populates="catalog_parts")
+    twin: Optional[Twin] = Relationship(back_populates="catalog_parts")
+    partner_catalog_parts: List["PartnerCatalogPart"] = Relationship(back_populates="catalog_part")
+    batches: List["Batch"] = Relationship(back_populates="catalog_part")
+
+    __tablename__ = "catalog_part"
+
+
+class PartnerCatalogPart(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    business_partner_id: int = Field(foreign_key="business_partner.id", description="The ID of the associated business partner.")
+    catalog_part_id: int = Field(foreign_key="catalog_part.id", description="The ID of the associated catalog part.")
+    customer_part_id: str = Field(default="", description="The customer part ID.")
+
+    # Relationships
+    business_partner: BusinessPartner = Relationship(back_populates="partner_catalog_parts")
+    catalog_part: CatalogPart = Relationship(back_populates="partner_catalog_parts")
+    serialized_parts: List["SerializedPart"] = Relationship(back_populates="partner_catalog_part")
+    jis_parts: List["JISPart"] = Relationship(back_populates="partner_catalog_part")
+
+    # Composite Unique Constraint
+    __table_args__ = (
+        {"unique": ("business_partner_id", "catalog_part_id")},
path: ichub-backend/models/metadata_database/models.py
user: login: CDiezRodriguez
body: ```suggestion
        UniqueConstraint("business_partner_id", "catalog_part_id", name="uq_partner_catalog_part"),
```
created_at: 2025-04-28T14:59:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,254 @@
+#################################################################################
+# Eclipse Tractus-X - Industry Core Hub Backend
+#
+# Copyright (c) 2025 DRÄXLMAIER Group
+# (represented by Lisa Dräxlmaier GmbH)
+# Copyright (c) 2025 Contributors to the Eclipse Foundation
+#
+# See the NOTICE file(s) distributed with this work for additional
+# information regarding copyright ownership.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Apache License, Version 2.0 which is available at
+# https://www.apache.org/licenses/LICENSE-2.0.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied. See the
+# License for the specific language govern in permissions and limitations
+# under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#################################################################################
+
+from typing import Any, Dict, List, Optional
+from uuid import UUID
+from datetime import datetime
+from sqlmodel import Field, SQLModel, Relationship
+from sqlalchemy import Column, JSON
+
+class LegalEntity(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    bpnl: str = Field(index=True, unique=True, description="The BPNL of the legal entity.")
+
+    # Relationships
+    catalog_parts: List["CatalogPart"] = Relationship(back_populates="legal_entity")
+    enablement_service_stacks: List["EnablementServiceStack"] = Relationship(back_populates="legal_entity")
+
+    __tablename__ = "legal_entity"
+
+
+class BusinessPartner(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    name: str = Field(index=True, unique=True, description="The name of the business partner.")
+    bpnl: str = Field(index=True, unique=True, description="The BPNL of the business partner.")
+
+    # Relationships
+    partner_catalog_parts: List["PartnerCatalogPart"] = Relationship(back_populates="business_partner")
+    data_exchange_agreements: List["DataExchangeAgreement"] = Relationship(back_populates="business_partner")
+
+    __tablename__ = "business_partner"
+
+
+class Twin(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    global_id: UUID = Field(default_factory=UUID, unique=True, description="The global ID (aka. Catena-X ID) of the twin.")
+    aas_id: UUID = Field(default_factory=UUID, unique=True, description="The AAS ID of the twin.")
+    created_date: datetime = Field(default_factory=datetime.utcnow, description="The creation date of the twin.")
+    modified_date: datetime = Field(default_factory=datetime.utcnow, description="The last modification date of the twin.")
+    asset_class: Optional[str] = Field(default=None, description="The asset class of the twin.")
+    additional_context: Optional[str] = Field(default=None, description="Additional context for the twin.")
+
+    # Relationships
+    catalog_parts: List["CatalogPart"] = Relationship(back_populates="twin")
+    serialized_parts: List["SerializedPart"] = Relationship(back_populates="twin")
+    jis_parts: List["JISPart"] = Relationship(back_populates="twin")
+    twin_aspects: List["TwinAspect"] = Relationship(back_populates="twin")
+    twin_exchanges: List["TwinExchange"] = Relationship(back_populates="twin")
+    twin_registrations: List["TwinRegistration"] = Relationship(back_populates="twin")
+
+    __tablename__ = "twin"
+
+
+class CatalogPart(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    manufacturer_part_id: str = Field(index=True, unique=True, description="The manufacturer part ID.")
+    legal_entity_id: int = Field(foreign_key="legal_entity.id", description="The ID of the associated legal entity.")
+    twin_id: Optional[int] = Field(foreign_key="twin.id", description="The ID of the associated twin.")
+    category: Optional[str] = Field(default=None, description="The category of the catalog part.")
+    bpns: Optional[str] = Field(default=None, description="The optional site information (BPNS) of the catalog part.")
+
+    # Relationships
+    legal_entity: LegalEntity = Relationship(back_populates="catalog_parts")
+    twin: Optional[Twin] = Relationship(back_populates="catalog_parts")
+    partner_catalog_parts: List["PartnerCatalogPart"] = Relationship(back_populates="catalog_part")
+    batches: List["Batch"] = Relationship(back_populates="catalog_part")
+
+    __tablename__ = "catalog_part"
+
+
+class PartnerCatalogPart(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    business_partner_id: int = Field(foreign_key="business_partner.id", description="The ID of the associated business partner.")
+    catalog_part_id: int = Field(foreign_key="catalog_part.id", description="The ID of the associated catalog part.")
+    customer_part_id: str = Field(default="", description="The customer part ID.")
+
+    # Relationships
+    business_partner: BusinessPartner = Relationship(back_populates="partner_catalog_parts")
+    catalog_part: CatalogPart = Relationship(back_populates="partner_catalog_parts")
+    serialized_parts: List["SerializedPart"] = Relationship(back_populates="partner_catalog_part")
+    jis_parts: List["JISPart"] = Relationship(back_populates="partner_catalog_part")
+
+    # Composite Unique Constraint
+    __table_args__ = (
+        {"unique": ("business_partner_id", "catalog_part_id")},
+    )
+
+    __tablename__ = "partner_catalog_part"
+
+
+class SerializedPart(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    partner_catalog_part_id: int = Field(foreign_key="partner_catalog_part.id", description="The ID of the associated partner catalog part.")
+    part_instance_id: str = Field(index=True, unique=True, description="The part instance ID.")
+    van: Optional[str] = Field(default=None, description="The optional VAN (Vehicle Assembly Number).")
+    twin_id: int = Field(foreign_key="twin.id", description="The ID of the associated twin.")
+
+    # Relationships
+    partner_catalog_part: PartnerCatalogPart = Relationship(back_populates="serialized_parts")
+    twin: Twin = Relationship(back_populates="serialized_parts")
+
+    __tablename__ = "serialized_part"
+
+
+class JISPart(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    partner_catalog_part_id: int = Field(foreign_key="partner_catalog_part.id", description="The ID of the associated partner catalog part.")
+    jis_number: str = Field(index=True, unique=True, description="The JIS number.")
+    parent_order_number: Optional[str] = Field(default=None, description="The parent order number.")
+    jis_call_date: Optional[datetime] = Field(default=None, description="The JIS call date.")
+    twin_id: int = Field(foreign_key="twin.id", description="The ID of the associated twin.")
+
+    # Relationships
+    partner_catalog_part: PartnerCatalogPart = Relationship(back_populates="jis_parts")
+    twin: Twin = Relationship(back_populates="jis_parts")
+
+    __tablename__ = "jis_part"
+
+class Batch(SQLModel, table=True):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    batch_id: str = Field(index=True, unique=True, description="The batch ID.")
+    catalog_part_id: int = Field(foreign_key="catalog_part.id", description="The ID of the associated catalog part.")
+
+    # Relationships
+    catalog_part: CatalogPart = Relationship(back_populates="batches")
+    batch_business_partners: List["BatchBusinessPartner"] = Relationship(back_populates="batch")
+
+    # Composite Unique Constraint
+    __table_args__ = (
+        {"unique": ("catalog_part_id", "batch_id")},
path: ichub-backend/models/metadata_database/models.py
user: login: CDiezRodriguez
body: ```suggestion
        UniqueConstraint("catalog_part_id", "batch_id", name="uq_catalog_batch_id"),
```
created_at: 2025-04-28T15:00:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,69 @@
+#################################################################################
+# Eclipse Tractus-X - Industry Core Hub Backend
+#
+# Copyright (c) 2025 DRÄXLMAIER Group
+# (represented by Lisa Dräxlmaier GmbH)
+# Copyright (c) 2025 Contributors to the Eclipse Foundation
+#
+# See the NOTICE file(s) distributed with this work for additional
+# information regarding copyright ownership.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Apache License, Version 2.0 which is available at
+# https://www.apache.org/licenses/LICENSE-2.0.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied. See the
+# License for the specific language govern in permissions and limitations
+# under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#################################################################################
+
+from typing import List
+from pydantic import BaseModel, Field, field_validator
+
+def validate_bpnl(bpnl: str, field_name: str) -> str:
+    """Validates the BPNL format."""
+    if len(bpnl) != 16 or not bpnl.isalnum():
+        raise ValueError(f"{field_name} must be exactly 16 alphanumeric characters.")
+    if bpnl[0:3] != "BPNL":
+        raise ValueError(f"{field_name} must start with 'BPNL'.")
+    
+    return bpnl
+
+class BusinessPartner(BaseModel):
+    """Represents a partner with a validated name field."""
+    
+    name: str = Field(description="The unique name of the business partner.")
+    bpnl: str = Field(description="The Catena-X Business Partner Number (BPNL) of the business partner.")
+
+    @field_validator("bpnl")
+    def validate_bpnl(self, bpnl: str) -> str:
path: ichub-backend/models/services/partner_management.py
user: login: CDiezRodriguez
body: ```suggestion
    @staticmethod
    def validate_bpnl(bpnl: str) -> str:
```
created_at: 2025-04-28T15:03:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,23 @@
+#################################################################################
+# Eclipse Tractus-X - Industry Core Hub Backend
+#
+# Copyright (c) 2025 DRÄXLMAIER Group
+# (represented by Lisa Dräxlmaier GmbH)
+# Copyright (c) 2025 Contributors to the Eclipse Foundation
+#
+# See the NOTICE file(s) distributed with this work for additional
+# information regarding copyright ownership.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Apache License, Version 2.0 which is available at
+# https://www.apache.org/licenses/LICENSE-2.0.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+# either express or implied. See the
+# License for the specific language govern in permissions and limitations
+# under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#################################################################################
path: ichub-backend/models/services/__init__.py
user: login: CDiezRodriguez
body: ```suggestion
#################################################################################

from .part_management import (
    CatalogPartRead,
    CatalogPartCreate,
    CatalogPartDelete,
    CatalogPartQuery,
    PartnerCatalogPartCreate,
    PartnerCatalogPartDelete,
    BatchRead,
    BatchCreate,
    BatchDelete,
    BatchQuery,
    SerializedPartRead,
    SerializedPartCreate,
    SerializedPartDelete,
    SerializedPartQuery,
    JISPartRead,
    JISPartCreate,
    JISPartDelete,
    JISPartQuery
)

from .partner_management import (
    BusinessPartner,
    DataExchangeContractRead,
    DataExchangeContractCreate,
    DataExchangeAgreementCreate,
    DataExchangeAgreementRead
)

from .twin_management import (
    TwinAspectRegistrationStatus,
    TwinsAspectRegistrationMode,
    TwinAspectRegistration,
    TwinAspectRead,
    TwinAspectCreate,
    TwinRead,
    TwinCreateBase,
    TwinDetailsRead,
    CatalogPartTwinCreate,
    BatchTwinCreate,
    JISPartTwinCreate,
    SerializedPartTwinCreate
)

```
created_at: 2025-04-28T15:13:02Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-04-28T20:08:08Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=eclipse-tractusx_industry-core-hub&pullRequest=156) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [7 New issues](https://sonarcloud.io/project/issues?id=eclipse-tractusx_industry-core-hub&pullRequest=156&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=eclipse-tractusx_industry-core-hub&pullRequest=156&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=eclipse-tractusx_industry-core-hub&pullRequest=156&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=eclipse-tractusx_industry-core-hub&pullRequest=156&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=eclipse-tractusx_industry-core-hub&pullRequest=156&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=eclipse-tractusx_industry-core-hub&pullRequest=156)


author_association: NONE