Compare commits
2 Commits
cebe829dcd
...
a121c97cde
| Author | SHA256 | Date | |
|---|---|---|---|
| a121c97cde | |||
| e61af83d25 |
@@ -1,15 +1,17 @@
|
||||
{
|
||||
"labels": {
|
||||
"Create CTag": "Tag erstellen"
|
||||
},
|
||||
"fields": {
|
||||
"verkaufschancen": "Verkaufschancen",
|
||||
"kontakte": "Kontakte",
|
||||
"firmen": "Firmen"
|
||||
},
|
||||
"links": {
|
||||
"verkaufschancen": "Verkaufschancen",
|
||||
"kontakte": "Kontakte",
|
||||
"firmen": "Firmen"
|
||||
}
|
||||
}
|
||||
"labels": {
|
||||
"Create CTag": "Tag erstellen"
|
||||
},
|
||||
"fields": {
|
||||
"verkaufschancen": "Verkaufschancen",
|
||||
"kontakte": "Kontakte",
|
||||
"firmen": "Firmen",
|
||||
"interessenten": "Interessenten"
|
||||
},
|
||||
"links": {
|
||||
"verkaufschancen": "Verkaufschancen",
|
||||
"kontakte": "Kontakte",
|
||||
"firmen": "Firmen",
|
||||
"interessenten": "Interessenten"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"fields": {
|
||||
"cTags": "Tags"
|
||||
},
|
||||
"links": {
|
||||
"cTags": "Tags"
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
{
|
||||
"labels": {
|
||||
"Create CTag": "Create Tag"
|
||||
},
|
||||
"fields": {
|
||||
"verkaufschancen": "Verkaufschancen",
|
||||
"kontakte": "Kontakte",
|
||||
"firmen": "Firmen"
|
||||
},
|
||||
"links": {
|
||||
"verkaufschancen": "Verkaufschancen",
|
||||
"kontakte": "Kontakte",
|
||||
"firmen": "Firmen"
|
||||
}
|
||||
}
|
||||
"labels": {
|
||||
"Create CTag": "Create Tag"
|
||||
},
|
||||
"fields": {
|
||||
"verkaufschancen": "Verkaufschancen",
|
||||
"kontakte": "Kontakte",
|
||||
"firmen": "Firmen",
|
||||
"interessenten": "Leads"
|
||||
},
|
||||
"links": {
|
||||
"verkaufschancen": "Verkaufschancen",
|
||||
"kontakte": "Kontakte",
|
||||
"firmen": "Firmen",
|
||||
"interessenten": "Leads"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"fields": {
|
||||
"cTags": "Tags"
|
||||
},
|
||||
"links": {
|
||||
"cTags": "Tags"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
[
|
||||
"assignedUser",
|
||||
"teams",
|
||||
"createdAt",
|
||||
"createdBy",
|
||||
"modifiedAt",
|
||||
"streamUpdatedAt",
|
||||
"type",
|
||||
"industry",
|
||||
"description",
|
||||
"emailAddress",
|
||||
"phoneNumber",
|
||||
"targetLists",
|
||||
"billingAddressCountry",
|
||||
"billingAddress",
|
||||
"shippingAddress",
|
||||
"website",
|
||||
"campaign",
|
||||
"cTags"
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
[
|
||||
"assignedUser",
|
||||
"teams",
|
||||
"createdAt",
|
||||
"createdBy",
|
||||
"modifiedAt",
|
||||
"streamUpdatedAt",
|
||||
"accounts",
|
||||
"address",
|
||||
"emailAddress",
|
||||
"phoneNumber",
|
||||
"title",
|
||||
"targetLists",
|
||||
"campaign",
|
||||
"cTags"
|
||||
]
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"targetLists": {
|
||||
"index": 5
|
||||
},
|
||||
"cTags": {
|
||||
"index": 6
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
[
|
||||
"assignedUser",
|
||||
"teams",
|
||||
"createdAt",
|
||||
"createdBy",
|
||||
"modifiedAt",
|
||||
"streamUpdatedAt",
|
||||
"address",
|
||||
"accountName",
|
||||
"campaign",
|
||||
"emailAddress",
|
||||
"phoneNumber",
|
||||
"status",
|
||||
"source",
|
||||
"opportunityAmountConverted",
|
||||
"industry",
|
||||
"targetLists",
|
||||
"createdAccount",
|
||||
"cTags"
|
||||
]
|
||||
@@ -0,0 +1,20 @@
|
||||
[
|
||||
"assignedUser",
|
||||
"teams",
|
||||
"createdAt",
|
||||
"createdBy",
|
||||
"modifiedAt",
|
||||
"streamUpdatedAt",
|
||||
"account",
|
||||
"cAccount1",
|
||||
"amountConverted",
|
||||
"amountWeightedConverted",
|
||||
"campaign",
|
||||
"closeDate",
|
||||
"contacts",
|
||||
"leadSource",
|
||||
"probability",
|
||||
"stage",
|
||||
"description",
|
||||
"cTags"
|
||||
]
|
||||
@@ -15,6 +15,10 @@
|
||||
"firmen": {
|
||||
"layout": null,
|
||||
"selectPrimaryFilterName": null
|
||||
},
|
||||
"interessenten": {
|
||||
"layout": null,
|
||||
"selectPrimaryFilterName": null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"relationshipPanels": {
|
||||
"cTags": {
|
||||
"create": false,
|
||||
"rowActionsView": "custom:views/record/row-actions/tags-unlink-only"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,26 @@
|
||||
{
|
||||
"links": {
|
||||
"cAgentur": {
|
||||
"type": "hasMany",
|
||||
"foreign": "cAccount1",
|
||||
"entity": "Opportunity",
|
||||
"audited": false,
|
||||
"isCustom": true
|
||||
},
|
||||
"cTags": {
|
||||
"type": "hasMany",
|
||||
"relationName": "cAccountTag",
|
||||
"foreign": "firmen",
|
||||
"entity": "CTag",
|
||||
"audited": false,
|
||||
"isCustom": true
|
||||
}
|
||||
"links": {
|
||||
"cAgentur": {
|
||||
"type": "hasMany",
|
||||
"foreign": "cAccount1",
|
||||
"entity": "Opportunity",
|
||||
"audited": false,
|
||||
"isCustom": true
|
||||
},
|
||||
"cTags": {
|
||||
"type": "hasMany",
|
||||
"relationName": "cAccountTag",
|
||||
"foreign": "firmen",
|
||||
"entity": "CTag",
|
||||
"audited": false,
|
||||
"isCustom": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"fields": {
|
||||
"cTags": {
|
||||
"type": "linkMultiple",
|
||||
"view": "views/fields/link-multiple",
|
||||
"isCustom": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,113 +1,121 @@
|
||||
{
|
||||
"fields": {
|
||||
"name": {
|
||||
"type": "varchar",
|
||||
"required": true,
|
||||
"pattern": "$noBadCharacters"
|
||||
},
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "datetime",
|
||||
"readOnly": true
|
||||
},
|
||||
"modifiedAt": {
|
||||
"type": "datetime",
|
||||
"readOnly": true
|
||||
},
|
||||
"createdBy": {
|
||||
"type": "link",
|
||||
"readOnly": true,
|
||||
"view": "views/fields/user"
|
||||
},
|
||||
"modifiedBy": {
|
||||
"type": "link",
|
||||
"readOnly": true,
|
||||
"view": "views/fields/user"
|
||||
},
|
||||
"assignedUser": {
|
||||
"type": "link",
|
||||
"required": false,
|
||||
"view": "views/fields/assigned-user"
|
||||
},
|
||||
"teams": {
|
||||
"type": "linkMultiple",
|
||||
"view": "views/fields/teams"
|
||||
}
|
||||
"fields": {
|
||||
"name": {
|
||||
"type": "varchar",
|
||||
"required": true,
|
||||
"pattern": "$noBadCharacters"
|
||||
},
|
||||
"links": {
|
||||
"createdBy": {
|
||||
"type": "belongsTo",
|
||||
"entity": "User"
|
||||
},
|
||||
"modifiedBy": {
|
||||
"type": "belongsTo",
|
||||
"entity": "User"
|
||||
},
|
||||
"assignedUser": {
|
||||
"type": "belongsTo",
|
||||
"entity": "User"
|
||||
},
|
||||
"teams": {
|
||||
"type": "hasMany",
|
||||
"entity": "Team",
|
||||
"relationName": "entityTeam",
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"verkaufschancen": {
|
||||
"type": "hasMany",
|
||||
"relationName": "cOpportunityTag",
|
||||
"foreign": "cTags",
|
||||
"entity": "Opportunity",
|
||||
"audited": false,
|
||||
"isCustom": true
|
||||
},
|
||||
"kontakte": {
|
||||
"type": "hasMany",
|
||||
"relationName": "cContactTag",
|
||||
"foreign": "cTags",
|
||||
"entity": "Contact",
|
||||
"audited": false,
|
||||
"isCustom": true
|
||||
},
|
||||
"firmen": {
|
||||
"type": "hasMany",
|
||||
"relationName": "cAccountTag",
|
||||
"foreign": "cTags",
|
||||
"entity": "Account",
|
||||
"audited": false,
|
||||
"isCustom": true
|
||||
}
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"collection": {
|
||||
"orderBy": "createdAt",
|
||||
"order": "desc"
|
||||
"createdAt": {
|
||||
"type": "datetime",
|
||||
"readOnly": true
|
||||
},
|
||||
"indexes": {
|
||||
"name": {
|
||||
"columns": [
|
||||
"name",
|
||||
"deleted"
|
||||
]
|
||||
},
|
||||
"assignedUser": {
|
||||
"columns": [
|
||||
"assignedUserId",
|
||||
"deleted"
|
||||
]
|
||||
},
|
||||
"createdAt": {
|
||||
"columns": [
|
||||
"createdAt"
|
||||
]
|
||||
},
|
||||
"createdAtId": {
|
||||
"unique": true,
|
||||
"columns": [
|
||||
"createdAt",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
"modifiedAt": {
|
||||
"type": "datetime",
|
||||
"readOnly": true
|
||||
},
|
||||
"createdBy": {
|
||||
"type": "link",
|
||||
"readOnly": true,
|
||||
"view": "views/fields/user"
|
||||
},
|
||||
"modifiedBy": {
|
||||
"type": "link",
|
||||
"readOnly": true,
|
||||
"view": "views/fields/user"
|
||||
},
|
||||
"assignedUser": {
|
||||
"type": "link",
|
||||
"required": false,
|
||||
"view": "views/fields/assigned-user"
|
||||
},
|
||||
"teams": {
|
||||
"type": "linkMultiple",
|
||||
"view": "views/fields/teams"
|
||||
}
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"createdBy": {
|
||||
"type": "belongsTo",
|
||||
"entity": "User"
|
||||
},
|
||||
"modifiedBy": {
|
||||
"type": "belongsTo",
|
||||
"entity": "User"
|
||||
},
|
||||
"assignedUser": {
|
||||
"type": "belongsTo",
|
||||
"entity": "User"
|
||||
},
|
||||
"teams": {
|
||||
"type": "hasMany",
|
||||
"entity": "Team",
|
||||
"relationName": "entityTeam",
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"verkaufschancen": {
|
||||
"type": "hasMany",
|
||||
"relationName": "cOpportunityTag",
|
||||
"foreign": "cTags",
|
||||
"entity": "Opportunity",
|
||||
"audited": false,
|
||||
"isCustom": true
|
||||
},
|
||||
"kontakte": {
|
||||
"type": "hasMany",
|
||||
"relationName": "cContactTag",
|
||||
"foreign": "cTags",
|
||||
"entity": "Contact",
|
||||
"audited": false,
|
||||
"isCustom": true
|
||||
},
|
||||
"firmen": {
|
||||
"type": "hasMany",
|
||||
"relationName": "cAccountTag",
|
||||
"foreign": "cTags",
|
||||
"entity": "Account",
|
||||
"audited": false,
|
||||
"isCustom": true
|
||||
},
|
||||
"interessenten": {
|
||||
"type": "hasMany",
|
||||
"relationName": "cLeadTag",
|
||||
"foreign": "cTags",
|
||||
"entity": "Lead",
|
||||
"audited": false,
|
||||
"isCustom": true
|
||||
}
|
||||
},
|
||||
"collection": {
|
||||
"orderBy": "createdAt",
|
||||
"order": "desc"
|
||||
},
|
||||
"indexes": {
|
||||
"name": {
|
||||
"columns": [
|
||||
"name",
|
||||
"deleted"
|
||||
]
|
||||
},
|
||||
"assignedUser": {
|
||||
"columns": [
|
||||
"assignedUserId",
|
||||
"deleted"
|
||||
]
|
||||
},
|
||||
"createdAt": {
|
||||
"columns": [
|
||||
"createdAt"
|
||||
]
|
||||
},
|
||||
"createdAtId": {
|
||||
"unique": true,
|
||||
"columns": [
|
||||
"createdAt",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
{
|
||||
"links": {
|
||||
"cTags": {
|
||||
"type": "hasMany",
|
||||
"relationName": "cContactTag",
|
||||
"foreign": "kontakte",
|
||||
"entity": "CTag",
|
||||
"audited": false,
|
||||
"isCustom": true
|
||||
}
|
||||
"links": {
|
||||
"cTags": {
|
||||
"type": "hasMany",
|
||||
"relationName": "cContactTag",
|
||||
"foreign": "kontakte",
|
||||
"entity": "CTag",
|
||||
"audited": false,
|
||||
"isCustom": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"fields": {
|
||||
"cTags": {
|
||||
"type": "linkMultiple",
|
||||
"view": "views/fields/link-multiple",
|
||||
"isCustom": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"fields": {
|
||||
"cTags": {
|
||||
"type": "linkMultiple",
|
||||
"view": "views/fields/link-multiple",
|
||||
"isCustom": true
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"cTags": {
|
||||
"type": "hasMany",
|
||||
"relationName": "cLeadTag",
|
||||
"foreign": "interessenten",
|
||||
"entity": "CTag",
|
||||
"audited": false,
|
||||
"isCustom": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,167 +1,172 @@
|
||||
{
|
||||
"fields": {
|
||||
"stage": {
|
||||
"options": [
|
||||
"Interessant",
|
||||
"Abgelehnt",
|
||||
"Angefragt",
|
||||
"Agentur prüft",
|
||||
"Kunde prüft",
|
||||
"Identifiziert",
|
||||
"Brief versendet",
|
||||
"Anruf-Wiedervorlage",
|
||||
"Angerufen",
|
||||
"Ersttermin vereinbart",
|
||||
"Ersttermin durchgeführt",
|
||||
"Angebot",
|
||||
"Verhandlung",
|
||||
"Unterschriftsreif",
|
||||
"Gewonnen",
|
||||
"In Durchführung",
|
||||
"Abgeschl. / Bestandsk.",
|
||||
"Verloren",
|
||||
"Ghosted"
|
||||
],
|
||||
"default": "Interessant",
|
||||
"probabilityMap": {
|
||||
"Interessant": 5,
|
||||
"Abgelehnt": 0,
|
||||
"Angefragt": 15,
|
||||
"Agentur prüft": 20,
|
||||
"Kunde prüft": 30,
|
||||
"Identifiziert": 5,
|
||||
"Brief versendet": 8,
|
||||
"Anruf-Wiedervorlage": 10,
|
||||
"Angerufen": 12,
|
||||
"Ersttermin vereinbart": 20,
|
||||
"Ersttermin durchgeführt": 30,
|
||||
"Angebot": 40,
|
||||
"Verhandlung": 60,
|
||||
"Unterschriftsreif": 80,
|
||||
"Gewonnen": 100,
|
||||
"In Durchführung": 100,
|
||||
"Abgeschl. / Bestandsk.": 100,
|
||||
"Verloren": 0,
|
||||
"Ghosted": 0
|
||||
},
|
||||
"style": {
|
||||
"Interessant": null,
|
||||
"Abgelehnt": "info",
|
||||
"Angefragt": null,
|
||||
"Agentur prüft": null,
|
||||
"Kunde prüft": null,
|
||||
"Identifiziert": null,
|
||||
"Brief versendet": "warning",
|
||||
"Anruf-Wiedervorlage": "warning",
|
||||
"Angerufen": "warning",
|
||||
"Ersttermin vereinbart": "warning",
|
||||
"Ersttermin durchgeführt": "warning",
|
||||
"Angebot": null,
|
||||
"Verhandlung": null,
|
||||
"Unterschriftsreif": null,
|
||||
"Gewonnen": "success",
|
||||
"In Durchführung": "primary",
|
||||
"Abgeschl. / Bestandsk.": "info",
|
||||
"Verloren": "danger",
|
||||
"Ghosted": "info"
|
||||
}
|
||||
},
|
||||
"cLeadQuelle": {
|
||||
"type": "enum",
|
||||
"required": true,
|
||||
"options": [
|
||||
"Inbound Agentur",
|
||||
"Inbound Plattform-Lead",
|
||||
"Aktive Plattform-Suche",
|
||||
"Aktive Akquise",
|
||||
"Empfehlung / Netzwerk",
|
||||
"Sonstiges"
|
||||
],
|
||||
"style": {
|
||||
"Inbound Agentur": null,
|
||||
"Inbound Plattform-Lead": null,
|
||||
"Aktive Plattform-Suche": null,
|
||||
"Aktive Akquise": null,
|
||||
"Empfehlung / Netzwerk": null,
|
||||
"Sonstiges": null
|
||||
},
|
||||
"default": "Inbound Agentur",
|
||||
"maxLength": 100,
|
||||
"isCustom": true,
|
||||
"displayAsLabel": true
|
||||
},
|
||||
"cVerlustgrund": {
|
||||
"type": "enum",
|
||||
"options": [
|
||||
"Agentur Absage",
|
||||
"Kunde Absage",
|
||||
"Keine Einigung",
|
||||
"Eigene Absage",
|
||||
"Sonstiges",
|
||||
"Agentur Ghosted",
|
||||
"Kunde Ghosted"
|
||||
],
|
||||
"style": {
|
||||
"Agentur Absage": null,
|
||||
"Kunde Absage": null,
|
||||
"Keine Einigung": null,
|
||||
"Eigene Absage": null,
|
||||
"Sonstiges": null,
|
||||
"Agentur Ghosted": null,
|
||||
"Kunde Ghosted": null
|
||||
},
|
||||
"default": "Kunde Absage",
|
||||
"maxLength": 100,
|
||||
"isCustom": true
|
||||
},
|
||||
"cProjektlink": {
|
||||
"type": "url",
|
||||
"isCustom": true
|
||||
},
|
||||
"cVerguetungsmodell": {
|
||||
"type": "enum",
|
||||
"options": [
|
||||
"Agentur Honorar",
|
||||
"Direkt Festpreis",
|
||||
"Direkt Honorar",
|
||||
"Direkt Nutzenbasiert"
|
||||
],
|
||||
"style": {
|
||||
"Agentur Honorar": null,
|
||||
"Direkt Festpreis": null,
|
||||
"Direkt Honorar": null,
|
||||
"Direkt Nutzenbasiert": null
|
||||
},
|
||||
"default": "Agentur Honorar",
|
||||
"displayAsLabel": true,
|
||||
"maxLength": 100,
|
||||
"isCustom": true
|
||||
},
|
||||
"cAccount1": {
|
||||
"type": "link"
|
||||
},
|
||||
"amount": {
|
||||
"required": false
|
||||
},
|
||||
"closeDate": {
|
||||
"required": false
|
||||
}
|
||||
"fields": {
|
||||
"stage": {
|
||||
"options": [
|
||||
"Interessant",
|
||||
"Abgelehnt",
|
||||
"Angefragt",
|
||||
"Agentur prüft",
|
||||
"Kunde prüft",
|
||||
"Identifiziert",
|
||||
"Brief versendet",
|
||||
"Anruf-Wiedervorlage",
|
||||
"Angerufen",
|
||||
"Ersttermin vereinbart",
|
||||
"Ersttermin durchgeführt",
|
||||
"Angebot",
|
||||
"Verhandlung",
|
||||
"Unterschriftsreif",
|
||||
"Gewonnen",
|
||||
"In Durchführung",
|
||||
"Abgeschl. / Bestandsk.",
|
||||
"Verloren",
|
||||
"Ghosted"
|
||||
],
|
||||
"default": "Interessant",
|
||||
"probabilityMap": {
|
||||
"Interessant": 5,
|
||||
"Abgelehnt": 0,
|
||||
"Angefragt": 15,
|
||||
"Agentur prüft": 20,
|
||||
"Kunde prüft": 30,
|
||||
"Identifiziert": 5,
|
||||
"Brief versendet": 8,
|
||||
"Anruf-Wiedervorlage": 10,
|
||||
"Angerufen": 12,
|
||||
"Ersttermin vereinbart": 20,
|
||||
"Ersttermin durchgeführt": 30,
|
||||
"Angebot": 40,
|
||||
"Verhandlung": 60,
|
||||
"Unterschriftsreif": 80,
|
||||
"Gewonnen": 100,
|
||||
"In Durchführung": 100,
|
||||
"Abgeschl. / Bestandsk.": 100,
|
||||
"Verloren": 0,
|
||||
"Ghosted": 0
|
||||
},
|
||||
"style": {
|
||||
"Interessant": null,
|
||||
"Abgelehnt": "info",
|
||||
"Angefragt": null,
|
||||
"Agentur prüft": null,
|
||||
"Kunde prüft": null,
|
||||
"Identifiziert": null,
|
||||
"Brief versendet": "warning",
|
||||
"Anruf-Wiedervorlage": "warning",
|
||||
"Angerufen": "warning",
|
||||
"Ersttermin vereinbart": "warning",
|
||||
"Ersttermin durchgeführt": "warning",
|
||||
"Angebot": null,
|
||||
"Verhandlung": null,
|
||||
"Unterschriftsreif": null,
|
||||
"Gewonnen": "success",
|
||||
"In Durchführung": "primary",
|
||||
"Abgeschl. / Bestandsk.": "info",
|
||||
"Verloren": "danger",
|
||||
"Ghosted": "info"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"cAccount1": {
|
||||
"type": "belongsTo",
|
||||
"foreign": "cAgentur",
|
||||
"entity": "Account",
|
||||
"audited": false,
|
||||
"isCustom": true
|
||||
},
|
||||
"cTags": {
|
||||
"type": "hasMany",
|
||||
"relationName": "cOpportunityTag",
|
||||
"foreign": "verkaufschancen",
|
||||
"entity": "CTag",
|
||||
"audited": false,
|
||||
"isCustom": true
|
||||
}
|
||||
"cLeadQuelle": {
|
||||
"type": "enum",
|
||||
"required": true,
|
||||
"options": [
|
||||
"Inbound Agentur",
|
||||
"Inbound Plattform-Lead",
|
||||
"Aktive Plattform-Suche",
|
||||
"Aktive Akquise",
|
||||
"Empfehlung / Netzwerk",
|
||||
"Sonstiges"
|
||||
],
|
||||
"style": {
|
||||
"Inbound Agentur": null,
|
||||
"Inbound Plattform-Lead": null,
|
||||
"Aktive Plattform-Suche": null,
|
||||
"Aktive Akquise": null,
|
||||
"Empfehlung / Netzwerk": null,
|
||||
"Sonstiges": null
|
||||
},
|
||||
"default": "Inbound Agentur",
|
||||
"maxLength": 100,
|
||||
"isCustom": true,
|
||||
"displayAsLabel": true
|
||||
},
|
||||
"cVerlustgrund": {
|
||||
"type": "enum",
|
||||
"options": [
|
||||
"Agentur Absage",
|
||||
"Kunde Absage",
|
||||
"Keine Einigung",
|
||||
"Eigene Absage",
|
||||
"Sonstiges",
|
||||
"Agentur Ghosted",
|
||||
"Kunde Ghosted"
|
||||
],
|
||||
"style": {
|
||||
"Agentur Absage": null,
|
||||
"Kunde Absage": null,
|
||||
"Keine Einigung": null,
|
||||
"Eigene Absage": null,
|
||||
"Sonstiges": null,
|
||||
"Agentur Ghosted": null,
|
||||
"Kunde Ghosted": null
|
||||
},
|
||||
"default": "Kunde Absage",
|
||||
"maxLength": 100,
|
||||
"isCustom": true
|
||||
},
|
||||
"cProjektlink": {
|
||||
"type": "url",
|
||||
"isCustom": true
|
||||
},
|
||||
"cVerguetungsmodell": {
|
||||
"type": "enum",
|
||||
"options": [
|
||||
"Agentur Honorar",
|
||||
"Direkt Festpreis",
|
||||
"Direkt Honorar",
|
||||
"Direkt Nutzenbasiert"
|
||||
],
|
||||
"style": {
|
||||
"Agentur Honorar": null,
|
||||
"Direkt Festpreis": null,
|
||||
"Direkt Honorar": null,
|
||||
"Direkt Nutzenbasiert": null
|
||||
},
|
||||
"default": "Agentur Honorar",
|
||||
"displayAsLabel": true,
|
||||
"maxLength": 100,
|
||||
"isCustom": true
|
||||
},
|
||||
"cAccount1": {
|
||||
"type": "link"
|
||||
},
|
||||
"amount": {
|
||||
"required": false
|
||||
},
|
||||
"closeDate": {
|
||||
"required": false
|
||||
},
|
||||
"cTags": {
|
||||
"type": "linkMultiple",
|
||||
"view": "views/fields/link-multiple",
|
||||
"isCustom": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"cAccount1": {
|
||||
"type": "belongsTo",
|
||||
"foreign": "cAgentur",
|
||||
"entity": "Account",
|
||||
"audited": false,
|
||||
"isCustom": true
|
||||
},
|
||||
"cTags": {
|
||||
"type": "hasMany",
|
||||
"relationName": "cOpportunityTag",
|
||||
"foreign": "verkaufschancen",
|
||||
"entity": "CTag",
|
||||
"audited": false,
|
||||
"isCustom": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
97
espocrm-tags-filterfeld-claude-code.md
Normal file
97
espocrm-tags-filterfeld-claude-code.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# EspoCRM 9.3.8 — Make the custom "Tags" relation filterable on all four entities
|
||||
|
||||
Self-contained instruction for Claude Code on the EspoCRM host (sv006). It assumes nothing about any other repository or plan.
|
||||
|
||||
## Goal
|
||||
|
||||
This EspoCRM instance has a custom many-to-many "Tags" relation to a custom tag entity **`CTag`**. It currently exists on **Opportunity**, **Contact**, and **Account** (as a relationship / bottom panel only — *not* as a field), and **does not exist at all on `Lead`** yet.
|
||||
|
||||
Two things to achieve:
|
||||
|
||||
1. **On Opportunity, Contact, Account:** expose the existing relation as a **`linkMultiple` field** named `cTags`, so it can be added to the "Search Filters" layout (the list-view filter dropdown, the "three dots", only offers *fields*).
|
||||
2. **On Lead (Interessent):** build the **full** Tags setup from scratch in parity with the other three — create the relation, then add the field, label, bottom panel, the same panel polish, and the search filter.
|
||||
|
||||
End state: on all four entities you can tag records and filter the list by tags (Any of / All of / None of).
|
||||
|
||||
**Constraints:** For the existing three this must **not** create a new relationship or change data — only add a field bound (by name) to the relation that already exists. linkMultiple fields are `notCreatable` in the Field Manager, so the field is added via custom metadata + rebuild. For Lead, the relation *is* created (a new join table is expected). Mirror the existing three exactly.
|
||||
|
||||
## Verified facts (from live `/api/v1/Metadata`)
|
||||
|
||||
The relation is a symmetric custom many-to-many (each side `type: hasMany` with `relationName`, `isCustom: true`):
|
||||
|
||||
| Scope | owner link | foreign entity | foreign (reverse) link | relationName | status |
|
||||
|---|---|---|---|---|---|
|
||||
| Opportunity | `cTags` | `CTag` | `verkaufschancen` | `cOpportunityTag` | relation exists; field missing |
|
||||
| Contact | `cTags` | `CTag` | `kontakte` | `cContactTag` | relation exists; field missing |
|
||||
| Account | `cTags` | `CTag` | `firmen` | `cAccountTag` | relation exists; field missing |
|
||||
| **Lead** | `cTags` | `CTag` | `interessenten` | `cLeadTag` | **relation does NOT exist — must be created** |
|
||||
|
||||
Reference configs from the existing three (to mirror for Lead):
|
||||
- A known-good linkMultiple field: `Opportunity.fields.teams = { "type": "linkMultiple", "view": "views/fields/teams" }`.
|
||||
- `clientDefs.<Scope>.relationshipPanels.cTags = { "create": false, "rowActionsView": "custom:views/record/row-actions/tags-unlink-only" }` (identical on Opportunity/Contact/Account). The referenced row-actions view already exists in this instance.
|
||||
|
||||
Also note: the custom link field **`cAccount1`** ("Über Agentur") was added to **Opportunity's** Search-Filters layout earlier today via the layout API — preserve it.
|
||||
|
||||
## Part A — Opportunity, Contact, Account (relation already exists)
|
||||
|
||||
For each of the three scopes:
|
||||
|
||||
**A1. Field def** — merge into `custom/Espo/Custom/Resources/metadata/entityDefs/<Scope>.json`, key `fields`:
|
||||
```json
|
||||
"cTags": { "type": "linkMultiple", "view": "views/fields/link-multiple", "isCustom": true }
|
||||
```
|
||||
Binds to the existing `cTags` link by name. Do **not** add `relationName`/`midKeys`/`entity` to the field.
|
||||
|
||||
**A2. Label** — merge `fields.cTags = "Tags"` into `custom/Espo/Custom/Resources/i18n/de_DE/<Scope>.json` and `.../en_US/<Scope>.json`:
|
||||
```json
|
||||
{ "fields": { "cTags": "Tags" } }
|
||||
```
|
||||
|
||||
**A3. Search-Filters layout** — `custom/Espo/Custom/Resources/layouts/<Scope>/filters.json` is a flat JSON array of field names. **Read the current file and append `cTags`** (e.g. after `teams`); do not remove anything.
|
||||
- For **Opportunity** specifically: ensure the array also still contains `cAccount1` (added today); if absent, insert it right after `account`.
|
||||
|
||||
## Part B — Lead / Interessent (build full parity)
|
||||
|
||||
`Lead` is enabled (`scopes.Lead.disabled = false`, customizable). It has no Tags relation today. Replicate the exact setup the other three have.
|
||||
|
||||
**B1. Create the relationship.** Create a many-to-many relation between `Lead` and `CTag` using the same mechanism the existing tag relations were created with (Entity Manager / relationship creation), so the join table and mid keys are generated correctly — do not hand-roll if the proper tool is available. Target names (mirror the pattern):
|
||||
- Lead-side link: `cTags` (entity `CTag`)
|
||||
- CTag-side reverse link: `interessenten` (entity `Lead`)
|
||||
- relationName: `cLeadTag`
|
||||
- A new join table (e.g. `c_lead_tag`) is expected to be created on rebuild.
|
||||
|
||||
**B2. Field** — add the same field def as A1 to `Lead` entityDefs.
|
||||
|
||||
**B3. Labels** — mirror the others:
|
||||
- `Lead` i18n (de_DE + en_US): `fields.cTags = "Tags"` and `links.cTags = "Tags"`.
|
||||
- `CTag` i18n (de_DE + en_US): `links.interessenten = "Interessenten"` (en: "Leads").
|
||||
|
||||
**B4. Panel polish** — set in `custom/Espo/Custom/Resources/metadata/clientDefs/Lead.json`:
|
||||
```json
|
||||
"relationshipPanels": { "cTags": { "create": false, "rowActionsView": "custom:views/record/row-actions/tags-unlink-only" } }
|
||||
```
|
||||
(identical to the other three; the view already exists).
|
||||
|
||||
**B5. Tags bottom panel visible on Lead detail** — ensure the Tags panel appears on the Lead detail view exactly as on Opportunity/Contact/Account. Inspect how the panel is enabled on those three and reproduce it for Lead; verify visually in the UI.
|
||||
|
||||
**B6. Search-Filters layout** — append `cTags` to `custom/Espo/Custom/Resources/layouts/Lead/filters.json` (read + append).
|
||||
|
||||
## Rebuild
|
||||
|
||||
Run EspoCRM's rebuild so the new field(s) and (for Lead) the relation become active (e.g. `php command.php rebuild` inside the app container). For A this is metadata/ORM-cache only (relation tables already exist); for B the rebuild creates the new `c_lead_tag` join table.
|
||||
|
||||
## Verify
|
||||
|
||||
- `GET /api/v1/Metadata` (admin): `entityDefs.<Scope>.fields.cTags` exists with `type: "linkMultiple"` for **all four** scopes; `entityDefs.Lead.links.cTags` and `entityDefs.CTag.links.interessenten` exist.
|
||||
- UI, each of the four lists: filter ("three dots") → **"Tags"** appears; choosing it offers **Any of / All of / None of** with a tag autocomplete; filtering returns correct records.
|
||||
- The **Tags bottom panel** works on all four detail views (incl. Lead); the "+"/inline-create is gone and the row menu only offers "remove link" (parity).
|
||||
- No data changed on the existing three; **`cAccount1` ("Über Agentur")** still present in the Opportunity filter list.
|
||||
|
||||
## After applying
|
||||
|
||||
GUI/metadata customization → run **`snapshot_espocrm_custom.sh`** (+ commit in the `bin` repo) so the next `create_pod_espocrm.sh` run does not revert it. This single snapshot also captures today's earlier `cAccount1` filter-layout change.
|
||||
|
||||
## Notes / limits (context, no action)
|
||||
|
||||
- linkMultiple filter operators: `anyOf` (linkedWith), `allOf` (linkedWithAll = AND), `noneOf` (notLinkedWith), `isEmpty`, `isNotEmpty`.
|
||||
- *Include one tag AND exclude another in the same view* (e.g. "has `Sommerkampagne` but not `15 % Preisnachlass`") is **not** possible with the standard field filter (one filter per field, one operator). That needs a separate named preset filter and is out of scope here.
|
||||
165
phase2-ping-mcp-claude-code.md
Normal file
165
phase2-ping-mcp-claude-code.md
Normal file
@@ -0,0 +1,165 @@
|
||||
# Claude Code task — minimal "ping" MCP server on sv006 (reachability test)
|
||||
|
||||
**Audience:** Claude Code, run by user `mkt` on host **sv006**.
|
||||
**Goal:** Stand up a tiny, self-contained Model Context Protocol (MCP) server that exposes a single `ping` tool over the **Streamable HTTP** transport. It is a throwaway reachability probe — it does **not** talk to EspoCRM and needs no secrets. A separate reverse-proxy route will publish it as `https://mcp-crm.creature-go.com/mcp`; you only build the local container.
|
||||
|
||||
This is intentionally a stand-in. The port and container layout are chosen so the public hostname and proxy route can be reused **unchanged** for the real CRM-MCP server later.
|
||||
|
||||
## Fixed parameters (do not change — other components depend on them)
|
||||
|
||||
| Parameter | Value |
|
||||
|---|---|
|
||||
| Host loopback bind | `127.0.0.1:8095` (reserved "MCP port") |
|
||||
| Container internal port | `8095` |
|
||||
| MCP endpoint path | `/mcp` (Streamable HTTP, POST) |
|
||||
| Health path | `/health` (GET, returns `ok`) |
|
||||
| Container name | `ping_mcp_ctr` |
|
||||
| Image tag | `ping-mcp:1.0.0` |
|
||||
| Runtime | rootless Podman, user `mkt` |
|
||||
|
||||
The container must publish **only on host loopback** (`127.0.0.1:8095`). The rootless Traefik pod reaches it via the slirp4netns host-loopback address `10.0.2.2:8095` (same mechanism the EspoCRM pod already uses) — do not publish on `0.0.0.0`.
|
||||
|
||||
## Step 1 — Create the app directory `~/ping_mcp/`
|
||||
|
||||
**`~/ping_mcp/package.json`**
|
||||
```json
|
||||
{
|
||||
"name": "ping-mcp",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.12.0",
|
||||
"express": "^4.21.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
Use the current published versions if these are outdated; the only requirement is a Streamable-HTTP-capable `@modelcontextprotocol/sdk`.
|
||||
|
||||
**`~/ping_mcp/server.js`**
|
||||
```js
|
||||
import express from "express";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
||||
|
||||
const PORT = 8095;
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
// Plain HTTP health check (NOT part of MCP) — for humans / curl.
|
||||
app.get("/health", (_req, res) => res.status(200).send("ok"));
|
||||
|
||||
function buildServer() {
|
||||
const server = new McpServer({ name: "ping-mcp", version: "1.0.0" });
|
||||
server.tool(
|
||||
"ping",
|
||||
"Reachability health-check. Returns pong with a timestamp and a server identity string.",
|
||||
{},
|
||||
async () => ({
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `pong @ ${new Date().toISOString()} — ping-mcp on sv006 (nonce ${randomUUID()})`,
|
||||
}],
|
||||
}),
|
||||
);
|
||||
return server;
|
||||
}
|
||||
|
||||
// Stateless Streamable HTTP: a fresh server + transport per request.
|
||||
app.post("/mcp", async (req, res) => {
|
||||
try {
|
||||
const server = buildServer();
|
||||
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
||||
res.on("close", () => { transport.close(); server.close(); });
|
||||
await server.connect(transport);
|
||||
await transport.handleRequest(req, res, req.body);
|
||||
} catch (err) {
|
||||
console.error("MCP request error:", err);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ jsonrpc: "2.0", error: { code: -32603, message: "Internal server error" }, id: null });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Stateless mode has no server-initiated stream / session teardown.
|
||||
const notAllowed = (_req, res) =>
|
||||
res.status(405).json({ jsonrpc: "2.0", error: { code: -32000, message: "Method not allowed (stateless)" }, id: null });
|
||||
app.get("/mcp", notAllowed);
|
||||
app.delete("/mcp", notAllowed);
|
||||
|
||||
app.listen(PORT, "0.0.0.0", () => console.log(`ping-mcp listening on 0.0.0.0:${PORT} (/mcp, /health)`));
|
||||
```
|
||||
|
||||
> If the installed SDK's API differs (e.g. `registerTool` instead of `tool`, or a changed `StreamableHTTPServerTransport` constructor), adapt the code. The contract that matters: a working **stateless Streamable HTTP** MCP endpoint at `POST /mcp` exposing exactly one tool named `ping`, plus a `GET /health` returning `ok`. Binding `0.0.0.0` inside the container is correct; the container is only published to host loopback.
|
||||
|
||||
**`~/ping_mcp/Containerfile`**
|
||||
```dockerfile
|
||||
FROM docker.io/library/node:22-bookworm-slim
|
||||
WORKDIR /app
|
||||
COPY package.json ./
|
||||
RUN npm install --omit=dev
|
||||
COPY server.js ./
|
||||
EXPOSE 8095
|
||||
USER node
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
Pin the base image to a concrete patch tag (e.g. `node:22.x.y-bookworm-slim`) consistent with the house pinning discipline used for the EspoCRM/MariaDB images.
|
||||
|
||||
## Step 2 — Build and run (rootless Podman, user `mkt`)
|
||||
|
||||
```bash
|
||||
cd ~/ping_mcp
|
||||
podman build -t ping-mcp:1.0.0 .
|
||||
podman run -d --name ping_mcp_ctr -p 127.0.0.1:8095:8095 ping-mcp:1.0.0
|
||||
```
|
||||
|
||||
## Step 3 — Autostart via user systemd + linger
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/systemd/user
|
||||
cd ~/.config/systemd/user
|
||||
podman generate systemd --new --name ping_mcp_ctr --files
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now container-ping_mcp_ctr.service
|
||||
loginctl enable-linger mkt # likely already enabled for the EspoCRM pod; harmless if so
|
||||
```
|
||||
|
||||
## Step 4 — Local verification (before handing off)
|
||||
|
||||
```bash
|
||||
# 1) Health endpoint
|
||||
curl -s http://127.0.0.1:8095/health # expect: ok
|
||||
|
||||
# 2) MCP initialize handshake (Streamable HTTP requires this Accept header)
|
||||
curl -s -X POST http://127.0.0.1:8095/mcp \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Accept: application/json, text/event-stream' \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'
|
||||
# expect: a JSON-RPC result containing serverInfo {name:"ping-mcp", ...} and capabilities.
|
||||
|
||||
# 3) tools/list
|
||||
curl -s -X POST http://127.0.0.1:8095/mcp \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Accept: application/json, text/event-stream' \
|
||||
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
|
||||
# expect: a tools array containing one tool named "ping".
|
||||
```
|
||||
The response body may be framed as `text/event-stream` (a `data:` line) rather than plain JSON — that is expected for Streamable HTTP. As long as the JSON-RPC result is present and well-formed, the server is good.
|
||||
|
||||
## Definition of done
|
||||
|
||||
- `curl http://127.0.0.1:8095/health` returns `ok`.
|
||||
- The `initialize` and `tools/list` calls return valid JSON-RPC results, and `tools/list` shows the `ping` tool.
|
||||
- `systemctl --user status container-ping_mcp_ctr.service` is active and enabled.
|
||||
- Report back: confirmation of the above, plus the exact base-image tag you pinned.
|
||||
|
||||
## Teardown (after the reachability test concludes)
|
||||
|
||||
```bash
|
||||
systemctl --user disable --now container-ping_mcp_ctr.service
|
||||
rm -f ~/.config/systemd/user/container-ping_mcp_ctr.service && systemctl --user daemon-reload
|
||||
podman rm -f ping_mcp_ctr
|
||||
podman rmi ping-mcp:1.0.0
|
||||
# ~/ping_mcp/ can be kept as a template or removed.
|
||||
```
|
||||
Reference in New Issue
Block a user