feat: Custom fields — JSONB with schema per entity (PRD §5.5)
Models: CustomFieldDef (schema definitions) + CustomFieldValue (entity values) - Per entity type: company, person, deal, project, invoice, ticket, contract - Field types: string, number, boolean, date, select, multiselect - Options for select/multiselect stored as JSON - Unique constraint: (entityType, key) for definitions, (entityType, entityId, fieldDefId) for values API /api/aos/custom-fields: - GET: list all definitions (+ filter by entityType) - POST: create definition (Zod validated, lowercase_snake_case key, unique check) - PATCH: set value on entity (upsert pattern) Seed: 10 field definitions across 6 entity types - company: branche, mitarbeiterzahl, umsatzklasse - person: entscheider, kontaktweg - deal: wettbewerber, entscheidungsgrund - project: prioritaet - invoice: kostenstelle - ticket: auswirkung Verified: 10 defs, 3 company fields, duplicate→409, invalid key→400
This commit is contained in:
1 parent
d5dc2a1832
commit
81acd9df7a
5 files changed
+199
No files matched your search
@@ -1215,3 +1215,20 @@ main()
|
||||
.finally(async () => {
|
||||
await db.$disconnect()
|
||||
})
|
||||
|
||||
// --- Custom Fields (PRD §5.5: JSONB with schema per entity) ---------------
|
||||
const customFields = [
|
||||
{ entityType: 'company', key: 'branche', label: 'Branche', type: 'select', options: JSON.stringify(['Handwerk', 'Gastronomie', 'Einzelhandel', 'Dienstleistung', 'Industrie', 'Gesundheit', 'Technologie']), required: false, sortOrder: 1 },
|
||||
{ entityType: 'company', key: 'mitarbeiterzahl', label: 'Mitarbeiterzahl', type: 'select', options: JSON.stringify(['1-10', '11-50', '51-200', '201-500', '500+']), required: false, sortOrder: 2 },
|
||||
{ entityType: 'company', key: 'umsatzklasse', label: 'Umsatzklasse', type: 'select', options: JSON.stringify(['< 100k', '100k-500k', '500k-2M', '2M-10M', '10M+']), required: false, sortOrder: 3 },
|
||||
{ entityType: 'person', key: 'entscheider', label: 'Entscheider?', type: 'boolean', required: false, sortOrder: 1 },
|
||||
{ entityType: 'person', key: 'kontaktweg', label: 'Bevorzugter Kontaktweg', type: 'select', options: JSON.stringify(['E-Mail', 'Telefon', 'Teams', 'Persönlich']), required: false, sortOrder: 2 },
|
||||
{ entityType: 'deal', key: 'wettbewerber', label: 'Wettbewerber', type: 'string', required: false, sortOrder: 1 },
|
||||
{ entityType: 'deal', key: 'entscheidungsgrund', label: 'Entscheidungsgrund', type: 'select', options: JSON.stringify(['Preis', 'Qualität', 'Geschwindigkeit', 'Referenzen', 'Technologie']), required: false, sortOrder: 2 },
|
||||
{ entityType: 'project', key: 'prioritaet', label: 'Priorität (kundenseitig)', type: 'select', options: JSON.stringify(['niedrig', 'normal', 'hoch', 'kritisch']), required: false, sortOrder: 1 },
|
||||
{ entityType: 'invoice', key: 'kostenstelle', label: 'Kostenstelle', type: 'string', required: false, sortOrder: 1 },
|
||||
{ entityType: 'ticket', key: 'auswirkung', label: 'Geschäftsauswirkung', type: 'select', options: JSON.stringify(['keine', 'niedrig', 'mittel', 'hoch', 'kritisch']), required: false, sortOrder: 1 },
|
||||
]
|
||||
for (const cf of customFields) {
|
||||
await db.customFieldDef.create({ data: cf })
|
||||
}
|
||||
Reference in new issue
Block a user