Files

250 lines
8.5 KiB
Markdown

# TwentyCRM DRE Custom Data Model
Created 2026-07-08 via `https://crm.debtrecoveryexperts.com/rest/metadata/` REST API.
Auth: Bearer token from `TWENTY_CRM_API_KEY` in `~/.hermes/.env`.
## Objects
### 1. Debtors (`debtor`/`debtors`)
- **Icon:** IconUsers
- **Label Identifier:** Name (auto-created TEXT field)
- **Custom fields:**
| Field | Type | Icon | Description |
|-------|------|------|-------------|
| contactInfo | TEXT | IconPhone | Primary contact phone number |
| email | TEXT | IconMail | Primary email address |
| physicalAddress | TEXT | IconMapPin | Physical address |
| businessType | SELECT | IconBuildingStore | INDIVIDUAL, SOLE_PROPRIETORSHIP, LLC, CORPORATION, PARTNERSHIP, OTHER |
### 2. Claims (`claim`/`claims`)
- **Icon:** IconFiles
- **Label Identifier:** Name (auto-created TEXT field)
- **Custom fields:**
| Field | Type | Icon | Description |
|-------|------|------|-------------|
| claimNumber | TEXT (unique) | IconHash | DRE-YYYY-NNNN format |
| amount | CURRENCY | IconCurrencyDollar | Claim amount in USD |
| status | SELECT | IconStatusChange | NEW, ACTIVE, NEGOTIATION, LEGAL, SETTLED, CLOSED, WRITE_OFF |
| tier | SELECT | IconLayersSelected | TIER_1, TIER_2, TIER_3 |
| clientReference | TEXT | IconFileDescription | Client reference number |
| dateAssigned | DATE_TIME | IconCalendarPlus | Date claim was assigned |
| dateResolved | DATE_TIME | IconCalendarCheck | Date claim was resolved |
### 3. Case Notes (`caseNote`/`caseNotes`)
- **Icon:** IconNotes
- **Label Identifier:** Name (auto-created TEXT field)
- **Custom fields:**
| Field | Type | Icon | Description |
|-------|------|------|-------------|
| content | RICH_TEXT | IconFileText | Note content |
| author | TEXT | IconUser | Note author name |
### 4. Payments (`payment`/`payments`)
- **Icon:** IconReceipt
- **Label Identifier:** Name (auto-created TEXT field)
- **Custom fields:**
| Field | Type | Icon | Description |
|-------|------|------|-------------|
| paymentAmount | CURRENCY | IconCurrencyDollar | Amount received |
| payer | TEXT | IconUser | Name of payer |
| paymentDate | DATE_TIME | IconCalendar | Date payment received |
| paymentMethod | SELECT | IconCreditCard | CHECK, WIRE_TRANSFER, CREDIT_CARD, CASH, ACH, OTHER |
## REST API Field Creation Pattern
```bash
API_KEY=$(grep TWENTY_CRM_API_KEY ~/.hermes/.env | cut -d= -f2)
# Create object
curl -s "https://crm.debtrecoveryexperts.com/rest/metadata/objects" \
-X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d '{
"nameSingular": "myObject",
"namePlural": "myObjects",
"labelSingular": "My Object",
"labelPlural": "My Objects",
"description": "...",
"icon": "IconFiles",
"isSearchable": true,
"isActive": true,
"isLabelSyncedWithName": true
}'
```
**Create a regular field:**
```bash
curl -s "https://crm.debtrecoveryexperts.com/rest/metadata/fields" \
-X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d '{
"name": "fieldName",
"label": "Field Name",
"description": "...",
"type": "TEXT",
"icon": "IconMail",
"isNullable": true,
"isUnique": false,
"objectMetadataId": "'"$OBJECT_ID"'"
}'
```
**Create a SELECT field with options:**
```bash
curl -s "https://crm.debtrecoveryexperts.com/rest/metadata/fields" \
-X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d '{
"name": "status",
"label": "Status",
"description": "...",
"type": "SELECT",
"icon": "IconStatusChange",
"isNullable": true,
"isUnique": false,
"objectMetadataId": "'"$OBJECT_ID"'",
"options": [
{"label": "New", "value": "NEW", "color": "blue", "position": 0},
{"label": "Active", "value": "ACTIVE", "color": "turquoise", "position": 1}
]
}'
```
**Available Colors for SELECT options:** blue, turquoise, green, yellow, orange, red, purple, pink, grey, sky
## Relations (MANY_TO_ONE → ONE_TO_MANY)
**IMPORTANT:** The REST API endpoint `POST /rest/metadata/fields` does NOT accept `relationCreationPayload` — it returns `"Relation creation payload is required"`. Relations MUST be created via the **GraphQL mutation endpoint** at `POST https://crm.debtrecoveryexperts.com/metadata`.
### GraphQL Mutation for RELATION Fields
```graphql
mutation CreateOneField($input: CreateOneFieldMetadataInput!) {
createOneField(input: $input) {
id
name
label
type
settings
relation {
type
sourceObjectMetadata { id nameSingular namePlural }
targetObjectMetadata { id nameSingular namePlural }
sourceFieldMetadata { id name }
targetFieldMetadata { id name }
}
object { id nameSingular }
}
}
```
### Variables (JSON)
```json
{
"input": {
"field": {
"name": "debtor",
"label": "Debtor",
"type": "RELATION",
"objectMetadataId": "<SOURCE_OBJECT_UUID>",
"relationCreationPayload": {
"type": "MANY_TO_ONE",
"targetObjectMetadataId": "<TARGET_OBJECT_UUID>",
"targetFieldLabel": "Claims",
"targetFieldIcon": "IconFileDescription"
}
}
}
}
```
### `relationCreationPayload` Fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `type` | `"MANY_TO_ONE"` \| `"ONE_TO_MANY"` | ✅ | Direction of the relation |
| `targetObjectMetadataId` | UUID string | ✅ | The object this relation points to |
| `targetFieldLabel` | string (max 63 chars) | ✅ | Label for the auto-generated reverse field on the target object |
| `targetFieldIcon` | string | ✅ | Icon name for the reverse field (e.g. `"IconCurrencyDollar"`) |
### How It Works
**MANY_TO_ONE** field on SourceObject → TargetObject:
- A foreign-key column (e.g. `debtorId`) is **automatically created** on the SourceObject table
- A reverse **ONE_TO_MANY** relation field is **automatically created** on the TargetObject
- `targetFieldLabel` and `targetFieldIcon` control the reverse field's label/icon
- The source-side `settings` will include `{"joinColumnName": "debtorId"}`
**ONE_TO_MANY** field on SourceObject → TargetObject:
- **No** join column on SourceObject (the FK lives on the other side)
- A reverse **MANY_TO_ONE** relation field (with join column) is created on TargetObject
### curl Example
```bash
API_KEY=$(grep TWENTY_CRM_API_KEY ~/.hermes/.env | cut -d= -f2)
# Create a MANY_TO_ONE relation: Payments → Claim
curl -s "https://crm.debtrecoveryexperts.com/metadata" \
-X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d '{
"query": "mutation CreateOneField($input: CreateOneFieldMetadataInput!) { createOneField(input: $input) { id name type relation { type targetFieldMetadata { id name } targetObjectMetadata { id nameSingular } } } }",
"variables": {
"input": {
"field": {
"name": "claim",
"label": "Claim",
"type": "RELATION",
"objectMetadataId": "PAYMENTS_OBJECT_UUID",
"relationCreationPayload": {
"type": "MANY_TO_ONE",
"targetObjectMetadataId": "CLAIMS_OBJECT_UUID",
"targetFieldLabel": "Payments",
"targetFieldIcon": "IconCurrencyDollar"
}
}
}
}
}'
```
### DRE Data Model Relations Table
| Source Object | Field | Relation Type | Target Object | Reverse Field |
|---------------|-------|---------------|---------------|---------------|
| Claims | `debtor` (MANY_TO_ONE) | MANY_TO_ONE | Debtors | `claims` (ONE_TO_MANY) |
| CaseNotes | `claim` (MANY_TO_ONE) | MANY_TO_ONE | Claims | `caseNotes` (ONE_TO_MANY) |
| Payments | `claim` (MANY_TO_ONE) | MANY_TO_ONE | Claims | `payments` (ONE_TO_MANY) |
## Reserved Field Names
These names trigger `INVALID_FIELD_INPUT: This name is reserved`:
- `address` (and likely other system-level names)
Use alternatives like `physicalAddress`, `mailingAddress`, etc.
## Verification Command
```bash
API_KEY=$(grep TWENTY_CRM_API_KEY ~/.hermes/.env | cut -d= -f2)
curl -s "https://crm.debtrecoveryexperts.com/rest/metadata/objects" \
-H "Authorization: Bearer $API_KEY" > /tmp/all_objects.json
python3 -c "
import json, sys
data = json.load(open('/tmp/all_objects.json'))
for obj in data.get('data', []):
if not obj['isSystem']:
print(f'\\n## {obj[\"labelSingular\"]} ({obj[\"nameSingular\"]})')
print(f' ID: {obj[\"id\"]}')
for f in obj.get('fields', []):
if not f['isSystem']:
print(f' - {f[\"label\"]} ({f[\"name\"]}) type={f[\"type\"]}')
"
```