Skip to content

Commit de423e5

Browse files
authored
Merge pull request #660 from objectstack-ai/copilot/enrich-crm-example-metadata
2 parents 553fd49 + 9ca0076 commit de423e5

19 files changed

+731
-211
lines changed

ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ Full adoption of Cloud namespace, contracts/integration/security/studio modules,
8181

8282
### CRM Example Metadata Enrichment ✅
8383

84-
Enriched all 8 CRM object definitions (`account`, `contact`, `opportunity`, `product`, `order`, `user`, `project_task`, `event`) to exercise the full `@objectstack/spec` feature set. Added `description` to all objects; field enrichments (`required`, `searchable`, `unique`, `defaultValue`, `helpText`, `placeholder`, `readonly`); diverse field types (`richtext`, `phone`, `avatar`, `color`, `multi-select`); 30+ new fields (tags, linkedin, expected_revenue, shipping_address, etc.); 2+ list views per object with sort/filter; select options with colors across all objects; updated seed data leveraging new fields.
84+
Enriched all 8 CRM object definitions (`account`, `contact`, `opportunity`, `product`, `order`, `user`, `project_task`, `event`) to exercise the full `@objectstack/spec` feature set. Added `description` to all objects; field enrichments (`required`, `searchable`, `unique`, `defaultValue`, `readonly`); diverse field types (`richtext`, `phone`, `avatar`, `color`, `multi-select`); 30+ new fields (tags, linkedin, expected_revenue, shipping_address, etc.); 2+ list views per object with sort/filter; select options with colors across all objects; updated seed data leveraging new fields. Added 21 object-level actions across all 8 objects (Send Email, Change Status, Assign Owner, Mark as Won/Lost, Generate Invoice, Reset Password, etc.) with `params`, `confirmText`, `variant`, `locations`, and `refreshAfter`. Added sections-based `form_layout` for all 8 objects organizing fields into logical groups (e.g., Basic Info, Financials, Address, Timeline) with `columns`, `collapsible`, and field grouping.
8585

8686
### Architecture
8787

examples/crm/objectstack.config.ts

Lines changed: 34 additions & 210 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,22 @@ import { OrderObject } from './src/objects/order.object';
88
import { UserObject } from './src/objects/user.object';
99
import { ProjectObject } from './src/objects/project.object';
1010
import { EventObject } from './src/objects/event.object';
11+
import { AccountView } from './src/views/account.view';
12+
import { ContactView } from './src/views/contact.view';
13+
import { OpportunityView } from './src/views/opportunity.view';
14+
import { ProductView } from './src/views/product.view';
15+
import { OrderView } from './src/views/order.view';
16+
import { UserView } from './src/views/user.view';
17+
import { EventView } from './src/views/event.view';
18+
import { ProjectView } from './src/views/project.view';
19+
import { AccountActions } from './src/actions/account.actions';
20+
import { ContactActions } from './src/actions/contact.actions';
21+
import { OpportunityActions } from './src/actions/opportunity.actions';
22+
import { ProductActions } from './src/actions/product.actions';
23+
import { OrderActions } from './src/actions/order.actions';
24+
import { UserActions } from './src/actions/user.actions';
25+
import { ProjectActions } from './src/actions/project.actions';
26+
import { EventActions } from './src/actions/event.actions';
1127

1228
export default defineStack({
1329
objects: [
@@ -21,218 +37,26 @@ export default defineStack({
2137
EventObject
2238
],
2339
views: [
24-
// --- Account Views ---
25-
{
26-
listViews: {
27-
all_accounts: {
28-
name: 'all_accounts',
29-
label: 'All Accounts',
30-
type: 'grid',
31-
data: { provider: 'object', object: 'account' },
32-
columns: ['name', 'industry', 'type', 'annual_revenue', 'rating', 'owner'],
33-
sort: [{ field: 'name', order: 'asc' }],
34-
},
35-
active_accounts: {
36-
name: 'active_accounts',
37-
label: 'Active Customers',
38-
type: 'grid',
39-
data: { provider: 'object', object: 'account' },
40-
columns: ['name', 'industry', 'annual_revenue', 'phone', 'owner'],
41-
filter: ['type', '=', 'Customer'],
42-
sort: [{ field: 'annual_revenue', order: 'desc' }],
43-
},
44-
},
45-
},
46-
// --- Contact Views ---
47-
{
48-
listViews: {
49-
all_contacts: {
50-
name: 'all_contacts',
51-
label: 'All Contacts',
52-
type: 'grid',
53-
data: { provider: 'object', object: 'contact' },
54-
columns: ['name', 'email', 'phone', 'title', 'account', 'status', 'priority'],
55-
sort: [{ field: 'name', order: 'asc' }],
56-
},
57-
active_contacts: {
58-
name: 'active_contacts',
59-
label: 'Active Contacts',
60-
type: 'grid',
61-
data: { provider: 'object', object: 'contact' },
62-
columns: ['name', 'email', 'title', 'account', 'status'],
63-
filter: ['is_active', '=', true],
64-
},
65-
},
66-
},
67-
// --- Opportunity Views ---
68-
{
69-
listViews: {
70-
all: {
71-
name: 'all',
72-
label: 'All Opportunities',
73-
type: 'grid',
74-
data: { provider: 'object', object: 'opportunity' },
75-
columns: ['name', 'amount', 'stage', 'close_date', 'probability', 'forecast_category'],
76-
sort: [{ field: 'close_date', order: 'asc' }],
77-
},
78-
pipeline: {
79-
name: 'pipeline',
80-
label: 'Pipeline',
81-
type: 'kanban',
82-
data: { provider: 'object', object: 'opportunity' },
83-
columns: ['name', 'amount', 'close_date', 'probability'],
84-
kanban: {
85-
groupByField: 'stage',
86-
columns: ['name', 'amount', 'close_date'],
87-
},
88-
},
89-
closing_this_month: {
90-
name: 'closing_this_month',
91-
label: 'Closing This Month',
92-
type: 'grid',
93-
data: { provider: 'object', object: 'opportunity' },
94-
columns: ['name', 'amount', 'stage', 'close_date', 'probability'],
95-
sort: [{ field: 'close_date', order: 'asc' }],
96-
},
97-
},
98-
},
99-
// --- Product Views ---
100-
{
101-
listViews: {
102-
all_products: {
103-
name: 'all_products',
104-
label: 'All Products',
105-
type: 'grid',
106-
data: { provider: 'object', object: 'product' },
107-
columns: ['name', 'sku', 'category', 'price', 'stock', 'is_active'],
108-
sort: [{ field: 'name', order: 'asc' }],
109-
},
110-
active_products: {
111-
name: 'active_products',
112-
label: 'Active Products',
113-
type: 'grid',
114-
data: { provider: 'object', object: 'product' },
115-
columns: ['name', 'sku', 'category', 'price', 'stock', 'tags'],
116-
filter: ['is_active', '=', true],
117-
},
118-
},
119-
},
120-
// --- Order Views ---
121-
{
122-
listViews: {
123-
all_orders: {
124-
name: 'all_orders',
125-
label: 'All Orders',
126-
type: 'grid',
127-
data: { provider: 'object', object: 'order' },
128-
columns: ['name', 'customer', 'amount', 'status', 'order_date', 'payment_method'],
129-
sort: [{ field: 'order_date', order: 'desc' }],
130-
},
131-
pending_orders: {
132-
name: 'pending_orders',
133-
label: 'Pending Orders',
134-
type: 'grid',
135-
data: { provider: 'object', object: 'order' },
136-
columns: ['name', 'customer', 'amount', 'order_date'],
137-
filter: ['status', '=', 'pending'],
138-
},
139-
},
140-
},
141-
// --- User Views ---
142-
{
143-
listViews: {
144-
all_users: {
145-
name: 'all_users',
146-
label: 'All Users',
147-
type: 'grid',
148-
data: { provider: 'object', object: 'user' },
149-
columns: ['name', 'email', 'role', 'department', 'active'],
150-
sort: [{ field: 'name', order: 'asc' }],
151-
},
152-
active_users: {
153-
name: 'active_users',
154-
label: 'Active Users',
155-
type: 'grid',
156-
data: { provider: 'object', object: 'user' },
157-
columns: ['name', 'email', 'role', 'title'],
158-
filter: ['active', '=', true],
159-
},
160-
},
161-
},
162-
// --- Event Views ---
163-
{
164-
listViews: {
165-
all_events: {
166-
name: 'all_events',
167-
label: 'All Events',
168-
type: 'grid',
169-
data: { provider: 'object', object: 'event' },
170-
columns: ['subject', 'start', 'end', 'location', 'type', 'status'],
171-
sort: [{ field: 'start', order: 'asc' }],
172-
},
173-
calendar: {
174-
name: 'calendar',
175-
label: 'Calendar',
176-
type: 'calendar',
177-
data: { provider: 'object', object: 'event' },
178-
columns: ['subject', 'start', 'end', 'type'],
179-
calendar: {
180-
startDateField: 'start',
181-
endDateField: 'end',
182-
titleField: 'subject',
183-
},
184-
},
185-
upcoming_meetings: {
186-
name: 'upcoming_meetings',
187-
label: 'Upcoming Meetings',
188-
type: 'grid',
189-
data: { provider: 'object', object: 'event' },
190-
columns: ['subject', 'start', 'location', 'organizer'],
191-
filter: ['type', '=', 'meeting'],
192-
sort: [{ field: 'start', order: 'asc' }],
193-
},
194-
},
195-
},
196-
// --- Project Task Views ---
197-
{
198-
listViews: {
199-
all_tasks: {
200-
name: 'all_tasks',
201-
label: 'All Tasks',
202-
type: 'grid',
203-
data: { provider: 'object', object: 'project_task' },
204-
columns: ['name', 'status', 'priority', 'start_date', 'end_date', 'progress', 'assignee'],
205-
sort: [{ field: 'start_date', order: 'asc' }],
206-
},
207-
board: {
208-
name: 'board',
209-
label: 'Board',
210-
type: 'kanban',
211-
data: { provider: 'object', object: 'project_task' },
212-
columns: ['name', 'priority', 'start_date', 'end_date'],
213-
kanban: {
214-
groupByField: 'status',
215-
columns: ['name', 'priority', 'start_date', 'end_date'],
216-
},
217-
},
218-
gantt: {
219-
name: 'gantt',
220-
label: 'Gantt',
221-
type: 'gantt',
222-
data: { provider: 'object', object: 'project_task' },
223-
columns: ['name', 'start_date', 'end_date', 'progress', 'status'],
224-
},
225-
timeline: {
226-
name: 'timeline',
227-
label: 'Timeline',
228-
type: 'timeline',
229-
data: { provider: 'object', object: 'project_task' },
230-
columns: ['name', 'start_date', 'status'],
231-
},
232-
},
233-
},
40+
AccountView,
41+
ContactView,
42+
OpportunityView,
43+
ProductView,
44+
OrderView,
45+
UserView,
46+
EventView,
47+
ProjectView,
23448
],
23549
reports: [],
50+
actions: [
51+
...AccountActions,
52+
...ContactActions,
53+
...OpportunityActions,
54+
...ProductActions,
55+
...OrderActions,
56+
...UserActions,
57+
...ProjectActions,
58+
...EventActions,
59+
],
23660
pages: [
23761
{
23862
name: 'crm_help',
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
export const AccountActions = [
2+
{
3+
name: 'account_send_email',
4+
label: 'Send Email',
5+
icon: 'mail',
6+
type: 'api' as const,
7+
locations: ['record_header' as const, 'list_item' as const],
8+
params: [
9+
{ name: 'to', label: 'To Email', type: 'email' as const, required: true },
10+
{ name: 'subject', label: 'Subject', type: 'text' as const, required: true },
11+
{ name: 'body', label: 'Message', type: 'textarea' as const },
12+
],
13+
successMessage: 'Email sent successfully',
14+
},
15+
{
16+
name: 'account_assign_owner',
17+
label: 'Assign Owner',
18+
icon: 'user-plus',
19+
type: 'api' as const,
20+
locations: ['record_header' as const, 'list_item' as const],
21+
params: [
22+
{ name: 'owner_id', label: 'New Owner', type: 'lookup' as const, required: true },
23+
],
24+
refreshAfter: true,
25+
successMessage: 'Owner assigned successfully',
26+
},
27+
{
28+
name: 'account_merge',
29+
label: 'Merge Accounts',
30+
icon: 'git-merge',
31+
type: 'api' as const,
32+
locations: ['record_more' as const],
33+
confirmText: 'Are you sure you want to merge these accounts? This action cannot be undone.',
34+
variant: 'danger' as const,
35+
refreshAfter: true,
36+
},
37+
];
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
export const ContactActions = [
2+
{
3+
name: 'contact_send_email',
4+
label: 'Send Email',
5+
icon: 'mail',
6+
type: 'api' as const,
7+
locations: ['record_header' as const, 'list_item' as const],
8+
params: [
9+
{ name: 'subject', label: 'Subject', type: 'text' as const, required: true },
10+
{ name: 'body', label: 'Message', type: 'textarea' as const },
11+
],
12+
successMessage: 'Email sent successfully',
13+
},
14+
{
15+
name: 'contact_convert_to_customer',
16+
label: 'Convert to Customer',
17+
icon: 'user-check',
18+
type: 'api' as const,
19+
locations: ['record_header' as const],
20+
confirmText: 'Convert this contact to a customer?',
21+
refreshAfter: true,
22+
successMessage: 'Contact converted to customer',
23+
},
24+
{
25+
name: 'contact_log_call',
26+
label: 'Log a Call',
27+
icon: 'phone',
28+
type: 'api' as const,
29+
locations: ['record_header' as const, 'list_item' as const],
30+
params: [
31+
{ name: 'call_subject', label: 'Subject', type: 'text' as const, required: true },
32+
{ name: 'call_notes', label: 'Notes', type: 'textarea' as const },
33+
{ name: 'call_duration', label: 'Duration (min)', type: 'number' as const },
34+
],
35+
successMessage: 'Call logged successfully',
36+
},
37+
];
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
export const EventActions = [
2+
{
3+
name: 'event_send_invitation',
4+
label: 'Send Invitation',
5+
icon: 'send',
6+
type: 'api' as const,
7+
locations: ['record_header' as const],
8+
params: [
9+
{ name: 'message', label: 'Optional Message', type: 'textarea' as const },
10+
],
11+
successMessage: 'Invitation sent to all participants',
12+
},
13+
{
14+
name: 'event_mark_completed',
15+
label: 'Mark as Completed',
16+
icon: 'check-circle',
17+
type: 'api' as const,
18+
locations: ['record_header' as const],
19+
confirmText: 'Mark this event as completed?',
20+
refreshAfter: true,
21+
successMessage: 'Event marked as completed',
22+
},
23+
{
24+
name: 'event_cancel',
25+
label: 'Cancel Event',
26+
icon: 'x-circle',
27+
type: 'api' as const,
28+
locations: ['record_more' as const],
29+
variant: 'danger' as const,
30+
params: [
31+
{ name: 'cancel_reason', label: 'Cancellation Reason', type: 'text' as const },
32+
{ name: 'notify_participants', label: 'Notify Participants', type: 'boolean' as const },
33+
],
34+
confirmText: 'Are you sure you want to cancel this event?',
35+
refreshAfter: true,
36+
successMessage: 'Event cancelled',
37+
},
38+
];

0 commit comments

Comments
 (0)