@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.
- package/LICENSE +21 -0
- package/README.md +28 -0
- package/dist/bin.js +125 -0
- package/package.json +27 -0
- package/templates/enterprise/CHANGELOG.md +140 -0
- package/templates/enterprise/README.md +352 -0
- package/templates/enterprise/__tests__/data-api.test.ts +546 -0
- package/templates/enterprise/__tests__/data-api.test.ts.backup +526 -0
- package/templates/enterprise/__tests__/metadata-api.test.ts +307 -0
- package/templates/enterprise/__tests__/metadata-loading.test.ts +250 -0
- package/templates/enterprise/jest.config.js +22 -0
- package/templates/enterprise/package.json +51 -0
- package/templates/enterprise/src/apps/erp.app.yml +4 -0
- package/templates/enterprise/src/core/attachment.object.yml +57 -0
- package/templates/enterprise/src/core/i18n/en/core.json +60 -0
- package/templates/enterprise/src/core/i18n/zh-CN/core.json +60 -0
- package/templates/enterprise/src/core/index.ts +24 -0
- package/templates/enterprise/src/core/organization.object.yml +78 -0
- package/templates/enterprise/src/core/user.object.yml +80 -0
- package/templates/enterprise/src/extensions/README.md +56 -0
- package/templates/enterprise/src/extensions/user.extension.object.yml +42 -0
- package/templates/enterprise/src/extensions/user.ts +26 -0
- package/templates/enterprise/src/index.ts +47 -0
- package/templates/enterprise/src/modules/crm/README.md +99 -0
- package/templates/enterprise/src/modules/crm/crm_account.object.yml +105 -0
- package/templates/enterprise/src/modules/crm/crm_contact.object.yml +103 -0
- package/templates/enterprise/src/modules/crm/crm_lead.object.yml +148 -0
- package/templates/enterprise/src/modules/crm/crm_opportunity.object.yml +128 -0
- package/templates/enterprise/src/modules/crm/i18n/en/crm.json +61 -0
- package/templates/enterprise/src/modules/crm/i18n/zh-CN/crm.json +61 -0
- package/templates/enterprise/src/modules/crm/index.ts +29 -0
- package/templates/enterprise/src/modules/finance/README.md +112 -0
- package/templates/enterprise/src/modules/finance/finance_budget.object.yml +108 -0
- package/templates/enterprise/src/modules/finance/finance_expense.object.yml +151 -0
- package/templates/enterprise/src/modules/finance/finance_invoice.object.yml +143 -0
- package/templates/enterprise/src/modules/finance/finance_payment.object.yml +96 -0
- package/templates/enterprise/src/modules/finance/index.ts +26 -0
- package/templates/enterprise/src/modules/hr/README.md +95 -0
- package/templates/enterprise/src/modules/hr/hr_department.object.yml +59 -0
- package/templates/enterprise/src/modules/hr/hr_employee.object.yml +137 -0
- package/templates/enterprise/src/modules/hr/hr_position.object.yml +79 -0
- package/templates/enterprise/src/modules/hr/hr_timesheet.object.yml +114 -0
- package/templates/enterprise/src/modules/hr/index.ts +26 -0
- package/templates/enterprise/src/modules/project/README.md +132 -0
- package/templates/enterprise/src/modules/project/index.ts +26 -0
- package/templates/enterprise/src/modules/project/project_milestone.object.yml +70 -0
- package/templates/enterprise/src/modules/project/project_project.object.yml +135 -0
- package/templates/enterprise/src/modules/project/project_task.object.yml +121 -0
- package/templates/enterprise/src/modules/project/project_timesheet_entry.object.yml +95 -0
- package/templates/enterprise/src/plugins/audit/audit.plugin.ts +23 -0
- package/templates/enterprise/src/plugins/audit/index.ts +6 -0
- package/templates/enterprise/src/plugins/audit/note.object.yml +3 -0
- package/templates/enterprise/src/shared/constants.ts +30 -0
- package/templates/enterprise/src/shared/utils.ts +54 -0
- package/templates/enterprise/src/shared/validators.ts +47 -0
- package/templates/enterprise/src/types/attachment.ts +41 -0
- package/templates/enterprise/src/types/crm_account.ts +61 -0
- package/templates/enterprise/src/types/crm_contact.ts +61 -0
- package/templates/enterprise/src/types/crm_lead.ts +77 -0
- package/templates/enterprise/src/types/crm_opportunity.ts +53 -0
- package/templates/enterprise/src/types/finance_budget.ts +61 -0
- package/templates/enterprise/src/types/finance_expense.ts +65 -0
- package/templates/enterprise/src/types/finance_invoice.ts +69 -0
- package/templates/enterprise/src/types/finance_payment.ts +49 -0
- package/templates/enterprise/src/types/hr_department.ts +37 -0
- package/templates/enterprise/src/types/hr_employee.ts +85 -0
- package/templates/enterprise/src/types/hr_position.ts +49 -0
- package/templates/enterprise/src/types/hr_timesheet.ts +57 -0
- package/templates/enterprise/src/types/index.ts +20 -0
- package/templates/enterprise/src/types/note.ts +9 -0
- package/templates/enterprise/src/types/organization.ts +53 -0
- package/templates/enterprise/src/types/project_milestone.ts +41 -0
- package/templates/enterprise/src/types/project_project.ts +69 -0
- package/templates/enterprise/src/types/project_task.ts +57 -0
- package/templates/enterprise/src/types/project_timesheet_entry.ts +45 -0
- package/templates/enterprise/src/types/user.ts +65 -0
- package/templates/enterprise/tsconfig.json +10 -0
- package/templates/enterprise/tsconfig.tsbuildinfo +1 -0
- package/templates/hello-world/CHANGELOG.md +33 -0
- package/templates/hello-world/README.md +29 -0
- package/templates/hello-world/package.json +24 -0
- package/templates/hello-world/src/index.ts +58 -0
- package/templates/hello-world/tsconfig.json +10 -0
- package/templates/starter/CHANGELOG.md +191 -0
- package/templates/starter/README.md +17 -0
- package/templates/starter/__tests__/projects-hooks-actions.test.ts +490 -0
- package/templates/starter/jest.config.js +22 -0
- package/templates/starter/package.json +51 -0
- package/templates/starter/src/README.pages.md +110 -0
- package/templates/starter/src/demo.app.yml +4 -0
- package/templates/starter/src/i18n/zh-CN/projects.json +22 -0
- package/templates/starter/src/index.ts +55 -0
- package/templates/starter/src/modules/kitchen-sink/kitchen_sink.data.yml +18 -0
- package/templates/starter/src/modules/kitchen-sink/kitchen_sink.object.yml +156 -0
- package/templates/starter/src/modules/projects/project_approval.workflow.yml +51 -0
- package/templates/starter/src/modules/projects/projects.action.ts +472 -0
- package/templates/starter/src/modules/projects/projects.data.yml +13 -0
- package/templates/starter/src/modules/projects/projects.hook.ts +339 -0
- package/templates/starter/src/modules/projects/projects.object.yml +148 -0
- package/templates/starter/src/modules/projects/projects.permission.yml +141 -0
- package/templates/starter/src/modules/projects/projects.validation.yml +37 -0
- package/templates/starter/src/modules/tasks/tasks.data.yml +23 -0
- package/templates/starter/src/modules/tasks/tasks.object.yml +34 -0
- package/templates/starter/src/modules/tasks/tasks.permission.yml +167 -0
- package/templates/starter/src/types/index.ts +3 -0
- package/templates/starter/src/types/kitchen_sink.ts +101 -0
- package/templates/starter/src/types/projects.ts +49 -0
- package/templates/starter/src/types/tasks.ts +33 -0
- package/templates/starter/tsconfig.json +11 -0
- package/templates/starter/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata API Tests for Enterprise Starter
|
|
3
|
+
*
|
|
4
|
+
* Tests metadata API 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
|
+
|
|
12
|
+
describe('Enterprise Metadata API', () => {
|
|
13
|
+
let app: ObjectQL;
|
|
14
|
+
|
|
15
|
+
beforeAll(async () => {
|
|
16
|
+
// Initialize ObjectQL
|
|
17
|
+
app = new ObjectQL({
|
|
18
|
+
datasources: {
|
|
19
|
+
default: new SqlDriver({
|
|
20
|
+
client: 'sqlite3',
|
|
21
|
+
connection: {
|
|
22
|
+
filename: ':memory:'
|
|
23
|
+
},
|
|
24
|
+
useNullAsDefault: true
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Load metadata
|
|
30
|
+
const srcDir = path.resolve(__dirname, '../src');
|
|
31
|
+
const loader = new ObjectLoader(app.metadata);
|
|
32
|
+
loader.load(srcDir);
|
|
33
|
+
|
|
34
|
+
await app.init();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterAll(async () => {
|
|
38
|
+
if (app && (app as any).datasources?.default) {
|
|
39
|
+
const driver = (app as any).datasources.default;
|
|
40
|
+
if (driver.knex) {
|
|
41
|
+
await driver.knex.destroy();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('Object Metadata Retrieval', () => {
|
|
47
|
+
it('should get all object configurations', () => {
|
|
48
|
+
const configs = app.getConfigs();
|
|
49
|
+
|
|
50
|
+
expect(configs).toBeDefined();
|
|
51
|
+
expect(Object.keys(configs).length).toBeGreaterThanOrEqual(15);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should get specific core object metadata', () => {
|
|
55
|
+
const userConfig = app.getObject('user');
|
|
56
|
+
|
|
57
|
+
expect(userConfig).toBeDefined();
|
|
58
|
+
expect(userConfig.name).toBe('user');
|
|
59
|
+
expect(userConfig.fields).toBeDefined();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should get specific CRM object metadata', () => {
|
|
63
|
+
const accountConfig = app.getObject('crm_account');
|
|
64
|
+
|
|
65
|
+
expect(accountConfig).toBeDefined();
|
|
66
|
+
expect(accountConfig.name).toBe('crm_account');
|
|
67
|
+
expect(accountConfig.fields).toBeDefined();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should get specific HR object metadata', () => {
|
|
71
|
+
const employeeConfig = app.getObject('hr_employee');
|
|
72
|
+
|
|
73
|
+
expect(employeeConfig).toBeDefined();
|
|
74
|
+
expect(employeeConfig.name).toBe('hr_employee');
|
|
75
|
+
expect(employeeConfig.fields).toBeDefined();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should get specific Project object metadata', () => {
|
|
79
|
+
const projectConfig = app.getObject('project_project');
|
|
80
|
+
|
|
81
|
+
expect(projectConfig).toBeDefined();
|
|
82
|
+
expect(projectConfig.name).toBe('project_project');
|
|
83
|
+
expect(projectConfig.fields).toBeDefined();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should get specific Finance object metadata', () => {
|
|
87
|
+
const invoiceConfig = app.getObject('finance_invoice');
|
|
88
|
+
|
|
89
|
+
expect(invoiceConfig).toBeDefined();
|
|
90
|
+
expect(invoiceConfig.name).toBe('finance_invoice');
|
|
91
|
+
expect(invoiceConfig.fields).toBeDefined();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('Metadata Registry Operations', () => {
|
|
96
|
+
it('should list all objects via metadata.list', () => {
|
|
97
|
+
const objects = app.metadata.list('object');
|
|
98
|
+
|
|
99
|
+
expect(objects).toBeDefined();
|
|
100
|
+
expect(Array.isArray(objects)).toBe(true);
|
|
101
|
+
expect(objects.length).toBeGreaterThanOrEqual(15);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should get object via metadata.get', () => {
|
|
105
|
+
const userMeta = app.metadata.get('object', 'user');
|
|
106
|
+
|
|
107
|
+
expect(userMeta).toBeDefined();
|
|
108
|
+
expect(userMeta.name).toBe('user');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should get CRM object via metadata.get', () => {
|
|
112
|
+
const accountMeta = app.metadata.get('object', 'crm_account');
|
|
113
|
+
|
|
114
|
+
expect(accountMeta).toBeDefined();
|
|
115
|
+
expect(accountMeta.name).toBe('crm_account');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should list extensions if present', () => {
|
|
119
|
+
const extensions = app.metadata.list('extension');
|
|
120
|
+
|
|
121
|
+
expect(extensions).toBeDefined();
|
|
122
|
+
expect(Array.isArray(extensions)).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should list apps if present', () => {
|
|
126
|
+
const apps = app.metadata.list('app');
|
|
127
|
+
|
|
128
|
+
expect(apps).toBeDefined();
|
|
129
|
+
expect(Array.isArray(apps)).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should list menus if present', () => {
|
|
133
|
+
const menus = app.metadata.list('menu');
|
|
134
|
+
|
|
135
|
+
expect(menus).toBeDefined();
|
|
136
|
+
expect(Array.isArray(menus)).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('Field Metadata', () => {
|
|
141
|
+
it('should retrieve User field metadata', () => {
|
|
142
|
+
const userConfig = app.getObject('user');
|
|
143
|
+
|
|
144
|
+
expect(userConfig.fields).toBeDefined();
|
|
145
|
+
expect(userConfig.fields.name).toBeDefined();
|
|
146
|
+
expect(userConfig.fields.email).toBeDefined();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should retrieve CRM Account field metadata', () => {
|
|
150
|
+
const accountConfig = app.getObject('crm_account');
|
|
151
|
+
|
|
152
|
+
expect(accountConfig.fields).toBeDefined();
|
|
153
|
+
expect(accountConfig.fields.name).toBeDefined();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should retrieve HR Employee field metadata', () => {
|
|
157
|
+
const employeeConfig = app.getObject('hr_employee');
|
|
158
|
+
|
|
159
|
+
expect(employeeConfig.fields).toBeDefined();
|
|
160
|
+
expect(employeeConfig.fields.first_name).toBeDefined();
|
|
161
|
+
expect(employeeConfig.fields.last_name).toBeDefined();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should retrieve Project field metadata', () => {
|
|
165
|
+
const projectConfig = app.getObject('project_project');
|
|
166
|
+
|
|
167
|
+
expect(projectConfig.fields).toBeDefined();
|
|
168
|
+
expect(projectConfig.fields.name).toBeDefined();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should retrieve Finance Invoice field metadata', () => {
|
|
172
|
+
const invoiceConfig = app.getObject('finance_invoice');
|
|
173
|
+
|
|
174
|
+
expect(invoiceConfig.fields).toBeDefined();
|
|
175
|
+
expect(invoiceConfig.fields.invoice_number).toBeDefined();
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('Module Organization Metadata', () => {
|
|
180
|
+
it('should identify objects by module prefix', () => {
|
|
181
|
+
const configs = app.getConfigs();
|
|
182
|
+
const objectNames = Object.keys(configs);
|
|
183
|
+
console.log('Object Names:', objectNames); // Debugging
|
|
184
|
+
|
|
185
|
+
const crmObjects = objectNames.filter(name => name.startsWith('crm_'));
|
|
186
|
+
const hrObjects = objectNames.filter(name => name.startsWith('hr_'));
|
|
187
|
+
const projectObjects = objectNames.filter(name => name.startsWith('project_'));
|
|
188
|
+
const financeObjects = objectNames.filter(name => name.startsWith('finance_'));
|
|
189
|
+
|
|
190
|
+
expect(crmObjects.length).toBeGreaterThanOrEqual(3);
|
|
191
|
+
expect(hrObjects.length).toBeGreaterThanOrEqual(3);
|
|
192
|
+
expect(projectObjects.length).toBeGreaterThanOrEqual(3);
|
|
193
|
+
expect(financeObjects.length).toBeGreaterThanOrEqual(2);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should have consistent naming convention', () => {
|
|
197
|
+
const configs = app.getConfigs();
|
|
198
|
+
const objectNames = Object.keys(configs);
|
|
199
|
+
|
|
200
|
+
// Check that module objects follow Module_Name pattern
|
|
201
|
+
const moduleObjects = objectNames.filter(name =>
|
|
202
|
+
name.startsWith('CRM_') ||
|
|
203
|
+
name.startsWith('HR_') ||
|
|
204
|
+
name.startsWith('Project_') ||
|
|
205
|
+
name.startsWith('Finance_')
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
moduleObjects.forEach(name => {
|
|
209
|
+
expect(name).toMatch(/^[A-Z][a-zA-Z]+_[A-Z]/);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe('Metadata Completeness', () => {
|
|
215
|
+
it('should have labels for all objects', () => {
|
|
216
|
+
const configs = app.getConfigs();
|
|
217
|
+
|
|
218
|
+
Object.values(configs).forEach((config: any) => {
|
|
219
|
+
expect(config.label || config.name).toBeDefined();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should have fields for all objects', () => {
|
|
224
|
+
const configs = app.getConfigs();
|
|
225
|
+
|
|
226
|
+
Object.values(configs).forEach((config: any) => {
|
|
227
|
+
expect(config.fields).toBeDefined();
|
|
228
|
+
expect(Object.keys(config.fields).length).toBeGreaterThan(0);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('should have valid field types', () => {
|
|
233
|
+
const configs = app.getConfigs();
|
|
234
|
+
const validTypes = ['string', 'text', 'textarea', 'number', 'boolean', 'date', 'datetime',
|
|
235
|
+
'select', 'lookup', 'master_detail', 'url', 'email', 'phone',
|
|
236
|
+
'currency', 'formula', 'html', 'markdown', 'password', 'file',
|
|
237
|
+
'image', 'avatar', 'location', 'percent'];
|
|
238
|
+
|
|
239
|
+
Object.values(configs).forEach((config: any) => {
|
|
240
|
+
Object.values(config.fields).forEach((field: any) => {
|
|
241
|
+
if (field.type) {
|
|
242
|
+
expect(validTypes).toContain(field.type);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe('Metadata Consistency', () => {
|
|
250
|
+
it('should return same metadata via different access methods', () => {
|
|
251
|
+
const viaGetObject = app.getObject('user');
|
|
252
|
+
const viaMetadataGet = app.metadata.get('object', 'user');
|
|
253
|
+
|
|
254
|
+
expect(viaGetObject.name).toBe(viaMetadataGet.name);
|
|
255
|
+
expect(viaGetObject.label).toBe(viaMetadataGet.label);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should have consistent object count', () => {
|
|
259
|
+
const viaGetConfigs = Object.keys(app.getConfigs()).length;
|
|
260
|
+
const viaMetadataList = app.metadata.list('object').length;
|
|
261
|
+
|
|
262
|
+
expect(viaGetConfigs).toBe(viaMetadataList);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe('Extension Metadata', () => {
|
|
267
|
+
it('should load user extension metadata if present', () => {
|
|
268
|
+
const extensions = app.metadata.list('extension');
|
|
269
|
+
|
|
270
|
+
if (extensions && extensions.length > 0) {
|
|
271
|
+
const userExtension = extensions.find((ext: any) =>
|
|
272
|
+
ext.id && ext.id.includes('user')
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
if (userExtension) {
|
|
276
|
+
expect(userExtension).toBeDefined();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
describe('Application Metadata', () => {
|
|
283
|
+
it('should load ERP app metadata if present', () => {
|
|
284
|
+
const apps = app.metadata.list('app');
|
|
285
|
+
|
|
286
|
+
if (apps && apps.length > 0) {
|
|
287
|
+
const erpApp = apps.find((app: any) =>
|
|
288
|
+
app.id && app.id.toLowerCase().includes('erp')
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
if (erpApp) {
|
|
292
|
+
expect(erpApp).toBeDefined();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe('Menu Metadata', () => {
|
|
299
|
+
it('should load menu metadata if present', () => {
|
|
300
|
+
const menus = app.metadata.list('menu');
|
|
301
|
+
|
|
302
|
+
if (menus && menus.length > 0) {
|
|
303
|
+
expect(menus[0]).toBeDefined();
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
});
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata Loading Tests for Enterprise Starter
|
|
3
|
+
*
|
|
4
|
+
* Tests that the enterprise-scale metadata organization is correctly loaded
|
|
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
|
+
|
|
12
|
+
describe('Enterprise Metadata Loading', () => {
|
|
13
|
+
let app: ObjectQL;
|
|
14
|
+
|
|
15
|
+
beforeAll(async () => {
|
|
16
|
+
// Initialize ObjectQL
|
|
17
|
+
app = new ObjectQL({
|
|
18
|
+
datasources: {
|
|
19
|
+
default: new SqlDriver({
|
|
20
|
+
client: 'sqlite3',
|
|
21
|
+
connection: {
|
|
22
|
+
filename: ':memory:'
|
|
23
|
+
},
|
|
24
|
+
useNullAsDefault: true
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Load metadata from src directory (modular structure)
|
|
30
|
+
const srcDir = path.resolve(__dirname, '../src');
|
|
31
|
+
const loader = new ObjectLoader(app.metadata);
|
|
32
|
+
loader.load(srcDir);
|
|
33
|
+
|
|
34
|
+
await app.init();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterAll(async () => {
|
|
38
|
+
if (app && (app as any).datasources?.default) {
|
|
39
|
+
const driver = (app as any).datasources.default;
|
|
40
|
+
if (driver.knex) {
|
|
41
|
+
await driver.knex.destroy();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('Core Objects', () => {
|
|
47
|
+
it('should load User core object', () => {
|
|
48
|
+
const userConfig = app.getObject('user');
|
|
49
|
+
|
|
50
|
+
expect(userConfig).toBeDefined();
|
|
51
|
+
expect(userConfig.name).toBe('user');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should load Organization core object', () => {
|
|
55
|
+
const orgConfig = app.getObject('organization');
|
|
56
|
+
|
|
57
|
+
expect(orgConfig).toBeDefined();
|
|
58
|
+
expect(orgConfig.name).toBe('organization');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should load Attachment core object', () => {
|
|
62
|
+
const attachmentConfig = app.getObject('attachment');
|
|
63
|
+
|
|
64
|
+
expect(attachmentConfig).toBeDefined();
|
|
65
|
+
expect(attachmentConfig.name).toBe('attachment');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('CRM Module Objects', () => {
|
|
70
|
+
it('should load CRM Account object', () => {
|
|
71
|
+
const accountConfig = app.getObject('crm_account');
|
|
72
|
+
|
|
73
|
+
expect(accountConfig).toBeDefined();
|
|
74
|
+
expect(accountConfig.name).toBe('crm_account');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should load CRM Contact object', () => {
|
|
78
|
+
const contactConfig = app.getObject('crm_contact');
|
|
79
|
+
|
|
80
|
+
expect(contactConfig).toBeDefined();
|
|
81
|
+
expect(contactConfig.name).toBe('crm_contact');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should load CRM Lead object', () => {
|
|
85
|
+
const leadConfig = app.getObject('crm_lead');
|
|
86
|
+
|
|
87
|
+
expect(leadConfig).toBeDefined();
|
|
88
|
+
expect(leadConfig.name).toBe('crm_lead');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should load CRM Opportunity object', () => {
|
|
92
|
+
const opportunityConfig = app.getObject('crm_opportunity');
|
|
93
|
+
|
|
94
|
+
expect(opportunityConfig).toBeDefined();
|
|
95
|
+
expect(opportunityConfig.name).toBe('crm_opportunity');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('HR Module Objects', () => {
|
|
100
|
+
it('should load HR Employee object', () => {
|
|
101
|
+
const employeeConfig = app.getObject('hr_employee');
|
|
102
|
+
|
|
103
|
+
expect(employeeConfig).toBeDefined();
|
|
104
|
+
expect(employeeConfig.name).toBe('hr_employee');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should load HR Department object', () => {
|
|
108
|
+
const deptConfig = app.getObject('hr_department');
|
|
109
|
+
|
|
110
|
+
expect(deptConfig).toBeDefined();
|
|
111
|
+
expect(deptConfig.name).toBe('hr_department');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should load HR Position object', () => {
|
|
115
|
+
const posConfig = app.getObject('hr_position');
|
|
116
|
+
|
|
117
|
+
expect(posConfig).toBeDefined();
|
|
118
|
+
expect(posConfig.name).toBe('hr_position');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should load HR Timesheet object', () => {
|
|
122
|
+
const timesheetConfig = app.getObject('hr_timesheet');
|
|
123
|
+
|
|
124
|
+
expect(timesheetConfig).toBeDefined();
|
|
125
|
+
expect(timesheetConfig.name).toBe('hr_timesheet');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('Project Module Objects', () => {
|
|
130
|
+
it('should load Project Project object', () => {
|
|
131
|
+
const projectConfig = app.getObject('project_project');
|
|
132
|
+
|
|
133
|
+
expect(projectConfig).toBeDefined();
|
|
134
|
+
expect(projectConfig.name).toBe('project_project');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should load Project Task object', () => {
|
|
138
|
+
const taskConfig = app.getObject('project_task');
|
|
139
|
+
|
|
140
|
+
expect(taskConfig).toBeDefined();
|
|
141
|
+
expect(taskConfig.name).toBe('project_task');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should load Project Milestone object', () => {
|
|
145
|
+
const milestoneConfig = app.getObject('project_milestone');
|
|
146
|
+
|
|
147
|
+
expect(milestoneConfig).toBeDefined();
|
|
148
|
+
expect(milestoneConfig.name).toBe('project_milestone');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should load Project Timesheet Entry object', () => {
|
|
152
|
+
const entryConfig = app.getObject('project_timesheet_entry');
|
|
153
|
+
|
|
154
|
+
expect(entryConfig).toBeDefined();
|
|
155
|
+
expect(entryConfig.name).toBe('project_timesheet_entry');
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe('Finance Module Objects', () => {
|
|
160
|
+
it('should load Finance Invoice object', () => {
|
|
161
|
+
const invoiceConfig = app.getObject('finance_invoice');
|
|
162
|
+
|
|
163
|
+
expect(invoiceConfig).toBeDefined();
|
|
164
|
+
expect(invoiceConfig.name).toBe('finance_invoice');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should load Finance Payment object', () => {
|
|
168
|
+
const paymentConfig = app.getObject('finance_payment');
|
|
169
|
+
|
|
170
|
+
expect(paymentConfig).toBeDefined();
|
|
171
|
+
expect(paymentConfig.name).toBe('finance_payment');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should load Finance Expense object', () => {
|
|
175
|
+
const expenseConfig = app.getObject('finance_expense');
|
|
176
|
+
|
|
177
|
+
expect(expenseConfig).toBeDefined();
|
|
178
|
+
expect(expenseConfig.name).toBe('finance_expense');
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('Module-based Organization', () => {
|
|
183
|
+
it('should load objects from multiple modules', () => {
|
|
184
|
+
const configs = app.getConfigs();
|
|
185
|
+
const objectNames = Object.keys(configs);
|
|
186
|
+
|
|
187
|
+
// Should have objects from different modules
|
|
188
|
+
const hasCRM = objectNames.some(name => name.startsWith('crm_'));
|
|
189
|
+
const hasHR = objectNames.some(name => name.startsWith('hr_'));
|
|
190
|
+
const hasProject = objectNames.some(name => name.startsWith('project_'));
|
|
191
|
+
const hasFinance = objectNames.some(name => name.startsWith('finance_'));
|
|
192
|
+
|
|
193
|
+
expect(hasCRM).toBe(true);
|
|
194
|
+
expect(hasHR).toBe(true);
|
|
195
|
+
expect(hasProject).toBe(true);
|
|
196
|
+
expect(hasFinance).toBe(true);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should have significant number of objects loaded', () => {
|
|
200
|
+
const configs = app.getConfigs();
|
|
201
|
+
const objectCount = Object.keys(configs).length;
|
|
202
|
+
|
|
203
|
+
// Enterprise starter should have at least 15 objects
|
|
204
|
+
expect(objectCount).toBeGreaterThanOrEqual(15);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('Extensions and Apps', () => {
|
|
209
|
+
it('should load User extension if present', () => {
|
|
210
|
+
const extensions = app.metadata.list('extension');
|
|
211
|
+
|
|
212
|
+
if (extensions && extensions.length > 0) {
|
|
213
|
+
expect(extensions.length).toBeGreaterThanOrEqual(1);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should load ERP app metadata if present', () => {
|
|
218
|
+
const apps = app.metadata.list('app');
|
|
219
|
+
|
|
220
|
+
if (apps && apps.length > 0) {
|
|
221
|
+
expect(apps.length).toBeGreaterThanOrEqual(1);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should load menu metadata if present', () => {
|
|
226
|
+
const menus = app.metadata.list('menu');
|
|
227
|
+
|
|
228
|
+
if (menus && menus.length > 0) {
|
|
229
|
+
expect(menus.length).toBeGreaterThanOrEqual(1);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe('Metadata Registry', () => {
|
|
235
|
+
it('should support listing all objects', () => {
|
|
236
|
+
const objects = app.metadata.list('object');
|
|
237
|
+
|
|
238
|
+
expect(objects).toBeDefined();
|
|
239
|
+
expect(objects.length).toBeGreaterThanOrEqual(15);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should support getting specific objects', () => {
|
|
243
|
+
const userMeta = app.metadata.get('object', 'user');
|
|
244
|
+
const accountMeta = app.metadata.get('object', 'crm_account');
|
|
245
|
+
|
|
246
|
+
expect(userMeta).toBeDefined();
|
|
247
|
+
expect(accountMeta).toBeDefined();
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
preset: 'ts-jest',
|
|
3
|
+
testEnvironment: 'node',
|
|
4
|
+
testMatch: ['**/__tests__/**/*.test.ts'],
|
|
5
|
+
moduleNameMapper: {
|
|
6
|
+
'^@objectql/core$': '<rootDir>/../../foundation/core/src',
|
|
7
|
+
'^@objectql/types$': '<rootDir>/../../foundation/types/src',
|
|
8
|
+
'^@objectql/platform-node$': '<rootDir>/../../foundation/platform-node/src',
|
|
9
|
+
'^@objectql/driver-sql$': '<rootDir>/../../drivers/sql/src',
|
|
10
|
+
'^@objectql/driver-mongo$': '<rootDir>/../../drivers/mongo/src',
|
|
11
|
+
'^@objectql/server$': '<rootDir>/../../runtime/server/src',
|
|
12
|
+
},
|
|
13
|
+
transform: {
|
|
14
|
+
'^.+\\.ts$': ['ts-jest', {
|
|
15
|
+
isolatedModules: true,
|
|
16
|
+
}],
|
|
17
|
+
},
|
|
18
|
+
collectCoverageFrom: [
|
|
19
|
+
'src/**/*.ts',
|
|
20
|
+
'!src/**/*.d.ts',
|
|
21
|
+
],
|
|
22
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@objectql/example-enterprise-erp",
|
|
3
|
+
"version": "1.8.4",
|
|
4
|
+
"description": "ObjectQL Enterprise Example - CRM & HR Demo",
|
|
5
|
+
"private": true,
|
|
6
|
+
"keywords": [
|
|
7
|
+
"objectql",
|
|
8
|
+
"module",
|
|
9
|
+
"enterprise",
|
|
10
|
+
"crm",
|
|
11
|
+
"hr",
|
|
12
|
+
"workflow",
|
|
13
|
+
"standard-library"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "ObjectQL Contributors",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/objectql/objectql.git",
|
|
20
|
+
"directory": "examples/showcase/enterprise-erp"
|
|
21
|
+
},
|
|
22
|
+
"main": "dist/index.js",
|
|
23
|
+
"types": "dist/index.d.ts",
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"start": "ts-node src/index.ts",
|
|
29
|
+
"codegen": "objectql generate -s src -o src/types",
|
|
30
|
+
"build": "npm run codegen && tsc && rsync -a --include '*/' --include '*.yml' --exclude '*' src/ dist/",
|
|
31
|
+
"test": "jest"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@objectql/core": "workspace:*",
|
|
35
|
+
"@objectql/types": "workspace:*",
|
|
36
|
+
"@objectql/driver-sql": "workspace:*",
|
|
37
|
+
"@objectql/platform-node": "workspace:*",
|
|
38
|
+
"sqlite3": "^5.1.7"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@objectql/core": "workspace:*",
|
|
42
|
+
"@objectql/types": "workspace:*",
|
|
43
|
+
"@objectql/cli": "workspace:*",
|
|
44
|
+
"@objectql/driver-sql": "workspace:*",
|
|
45
|
+
"@objectql/platform-node": "workspace:*",
|
|
46
|
+
"@types/jest": "^30.0.0",
|
|
47
|
+
"jest": "^30.2.0",
|
|
48
|
+
"ts-jest": "^29.4.6",
|
|
49
|
+
"typescript": "^5.3.0"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: attachment
|
|
2
|
+
label: Attachment
|
|
3
|
+
description: File attachment for any object
|
|
4
|
+
icon: attachment-line
|
|
5
|
+
|
|
6
|
+
fields:
|
|
7
|
+
name:
|
|
8
|
+
type: text
|
|
9
|
+
required: true
|
|
10
|
+
label: File Name
|
|
11
|
+
index: true
|
|
12
|
+
|
|
13
|
+
file_url:
|
|
14
|
+
type: file
|
|
15
|
+
required: true
|
|
16
|
+
label: File URL
|
|
17
|
+
|
|
18
|
+
file_size:
|
|
19
|
+
type: number
|
|
20
|
+
label: File Size (bytes)
|
|
21
|
+
|
|
22
|
+
file_type:
|
|
23
|
+
type: text
|
|
24
|
+
label: MIME Type
|
|
25
|
+
index: true
|
|
26
|
+
|
|
27
|
+
related_to:
|
|
28
|
+
type: text
|
|
29
|
+
label: Related Object Name
|
|
30
|
+
index: true
|
|
31
|
+
|
|
32
|
+
related_id:
|
|
33
|
+
type: text
|
|
34
|
+
label: Related Record ID
|
|
35
|
+
index: true
|
|
36
|
+
|
|
37
|
+
uploaded_by:
|
|
38
|
+
type: lookup
|
|
39
|
+
reference_to: user
|
|
40
|
+
label: Uploaded By
|
|
41
|
+
|
|
42
|
+
description:
|
|
43
|
+
type: textarea
|
|
44
|
+
label: Description
|
|
45
|
+
|
|
46
|
+
tags:
|
|
47
|
+
type: text
|
|
48
|
+
label: Tags (comma-separated)
|
|
49
|
+
|
|
50
|
+
indexes:
|
|
51
|
+
# For finding attachments of a specific record
|
|
52
|
+
related_composite_idx:
|
|
53
|
+
fields: [related_to, related_id]
|
|
54
|
+
|
|
55
|
+
# For file type filtering
|
|
56
|
+
type_created_idx:
|
|
57
|
+
fields: [file_type, created_at]
|