> ## Documentation Index
> Fetch the complete documentation index at: https://docs.firstresonance.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Example actions

> Worked ION Action configurations for common validations across quality, procedures, purchases, and receipts.

Read [Create an action](/automate-with-ion/ion-actions/manage-actions#create-an-action) before working through these examples. Each one below gives the field values for one ION Action: its title, target, and event, followed by the context query it reads and the code it runs.

## Block a status transition based on a related collection

Stops an issue from moving to In Review while any of its redlines sit on a step that isn't complete or canceled. The technique: read a related collection from the context and require every record in it to be in an acceptable state.

| Field  | Value                                             |
| ------ | ------------------------------------------------- |
| Title  | Open redlines prevent moving issue into In Review |
| Target | `ISSUE`                                           |
| Event  | `UPDATE`                                          |

**Context**

```graphql theme={null}
{
  issue(id: $id) {
    title
    status
    redlines { id step { status } }
  }
}
```

**Code**

```python theme={null}
if (
    context.get('issue', {}).get('status') == 'IN_REVIEW'
    and context.get('issue', {}).get('redlines') is not None
    and not all(
        redline.get('step', {}).get('status') in ['COMPLETE', 'CANCELED']
        for redline in context.get('issue', {}).get('redlines')
    )
):
    raise ValidationError()
```

## Restrict an action to specific roles

Blocks moving an issue back to In Review from a resolved state unless the acting user holds a quality role. The technique: branch on the acting user's roles from `currentUser`, and read the prior value from `changes` to detect the reopen.

| Field  | Value                                  |
| ------ | -------------------------------------- |
| Title  | No permission to reopen a closed issue |
| Target | `ISSUE`                                |
| Event  | `UPDATE`                               |

**Context**

```graphql theme={null}
{
  issue(id: $id) {
    title
    status
  }
}
```

**Code**

```python theme={null}
allowed_roles = {'Quality Engineer', 'Quality Inspector', 'Reliability Engineer', 'admin'}
if (
    context.get('issue', {}).get('status') == 'IN_REVIEW'
    and context.get('changes', {}).get('issues', {}).get('status', {}).get('old') == 'resolved'
    and not set(context.get('currentUser', {}).get('roles', [])).intersection(allowed_roles)
):
    raise ValidationError()
```

## Check a custom attribute

Stops a part from being added to a kit when its `On CAPA?` attribute is set. The technique: scan a list of `{ key, value }` attributes on a related object for a specific key.

| Field  | Value                       |
| ------ | --------------------------- |
| Title  | Cannot add CAPA part to kit |
| Target | `PARTKITITEM`               |
| Event  | `CREATE`                    |

**Context**

```graphql theme={null}
{
  partKitItem(id: $id) {
    part {
      attributes { key value }
    }
  }
}
```

**Code**

```python theme={null}
if any(
    attr.get('key') == 'On CAPA?' and attr.get('value') is not None
    for attr in context.get('partKitItem', {}).get('part', {}).get('attributes', [])
):
    raise ValidationError()
```

## Require a field on every item in a collection

Blocks moving a procedure to In Review when any step has no upstream or downstream dependency. The technique: gate on a status change read from `changes`, then iterate the object's child collection and require a field on each.

| Field  | Value                          |
| ------ | ------------------------------ |
| Title  | Check if step has dependencies |
| Target | `PROCEDURE`                    |
| Event  | `UPDATE`                       |

**Context**

```graphql theme={null}
{
  procedure(id: $id) {
    id
    steps {
      upstreamStepIds
      downstreamStepIds
    }
  }
}
```

**Code**

```python theme={null}
status_change = context.get('changes', {}).get('procedures', {}).get('status', {})
if status_change.get('new') == 'in_review' and any(
    not (step.get('upstreamStepIds') or step.get('downstreamStepIds'))
    for step in context.get('procedure', {}).get('steps', [])
):
    raise ValidationError()
```

## Match records across a nested collection

For a Build procedure, blocks the move to In Review until reviewers from the Responsible Engineer, Production, and Mission Assurance teams have been added. The technique: flatten a nested collection (reviewers, then their teams) and confirm every required value is present.

| Field  | Value                                          |
| ------ | ---------------------------------------------- |
| Title  | Procedure requires the correct teams to review |
| Target | `PROCEDURE`                                    |
| Event  | `UPDATE`                                       |

**Context**

```graphql theme={null}
{
  procedure(id: $id) {
    attributes { key value }
    reviewRequests { id status reviewer { id teams { name } roles { name } } }
  }
}
```

**Code**

```python theme={null}
status_change = context.get('changes', {}).get('procedures', {}).get('status', {})
procedure = context.get('procedure', {})
is_build = any(
    attr.get('key') == 'Procedure Type' and attr.get('value') == 'Build'
    for attr in procedure.get('attributes', [])
)
required_teams = ['Responsible Engineer', 'Production', 'Mission Assurance']
reviewer_team_names = [
    team.get('name', '')
    for review in procedure.get('reviewRequests', [])
    for team in review.get('reviewer', {}).get('teams', [])
]
has_required = all(
    any(required in name for name in reviewer_team_names)
    for required in required_teams
)
if status_change.get('new') == 'in_review' and is_build and not has_required:
    raise ValidationError()
```

## Require a field before a status change

Blocks a purchase order line from moving to Requested when it has no need date. The technique: gate on a status change read from `changes`, then require a scalar field on the object.

| Field  | Value                                     |
| ------ | ----------------------------------------- |
| Title  | Purchase line items must have a need date |
| Target | `PURCHASEORDERLINE`                       |
| Event  | `UPDATE`                                  |

**Context**

```graphql theme={null}
{
  purchaseOrderLine(id: $id) {
    id
    needDate
  }
}
```

**Code**

```python theme={null}
status_change = context.get('changes', {}).get('purchaseOrderLines', {}).get('status', {})
if (
    status_change.get('new') == 'requested'
    and context.get('purchaseOrderLine', {}).get('needDate') is None
):
    raise ValidationError()
```

<Note>
  To enforce purchase order approval levels, use the built-in [purchase order approvals](/manage-supply-chain/purchasing/purchase-orders/purchase-order-approvals) instead of an ION Action. It's easier to configure and maintain.
</Note>

## Require multiple fields on creation

Blocks creating a part of type Part until the required fields are filled in. The technique: run on the `CREATE` event, guard on the object's type, then check a list of required fields at once.

| Field  | Value                             |
| ------ | --------------------------------- |
| Title  | Required fields for part creation |
| Target | `PART`                            |
| Event  | `CREATE`                          |

**Context**

```graphql theme={null}
{
  part(id: $id) {
    id
    revision
    description
    trackingType
    sourcingStrategy
    partType
    unitOfMeasure { id }
  }
}
```

**Code**

```python theme={null}
part = context.get('part', {})
required_fields = ['revision', 'description', 'trackingType', 'sourcingStrategy', 'unitOfMeasure']
if part.get('partType') == 'PART' and any(not part.get(field) for field in required_fields):
    raise ValidationError()
```

## Related

* [Manage actions](/automate-with-ion/ion-actions/manage-actions)
* [View action execution logs](/automate-with-ion/ion-actions/view-action-execution-logs)
