@objectql/create 4.0.0 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/templates/enterprise/CHANGELOG.md +20 -0
- package/templates/enterprise/README.md +2 -2
- package/templates/enterprise/package.json +2 -2
- package/templates/enterprise/src/core/attachment.object.yml +10 -1
- package/templates/enterprise/src/core/organization.object.yml +41 -6
- package/templates/enterprise/src/core/user.object.yml +37 -12
- package/templates/enterprise/src/extensions/README.md +4 -4
- package/templates/enterprise/src/extensions/user_extension.object.yml +65 -0
- package/templates/enterprise/src/modules/crm/crm_account.object.yml +57 -13
- package/templates/enterprise/src/modules/crm/crm_contact.object.yml +42 -8
- package/templates/enterprise/src/modules/crm/crm_lead.object.yml +82 -21
- package/templates/enterprise/src/modules/crm/crm_opportunity.object.yml +65 -15
- package/templates/enterprise/src/modules/finance/finance_budget.object.yml +67 -16
- package/templates/enterprise/src/modules/finance/finance_expense.object.yml +75 -19
- package/templates/enterprise/src/modules/finance/finance_invoice.object.yml +61 -16
- package/templates/enterprise/src/modules/finance/finance_payment.object.yml +65 -16
- package/templates/enterprise/src/modules/hr/hr_department.object.yml +29 -2
- package/templates/enterprise/src/modules/hr/hr_employee.object.yml +50 -8
- package/templates/enterprise/src/modules/hr/hr_position.object.yml +46 -10
- package/templates/enterprise/src/modules/hr/hr_timesheet.object.yml +44 -8
- package/templates/enterprise/src/modules/project/project_milestone.object.yml +36 -4
- package/templates/enterprise/src/modules/project/project_project.object.yml +64 -13
- package/templates/enterprise/src/modules/project/project_task.object.yml +70 -9
- package/templates/enterprise/src/modules/project/project_timesheet_entry.object.yml +39 -4
- package/templates/enterprise/src/plugins/audit/audit.plugin.ts +1 -1
- package/templates/enterprise/src/plugins/audit/note.object.yml +17 -0
- package/templates/enterprise/tsconfig.tsbuildinfo +1 -1
- package/templates/hello-world/CHANGELOG.md +16 -0
- package/templates/hello-world/README.md +73 -10
- package/templates/hello-world/package.json +1 -1
- package/templates/hello-world/src/index.ts +17 -5
- package/templates/starter/CHANGELOG.md +20 -0
- package/templates/starter/package.json +1 -1
- package/templates/starter/src/modules/projects/projects.action.ts +195 -346
- package/templates/starter/src/modules/projects/projects.hook.ts +98 -263
- package/templates/starter/src/modules/projects/projects.object.yml +65 -6
- package/templates/starter/src/modules/projects/projects.validation.yml +13 -4
- package/templates/starter/src/seed.ts +1 -1
- package/templates/starter/tsconfig.tsbuildinfo +1 -1
- package/templates/enterprise/__tests__/data-api.test.ts +0 -554
- package/templates/enterprise/__tests__/data-api.test.ts.backup +0 -526
- package/templates/enterprise/__tests__/metadata-api.test.ts +0 -315
- package/templates/enterprise/__tests__/metadata-loading.test.ts +0 -258
- package/templates/enterprise/src/extensions/user.extension.object.yml +0 -42
- package/templates/starter/__tests__/projects-hooks-actions.test.ts +0 -498
|
@@ -7,474 +7,323 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { ActionDefinition } from '@objectql/types';
|
|
10
|
-
import { Projects } from '../../types';
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
|
-
* Project Actions -
|
|
12
|
+
* Project Actions - Custom Business Operations
|
|
14
13
|
*
|
|
15
|
-
* This file
|
|
16
|
-
*
|
|
17
|
-
* 2. Global actions (operate on collections)
|
|
18
|
-
* 3. Input validation
|
|
19
|
-
* 4. Multi-step business logic
|
|
20
|
-
* 5. Error handling
|
|
14
|
+
* This file implements custom RPC actions for the Project object.
|
|
15
|
+
* Actions are explicitly invoked by users or systems (not triggered by CRUD).
|
|
21
16
|
*/
|
|
22
17
|
|
|
18
|
+
// ===== RECORD ACTIONS =====
|
|
19
|
+
// These actions operate on a specific project record (require ID)
|
|
20
|
+
|
|
23
21
|
/**
|
|
24
|
-
*
|
|
22
|
+
* Complete Action
|
|
23
|
+
*
|
|
24
|
+
* Marks a project as completed.
|
|
25
|
+
* Type: Record Action (operates on a single project)
|
|
25
26
|
*/
|
|
26
27
|
interface CompleteInput {
|
|
27
28
|
comment?: string;
|
|
28
|
-
completion_date?: Date;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
export const complete: ActionDefinition<Projects, CompleteInput> = {
|
|
41
|
-
handler: async ({ id, input, api, user }) => {
|
|
42
|
-
const { comment, completion_date } = input;
|
|
43
|
-
|
|
44
|
-
console.log(`[Action] Completing project ${id} by ${user?.id}`);
|
|
45
|
-
|
|
46
|
-
// 1. Validate - Fetch current state
|
|
47
|
-
const project = await api.findOne('projects', id!);
|
|
48
|
-
|
|
31
|
+
export const complete: ActionDefinition<any, CompleteInput> = {
|
|
32
|
+
handler: async ({ id, input, api, user, objectName }) => {
|
|
33
|
+
// Validate id is provided
|
|
34
|
+
if (!id) {
|
|
35
|
+
throw new Error('Project ID is required');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Fetch current project state
|
|
39
|
+
const project = await api.findOne(objectName, id);
|
|
49
40
|
if (!project) {
|
|
50
41
|
throw new Error('Project not found');
|
|
51
42
|
}
|
|
52
|
-
|
|
43
|
+
|
|
44
|
+
// Validate project is not already completed
|
|
53
45
|
if (project.status === 'completed') {
|
|
54
46
|
throw new Error('Project is already completed');
|
|
55
47
|
}
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
if (!user?.isAdmin && project.owner !== user?.id) {
|
|
60
|
-
throw new Error('Only the project owner or admin can complete the project');
|
|
61
|
-
}
|
|
62
|
-
*/
|
|
63
|
-
|
|
64
|
-
// 3. Perform update with atomic operation
|
|
65
|
-
const updateData: any = {
|
|
48
|
+
|
|
49
|
+
// Update project status to completed
|
|
50
|
+
await api.update(objectName, id, {
|
|
66
51
|
status: 'completed',
|
|
67
|
-
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// Add completion comment to description
|
|
71
|
-
if (comment) {
|
|
72
|
-
updateData.description = project.description
|
|
73
|
-
? `${project.description}\n\n[Completed on ${new Date().toISOString()}]: ${comment}`
|
|
74
|
-
: `[Completed on ${new Date().toISOString()}]: ${comment}`;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
await api.update('projects', id!, updateData);
|
|
78
|
-
|
|
79
|
-
// 4. Optional: Create completion record or notification
|
|
80
|
-
/*
|
|
81
|
-
await api.create('project_completions', {
|
|
82
|
-
project_id: id,
|
|
83
|
-
completed_by: user?.id,
|
|
52
|
+
completed_by: user?.id || 'system',
|
|
84
53
|
completed_at: new Date(),
|
|
85
|
-
|
|
54
|
+
completion_comment: input.comment
|
|
86
55
|
});
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
success: true,
|
|
92
|
-
message: "Project completed successfully",
|
|
93
|
-
project_id: id,
|
|
94
|
-
completed_at: updateData.end_date
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
success: true,
|
|
59
|
+
message: `Project "${project.name}" completed successfully`
|
|
95
60
|
};
|
|
96
61
|
}
|
|
97
62
|
};
|
|
98
63
|
|
|
99
64
|
/**
|
|
100
|
-
*
|
|
65
|
+
* Approve Action
|
|
66
|
+
*
|
|
67
|
+
* Approves a planned project and moves it to in_progress status.
|
|
68
|
+
* Type: Record Action (operates on a single project)
|
|
101
69
|
*/
|
|
102
70
|
interface ApproveInput {
|
|
103
71
|
comment: string;
|
|
104
72
|
}
|
|
105
73
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
handler: async ({ id, input, api, user }) => {
|
|
116
|
-
const { comment } = input;
|
|
117
|
-
|
|
118
|
-
// 1. Validate input
|
|
119
|
-
if (!comment || comment.trim().length === 0) {
|
|
74
|
+
export const approve: ActionDefinition<any, ApproveInput> = {
|
|
75
|
+
handler: async ({ id, input, api, user, objectName }) => {
|
|
76
|
+
// Validate id is provided
|
|
77
|
+
if (!id) {
|
|
78
|
+
throw new Error('Project ID is required');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Validate approval comment is required
|
|
82
|
+
if (!input.comment || input.comment.trim() === '') {
|
|
120
83
|
throw new Error('Approval comment is required');
|
|
121
84
|
}
|
|
122
|
-
|
|
123
|
-
//
|
|
124
|
-
const project = await api.findOne(
|
|
125
|
-
|
|
85
|
+
|
|
86
|
+
// Fetch current project state
|
|
87
|
+
const project = await api.findOne(objectName, id);
|
|
126
88
|
if (!project) {
|
|
127
89
|
throw new Error('Project not found');
|
|
128
90
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// 3. Check budget threshold
|
|
135
|
-
if (project.budget > 100000 && !user?.isAdmin) {
|
|
136
|
-
throw new Error('Projects with budget over $100,000 require admin approval');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// 4. Perform approval
|
|
140
|
-
await api.update('projects', id!, {
|
|
91
|
+
|
|
92
|
+
// Update project to in_progress status
|
|
93
|
+
await api.update(objectName, id, {
|
|
141
94
|
status: 'in_progress',
|
|
142
|
-
approved_by: user?.id,
|
|
95
|
+
approved_by: user?.id || 'system',
|
|
143
96
|
approved_at: new Date(),
|
|
144
|
-
approval_comment: comment
|
|
97
|
+
approval_comment: input.comment
|
|
145
98
|
});
|
|
146
|
-
|
|
147
|
-
// 5. Log approval
|
|
148
|
-
console.log(`[Action] Project ${project.name} approved by ${user?.id}`);
|
|
149
|
-
|
|
99
|
+
|
|
150
100
|
return {
|
|
151
101
|
success: true,
|
|
152
|
-
message:
|
|
102
|
+
message: `Project "${project.name}" approved`,
|
|
153
103
|
new_status: 'in_progress'
|
|
154
104
|
};
|
|
155
105
|
}
|
|
156
106
|
};
|
|
157
107
|
|
|
158
108
|
/**
|
|
159
|
-
*
|
|
109
|
+
* Clone Action
|
|
110
|
+
*
|
|
111
|
+
* Creates a copy of an existing project.
|
|
112
|
+
* Type: Record Action (operates on a single project)
|
|
160
113
|
*/
|
|
161
114
|
interface CloneInput {
|
|
162
115
|
new_name: string;
|
|
163
116
|
copy_tasks?: boolean;
|
|
164
117
|
}
|
|
165
118
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
* - Copying related data
|
|
172
|
-
* - Complex multi-step operations
|
|
173
|
-
*/
|
|
174
|
-
export const clone: ActionDefinition<Projects, CloneInput> = {
|
|
175
|
-
handler: async ({ id, input, api, user }) => {
|
|
176
|
-
const { new_name, copy_tasks = false } = input;
|
|
177
|
-
|
|
178
|
-
// 1. Validate input
|
|
179
|
-
if (!new_name || new_name.trim().length === 0) {
|
|
180
|
-
throw new Error('New project name is required');
|
|
119
|
+
export const clone: ActionDefinition<any, CloneInput> = {
|
|
120
|
+
handler: async ({ id, input, api, user, objectName }) => {
|
|
121
|
+
// Validate id is provided
|
|
122
|
+
if (!id) {
|
|
123
|
+
throw new Error('Project ID is required');
|
|
181
124
|
}
|
|
182
|
-
|
|
183
|
-
//
|
|
184
|
-
const sourceProject = await api.findOne(
|
|
185
|
-
|
|
125
|
+
|
|
126
|
+
// Fetch source project
|
|
127
|
+
const sourceProject = await api.findOne(objectName, id);
|
|
186
128
|
if (!sourceProject) {
|
|
187
129
|
throw new Error('Source project not found');
|
|
188
130
|
}
|
|
189
|
-
|
|
190
|
-
//
|
|
191
|
-
const
|
|
192
|
-
name: new_name,
|
|
193
|
-
description:
|
|
194
|
-
status: 'planned', // Always start as planned
|
|
131
|
+
|
|
132
|
+
// Create new project with cloned data
|
|
133
|
+
const newProject = await api.create(objectName, {
|
|
134
|
+
name: input.new_name,
|
|
135
|
+
description: sourceProject.description,
|
|
195
136
|
priority: sourceProject.priority,
|
|
196
|
-
owner: user?.id || sourceProject.owner, // Assign to current user
|
|
197
137
|
budget: sourceProject.budget,
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
//
|
|
205
|
-
if (copy_tasks) {
|
|
206
|
-
|
|
207
|
-
const tasks = await api.find('tasks', {
|
|
208
|
-
filters: [['project_id', '=', id]]
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
for (const task of tasks) {
|
|
212
|
-
await api.create('tasks', {
|
|
213
|
-
name: task.name,
|
|
214
|
-
description: task.description,
|
|
215
|
-
project_id: newProject._id,
|
|
216
|
-
status: 'pending', // Reset status
|
|
217
|
-
priority: task.priority
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
*/
|
|
138
|
+
status: 'planned', // Always start cloned projects as planned
|
|
139
|
+
owner: user?.id || 'system', // Assign to current user
|
|
140
|
+
cloned_from: id,
|
|
141
|
+
cloned_at: new Date()
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// TODO: Copy tasks if requested (when tasks functionality is implemented)
|
|
145
|
+
if (input.copy_tasks) {
|
|
146
|
+
// This would copy related tasks
|
|
221
147
|
}
|
|
222
|
-
|
|
223
|
-
console.log(`[Action] Project cloned: ${sourceProject.name} -> ${new_name}`);
|
|
224
|
-
|
|
148
|
+
|
|
225
149
|
return {
|
|
226
150
|
success: true,
|
|
227
|
-
message:
|
|
228
|
-
|
|
229
|
-
new_project_id: newProject._id,
|
|
230
|
-
tasks_copied: copy_tasks
|
|
151
|
+
message: `Project cloned successfully`,
|
|
152
|
+
new_project_id: newProject._id
|
|
231
153
|
};
|
|
232
154
|
}
|
|
233
155
|
};
|
|
234
156
|
|
|
157
|
+
// ===== GLOBAL ACTIONS =====
|
|
158
|
+
// These actions operate on the collection (no specific ID required)
|
|
159
|
+
|
|
235
160
|
/**
|
|
236
|
-
*
|
|
161
|
+
* Import Projects Action
|
|
162
|
+
*
|
|
163
|
+
* Bulk imports projects from external data sources.
|
|
164
|
+
* Type: Global Action (operates on the collection)
|
|
237
165
|
*/
|
|
238
166
|
interface ImportProjectsInput {
|
|
239
|
-
source:
|
|
240
|
-
data
|
|
241
|
-
|
|
167
|
+
source: string;
|
|
168
|
+
data: Array<{
|
|
169
|
+
name?: string;
|
|
170
|
+
description?: string;
|
|
171
|
+
status?: string;
|
|
172
|
+
priority?: string;
|
|
173
|
+
budget?: number;
|
|
174
|
+
}>;
|
|
242
175
|
}
|
|
243
176
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
export const import_projects: ActionDefinition<Projects, ImportProjectsInput> = {
|
|
254
|
-
handler: async ({ input, api, user }) => {
|
|
255
|
-
const { source, data, file_url } = input;
|
|
256
|
-
|
|
257
|
-
console.log(`[Action] Importing projects from ${source} by ${user?.id}`);
|
|
258
|
-
|
|
259
|
-
// 1. Validate input
|
|
260
|
-
if (!data && !file_url) {
|
|
261
|
-
throw new Error('Either data array or file_url must be provided');
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// 2. Fetch data based on source
|
|
265
|
-
let projectsData: any[] = data || [];
|
|
266
|
-
|
|
267
|
-
if (file_url) {
|
|
268
|
-
// Example: Fetch from URL
|
|
269
|
-
/*
|
|
270
|
-
const response = await fetch(file_url);
|
|
271
|
-
projectsData = await response.json();
|
|
272
|
-
*/
|
|
273
|
-
throw new Error('file_url import not yet implemented');
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// 3. Validate and import each project
|
|
277
|
-
const results = {
|
|
278
|
-
successCount: 0,
|
|
279
|
-
failed: 0,
|
|
280
|
-
errors: [] as any[]
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
for (let i = 0; i < projectsData.length; i++) {
|
|
284
|
-
const projectData = projectsData[i];
|
|
285
|
-
|
|
177
|
+
export const import_projects: ActionDefinition<any, ImportProjectsInput> = {
|
|
178
|
+
handler: async ({ input, api, user, objectName }) => {
|
|
179
|
+
const errors: Array<{ index: number; error: string }> = [];
|
|
180
|
+
let successCount = 0;
|
|
181
|
+
|
|
182
|
+
// Process each project in the data array
|
|
183
|
+
for (let i = 0; i < input.data.length; i++) {
|
|
184
|
+
const projectData = input.data[i];
|
|
185
|
+
|
|
286
186
|
try {
|
|
287
187
|
// Validate required fields
|
|
288
|
-
if (!projectData.name) {
|
|
188
|
+
if (!projectData.name || projectData.name.trim() === '') {
|
|
289
189
|
throw new Error('Project name is required');
|
|
290
190
|
}
|
|
291
|
-
|
|
292
|
-
//
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
// Create project
|
|
304
|
-
await api.create('projects', importData);
|
|
305
|
-
results.successCount++;
|
|
306
|
-
|
|
191
|
+
|
|
192
|
+
// Create the project
|
|
193
|
+
await api.create(objectName, {
|
|
194
|
+
...projectData,
|
|
195
|
+
imported_from: input.source,
|
|
196
|
+
imported_by: user?.id || 'system',
|
|
197
|
+
imported_at: new Date()
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
successCount++;
|
|
307
201
|
} catch (error: any) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
name: projectData.name || 'Unknown',
|
|
312
|
-
error: error.message
|
|
202
|
+
errors.push({
|
|
203
|
+
index: i,
|
|
204
|
+
error: error.message || 'Unknown error'
|
|
313
205
|
});
|
|
314
206
|
}
|
|
315
207
|
}
|
|
316
|
-
|
|
317
|
-
console.log(`[Action] Import completed: ${results.successCount} succeeded, ${results.failed} failed`);
|
|
318
|
-
|
|
208
|
+
|
|
319
209
|
return {
|
|
320
|
-
success:
|
|
321
|
-
message: `Imported ${
|
|
322
|
-
|
|
210
|
+
success: true,
|
|
211
|
+
message: `Imported ${successCount} projects`,
|
|
212
|
+
successCount,
|
|
213
|
+
failed: errors.length,
|
|
214
|
+
errors
|
|
323
215
|
};
|
|
324
216
|
}
|
|
325
217
|
};
|
|
326
218
|
|
|
327
219
|
/**
|
|
328
|
-
*
|
|
220
|
+
* Bulk Update Status Action
|
|
221
|
+
*
|
|
222
|
+
* Updates the status of multiple projects at once.
|
|
223
|
+
* Type: Global Action (operates on multiple records)
|
|
329
224
|
*/
|
|
330
225
|
interface BulkUpdateStatusInput {
|
|
331
226
|
project_ids: string[];
|
|
332
|
-
new_status:
|
|
227
|
+
new_status: string;
|
|
333
228
|
}
|
|
334
229
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
*/
|
|
343
|
-
export const bulk_update_status: ActionDefinition<Projects, BulkUpdateStatusInput> = {
|
|
344
|
-
handler: async ({ input, api, user }) => {
|
|
345
|
-
const { project_ids, new_status } = input;
|
|
346
|
-
|
|
347
|
-
// 1. Validate input
|
|
348
|
-
if (!project_ids || project_ids.length === 0) {
|
|
349
|
-
throw new Error('At least one project ID must be provided');
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (project_ids.length > 100) {
|
|
353
|
-
throw new Error('Cannot update more than 100 projects at once');
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// 2. Update each project
|
|
357
|
-
// Note: This uses an N+1 query pattern (fetching each project individually)
|
|
358
|
-
// For production with large batches, consider fetching all projects at once:
|
|
359
|
-
// const projects = await api.find('projects', { filters: [['_id', 'in', project_ids]] });
|
|
360
|
-
// However, for this example with a 100-project limit, the current approach
|
|
361
|
-
// provides clearer per-record validation and error handling.
|
|
362
|
-
const results = {
|
|
363
|
-
updated: 0,
|
|
364
|
-
skipped: 0,
|
|
365
|
-
errors: [] as any[]
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
for (const id of project_ids) {
|
|
230
|
+
export const bulk_update_status: ActionDefinition<any, BulkUpdateStatusInput> = {
|
|
231
|
+
handler: async ({ input, api, objectName }) => {
|
|
232
|
+
let updated = 0;
|
|
233
|
+
let skipped = 0;
|
|
234
|
+
|
|
235
|
+
// Process each project
|
|
236
|
+
for (const projectId of input.project_ids) {
|
|
369
237
|
try {
|
|
370
|
-
const project = await api.findOne(
|
|
238
|
+
const project = await api.findOne(objectName, projectId);
|
|
371
239
|
|
|
372
240
|
if (!project) {
|
|
373
|
-
|
|
374
|
-
project_id: id,
|
|
375
|
-
error: 'Project not found'
|
|
376
|
-
});
|
|
377
|
-
results.skipped++;
|
|
241
|
+
skipped++;
|
|
378
242
|
continue;
|
|
379
243
|
}
|
|
380
|
-
|
|
381
|
-
//
|
|
382
|
-
if (project.status === 'completed'
|
|
383
|
-
|
|
384
|
-
project_id: id,
|
|
385
|
-
name: project.name,
|
|
386
|
-
error: 'Cannot change status of completed projects'
|
|
387
|
-
});
|
|
388
|
-
results.skipped++;
|
|
244
|
+
|
|
245
|
+
// Skip completed projects (they cannot be changed)
|
|
246
|
+
if (project.status === 'completed') {
|
|
247
|
+
skipped++;
|
|
389
248
|
continue;
|
|
390
249
|
}
|
|
391
|
-
|
|
392
|
-
//
|
|
393
|
-
await api.update(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
} catch (error: any) {
|
|
397
|
-
results.errors.push({
|
|
398
|
-
project_id: id,
|
|
399
|
-
error: error.message
|
|
250
|
+
|
|
251
|
+
// Update the status
|
|
252
|
+
await api.update(objectName, projectId, {
|
|
253
|
+
status: input.new_status
|
|
400
254
|
});
|
|
401
|
-
|
|
255
|
+
|
|
256
|
+
updated++;
|
|
257
|
+
} catch (error) {
|
|
258
|
+
skipped++;
|
|
402
259
|
}
|
|
403
260
|
}
|
|
404
|
-
|
|
405
|
-
console.log(`[Action] Bulk update: ${results.updated} updated, ${results.skipped} skipped`);
|
|
406
|
-
|
|
261
|
+
|
|
407
262
|
return {
|
|
408
|
-
success:
|
|
409
|
-
message: `Updated ${
|
|
410
|
-
|
|
263
|
+
success: true,
|
|
264
|
+
message: `Updated ${updated} projects`,
|
|
265
|
+
updated,
|
|
266
|
+
skipped
|
|
411
267
|
};
|
|
412
268
|
}
|
|
413
269
|
};
|
|
414
270
|
|
|
415
271
|
/**
|
|
416
|
-
*
|
|
272
|
+
* Generate Report Action
|
|
417
273
|
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
* - Data collection across records
|
|
421
|
-
* - Computed results
|
|
274
|
+
* Generates statistical reports about projects.
|
|
275
|
+
* Type: Global Action (analytics on the collection)
|
|
422
276
|
*/
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
277
|
+
interface GenerateReportInput {
|
|
278
|
+
// Optional filters could be added here
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export const generate_report: ActionDefinition<any, GenerateReportInput> = {
|
|
282
|
+
handler: async ({ api, objectName }) => {
|
|
283
|
+
// Fetch all projects
|
|
284
|
+
const projects = await api.find(objectName, {});
|
|
285
|
+
|
|
286
|
+
// Calculate statistics
|
|
431
287
|
const report = {
|
|
432
|
-
total_projects:
|
|
288
|
+
total_projects: projects.length,
|
|
433
289
|
by_status: {
|
|
434
290
|
planned: 0,
|
|
435
291
|
in_progress: 0,
|
|
436
292
|
completed: 0
|
|
437
|
-
},
|
|
438
|
-
by_priority: {
|
|
439
|
-
low: 0,
|
|
440
|
-
normal: 0,
|
|
441
|
-
high: 0
|
|
442
|
-
},
|
|
293
|
+
} as Record<string, number>,
|
|
294
|
+
by_priority: {} as Record<string, number>,
|
|
443
295
|
total_budget: 0,
|
|
444
|
-
average_budget: 0
|
|
445
|
-
generated_at: new Date(),
|
|
446
|
-
generated_by: user?.id
|
|
296
|
+
average_budget: 0
|
|
447
297
|
};
|
|
448
|
-
|
|
449
|
-
//
|
|
450
|
-
|
|
298
|
+
|
|
299
|
+
// Aggregate data
|
|
300
|
+
projects.forEach((project: any) => {
|
|
451
301
|
// Count by status
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
report.by_status[status as keyof typeof report.by_status]++;
|
|
302
|
+
if (project.status) {
|
|
303
|
+
report.by_status[project.status] = (report.by_status[project.status] || 0) + 1;
|
|
455
304
|
}
|
|
456
|
-
|
|
305
|
+
|
|
457
306
|
// Count by priority
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
307
|
+
if (project.priority) {
|
|
308
|
+
report.by_priority[project.priority] = (report.by_priority[project.priority] || 0) + 1;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Sum budgets
|
|
312
|
+
if (project.budget) {
|
|
313
|
+
report.total_budget += project.budget;
|
|
461
314
|
}
|
|
462
|
-
|
|
463
|
-
// Sum budget
|
|
464
|
-
report.total_budget += project.budget || 0;
|
|
465
315
|
});
|
|
466
|
-
|
|
467
|
-
//
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
console.log(`[Action] Report generated: ${report.total_projects} projects analyzed`);
|
|
473
|
-
|
|
316
|
+
|
|
317
|
+
// Calculate average budget
|
|
318
|
+
if (projects.length > 0) {
|
|
319
|
+
report.average_budget = report.total_budget / projects.length;
|
|
320
|
+
}
|
|
321
|
+
|
|
474
322
|
return {
|
|
475
323
|
success: true,
|
|
476
324
|
message: 'Report generated successfully',
|
|
477
|
-
report
|
|
325
|
+
report,
|
|
326
|
+
generated_at: new Date()
|
|
478
327
|
};
|
|
479
328
|
}
|
|
480
329
|
};
|