Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion csfunctions/events/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

from pydantic import Field

from .custom_operations import (
CustomOperationDocumentData,
CustomOperationDocumentEvent,
CustomOperationPartData,
CustomOperationPartEvent,
)
from .dialog_data import DocumentReleasedDialogData, PartReleasedDialogData
from .document_create_check import DocumentCreateCheckData, DocumentCreateCheckEvent
from .document_field_calculation import DocumentFieldCalculationData, DocumentFieldCalculationEvent
Expand Down Expand Up @@ -41,7 +47,9 @@
| DocumentCreateCheckEvent
| DocumentModifyCheckEvent
| PartCreateCheckEvent
| PartModifyCheckEvent,
| PartModifyCheckEvent
| CustomOperationDocumentEvent
| CustomOperationPartEvent,
Field(discriminator="name"),
]
EventData = (
Expand All @@ -62,6 +70,8 @@
| DocumentModifyCheckData
| PartCreateCheckData
| PartModifyCheckData
| CustomOperationDocumentData
| CustomOperationPartData
)

__all__ = [
Expand Down Expand Up @@ -99,4 +109,8 @@
"PartCreateCheckEvent",
"PartModifyCheckData",
"PartModifyCheckEvent",
"CustomOperationDocumentData",
"CustomOperationDocumentEvent",
"CustomOperationPartData",
"CustomOperationPartEvent",
]
2 changes: 2 additions & 0 deletions csfunctions/events/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ class EventNames(str, Enum):
PART_MODIFY_CHECK = "part_modify_check"
ENGINEERING_CHANGE_STATUS_CHANGED = "engineering_change_status_changed"
ENGINEERING_CHANGE_STATUS_CHANGE_CHECK = "engineering_change_status_change_check"
CUSTOM_OPERATION_DOCUMENT = "custom_operation_document"
CUSTOM_OPERATION_PART = "custom_operation_part"


class BaseEvent(BaseModel):
Expand Down
39 changes: 39 additions & 0 deletions csfunctions/events/custom_operations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from typing import Literal

from pydantic import BaseModel, Field

from csfunctions.objects import Document, Part

from .base import BaseEvent, EventNames


# ----------- DOCUMENTS -----------
class CustomOperationDocumentData(BaseModel):
documents: list[Document] = Field(..., description="List of documents that the custom operation was called on")
parts: list[Part] = Field(..., description="List of parts that belong to the documents")


class CustomOperationDocumentEvent(BaseEvent):
"""
Event triggered when a custom operation is called on a document.
"""

name: Literal[EventNames.CUSTOM_OPERATION_DOCUMENT] = EventNames.CUSTOM_OPERATION_DOCUMENT
data: CustomOperationDocumentData


# ----------- PARTS -----------


class CustomOperationPartData(BaseModel):
parts: list[Part] = Field(..., description="List of parts that the custom operation was called on")
documents: list[Document] = Field(..., description="List of documents that belong to the parts")


class CustomOperationPartEvent(BaseEvent):
"""
Event triggered when a custom operation is called on a part.
"""

name: Literal[EventNames.CUSTOM_OPERATION_PART] = EventNames.CUSTOM_OPERATION_PART
data: CustomOperationPartData
116 changes: 116 additions & 0 deletions docs/examples/basic_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Basic Report

This example shows how you can use custom operations to generate a basic report on a document and attach that report to the document.

The example uses [python-docx](https://python-docx.readthedocs.io/en/latest/) to generate a Word file.
To install the library in your Function, you need to add it to the `requirements.txt`:

```requirements.txt
contactsoftware-functions
python-docx
```

```python
import os
import tempfile
from datetime import datetime

import requests
from docx import Document as DocxDocument

from csfunctions import MetaData, Service
from csfunctions.events import CustomOperationDocumentEvent
from csfunctions.objects import Document


def simple_report(metadata: MetaData, event: CustomOperationDocumentEvent, service: Service):
"""
Generates a simple report for each document the custom operation is called on.
The report contains basic information about the document and is saved as a new file
named "myreport.docx" within the document.
"""

for document in event.data.documents:
# generate a report for each document
report = _create_report(document, metadata)

temp_file_path = None
try:
# we need to use a tempfile, because the rest of the filesystem is read-only
with tempfile.NamedTemporaryFile(suffix=".docx", delete=False) as tmp:
temp_file_path = tmp.name
report.save(temp_file_path)

# check if the document already has a report file, so we can overwrite it
file_name = "myreport.docx"
existing_file = next((file for file in document.files if file.cdbf_name == file_name), None)

with open(temp_file_path, "rb") as file_stream:
if existing_file:
# overwrite the existing report file
# we set check_access to false to allow attaching reports to released documents
service.file_upload.upload_file_content(
file_object_id=existing_file.cdb_object_id, stream=file_stream, check_access=False
)
else:
# create a new one
# we set check_access to false to allow attaching reports to released documents
service.file_upload.upload_new_file(
parent_object_id=document.cdb_object_id, # type: ignore
filename=file_name,
stream=file_stream,
check_access=False,
)
finally:
if temp_file_path:
# Clean up temp file
os.unlink(temp_file_path)


def _fetch_person_name(persno: str, metadata: MetaData) -> str | None:
"""Fetches the name of a person given their personnel number via GraphQL."""
graphql_url = str(metadata.db_service_url).rstrip("/") + "/graphql/v1"
headers = {"Authorization": f"Bearer {metadata.service_token}"}

query = f"""
{{
persons(personalnummer: \"{persno}\", max_rows: 1) {{
name
}}
}}
"""
response = requests.post(
graphql_url,
headers=headers,
json={"query": query},
)
response.raise_for_status()
data = response.json()
persons = data["data"]["persons"]
if persons:
return persons[0]["name"]
return None


def _create_report(document: Document, metadata: MetaData) -> DocxDocument:
"""Creates a simple Word report for the given document."""
doc = DocxDocument()

doc.add_heading("Simple Report", 0)

report_time_string = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
doc.add_paragraph(f"Report generated on: {report_time_string}")

# add some basic information about the document
doc.add_heading("Document Information", level=1)
doc.add_paragraph(f"Document ID: {document.z_nummer}@{document.z_index}")
doc.add_paragraph(f"Title: {document.titel}")
doc.add_paragraph(f"Created On: {document.cdb_cdate}")

# Fetch the name of the person who created the document via GraphQL
person_name = _fetch_person_name(document.cdb_cpersno, metadata)
doc.add_paragraph(f"Created By: {person_name or document.cdb_cpersno}")

return doc

```
41 changes: 41 additions & 0 deletions docs/reference/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,44 @@ This event is fired when a user tries to modify an engineering change's status.
| target_status | int | The status the engineering change will be set to |
| documents | list[[Document](objects.md#document)] | List of documents attached to the engineering change |
| parts | list[[Part](objects.md#part)] | List of parts attached to the engineering change |


## CustomOperationDocumentEvent
`csfunctions.events.CustomOperationDocumentEvent`


This event is triggered when a custom operation is called on one or more documents.

**Supported actions:**

- [StartWorkflowAction](actions.md#startworkflowaction)
- [AbortAndShowErrorAction](actions.md#abortandshowerroraction)

**CustomOperationDocumentEvent.name:** custom_operation_document

**CustomOperationDocumentEvent.data:**

| Attribute | Type | Description |
| --------- | ------------------------------------- | ---------------------------------------------------------- |
| documents | list[[Document](objects.md#document)] | List of documents that the custom operation was called on. |
| parts | list[[Part](objects.md#part)] | List of parts that belong to the documents. |

## CustomOperationPartEvent
`csfunctions.events.CustomOperationPartEvent`


This event is triggered when a custom operation is called on one or more parts.

**Supported actions:**

- [StartWorkflowAction](actions.md#startworkflowaction)
- [AbortAndShowErrorAction](actions.md#abortandshowerroraction)

**CustomOperationPartEvent.name:** custom_operation_part

**CustomOperationPartEvent.data:**

| Attribute | Type | Description |
| --------- | ------------------------------------- | ------------------------------------------------------ |
| parts | list[[Part](objects.md#part)] | List of parts that the custom operation was called on. |
| documents | list[[Document](objects.md#document)] | List of documents that belong to the parts. |
110 changes: 110 additions & 0 deletions json_schemas/request.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,108 @@
"title": "Briefcase",
"type": "object"
},
"CustomOperationDocumentData": {
"properties": {
"documents": {
"description": "List of documents that the custom operation was called on",
"items": {
"$ref": "#/$defs/Document"
},
"title": "Documents",
"type": "array"
},
"parts": {
"description": "List of parts that belong to the documents",
"items": {
"$ref": "#/$defs/Part"
},
"title": "Parts",
"type": "array"
}
},
"required": [
"documents",
"parts"
],
"title": "CustomOperationDocumentData",
"type": "object"
},
"CustomOperationDocumentEvent": {
"description": "Event triggered when a custom operation is called on a document.",
"properties": {
"name": {
"const": "custom_operation_document",
"default": "custom_operation_document",
"title": "Name",
"type": "string"
},
"event_id": {
"description": "unique identifier",
"title": "Event Id",
"type": "string"
},
"data": {
"$ref": "#/$defs/CustomOperationDocumentData"
}
},
"required": [
"event_id",
"data"
],
"title": "CustomOperationDocumentEvent",
"type": "object"
},
"CustomOperationPartData": {
"properties": {
"parts": {
"description": "List of parts that the custom operation was called on",
"items": {
"$ref": "#/$defs/Part"
},
"title": "Parts",
"type": "array"
},
"documents": {
"description": "List of documents that belong to the parts",
"items": {
"$ref": "#/$defs/Document"
},
"title": "Documents",
"type": "array"
}
},
"required": [
"parts",
"documents"
],
"title": "CustomOperationPartData",
"type": "object"
},
"CustomOperationPartEvent": {
"description": "Event triggered when a custom operation is called on a part.",
"properties": {
"name": {
"const": "custom_operation_part",
"default": "custom_operation_part",
"title": "Name",
"type": "string"
},
"event_id": {
"description": "unique identifier",
"title": "Event Id",
"type": "string"
},
"data": {
"$ref": "#/$defs/CustomOperationPartData"
}
},
"required": [
"event_id",
"data"
],
"title": "CustomOperationPartEvent",
"type": "object"
},
"Document": {
"description": "Normal Document that doesn't contain a CAD-Model.",
"properties": {
Expand Down Expand Up @@ -7957,6 +8059,8 @@
"event": {
"discriminator": {
"mapping": {
"custom_operation_document": "#/$defs/CustomOperationDocumentEvent",
"custom_operation_part": "#/$defs/CustomOperationPartEvent",
"document_create_check": "#/$defs/DocumentCreateCheckEvent",
"document_field_calculation": "#/$defs/DocumentFieldCalculationEvent",
"document_modify_check": "#/$defs/DocumentModifyCheckEvent",
Expand Down Expand Up @@ -8028,6 +8132,12 @@
},
{
"$ref": "#/$defs/PartModifyCheckEvent"
},
{
"$ref": "#/$defs/CustomOperationDocumentEvent"
},
{
"$ref": "#/$defs/CustomOperationPartEvent"
}
],
"title": "Event"
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ nav:
- examples/enforce_field_rules.md
- examples/field_calculation.md
- examples/workflows.md
- examples/basic_report.md
- Reference:
- reference/events.md
- reference/objects.md
Expand Down
Loading