@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,526 @@
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
+ console.log('DEBUG USER: result =', JSON.stringify(result));
64
+ console.log('DEBUG USER: result.id =', result.id);
65
+
66
+ expect(result).toBeDefined();
67
+ expect(result.id).toBeDefined();
68
+ expect(result.id).not.toBeNull();
69
+ expect(result.name).toBe('John Doe');
70
+
71
+ userId = result.id;
72
+ });
73
+
74
+ it('should find users', async () => {
75
+ const ctx = app.createContext({ isSystem: true });
76
+ const results = await ctx.object('user').find({});
77
+
78
+ expect(results).toBeDefined();
79
+ expect(Array.isArray(results)).toBe(true);
80
+ expect(results.length).toBeGreaterThanOrEqual(1);
81
+ });
82
+
83
+ it('should find user by id', async () => {
84
+ const ctx = app.createContext({ isSystem: true });
85
+ const result = await ctx.object('user').findOne(userId);
86
+
87
+ expect(result).toBeDefined();
88
+ expect(result.id).toBe(userId);
89
+ expect(result.name).toBe('John Doe');
90
+ });
91
+
92
+ it('should update user', async () => {
93
+ const ctx = app.createContext({ isSystem: true });
94
+ await ctx.object('user').update(userId, {
95
+ email: 'john.doe@example.com'
96
+ });
97
+
98
+ const updated = await ctx.object('user').findOne(userId);
99
+ expect(updated.email).toBe('john.doe@example.com');
100
+ });
101
+
102
+ it('should delete user', async () => {
103
+ const ctx = app.createContext({ isSystem: true });
104
+ await ctx.object('user').delete(userId);
105
+
106
+ const deleted = await ctx.object('user').findOne(userId);
107
+ expect(deleted).toBeNull();
108
+ });
109
+ });
110
+
111
+ describe('Organization CRUD', () => {
112
+ let orgId: string;
113
+
114
+ it('should create an organization', async () => {
115
+ const ctx = app.createContext({ isSystem: true });
116
+ const result = await ctx.object('organization').create({
117
+ name: 'Acme Corp',
118
+ code: 'ACME'
119
+ });
120
+
121
+ expect(result).toBeDefined();
122
+ expect(result.id).toBeDefined();
123
+ expect(result.name).toBe('Acme Corp');
124
+
125
+ orgId = result.id;
126
+ });
127
+
128
+ it('should find organizations', async () => {
129
+ const ctx = app.createContext({ isSystem: true });
130
+ const results = await ctx.object('organization').find({});
131
+
132
+ expect(results).toBeDefined();
133
+ expect(results.length).toBeGreaterThanOrEqual(1);
134
+ });
135
+
136
+ it('should delete organization', async () => {
137
+ const ctx = app.createContext({ isSystem: true });
138
+ await ctx.object('organization').delete(orgId);
139
+ });
140
+ });
141
+ });
142
+
143
+ describe('CRM Module Operations', () => {
144
+ describe('Account CRUD', () => {
145
+ let accountId: string;
146
+
147
+ it('should create a CRM account', async () => {
148
+ const ctx = app.createContext({ isSystem: true });
149
+ const result = await ctx.object('crm_account').create({
150
+ name: 'Global Solutions Inc',
151
+ industry: 'Technology'
152
+ });
153
+
154
+ expect(result).toBeDefined();
155
+ expect(result.id).toBeDefined();
156
+ expect(result.name).toBe('Global Solutions Inc');
157
+
158
+ accountId = result.id;
159
+ });
160
+
161
+ it('should find CRM accounts', async () => {
162
+ const ctx = app.createContext({ isSystem: true });
163
+ const results = await ctx.object('crm_account').find({});
164
+
165
+ expect(results).toBeDefined();
166
+ expect(results.length).toBeGreaterThanOrEqual(1);
167
+ });
168
+
169
+ it('should update CRM account', async () => {
170
+ const ctx = app.createContext({ isSystem: true });
171
+ await ctx.object('crm_account').update(accountId, {
172
+ industry: 'IT Services'
173
+ });
174
+
175
+ const updated = await ctx.object('crm_account').findOne(accountId);
176
+ expect(updated.industry).toBe('IT Services');
177
+ });
178
+
179
+ it('should delete CRM account', async () => {
180
+ const ctx = app.createContext({ isSystem: true });
181
+ await ctx.object('crm_account').delete(accountId);
182
+ });
183
+ });
184
+
185
+ describe('Contact and Lead CRUD', () => {
186
+ it('should create a CRM contact', async () => {
187
+ const ctx = app.createContext({ isSystem: true });
188
+ // First create an account (required for contact)
189
+ const account = await ctx.object('crm_account').create({
190
+ name: 'Contact Test Company',
191
+ account_number: 'CTC001'
192
+ });
193
+
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
+ console.log('DEBUG: dept =', JSON.stringify(dept));
239
+ console.log('DEBUG: dept.id =', dept.id);
240
+ console.log('DEBUG: dept._id =', (dept as any)._id);
241
+
242
+ expect(dept).toBeDefined();
243
+ expect(dept.id).toBeDefined();
244
+ expect(dept.id).not.toBeNull();
245
+
246
+ const pos = await ctx.object('hr_position').create({
247
+ id: generateId(),
248
+ title: 'Software Engineer', // Position uses 'title', not 'name'
249
+ code: 'SWE'
250
+ });
251
+
252
+ console.log('DEBUG: pos =', JSON.stringify(pos));
253
+
254
+ expect(pos).toBeDefined();
255
+ expect(pos.id).toBeDefined();
256
+ expect(pos.id).not.toBeNull();
257
+
258
+ const result = await ctx.object('hr_employee').create({
259
+ id: generateId(),
260
+ first_name: 'Alice',
261
+ last_name: 'Brown',
262
+ employee_number: 'EMP001',
263
+ email: 'alice.brown@example.com', // Required
264
+ department: dept.id, // Required
265
+ position: pos.id, // Required
266
+ hire_date: '2024-01-01' // Required
267
+ });
268
+
269
+ expect(result).toBeDefined();
270
+ expect(result.id).toBeDefined();
271
+ expect(result.id).not.toBeNull();
272
+ expect(result.first_name).toBe('Alice');
273
+
274
+ employeeId = result.id;
275
+ });
276
+
277
+ it('should find HR employees', async () => {
278
+ const ctx = app.createContext({ isSystem: true });
279
+ const results = await ctx.object('hr_employee').find({});
280
+
281
+ expect(results).toBeDefined();
282
+ expect(results.length).toBeGreaterThanOrEqual(1);
283
+ });
284
+
285
+ it('should delete HR employee', async () => {
286
+ const ctx = app.createContext({ isSystem: true });
287
+ await ctx.object('hr_employee').delete(employeeId);
288
+ });
289
+ });
290
+
291
+ describe('Department CRUD', () => {
292
+ it('should create an HR department', async () => {
293
+ const ctx = app.createContext({ isSystem: true });
294
+ const result = await ctx.object('hr_department').create({
295
+ name: 'Sales Department',
296
+ code: 'SALES' // Use unique code
297
+ });
298
+
299
+ expect(result).toBeDefined();
300
+ expect(result.id).toBeDefined();
301
+ expect(result.name).toBe('Sales Department');
302
+ });
303
+ });
304
+ });
305
+
306
+ describe('Project Module Operations', () => {
307
+ describe('Project CRUD', () => {
308
+ let projectId: string;
309
+
310
+ it('should create a project', async () => {
311
+ const ctx = app.createContext({ isSystem: true });
312
+ // Create a user first (required as project owner)
313
+ const user = await ctx.object('user').create({
314
+ name: 'Project Manager',
315
+ email: 'pm@example.com',
316
+ username: 'pmuser'
317
+ });
318
+
319
+ expect(user).toBeDefined();
320
+ expect(user.id).toBeDefined();
321
+
322
+ const result = await ctx.object('project_project').create({
323
+ name: 'Website Redesign',
324
+ code: 'WEB-001',
325
+ status: 'planning', // Required field
326
+ owner: user.id, // Required field
327
+ start_date: '2024-01-01' // Required field
328
+ });
329
+
330
+ expect(result).toBeDefined();
331
+ expect(result.id).toBeDefined();
332
+ expect(result.name).toBe('Website Redesign');
333
+
334
+ projectId = result.id;
335
+ });
336
+
337
+ it('should find projects', async () => {
338
+ const ctx = app.createContext({ isSystem: true });
339
+ const results = await ctx.object('project_project').find({});
340
+
341
+ expect(results).toBeDefined();
342
+ expect(results.length).toBeGreaterThanOrEqual(1);
343
+ });
344
+
345
+ it('should delete project', async () => {
346
+ const ctx = app.createContext({ isSystem: true });
347
+ await ctx.object('project_project').delete(projectId);
348
+ });
349
+ });
350
+
351
+ describe('Task CRUD', () => {
352
+ it('should create a project task', async () => {
353
+ const ctx = app.createContext({ isSystem: true });
354
+ // Create a user and project first (required for task)
355
+ const user = await ctx.object('user').create({
356
+ name: 'Task Owner',
357
+ email: 'taskowner@example.com',
358
+ username: 'taskuser'
359
+ });
360
+
361
+ expect(user).toBeDefined();
362
+ expect(user.id).toBeDefined();
363
+
364
+ const project = await ctx.object('project_project').create({
365
+ name: 'Test Project',
366
+ code: 'TEST-001',
367
+ status: 'planning',
368
+ owner: user.id, // Required
369
+ start_date: '2024-01-01' // Required
370
+ });
371
+
372
+ expect(project).toBeDefined();
373
+ expect(project.id).toBeDefined();
374
+
375
+ const result = await ctx.object('project_task').create({
376
+ name: 'Design mockups',
377
+ description: 'Create initial design mockups',
378
+ project: project.id // Required field
379
+ });
380
+
381
+ expect(result).toBeDefined();
382
+ expect(result.id).toBeDefined();
383
+ expect(result.name).toBe('Design mockups');
384
+ });
385
+ });
386
+ });
387
+
388
+ describe('Finance Module Operations', () => {
389
+ describe('Invoice CRUD', () => {
390
+ it('should create a finance invoice', async () => {
391
+ const ctx = app.createContext({ isSystem: true });
392
+ // Create an account first (required for invoice)
393
+ const account = await ctx.object('crm_account').create({
394
+ name: 'Invoice Test Company',
395
+ account_number: 'ITC001'
396
+ });
397
+
398
+ expect(account).toBeDefined();
399
+ expect(account.id).toBeDefined();
400
+
401
+ const result = await ctx.object('finance_invoice').create({
402
+ invoice_number: 'INV-001',
403
+ total_amount: 1000,
404
+ account: account.id, // Required
405
+ invoice_date: '2024-01-01', // Required
406
+ due_date: '2024-01-31', // Required
407
+ subtotal: 1000 // Required
408
+ });
409
+
410
+ expect(result).toBeDefined();
411
+ expect(result.id).toBeDefined();
412
+ expect(result.invoice_number).toBe('INV-001');
413
+ });
414
+ });
415
+
416
+ describe('Payment CRUD', () => {
417
+ it('should create a finance payment', async () => {
418
+ const ctx = app.createContext({ isSystem: true });
419
+ // Create an account first (required for payment)
420
+ const account = await ctx.object('crm_account').create({
421
+ name: 'Payment Test Company',
422
+ account_number: 'PTC001'
423
+ });
424
+
425
+ expect(account).toBeDefined();
426
+ expect(account.id).toBeDefined();
427
+
428
+ const result = await ctx.object('finance_payment').create({
429
+ payment_number: 'PAY-001', // Required
430
+ amount: 500,
431
+ payment_method: 'bank_transfer', // Use underscore format
432
+ account: account.id, // Required
433
+ payment_date: '2024-01-01' // Required
434
+ });
435
+
436
+ expect(result).toBeDefined();
437
+ expect(result.id).toBeDefined();
438
+ expect(result.amount).toBe(500);
439
+ });
440
+ });
441
+ });
442
+
443
+ describe('Cross-Module Operations', () => {
444
+ it('should handle operations across multiple modules', async () => {
445
+ const ctx = app.createContext({ isSystem: true });
446
+
447
+ // Create records in different modules
448
+ const account = await ctx.object('crm_account').create({
449
+ name: 'Multi-Module Test',
450
+ account_number: 'MMT001'
451
+ });
452
+
453
+ expect(account).toBeDefined();
454
+ expect(account.id).toBeDefined();
455
+
456
+ // Create required department and position first
457
+ const dept = await ctx.object('hr_department').create({
458
+ name: 'Cross Test Dept',
459
+ code: 'CTD'
460
+ });
461
+
462
+ expect(dept).toBeDefined();
463
+ expect(dept.id).toBeDefined();
464
+
465
+ const pos = await ctx.object('hr_position').create({
466
+ title: 'Cross Test Position', // Position uses 'title', not 'name'
467
+ code: 'CTP'
468
+ });
469
+
470
+ expect(pos).toBeDefined();
471
+ expect(pos.id).toBeDefined();
472
+
473
+ const employee = await ctx.object('hr_employee').create({
474
+ first_name: 'Test',
475
+ last_name: 'Employee',
476
+ employee_number: 'TEST001',
477
+ email: 'test.employee@example.com', // Required
478
+ department: dept.id, // Required
479
+ position: pos.id, // Required
480
+ hire_date: '2024-01-01' // Required
481
+ });
482
+
483
+ expect(employee).toBeDefined();
484
+ expect(employee.id).toBeDefined();
485
+
486
+ // Create a user for project owner
487
+ const user = await ctx.object('user').create({
488
+ name: 'Cross Test User',
489
+ email: 'crosstest@example.com',
490
+ username: 'crosstestuser'
491
+ });
492
+
493
+ expect(user).toBeDefined();
494
+ expect(user.id).toBeDefined();
495
+
496
+ const project = await ctx.object('project_project').create({
497
+ name: 'Cross-Module Project',
498
+ code: 'CROSS-001',
499
+ status: 'planning',
500
+ owner: user.id, // Required
501
+ start_date: '2024-01-01' // Required
502
+ });
503
+
504
+ expect(account.id).toBeDefined();
505
+ expect(employee.id).toBeDefined();
506
+ expect(project.id).toBeDefined();
507
+
508
+ // Cleanup
509
+ await ctx.object('crm_account').delete(account.id);
510
+ await ctx.object('hr_employee').delete(employee.id);
511
+ await ctx.object('project_project').delete(project.id);
512
+ });
513
+
514
+ it('should count records across modules', async () => {
515
+ const ctx = app.createContext({ isSystem: true });
516
+
517
+ const accountCount = await ctx.object('crm_account').count([]);
518
+ const employeeCount = await ctx.object('hr_employee').count([]);
519
+ const projectCount = await ctx.object('project_project').count([]);
520
+
521
+ expect(typeof accountCount).toBe('number');
522
+ expect(typeof employeeCount).toBe('number');
523
+ expect(typeof projectCount).toBe('number');
524
+ });
525
+ });
526
+ });