"""Synchronous GLPI ``/Assistance/Ticket/{id}/Timeline/Document`` mixin.
The mixin exposes list, fetch, link, and unlink helpers for the timeline
document endpoint that links existing GLPI documents to a ticket.
Notes
-----
The live GLPI v2 server returns each entry of the list endpoint wrapped
in a ``{"type": "Document_Item", "item": {...}}`` envelope, even though
the OpenAPI contract documents a flat array of ``Document_Item``. The
``item`` value is a full ``Document`` record (matching :class:`GetDocument`),
not a ``Document_Item`` link record — real behaviour wins over the contract.
:func:`list_ticket_timeline_documents` unwraps the envelope through the shared
``TransportMixin._resource_list`` helper and deserialises each inner object
as :class:`GetDocument`.
"""
from __future__ import annotations
from glpi_python_client.clients.commons._constants import (
TICKET_ENDPOINT,
TIMELINE_DOCUMENT_SUFFIX,
GlpiId,
)
from glpi_python_client.clients.commons._transport import TransportMixin
from glpi_python_client.models.api_schema.assistance.timeline._document import (
DeleteTimelineDocument,
PatchTimelineDocument,
PostTimelineDocument,
)
from glpi_python_client.models.api_schema.management._document import GetDocument
class TimelineDocumentMixin(TransportMixin):
"""Synchronous CRUD helpers for the ticket document timeline endpoint."""
def list_ticket_timeline_documents(self, ticket_id: GlpiId) -> list[GetDocument]:
"""List all documents linked to one ticket timeline.
Parameters
----------
ticket_id : GlpiId
Numeric identifier of the parent ticket.
Returns
-------
list[GetDocument]
Document records returned by the GLPI server. The live API
wraps each entry in a ``{"type": "Document_Item", "item": {...}}``
envelope whose ``item`` value is a full ``Document`` record; the
envelope is unwrapped automatically.
"""
return self._resource_list(
f"{TICKET_ENDPOINT}/{ticket_id}/{TIMELINE_DOCUMENT_SUFFIX}",
GetDocument,
failure_message=(
f"Failed to list timeline documents for ticket {ticket_id}"
),
unwrap_envelope=True,
)
def get_ticket_timeline_document(
self, ticket_id: GlpiId, document_link_id: GlpiId
) -> GetDocument:
"""Fetch one document linked to the ticket timeline by its document ID.
Parameters
----------
ticket_id : GlpiId
Numeric identifier of the parent ticket.
document_link_id : GlpiId
Numeric identifier of the linked document to retrieve.
Returns
-------
GetDocument
Validated document payload.
Raises
------
ValueError
If the GLPI server returns a non-success HTTP status.
"""
return self._resource_get(
f"{TICKET_ENDPOINT}/{ticket_id}/"
f"{TIMELINE_DOCUMENT_SUFFIX}/{document_link_id}",
GetDocument,
failure_message=(
f"Failed to get timeline document {document_link_id} on "
f"ticket {ticket_id}"
),
)
def link_ticket_timeline_document(
self, ticket_id: GlpiId, document_link: PostTimelineDocument
) -> int:
"""Link an existing GLPI document to one ticket timeline.
Parameters
----------
ticket_id : GlpiId
Numeric identifier of the parent ticket.
document_link : PostTimelineDocument
Request body describing the link (typically the timeline
position; document and ticket identifiers are inferred from
the URL).
Returns
-------
int
Identifier assigned by the GLPI server to the new link.
Raises
------
ValueError
If the create response is missing ``id`` or returns a
non-success HTTP status.
"""
return self._resource_create(
f"{TICKET_ENDPOINT}/{ticket_id}/{TIMELINE_DOCUMENT_SUFFIX}",
document_link,
failure_message=(f"Failed to link timeline document on ticket {ticket_id}"),
missing_message=(
"GLPI timeline document link response did not include an ID"
),
log_message_factory=(
lambda new_id: (
f"GLPI API linked timeline document {new_id} on ticket {ticket_id}"
)
),
)
def update_ticket_timeline_document(
self,
ticket_id: GlpiId,
document_link_id: GlpiId,
document_link: PatchTimelineDocument,
) -> None:
"""Update one timeline document link with a partial body.
Parameters
----------
ticket_id : GlpiId
Numeric identifier of the parent ticket.
document_link_id : GlpiId
Numeric identifier of the timeline document link to update.
document_link : PatchTimelineDocument
Partial request body.
Returns
-------
None
Raises
------
ValueError
If the GLPI server returns a non-success HTTP status.
"""
self._resource_update(
f"{TICKET_ENDPOINT}/{ticket_id}/"
f"{TIMELINE_DOCUMENT_SUFFIX}/{document_link_id}",
document_link,
failure_message=(
f"Failed to update timeline document {document_link_id} on "
f"ticket {ticket_id}"
),
log_message=(
f"GLPI API updated timeline document {document_link_id} on "
f"ticket {ticket_id}"
),
)
def unlink_ticket_timeline_document(
self,
ticket_id: GlpiId,
document_link_id: GlpiId,
*,
force: bool | None = None,
) -> None:
"""Unlink one timeline document from a ticket.
Parameters
----------
ticket_id : GlpiId
Numeric identifier of the parent ticket.
document_link_id : GlpiId
Numeric identifier of the timeline document link to remove.
force : bool | None, optional
When ``True`` the link is permanently deleted instead of
being moved to the trash.
Returns
-------
None
Raises
------
ValueError
If the GLPI server returns a non-success HTTP status.
"""
self._resource_delete(
f"{TICKET_ENDPOINT}/{ticket_id}/"
f"{TIMELINE_DOCUMENT_SUFFIX}/{document_link_id}",
failure_message=(
f"Failed to unlink timeline document {document_link_id} on "
f"ticket {ticket_id}"
),
log_message=(
f"GLPI API unlinked timeline document {document_link_id} on "
f"ticket {ticket_id}"
),
force=force,
delete_model_cls=DeleteTimelineDocument,
)
__all__ = ["TimelineDocumentMixin"]