Skip to content

Commit 5098cce

Browse files
author
Jens Kürten
committed
improve docs
1 parent 85c703b commit 5098cce

16 files changed

+869
-65
lines changed

docs/assets/connect_function.png

13.5 KB
Loading

docs/assets/create_codespace.png

21.8 KB
Loading
17.7 KB
Loading

docs/assets/portal-user-menu.png

9.95 KB
Loading

docs/assets/private_repo.png

17.7 KB
Loading

docs/assets/use_template.png

11.7 KB
Loading

docs/examples/enforce_field_rules.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
Functions can be used to validate user input and thus ensure that fields on e.g. parts or documents are filled out correctly.
2+
3+
4+
### Required field based on Part category
5+
This example shows how you can enforce parts of category *"Single Part"* to have a material assigned to them.
6+
7+
The example Function can be connected to the [PartCreateCheckEvent](../reference/events.md#partcreatecheckevent) and [PartModifyCheckEvent](../reference/events.md#partmodifycheckevent) and will return an [AbortAndShowErrorAction](../reference/actions.md#abortandshowerroraction) to abort the creation or modification of the part if the condition is not met.
8+
9+
```python
10+
from csfunctions import MetaData, Service
11+
from csfunctions.actions import AbortAndShowErrorAction
12+
from csfunctions.events import (
13+
PartCreateCheckEvent,
14+
PartModifyCheckEvent,
15+
)
16+
17+
18+
def single_part_needs_material(
19+
metadata: MetaData,
20+
event: PartCreateCheckEvent | PartModifyCheckEvent,
21+
service: Service,
22+
):
23+
"""
24+
If a part of category ' Single Part' is created, a material has to be assigned.
25+
This should be checked when the part is created or modified.
26+
"""
27+
28+
for part in event.data.parts:
29+
# the event contains a list of parts that are about to be created
30+
if part.t_kategorie_name_en == "Single Part" and not part.material_object_id:
31+
return AbortAndShowErrorAction(
32+
message="A material has to be assigned to a part of category 'Single Part'."
33+
)
34+
35+
```
36+
37+
### Require parts to be classified before release
38+
39+
Classification is a powerful tool for organizing your parts, however the best tool only works if users use it.
40+
With this example Function you can require parts to be classified before they can be released.
41+
42+
This Function should be connected to the [PartReleaseCheckEvent](../reference/events.md#partreleasecheckevent) and will return an [AbortAndShowErrorAction](../reference/actions.md#abortandshowerroraction) to prevent the release, if classification data is missing.
43+
44+
The example code shows you how to fetch classification data for parts from the [CIM Database Cloud GraphQL API](https://saas-docs.contact-cloud.com/latest-en/admin/admin-contact_cloud/saas_admin/webhooks_graphql){:target="_blank"}. The Function then checks if any classification data is present, however you can easily expand this to check for specific classes.
45+
46+
```python
47+
from csfunctions import MetaData, Service
48+
from csfunctions.actions import AbortAndShowErrorAction
49+
from csfunctions.events import (
50+
51+
PartReleaseCheckEvent,
52+
)
53+
import requests
54+
55+
56+
def fetch_part_classification_property_codes(cdb_object_id: str, metadata: MetaData) -> list[str]:
57+
"""
58+
Returns a list of classification property codes for a given object ID.
59+
"""
60+
61+
graphql_url = str(metadata.db_service_url).rstrip("/") + "/graphql/v1"
62+
query = f"""{{
63+
object_property_values(ref_object_id: "{cdb_object_id}") {{
64+
property_code
65+
}}
66+
}}
67+
"""
68+
response = requests.post(
69+
graphql_url,
70+
headers={"Authorization": f"Bearer {metadata.service_token}"},
71+
json={"query": query},
72+
)
73+
response.raise_for_status()
74+
data = response.json()
75+
return [
76+
item["property_code"]
77+
for item in data["data"]["object_property_values"]
78+
]
79+
80+
81+
def parts_need_classification(
82+
metadata: MetaData,
83+
event: PartReleaseCheckEvent ,
84+
service: Service,
85+
):
86+
"""
87+
Parts need to be classified before they can be released.
88+
"""
89+
90+
for part in event.data.parts:
91+
# the event contains a list of parts that are about to be released
92+
# for each part fetch the classification property codes and check if they are empty
93+
property_codes = fetch_part_classification_property_codes(part.cdb_object_id, metadata)
94+
if not property_codes:
95+
return AbortAndShowErrorAction(
96+
message=f"The part '{part.eng_benennung or part.benennung}' is missing classification data."
97+
)
98+
99+
```

docs/examples/field_calculation.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Field calculation
2+
3+
The data sheet editor in CIM Database Cloud already allows you to define some basic [field calculations](https://saas-docs.contact-cloud.com/2025.13.1-en/admin/admin-contact_cloud/saas_admin/app_setup_data_edit_field_calc){:target="_blank"} to fill out fields automatically.
4+
5+
However, the Python expressions available in the datasheet editor are limited. Functions allow for much more freedom in defining your field calculations, and allow you to do things like *"fetching external data"* or *"referencing other objects"*.
6+
7+
Field calculations with Functions utilize the "<Object\>FieldCalulationEvent", e.g. [PartFieldCalculationEvent](../reference/events.md#partfieldcalculationevent), which expect the response to contain a `DataResponse` with a dictionary containing the fields that should be updated.
8+
9+
```python
10+
return DataResponse(data={somefield="new value"})
11+
```
12+
13+
14+
## Custom part number for external parts
15+
16+
This example shows you the basics of calculating fields with Functions and how to use the `service` parameter to generate a fresh number.
17+
18+
The example Function checks if the part is an *"External"* part and generates a custom part number for it.
19+
20+
```python
21+
from csfunctions import DataResponse
22+
from csfunctions.events import PartFieldCalculationEvent
23+
from csfunctions.metadata import MetaData
24+
from csfunctions.service import Service
25+
26+
27+
def calculate_part_number(metadata: MetaData, event: PartFieldCalculationEvent, service: Service):
28+
"""
29+
Example Function.
30+
This function is triggered when a part fields should be calculated.
31+
For "External" parts we want to set the part number as "E-000123".
32+
All other parts should keep the standard part number.
33+
34+
"""
35+
if event.data.action != "create":
36+
# part number can only be set when the part is created
37+
return
38+
39+
# match "External Single Part" or "External Assembly"
40+
if event.data.part.t_kategorie_name_en.startswith("External"):
41+
42+
# generate a new number using the service
43+
new_number = service.generator.get_number("external_part_number")
44+
45+
# new_number is an integer, so we need to convert it to a string
46+
# and pad it with leading zeros to 6 digits
47+
new_part_number = str(new_number).zfill(6)
48+
49+
# then add the prefix "E-" to the number
50+
new_part_number = "E-" + new_part_number
51+
52+
# finally we return the new part number (teilenummer)
53+
return DataResponse(data={"teilenummer": new_part_number})
54+
```
55+
56+
!!! tip
57+
You can check `event.data.action` to decide for which operations (*copy*,*create*,*index* and *modify*) you want your field calculation to return a new value.
58+
Some fields, like part number (*teilenummer*) can only be set during the initial creation.
59+
60+
## Translate a field with DeepL
61+
62+
Inside Functions you can fetch data from external systems and fill out fields based on that data. This is something that would not be possible with the field calculations in the datasheet editor. You could use this for example to fetch new part numbers from an ERP system.
63+
64+
This example uses the API from [DeepL](https://www.deepl.com) to translate a field from German to English. The example uses the additional attributes 1 and 2 on parts, but you can of course change that to any attributes that fit your use-case.
65+
66+
```python
67+
import os
68+
from csfunctions import DataResponse
69+
from csfunctions.events import PartFieldCalculationEvent
70+
import requests
71+
72+
# set the DEEPL_API_KEY during deployment like this:
73+
# cfc env deploy <environment name> --environment-variables "DEEPL_API_KEY=<your API key>"
74+
DEEPL_API_KEY = os.getenv("DEEPL_API_KEY")
75+
76+
def part_field_calculation(metadata, event: PartFieldCalculationEvent, service):
77+
if event.data.action != "create":
78+
# only translate on creation
79+
return
80+
81+
if event.data.part.cssaas_frame_add_attr_1:
82+
translated_text = translate_text(
83+
event.data.part.cssaas_frame_add_attr_1, "EN", "DE")
84+
return DataResponse(data={"cssaas_frame_add_attr_2": translated_text})
85+
86+
def translate_text(text, target_lang, source_lang=None):
87+
url = "https://api-free.deepl.com/v2/translate"
88+
data = {
89+
"auth_key": DEEPL_API_KEY,
90+
"text": text,
91+
"target_lang": target_lang.upper()
92+
}
93+
if source_lang:
94+
data["source_lang"] = source_lang.upper()
95+
96+
response = requests.post(url, data=data)
97+
response.raise_for_status()
98+
return response.json()["translations"][0]["text"]
99+
100+
```
101+
102+
!!! note
103+
This example requires a DeepL API key to function. Adding secrets like API keys to your code is a bad practice, which is why the example fetches the API key from an environment variable.
104+
105+
You can set environment variables during deployment of your Function to the CIM Database Cloud Functions infrastructure like this:
106+
107+
`cfc env deploy <environment name> --environment-variables "DEEPL_API_KEY=<your API key>"`

docs/examples/index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Examples
2+
3+
This section contains example Functions that you can copy and adapt to your specific use case.
4+
Remember to [register the Function](../getting_started.md#register-the-function) in the `environment.yaml` after copying it into your code base.

docs/examples/workflows.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Working with workflows
2+
3+
Functions can interact with workflows. You can trigger Functions from within workflows using the [Trigger Webhook](https://saas-docs.contact-cloud.com/latest-en/admin/admin-contact_cloud/saas_admin/webhooks_workflow){:target="_blank"} task and you can even start new workflows by using the [StartWorkflowAction](../reference/actions.md#startworkflowaction)!
4+
5+
6+
## Start workflow on EC status change
7+
8+
This example shows you how to start a workflow template in response to an engineering change status change.
9+
10+
!!! note
11+
Starting workflows in response to engineering change status changes is already possible in CIM Database Cloud without the use of Functions. However Functions allow you to dynamically select different templates and fill out task parameters, based on the nature of the change.
12+
13+
This example uses a very simple template, containing just an *information task*. If an engineering change contains external parts, users with the *External Part Manager* Role should be notified of the planned change during the evaluation phase.
14+
15+
You can easily adapt this example to your use-case, by adding additional tasks to the template or changing the conditions under which the workflow should be started.
16+
17+
18+
```python
19+
from csfunctions.actions.start_workflow import (
20+
StartWorkflowAction,
21+
Subject,
22+
TaskConfiguration,
23+
)
24+
from csfunctions.events import EngineeringChangeStatusChangedEvent
25+
from csfunctions import MetaData
26+
27+
# change these to match your template and roles!!!
28+
TEMPLATE_ID = "PT00000002"
29+
INFORMATION_TASK_ID = "T00000008"
30+
INFORM_ROLE = "External Part Manager"
31+
32+
33+
def start_workflow_on_ec_status_change(
34+
metadata: MetaData, event: EngineeringChangeStatusChangedEvent, service
35+
):
36+
if event.data.engineering_change.status != 30:
37+
# only start the workflow if the status changed to 30 (Evaluation)
38+
return
39+
40+
# check if the ec contains external parts
41+
if not any(
42+
part.t_kategorie_name_en.startswith("External")
43+
for part in event.data.engineering_change.planned_changes_parts
44+
):
45+
# no external parts, so we don't need to start the workflow
46+
return
47+
48+
return StartWorkflowAction(
49+
template_id=TEMPLATE_ID,
50+
title=f"Information about EC {event.data.engineering_change.cdb_ec_id}",
51+
# attach the engineering change to the workflow
52+
global_briefcase_object_ids=[
53+
event.data.engineering_change.cdb_object_id],
54+
task_configurations=[
55+
TaskConfiguration(
56+
task_id=INFORMATION_TASK_ID,
57+
description="A an engineering change containing external parts moved to the evaluation phase.",
58+
recipients=[
59+
Subject(
60+
subject_type="Common Role",
61+
subject_id=INFORM_ROLE,
62+
)
63+
],
64+
)
65+
66+
],
67+
)
68+
```
69+
70+
!!! note
71+
To sucessfully execute this example you need to:
72+
73+
- Create a workflow template with an information task and adjust the `TEMPLATE_ID` and `INFORMATION_TASK_ID` to match them
74+
75+
- Create and assign an "External Part Manager" role to a user

0 commit comments

Comments
 (0)