@objectql/create 1.0.0

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.
Files changed (110) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +28 -0
  3. package/dist/bin.js +125 -0
  4. package/package.json +27 -0
  5. package/templates/enterprise/CHANGELOG.md +140 -0
  6. package/templates/enterprise/README.md +352 -0
  7. package/templates/enterprise/__tests__/data-api.test.ts +546 -0
  8. package/templates/enterprise/__tests__/data-api.test.ts.backup +526 -0
  9. package/templates/enterprise/__tests__/metadata-api.test.ts +307 -0
  10. package/templates/enterprise/__tests__/metadata-loading.test.ts +250 -0
  11. package/templates/enterprise/jest.config.js +22 -0
  12. package/templates/enterprise/package.json +51 -0
  13. package/templates/enterprise/src/apps/erp.app.yml +4 -0
  14. package/templates/enterprise/src/core/attachment.object.yml +57 -0
  15. package/templates/enterprise/src/core/i18n/en/core.json +60 -0
  16. package/templates/enterprise/src/core/i18n/zh-CN/core.json +60 -0
  17. package/templates/enterprise/src/core/index.ts +24 -0
  18. package/templates/enterprise/src/core/organization.object.yml +78 -0
  19. package/templates/enterprise/src/core/user.object.yml +80 -0
  20. package/templates/enterprise/src/extensions/README.md +56 -0
  21. package/templates/enterprise/src/extensions/user.extension.object.yml +42 -0
  22. package/templates/enterprise/src/extensions/user.ts +26 -0
  23. package/templates/enterprise/src/index.ts +47 -0
  24. package/templates/enterprise/src/modules/crm/README.md +99 -0
  25. package/templates/enterprise/src/modules/crm/crm_account.object.yml +105 -0
  26. package/templates/enterprise/src/modules/crm/crm_contact.object.yml +103 -0
  27. package/templates/enterprise/src/modules/crm/crm_lead.object.yml +148 -0
  28. package/templates/enterprise/src/modules/crm/crm_opportunity.object.yml +128 -0
  29. package/templates/enterprise/src/modules/crm/i18n/en/crm.json +61 -0
  30. package/templates/enterprise/src/modules/crm/i18n/zh-CN/crm.json +61 -0
  31. package/templates/enterprise/src/modules/crm/index.ts +29 -0
  32. package/templates/enterprise/src/modules/finance/README.md +112 -0
  33. package/templates/enterprise/src/modules/finance/finance_budget.object.yml +108 -0
  34. package/templates/enterprise/src/modules/finance/finance_expense.object.yml +151 -0
  35. package/templates/enterprise/src/modules/finance/finance_invoice.object.yml +143 -0
  36. package/templates/enterprise/src/modules/finance/finance_payment.object.yml +96 -0
  37. package/templates/enterprise/src/modules/finance/index.ts +26 -0
  38. package/templates/enterprise/src/modules/hr/README.md +95 -0
  39. package/templates/enterprise/src/modules/hr/hr_department.object.yml +59 -0
  40. package/templates/enterprise/src/modules/hr/hr_employee.object.yml +137 -0
  41. package/templates/enterprise/src/modules/hr/hr_position.object.yml +79 -0
  42. package/templates/enterprise/src/modules/hr/hr_timesheet.object.yml +114 -0
  43. package/templates/enterprise/src/modules/hr/index.ts +26 -0
  44. package/templates/enterprise/src/modules/project/README.md +132 -0
  45. package/templates/enterprise/src/modules/project/index.ts +26 -0
  46. package/templates/enterprise/src/modules/project/project_milestone.object.yml +70 -0
  47. package/templates/enterprise/src/modules/project/project_project.object.yml +135 -0
  48. package/templates/enterprise/src/modules/project/project_task.object.yml +121 -0
  49. package/templates/enterprise/src/modules/project/project_timesheet_entry.object.yml +95 -0
  50. package/templates/enterprise/src/plugins/audit/audit.plugin.ts +23 -0
  51. package/templates/enterprise/src/plugins/audit/index.ts +6 -0
  52. package/templates/enterprise/src/plugins/audit/note.object.yml +3 -0
  53. package/templates/enterprise/src/shared/constants.ts +30 -0
  54. package/templates/enterprise/src/shared/utils.ts +54 -0
  55. package/templates/enterprise/src/shared/validators.ts +47 -0
  56. package/templates/enterprise/src/types/attachment.ts +41 -0
  57. package/templates/enterprise/src/types/crm_account.ts +61 -0
  58. package/templates/enterprise/src/types/crm_contact.ts +61 -0
  59. package/templates/enterprise/src/types/crm_lead.ts +77 -0
  60. package/templates/enterprise/src/types/crm_opportunity.ts +53 -0
  61. package/templates/enterprise/src/types/finance_budget.ts +61 -0
  62. package/templates/enterprise/src/types/finance_expense.ts +65 -0
  63. package/templates/enterprise/src/types/finance_invoice.ts +69 -0
  64. package/templates/enterprise/src/types/finance_payment.ts +49 -0
  65. package/templates/enterprise/src/types/hr_department.ts +37 -0
  66. package/templates/enterprise/src/types/hr_employee.ts +85 -0
  67. package/templates/enterprise/src/types/hr_position.ts +49 -0
  68. package/templates/enterprise/src/types/hr_timesheet.ts +57 -0
  69. package/templates/enterprise/src/types/index.ts +20 -0
  70. package/templates/enterprise/src/types/note.ts +9 -0
  71. package/templates/enterprise/src/types/organization.ts +53 -0
  72. package/templates/enterprise/src/types/project_milestone.ts +41 -0
  73. package/templates/enterprise/src/types/project_project.ts +69 -0
  74. package/templates/enterprise/src/types/project_task.ts +57 -0
  75. package/templates/enterprise/src/types/project_timesheet_entry.ts +45 -0
  76. package/templates/enterprise/src/types/user.ts +65 -0
  77. package/templates/enterprise/tsconfig.json +10 -0
  78. package/templates/enterprise/tsconfig.tsbuildinfo +1 -0
  79. package/templates/hello-world/CHANGELOG.md +33 -0
  80. package/templates/hello-world/README.md +29 -0
  81. package/templates/hello-world/package.json +24 -0
  82. package/templates/hello-world/src/index.ts +58 -0
  83. package/templates/hello-world/tsconfig.json +10 -0
  84. package/templates/starter/CHANGELOG.md +191 -0
  85. package/templates/starter/README.md +17 -0
  86. package/templates/starter/__tests__/projects-hooks-actions.test.ts +490 -0
  87. package/templates/starter/jest.config.js +22 -0
  88. package/templates/starter/package.json +51 -0
  89. package/templates/starter/src/README.pages.md +110 -0
  90. package/templates/starter/src/demo.app.yml +4 -0
  91. package/templates/starter/src/i18n/zh-CN/projects.json +22 -0
  92. package/templates/starter/src/index.ts +55 -0
  93. package/templates/starter/src/modules/kitchen-sink/kitchen_sink.data.yml +18 -0
  94. package/templates/starter/src/modules/kitchen-sink/kitchen_sink.object.yml +156 -0
  95. package/templates/starter/src/modules/projects/project_approval.workflow.yml +51 -0
  96. package/templates/starter/src/modules/projects/projects.action.ts +472 -0
  97. package/templates/starter/src/modules/projects/projects.data.yml +13 -0
  98. package/templates/starter/src/modules/projects/projects.hook.ts +339 -0
  99. package/templates/starter/src/modules/projects/projects.object.yml +148 -0
  100. package/templates/starter/src/modules/projects/projects.permission.yml +141 -0
  101. package/templates/starter/src/modules/projects/projects.validation.yml +37 -0
  102. package/templates/starter/src/modules/tasks/tasks.data.yml +23 -0
  103. package/templates/starter/src/modules/tasks/tasks.object.yml +34 -0
  104. package/templates/starter/src/modules/tasks/tasks.permission.yml +167 -0
  105. package/templates/starter/src/types/index.ts +3 -0
  106. package/templates/starter/src/types/kitchen_sink.ts +101 -0
  107. package/templates/starter/src/types/projects.ts +49 -0
  108. package/templates/starter/src/types/tasks.ts +33 -0
  109. package/templates/starter/tsconfig.json +11 -0
  110. package/templates/starter/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,546 @@
1
+ /**
2
+ * Data API Tests for Enterprise Starter
3
+ *
4
+ * Tests CRUD operations for enterprise objects
5
+ */
6
+
7
+ import { ObjectQL } from '@objectql/core';
8
+ import { SqlDriver } from '@objectql/driver-sql';
9
+ import { ObjectLoader } from '@objectql/platform-node';
10
+ import * as path from 'path';
11
+ import { nanoid } from 'nanoid';
12
+
13
+ // Helper to generate IDs since SQL driver doesn't auto-generate them
14
+ const generateId = () => nanoid(16);
15
+
16
+ describe('Enterprise Data API', () => {
17
+ let app: ObjectQL;
18
+
19
+ beforeAll(async () => {
20
+ // Initialize ObjectQL
21
+ app = new ObjectQL({
22
+ datasources: {
23
+ default: new SqlDriver({
24
+ client: 'sqlite3',
25
+ connection: {
26
+ filename: ':memory:'
27
+ },
28
+ useNullAsDefault: true
29
+ })
30
+ }
31
+ });
32
+
33
+ // Load metadata
34
+ const srcDir = path.resolve(__dirname, '../src');
35
+ const loader = new ObjectLoader(app.metadata);
36
+ loader.load(srcDir);
37
+
38
+ await app.init();
39
+ });
40
+
41
+ afterAll(async () => {
42
+ if (app && (app as any).datasources?.default) {
43
+ const driver = (app as any).datasources.default;
44
+ if (driver.knex) {
45
+ await driver.knex.destroy();
46
+ }
47
+ }
48
+ });
49
+
50
+ describe('Core Object Operations', () => {
51
+ describe('User CRUD', () => {
52
+ let userId: string;
53
+
54
+ it('should create a user', async () => {
55
+ const ctx = app.createContext({ isSystem: true });
56
+ const result = await ctx.object('user').create({
57
+ id: generateId(), // Provide ID manually
58
+ name: 'John Doe',
59
+ email: 'john@example.com',
60
+ username: 'johndoe'
61
+ });
62
+
63
+
64
+ expect(result).toBeDefined();
65
+ expect(result.id).toBeDefined();
66
+ expect(result.id).not.toBeNull();
67
+ expect(result.name).toBe('John Doe');
68
+
69
+ userId = result.id;
70
+ });
71
+
72
+ it('should find users', async () => {
73
+ const ctx = app.createContext({ isSystem: true });
74
+ const results = await ctx.object('user').find({});
75
+
76
+ expect(results).toBeDefined();
77
+ expect(Array.isArray(results)).toBe(true);
78
+ expect(results.length).toBeGreaterThanOrEqual(1);
79
+ });
80
+
81
+ it('should find user by id', async () => {
82
+ const ctx = app.createContext({ isSystem: true });
83
+ const result = await ctx.object('user').findOne(userId);
84
+
85
+ expect(result).toBeDefined();
86
+ expect(result.id).toBe(userId);
87
+ expect(result.name).toBe('John Doe');
88
+ });
89
+
90
+ it('should update user', async () => {
91
+ const ctx = app.createContext({ isSystem: true });
92
+ await ctx.object('user').update(userId, {
93
+ email: 'john.doe@example.com'
94
+ });
95
+
96
+ const updated = await ctx.object('user').findOne(userId);
97
+ expect(updated.email).toBe('john.doe@example.com');
98
+ });
99
+
100
+ it('should delete user', async () => {
101
+ const ctx = app.createContext({ isSystem: true });
102
+ await ctx.object('user').delete(userId);
103
+
104
+ const deleted = await ctx.object('user').findOne(userId);
105
+ expect(deleted).toBeFalsy();
106
+ });
107
+ });
108
+
109
+ describe('Organization CRUD', () => {
110
+ let orgId: string;
111
+
112
+ it('should create an organization', async () => {
113
+ const ctx = app.createContext({ isSystem: true });
114
+ const result = await ctx.object('organization').create({
115
+ name: 'Acme Corp',
116
+ code: 'ACME'
117
+ });
118
+
119
+ expect(result).toBeDefined();
120
+ expect(result.id).toBeDefined();
121
+ expect(result.name).toBe('Acme Corp');
122
+
123
+ orgId = result.id;
124
+ });
125
+
126
+ it('should find organizations', async () => {
127
+ const ctx = app.createContext({ isSystem: true });
128
+ const results = await ctx.object('organization').find({});
129
+
130
+ expect(results).toBeDefined();
131
+ expect(results.length).toBeGreaterThanOrEqual(1);
132
+ });
133
+
134
+ it('should delete organization', async () => {
135
+ const ctx = app.createContext({ isSystem: true });
136
+ await ctx.object('organization').delete(orgId);
137
+ });
138
+ });
139
+ });
140
+
141
+ describe('CRM Module Operations', () => {
142
+ describe('Account CRUD', () => {
143
+ let accountId: string;
144
+
145
+ it('should create a CRM account', async () => {
146
+ const ctx = app.createContext({ isSystem: true });
147
+ const result = await ctx.object('crm_account').create({
148
+ name: 'Global Solutions Inc',
149
+ industry: 'Technology'
150
+ });
151
+
152
+ expect(result).toBeDefined();
153
+ expect(result.id).toBeDefined();
154
+ expect(result.name).toBe('Global Solutions Inc');
155
+
156
+ accountId = result.id;
157
+ });
158
+
159
+ it('should find CRM accounts', async () => {
160
+ const ctx = app.createContext({ isSystem: true });
161
+ const results = await ctx.object('crm_account').find({});
162
+
163
+ expect(results).toBeDefined();
164
+ expect(results.length).toBeGreaterThanOrEqual(1);
165
+ });
166
+
167
+ it('should update CRM account', async () => {
168
+ const ctx = app.createContext({ isSystem: true });
169
+ await ctx.object('crm_account').update(accountId, {
170
+ industry: 'IT Services'
171
+ });
172
+
173
+ const updated = await ctx.object('crm_account').findOne(accountId);
174
+ expect(updated.industry).toBe('IT Services');
175
+ });
176
+
177
+ it('should delete CRM account', async () => {
178
+ const ctx = app.createContext({ isSystem: true });
179
+ await ctx.object('crm_account').delete(accountId);
180
+ });
181
+ });
182
+
183
+ describe('Contact and Lead CRUD', () => {
184
+ it('should create a CRM contact', async () => {
185
+ const ctx = app.createContext({ isSystem: true });
186
+ // First create an account (required for contact)
187
+ const account = await ctx.object('crm_account').create({
188
+ id: generateId(),
189
+ name: 'Contact Test Company',
190
+ account_number: 'CTC001'
191
+ });
192
+
193
+ console.log('CRM Account:', account); // Debugging
194
+ expect(account).toBeDefined();
195
+ expect(account.id).toBeDefined();
196
+
197
+ const result = await ctx.object('crm_contact').create({
198
+ first_name: 'Jane',
199
+ last_name: 'Smith',
200
+ email: 'jane.smith@example.com',
201
+ account: account.id // Required field
202
+ });
203
+
204
+ expect(result).toBeDefined();
205
+ expect(result.id).toBeDefined();
206
+ expect(result.first_name).toBe('Jane');
207
+ });
208
+
209
+ it('should create a CRM lead', async () => {
210
+ const ctx = app.createContext({ isSystem: true });
211
+ const result = await ctx.object('crm_lead').create({
212
+ first_name: 'Bob',
213
+ last_name: 'Johnson',
214
+ company: 'Tech Startup',
215
+ status: 'new' // Required field
216
+ });
217
+
218
+ expect(result).toBeDefined();
219
+ expect(result.id).toBeDefined();
220
+ expect(result.company).toBe('Tech Startup');
221
+ });
222
+ });
223
+ });
224
+
225
+ describe('HR Module Operations', () => {
226
+ describe('Employee CRUD', () => {
227
+ let employeeId: string;
228
+
229
+ it('should create an HR employee', async () => {
230
+ const ctx = app.createContext({ isSystem: true });
231
+ // Create required department and position first
232
+ const dept = await ctx.object('hr_department').create({
233
+ id: generateId(),
234
+ name: 'Engineering Dept',
235
+ code: 'ENGD'
236
+ });
237
+
238
+
239
+ expect(dept).toBeDefined();
240
+ expect(dept.id).toBeDefined();
241
+ expect(dept.id).not.toBeNull();
242
+
243
+ const pos = await ctx.object('hr_position').create({
244
+ id: generateId(),
245
+ title: 'Software Engineer', // Position uses 'title', not 'name'
246
+ code: 'SWE'
247
+ });
248
+
249
+
250
+ expect(pos).toBeDefined();
251
+ expect(pos.id).toBeDefined();
252
+ expect(pos.id).not.toBeNull();
253
+
254
+ const result = await ctx.object('hr_employee').create({
255
+ id: generateId(),
256
+ first_name: 'Alice',
257
+ last_name: 'Brown',
258
+ employee_number: 'EMP001',
259
+ email: 'alice.brown@example.com', // Required
260
+ department: dept.id, // Required
261
+ position: pos.id, // Required
262
+ hire_date: '2024-01-01', // Required
263
+ status: 'active',
264
+ employment_type: 'full_time'
265
+ });
266
+
267
+ expect(result).toBeDefined();
268
+ expect(result.id).toBeDefined();
269
+ expect(result.id).not.toBeNull();
270
+ expect(result.first_name).toBe('Alice');
271
+
272
+ employeeId = result.id;
273
+ });
274
+
275
+ it('should find HR employees', async () => {
276
+ const ctx = app.createContext({ isSystem: true });
277
+ const results = await ctx.object('hr_employee').find({});
278
+
279
+ expect(results).toBeDefined();
280
+ expect(results.length).toBeGreaterThanOrEqual(1);
281
+ });
282
+
283
+ it('should delete HR employee', async () => {
284
+ const ctx = app.createContext({ isSystem: true });
285
+ await ctx.object('hr_employee').delete(employeeId);
286
+ });
287
+ });
288
+
289
+ describe('Department CRUD', () => {
290
+ it('should create an HR department', async () => {
291
+ const ctx = app.createContext({ isSystem: true });
292
+ const result = await ctx.object('hr_department').create({
293
+ id: generateId(),
294
+ name: 'Sales Department',
295
+ code: 'SALES' // Use unique code
296
+ });
297
+
298
+ expect(result).toBeDefined();
299
+ expect(result.id).toBeDefined();
300
+ expect(result.name).toBe('Sales Department');
301
+ });
302
+ });
303
+ });
304
+
305
+ describe('Project Module Operations', () => {
306
+ describe('Project CRUD', () => {
307
+ let projectId: string;
308
+
309
+ it('should create a project', async () => {
310
+ const ctx = app.createContext({ isSystem: true });
311
+ // Create a user first (required as project owner)
312
+ const user = await ctx.object('user').create({
313
+ id: generateId(),
314
+ name: 'Project Manager',
315
+ email: 'pm@example.com',
316
+ username: 'pmuser'
317
+ });
318
+
319
+ console.log('Project User:', user); // Debugging
320
+ expect(user).toBeDefined();
321
+ expect(user.id).toBeDefined();
322
+
323
+ const result = await ctx.object('project_project').create({
324
+ id: generateId(),
325
+ name: 'Website Redesign',
326
+ code: 'WEB-001',
327
+ status: 'planning', // Required field
328
+ owner: user.id, // Required field
329
+ start_date: '2024-01-01' // Required field
330
+ });
331
+
332
+ expect(result).toBeDefined();
333
+ expect(result.id).toBeDefined();
334
+ expect(result.name).toBe('Website Redesign');
335
+
336
+ projectId = result.id;
337
+ });
338
+
339
+ it('should find projects', async () => {
340
+ const ctx = app.createContext({ isSystem: true });
341
+ const results = await ctx.object('project_project').find({});
342
+
343
+ expect(results).toBeDefined();
344
+ expect(results.length).toBeGreaterThanOrEqual(1);
345
+ });
346
+
347
+ it('should delete project', async () => {
348
+ const ctx = app.createContext({ isSystem: true });
349
+ await ctx.object('project_project').delete(projectId);
350
+ });
351
+ });
352
+
353
+ describe('Task CRUD', () => {
354
+ it('should create a project task', async () => {
355
+ const ctx = app.createContext({ isSystem: true });
356
+ // Create a user and project first (required for task)
357
+ const user = await ctx.object('user').create({
358
+ id: generateId(),
359
+ name: 'Task Owner',
360
+ email: 'taskowner@example.com',
361
+ username: 'taskuser'
362
+ });
363
+
364
+ expect(user).toBeDefined();
365
+ expect(user.id).toBeDefined();
366
+
367
+ const project = await ctx.object('project_project').create({
368
+ id: generateId(),
369
+ name: 'Test Project',
370
+ code: 'TEST-001',
371
+ status: 'planning',
372
+ owner: user.id, // Required
373
+ start_date: '2024-01-01' // Required
374
+ });
375
+
376
+ expect(project).toBeDefined();
377
+ expect(project.id).toBeDefined();
378
+
379
+ const result = await ctx.object('project_task').create({
380
+ id: generateId(),
381
+ name: 'Design mockups',
382
+ description: 'Create initial design mockups',
383
+ project: project.id, // Required field
384
+ status: 'pending' // Required field
385
+ });
386
+
387
+ expect(result).toBeDefined();
388
+ expect(result.id).toBeDefined();
389
+ expect(result.name).toBe('Design mockups');
390
+ });
391
+ });
392
+ });
393
+
394
+ describe('Finance Module Operations', () => {
395
+ describe('Invoice CRUD', () => {
396
+ it('should create a finance invoice', async () => {
397
+ const ctx = app.createContext({ isSystem: true });
398
+ // Create an account first (required for invoice)
399
+ const account = await ctx.object('crm_account').create({
400
+ id: generateId(),
401
+ name: 'Invoice Test Company',
402
+ account_number: 'ITC001'
403
+ });
404
+
405
+ expect(account).toBeDefined();
406
+ expect(account.id).toBeDefined();
407
+
408
+ const result = await ctx.object('finance_invoice').create({
409
+ id: generateId(),
410
+ invoice_number: 'INV-001',
411
+ total_amount: 1000,
412
+ account: account.id, // Required
413
+ invoice_date: '2024-01-01', // Required
414
+ due_date: '2024-01-31', // Required
415
+ subtotal: 1000, // Required
416
+ status: 'draft'
417
+ });
418
+
419
+ expect(result).toBeDefined();
420
+ expect(result.id).toBeDefined();
421
+ expect(result.invoice_number).toBe('INV-001');
422
+ });
423
+ });
424
+
425
+ describe('Payment CRUD', () => {
426
+ it('should create a finance payment', async () => {
427
+ const ctx = app.createContext({ isSystem: true });
428
+ // Create an account first (required for payment)
429
+ const account = await ctx.object('crm_account').create({
430
+ id: generateId(),
431
+ name: 'Payment Test Company',
432
+ account_number: 'PTC001'
433
+ });
434
+
435
+ expect(account).toBeDefined();
436
+ expect(account.id).toBeDefined();
437
+
438
+ const result = await ctx.object('finance_payment').create({
439
+ id: generateId(),
440
+ payment_number: 'PAY-001', // Required
441
+ amount: 500,
442
+ payment_method: 'bank_transfer', // Use underscore format
443
+ account: account.id, // Required
444
+ payment_date: '2024-01-01', // Required
445
+ status: 'completed'
446
+ });
447
+
448
+ expect(result).toBeDefined();
449
+ expect(result.id).toBeDefined();
450
+ expect(result.amount).toBe(500);
451
+ });
452
+ });
453
+ });
454
+
455
+ describe('Cross-Module Operations', () => {
456
+ it('should handle operations across multiple modules', async () => {
457
+ const ctx = app.createContext({ isSystem: true });
458
+
459
+ // Create records in different modules
460
+ const account = await ctx.object('crm_account').create({
461
+ id: generateId(),
462
+ name: 'Multi-Module Test',
463
+ account_number: 'MMT001'
464
+ });
465
+
466
+ expect(account).toBeDefined();
467
+ expect(account.id).toBeDefined();
468
+
469
+ // Create required department and position first
470
+ const dept = await ctx.object('hr_department').create({
471
+ id: generateId(),
472
+ name: 'Cross Test Dept',
473
+ code: 'CTD'
474
+ });
475
+
476
+ expect(dept).toBeDefined();
477
+ expect(dept.id).toBeDefined();
478
+
479
+ const pos = await ctx.object('hr_position').create({
480
+ id: generateId(),
481
+ title: 'Cross Test Position', // Position uses 'title', not 'name'
482
+ code: 'CTP'
483
+ });
484
+
485
+ expect(pos).toBeDefined();
486
+ expect(pos.id).toBeDefined();
487
+
488
+ const employee = await ctx.object('hr_employee').create({
489
+ id: generateId(),
490
+ first_name: 'Test',
491
+ last_name: 'Employee',
492
+ employee_number: 'TEST001',
493
+ email: 'test.employee@example.com', // Required
494
+ department: dept.id, // Required
495
+ position: pos.id, // Required
496
+ hire_date: '2024-01-01', // Required
497
+ employment_type: 'full_time',
498
+ status: 'active'
499
+ });
500
+
501
+ expect(employee).toBeDefined();
502
+ expect(employee.id).toBeDefined();
503
+
504
+ // Create a user for project owner
505
+ const user = await ctx.object('user').create({
506
+ id: generateId(),
507
+ name: 'Cross Test User',
508
+ email: 'crosstest@example.com',
509
+ username: 'crosstestuser'
510
+ });
511
+
512
+ expect(user).toBeDefined();
513
+ expect(user.id).toBeDefined();
514
+
515
+ const project = await ctx.object('project_project').create({
516
+ id: generateId(),
517
+ name: 'Cross-Module Project',
518
+ code: 'CROSS-001',
519
+ status: 'planning',
520
+ owner: user.id, // Required
521
+ start_date: '2024-01-01' // Required
522
+ });
523
+
524
+ expect(account.id).toBeDefined();
525
+ expect(employee.id).toBeDefined();
526
+ expect(project.id).toBeDefined();
527
+
528
+ // Cleanup
529
+ await ctx.object('crm_account').delete(account.id);
530
+ await ctx.object('hr_employee').delete(employee.id);
531
+ await ctx.object('project_project').delete(project.id);
532
+ });
533
+
534
+ it('should count records across modules', async () => {
535
+ const ctx = app.createContext({ isSystem: true });
536
+
537
+ const accountCount = await ctx.object('crm_account').count([]);
538
+ const employeeCount = await ctx.object('hr_employee').count([]);
539
+ const projectCount = await ctx.object('project_project').count([]);
540
+
541
+ expect(typeof accountCount).toBe('number');
542
+ expect(typeof employeeCount).toBe('number');
543
+ expect(typeof projectCount).toBe('number');
544
+ });
545
+ });
546
+ });