CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/util/db-schema/crm.ts
Views: 687
1
/*
2
NOTES:
3
4
- right now the Table function types.ts also automatically does some
5
extra things for all crm_ tables to ensure safety, e.g., ensuring admin.
6
7
- do NOT use defaults for get.fields! Those are set by the frontend crm code when creating records.
8
Also, they are not compatible with null-ing fields.
9
10
*/
11
12
import { FieldSpec, Table } from "./types";
13
import { blue, green, red, yellow } from "@ant-design/colors";
14
15
export const NOTES: FieldSpec = {
16
type: "string",
17
desc: "Open ended text in markdown about this item.",
18
render: {
19
type: "markdown",
20
editable: true,
21
},
22
} as const;
23
24
export const ID: FieldSpec = {
25
type: "integer",
26
desc: "Automatically generated sequential id that uniquely determines this row.",
27
pg_type: "SERIAL UNIQUE",
28
noCoerce: true,
29
} as const;
30
31
const TAG_TYPE = `INTEGER[]`;
32
33
const TAGS_FIELD: FieldSpec = {
34
type: "array",
35
pg_type: TAG_TYPE,
36
desc: "Tags applied to this record.",
37
render: { type: "tags", editable: true },
38
} as const;
39
40
const PRORITIES_FIELD: FieldSpec = {
41
type: "string",
42
pg_type: "VARCHAR(30)",
43
desc: "Priority of this record",
44
render: {
45
type: "select",
46
editable: true,
47
options: ["low", "normal", "high", "urgent"],
48
colors: [yellow[5], blue[5], green[5], red[5]],
49
priority: true,
50
},
51
} as const;
52
53
const STATUS_FIELD: FieldSpec = {
54
type: "string",
55
pg_type: "VARCHAR(30)",
56
desc: "Status of this record",
57
render: {
58
type: "select",
59
editable: true,
60
options: ["new", "open", "pending", "active", "solved"],
61
colors: [yellow[5], red[5], green[5], blue[5], "#888"],
62
},
63
} as const;
64
65
export const CREATED: FieldSpec = {
66
type: "timestamp",
67
desc: "When the record was created.",
68
} as const;
69
70
export const LAST_EDITED: FieldSpec = {
71
type: "timestamp",
72
desc: "When this record was last edited.",
73
} as const;
74
75
const LAST_MODIFIED_BY: FieldSpec = {
76
type: "uuid",
77
desc: "Account that last modified this task.",
78
render: { type: "account" },
79
} as const;
80
81
const ASSIGNEE: FieldSpec = {
82
type: "uuid",
83
desc: "Account that is responsible for resolving this.",
84
render: {
85
type: "assignee",
86
editable: true,
87
},
88
} as const;
89
90
Table({
91
name: "crm_people",
92
fields: {
93
id: ID,
94
created: CREATED,
95
last_edited: LAST_EDITED,
96
name: {
97
type: "string",
98
pg_type: "VARCHAR(254)",
99
desc: "The name of this person.",
100
render: {
101
type: "text",
102
maxLength: 254,
103
editable: true,
104
},
105
},
106
email_addresses: {
107
type: "array",
108
pg_type: "VARCHAR(1000)",
109
desc: "Email addresses for this person, separated by commas",
110
render: {
111
type: "text",
112
maxLength: 1000,
113
editable: true,
114
},
115
},
116
account_ids: {
117
title: "Accounts",
118
type: "array",
119
pg_type: "UUID[]",
120
desc: "Array of 0 or more uuid's of CoCalc accounts that this person may have.",
121
render: {
122
type: "accounts",
123
editable: true,
124
},
125
},
126
deleted: {
127
type: "boolean",
128
desc: "True if the person has been deleted.",
129
},
130
notes: NOTES,
131
// https://stackoverflow.com/questions/13837258/what-is-an-appropriate-data-type-to-store-a-timezone
132
timezone: {
133
type: "string",
134
desc: "The person's time zone, e.g., 'Europe/Paris' or 'US/Pacific'.",
135
render: {
136
type: "text",
137
maxLength: 254,
138
editable: true,
139
},
140
},
141
tags: TAGS_FIELD,
142
},
143
rules: {
144
desc: "People",
145
primary_key: "id",
146
user_query: {
147
get: {
148
pg_where: [],
149
admin: true,
150
fields: {
151
id: null,
152
created: null,
153
last_edited: null,
154
email_addresses: null,
155
name: null,
156
account_ids: null,
157
deleted: null,
158
notes: null,
159
tags: null,
160
},
161
},
162
set: {
163
admin: true,
164
fields: {
165
id: true,
166
created: true,
167
last_edited: true,
168
name: true,
169
email_addresses: true,
170
account_ids: true,
171
deleted: true,
172
notes: true,
173
tags: null,
174
},
175
required_fields: {
176
last_edited: true, // TODO: make automatic on any set query
177
},
178
},
179
},
180
},
181
});
182
183
const ORGANIZATIONS = {
184
type: "array",
185
pg_type: "INTEGER[]",
186
desc: "Zero or more organizations in the Organizations table",
187
render: {
188
type: "organizations",
189
editable: true,
190
},
191
} as FieldSpec;
192
193
export const CREATED_BY = {
194
type: "uuid",
195
desc: "Account that created this record.",
196
render: { type: "account" },
197
} as FieldSpec;
198
199
const PERSON = {
200
type: "integer",
201
desc: "One person in the People table",
202
render: {
203
type: "person",
204
editable: true,
205
},
206
} as FieldSpec;
207
208
const PEOPLE = {
209
type: "array",
210
pg_type: "INTEGER[]",
211
desc: "Array of 0 or more people in the People table that are connected with this",
212
render: {
213
type: "people",
214
editable: true,
215
},
216
} as FieldSpec;
217
218
// TODO: add image -- probably want to use blob table (?) but maybe do like with projects. Not sure.
219
Table({
220
name: "crm_organizations",
221
fields: {
222
id: ID,
223
created: CREATED,
224
last_edited: LAST_EDITED,
225
name: {
226
type: "string",
227
pg_type: "VARCHAR(254)",
228
desc: "The name of this organization.",
229
render: {
230
type: "text",
231
maxLength: 254,
232
editable: true,
233
},
234
},
235
people: PEOPLE,
236
organizations: {
237
title: "Related Organizations",
238
type: "array",
239
pg_type: "INTEGER[]",
240
desc: "Array of 0 or more organization that are connected with this organization",
241
render: {
242
type: "organizations",
243
editable: true,
244
},
245
},
246
deleted: {
247
type: "boolean",
248
desc: "True if this org has been deleted.",
249
},
250
notes: NOTES,
251
timezone: {
252
type: "string",
253
desc: "The organizations's time zone, e.g., 'Europe/Paris' or 'US/Pacific'.",
254
render: {
255
type: "text",
256
editable: true,
257
},
258
},
259
domain: {
260
type: "string",
261
pg_type: "VARCHAR(254)", // todo -- should this be an array of domain names?
262
desc: "Domain name of this org, e.g., math.washington.edu.",
263
render: {
264
type: "text",
265
editable: true,
266
maxLength: 254,
267
},
268
},
269
tags: TAGS_FIELD,
270
},
271
rules: {
272
desc: "Organizations",
273
primary_key: "id",
274
user_query: {
275
get: {
276
pg_where: [],
277
admin: true,
278
fields: {
279
id: null,
280
created: null,
281
last_edited: null,
282
name: null,
283
people: null,
284
organizations: null,
285
deleted: null,
286
notes: null,
287
domain: null,
288
tags: null,
289
},
290
},
291
set: {
292
admin: true,
293
fields: {
294
id: true,
295
created: true,
296
last_edited: true,
297
name: true,
298
people: true,
299
organizations: true,
300
deleted: true,
301
notes: true,
302
domain: true,
303
tags: true,
304
},
305
required_fields: {
306
last_edited: true, // TODO: make automatic on any set query
307
},
308
},
309
},
310
},
311
});
312
313
Table({
314
name: "crm_support_tickets",
315
fields: {
316
id: ID,
317
subject: {
318
type: "string",
319
pg_type: "VARCHAR(254)",
320
desc: "Subject of the message. Must be short.",
321
render: {
322
type: "text",
323
maxLength: 254,
324
editable: true,
325
},
326
},
327
created: CREATED,
328
created_by: PERSON,
329
last_edited: LAST_EDITED,
330
last_modified_by: LAST_MODIFIED_BY,
331
assignee: ASSIGNEE,
332
tasks: {
333
title: "Tasks",
334
type: "array",
335
pg_type: "integer[]",
336
desc: "Tasks associated with this support ticket.",
337
},
338
cc: {
339
type: "array",
340
pg_type: "UUID[]",
341
desc: "Zero or more support accounts that care to be contacted about updates to this ticket.",
342
},
343
tags: TAGS_FIELD,
344
priority: PRORITIES_FIELD,
345
status: STATUS_FIELD,
346
type: {
347
type: "string",
348
pg_type: "VARCHAR(30)",
349
desc: "The type of this ticket: question, incident, problem, task, etc.",
350
render: { type: "text", editable: true, maxLength: 30 },
351
},
352
},
353
rules: {
354
desc: "Support Tickets",
355
primary_key: "id",
356
user_query: {
357
get: {
358
pg_where: [],
359
admin: true,
360
fields: {
361
id: null,
362
subject: null,
363
created: null,
364
created_by: null,
365
last_edited: null,
366
last_modified_by: null,
367
assignee: null,
368
tasks: null,
369
cc: null,
370
tags: null,
371
type: null,
372
priority: null,
373
status: null,
374
},
375
},
376
set: {
377
admin: true,
378
fields: {
379
id: true,
380
subject: true,
381
created: true,
382
last_edited: true,
383
last_modified_by: true,
384
created_by: true,
385
assignee: true,
386
tasks: true,
387
cc: true,
388
tags: true,
389
type: true,
390
priority: true,
391
status: true,
392
},
393
required_fields: {
394
last_edited: true, // TODO: make automatic on any set query
395
},
396
},
397
},
398
},
399
});
400
401
Table({
402
name: "crm_support_messages",
403
fields: {
404
id: ID,
405
ticket_id: {
406
type: "integer",
407
desc: "Support ticket id that this message is connected to.",
408
},
409
created: {
410
type: "timestamp",
411
desc: "When the message was created. (We may save periodically before actually marking it sent.)",
412
},
413
last_edited: {
414
type: "timestamp",
415
desc: "When this message was actually sent.",
416
},
417
sent_by: PERSON,
418
body: {
419
type: "string",
420
desc: "Actual content of the message. This is interpretted as markdown.",
421
render: {
422
type: "markdown",
423
editable: true,
424
maxLength: 20000,
425
},
426
},
427
internal: {
428
type: "boolean",
429
desc: "If true, the message is internal and only visible to support staff.",
430
render: {
431
type: "boolean",
432
editable: true,
433
},
434
},
435
},
436
rules: {
437
desc: "Support Messages",
438
primary_key: "id",
439
user_query: {
440
get: {
441
pg_where: [],
442
admin: true,
443
fields: {
444
id: null,
445
ticket_id: null,
446
created: null,
447
last_edited: null,
448
sent_by: null,
449
body: null,
450
internal: null,
451
},
452
},
453
set: {
454
admin: true,
455
fields: {
456
id: true,
457
ticket_id: true,
458
created: true,
459
last_edited: true,
460
sent_by: true,
461
body: true,
462
internal: true,
463
},
464
required_fields: {
465
last_edited: true, // TODO: make automatic on any set query
466
},
467
},
468
},
469
},
470
});
471
472
Table({
473
name: "crm_tasks",
474
fields: {
475
id: ID,
476
subject: {
477
type: "string",
478
pg_type: "VARCHAR(254)",
479
desc: "Short summary of this tasks.",
480
render: {
481
type: "text",
482
maxLength: 254,
483
editable: true,
484
},
485
},
486
due_date: {
487
title: "Due",
488
type: "timestamp",
489
desc: "When this task is due.",
490
render: {
491
type: "timestamp",
492
editable: true,
493
},
494
},
495
created: CREATED,
496
last_edited: LAST_EDITED,
497
closed: {
498
type: "timestamp",
499
title: "When closed",
500
desc: "When the task was marked as done.",
501
render: {
502
type: "timestamp",
503
editable: false,
504
},
505
},
506
done: {
507
type: "boolean",
508
desc: "The task is done.",
509
render: {
510
type: "boolean",
511
editable: true,
512
whenField: "closed",
513
},
514
},
515
status: STATUS_FIELD,
516
progress: {
517
type: "integer",
518
desc: "Progress on this task, as a number from 0 to 100.",
519
render: {
520
type: "percent",
521
editable: true,
522
steps: 5,
523
},
524
},
525
priority: PRORITIES_FIELD,
526
support_ticket: {
527
type: "integer",
528
desc: "Support ticket that this task is connected to, if any.",
529
},
530
people: PEOPLE,
531
organizations: ORGANIZATIONS,
532
created_by: CREATED_BY,
533
last_modified_by: LAST_MODIFIED_BY,
534
assignee: ASSIGNEE,
535
cc: {
536
type: "array",
537
pg_type: "UUID[]",
538
desc: "Zero or more accounts that care to be contacted/notified about updates to this task.",
539
},
540
tags: TAGS_FIELD,
541
description: {
542
type: "string",
543
desc: "Full markdown task description",
544
render: {
545
type: "markdown",
546
editable: true,
547
},
548
},
549
},
550
rules: {
551
desc: "Tasks",
552
primary_key: "id",
553
user_query: {
554
get: {
555
pg_where: [],
556
admin: true,
557
fields: {
558
id: null,
559
subject: null,
560
due_date: null,
561
created: null,
562
done: null,
563
closed: null,
564
last_edited: null,
565
status: null,
566
progress: null,
567
priority: null,
568
people: null,
569
organizations: null,
570
support_ticket: null,
571
created_by: null,
572
last_modified_by: null,
573
assignee: null,
574
cc: null,
575
tags: null,
576
description: null,
577
},
578
},
579
set: {
580
admin: true,
581
fields: {
582
id: true,
583
subject: true,
584
due_date: true,
585
created: true,
586
done: true,
587
closed: true,
588
last_edited: true,
589
status: true,
590
progress: true,
591
priority: true,
592
people: true,
593
organizations: null,
594
support_ticket: true,
595
created_by: true,
596
last_modified_by: true,
597
assignee: true,
598
cc: true,
599
tags: true,
600
description: true,
601
},
602
required_fields: {
603
last_edited: true, // TODO: make automatic on any set query
604
},
605
},
606
},
607
},
608
});
609
610
// Table of all hashtags across our crm system. Note that these settings
611
// are very global. We may later make a similar table that is scoped to
612
// a project, file, user, etc...
613
Table({
614
name: "crm_tags",
615
fields: {
616
id: ID,
617
name: {
618
title: "Tag",
619
type: "string",
620
desc: "The name of the tag.",
621
pg_type: "VARCHAR(30)",
622
render: { type: "text", editable: true, maxLength: 30, tag: true },
623
unique: true,
624
},
625
icon: {
626
type: "string",
627
desc: "Name of icon to show with tag",
628
pg_type: "VARCHAR(100)", // ???
629
render: { type: "icon", editable: true },
630
},
631
description: {
632
type: "string",
633
desc: "Description of the tag.",
634
pg_type: "VARCHAR(254)",
635
render: { type: "markdown", editable: true },
636
},
637
color: {
638
type: "string",
639
desc: "color",
640
pg_type: "VARCHAR(30)",
641
render: { type: "color", editable: true },
642
},
643
notes: NOTES,
644
created: CREATED,
645
last_edited: LAST_EDITED,
646
last_modified_by: LAST_MODIFIED_BY,
647
},
648
rules: {
649
desc: "Table of all tags across our crm system.",
650
primary_key: "id",
651
user_query: {
652
get: {
653
admin: true,
654
pg_where: [],
655
fields: {
656
id: null,
657
name: null,
658
icon: null,
659
description: null,
660
notes: null,
661
color: null,
662
created: null,
663
last_edited: null,
664
last_modified_by: null,
665
},
666
},
667
set: {
668
admin: true,
669
fields: {
670
id: true,
671
name: true,
672
icon: true,
673
description: true,
674
notes: true,
675
color: true,
676
created: true,
677
last_edited: true,
678
last_modified_by: true,
679
},
680
required_fields: {
681
last_edited: true, // TODO: make automatic on any set query
682
},
683
},
684
},
685
},
686
});
687
688
Table({
689
name: "crm_leads",
690
fields: {
691
id: ID,
692
created: CREATED,
693
last_edited: LAST_EDITED,
694
people: PEOPLE,
695
deleted: {
696
type: "boolean",
697
desc: "True if the lead has been deleted.",
698
},
699
notes: {
700
type: "string",
701
desc: "Open ended text in markdown about this lead.",
702
render: { type: "markdown", editable: true },
703
},
704
assignee: ASSIGNEE,
705
tags: TAGS_FIELD,
706
status: {
707
type: "string",
708
pg_type: "VARCHAR(30)",
709
desc: "Status of this lead",
710
render: {
711
type: "select",
712
editable: true,
713
options: [
714
"Contact in Future",
715
"Attempted to Contact",
716
"Contacted",
717
"Junk Lead",
718
"Lost Lead",
719
"Not Contacted",
720
"Pre Qualified",
721
"Not Qualified",
722
],
723
colors: [
724
yellow[5],
725
green[4],
726
green[5],
727
red[5],
728
red[6],
729
yellow[5],
730
blue[5],
731
blue[6],
732
],
733
},
734
},
735
rating: {
736
type: "string",
737
pg_type: "VARCHAR(30)",
738
desc: "Rating of this lead",
739
render: {
740
type: "select",
741
editable: true,
742
priority: true,
743
options: [
744
"-None-",
745
"Shut Down",
746
"Project Canceled",
747
"Market Failed",
748
"Active",
749
"Acquired",
750
],
751
colors: [yellow[5], red[4], red[5], red[6], green[5], blue[5]],
752
},
753
},
754
annual_revenue: {
755
type: "number",
756
desc: "Rough estimate of possible annual revenue that could result from this lead.",
757
render: { type: "number", editable: true, format: "money", min: 0 },
758
},
759
},
760
rules: {
761
desc: "CRM Leads",
762
primary_key: "id",
763
user_query: {
764
get: {
765
pg_where: [],
766
admin: true,
767
fields: {
768
id: null,
769
created: null,
770
last_edited: null,
771
people: null,
772
deleted: null,
773
notes: null,
774
tags: null,
775
assignee: null,
776
status: null,
777
rating: null,
778
annual_revenue: null,
779
},
780
},
781
set: {
782
admin: true,
783
fields: {
784
id: true,
785
created: true,
786
last_edited: true,
787
people: true,
788
deleted: true,
789
notes: true,
790
tags: true,
791
assignee: true,
792
status: true,
793
rating: true,
794
annual_revenue: true,
795
},
796
required_fields: {
797
last_edited: true,
798
},
799
},
800
},
801
},
802
});
803
804