@flusys/nestjs-form-builder 1.1.0-beta → 1.1.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 (54) hide show
  1. package/README.md +723 -0
  2. package/cjs/controllers/form-result.controller.js +67 -5
  3. package/cjs/controllers/form.controller.js +48 -15
  4. package/cjs/docs/form-builder-swagger.config.js +6 -100
  5. package/cjs/dtos/form-result.dto.js +6 -93
  6. package/cjs/dtos/form.dto.js +21 -163
  7. package/cjs/entities/form-with-company.entity.js +12 -2
  8. package/cjs/entities/form.entity.js +103 -3
  9. package/cjs/entities/index.js +28 -16
  10. package/cjs/index.js +1 -0
  11. package/cjs/interfaces/form-result.interface.js +1 -6
  12. package/cjs/modules/form-builder.module.js +57 -83
  13. package/cjs/services/form-builder-config.service.js +6 -16
  14. package/cjs/services/form-builder-datasource.provider.js +17 -63
  15. package/cjs/services/form-result.service.js +107 -181
  16. package/cjs/services/form.service.js +56 -72
  17. package/cjs/utils/computed-field.utils.js +17 -29
  18. package/cjs/utils/permission.utils.js +11 -16
  19. package/controllers/form-result.controller.d.ts +10 -12
  20. package/dtos/form-result.dto.d.ts +2 -19
  21. package/dtos/form.dto.d.ts +6 -32
  22. package/entities/form-with-company.entity.d.ts +2 -2
  23. package/entities/form.entity.d.ts +12 -2
  24. package/entities/index.d.ts +7 -2
  25. package/fesm/controllers/form-result.controller.js +69 -7
  26. package/fesm/controllers/form.controller.js +50 -17
  27. package/fesm/docs/form-builder-swagger.config.js +6 -100
  28. package/fesm/dtos/form-result.dto.js +9 -99
  29. package/fesm/dtos/form.dto.js +22 -165
  30. package/fesm/entities/form-with-company.entity.js +12 -2
  31. package/fesm/entities/form.entity.js +104 -4
  32. package/fesm/entities/index.js +18 -24
  33. package/fesm/index.js +2 -0
  34. package/fesm/modules/form-builder.module.js +57 -83
  35. package/fesm/services/form-builder-config.service.js +6 -16
  36. package/fesm/services/form-builder-datasource.provider.js +17 -63
  37. package/fesm/services/form-result.service.js +107 -181
  38. package/fesm/services/form.service.js +56 -72
  39. package/fesm/utils/computed-field.utils.js +17 -29
  40. package/fesm/utils/permission.utils.js +2 -9
  41. package/index.d.ts +1 -0
  42. package/interfaces/form-builder-module.interface.d.ts +4 -7
  43. package/interfaces/form-result.interface.d.ts +2 -9
  44. package/interfaces/form.interface.d.ts +2 -10
  45. package/modules/form-builder.module.d.ts +4 -3
  46. package/package.json +3 -3
  47. package/services/form-builder-config.service.d.ts +5 -3
  48. package/services/form-builder-datasource.provider.d.ts +3 -6
  49. package/services/form-result.service.d.ts +5 -0
  50. package/services/form.service.d.ts +13 -10
  51. package/utils/permission.utils.d.ts +0 -2
  52. package/cjs/entities/form-base.entity.js +0 -113
  53. package/entities/form-base.entity.d.ts +0 -13
  54. package/fesm/entities/form-base.entity.js +0 -106
@@ -39,29 +39,19 @@ function _ts_param(paramIndex, decorator) {
39
39
  };
40
40
  }
41
41
  let FormBuilderConfigService = class FormBuilderConfigService {
42
- /**
43
- * Check if company feature is enabled
44
- */ isCompanyFeatureEnabled() {
45
- return this.options.bootstrapAppConfig?.enableCompanyFeature ?? false;
42
+ isCompanyFeatureEnabled(tenant) {
43
+ return tenant?.enableCompanyFeature ?? this.options.bootstrapAppConfig?.enableCompanyFeature ?? false;
46
44
  }
47
- /**
48
- * Get database mode
49
- */ getDatabaseMode() {
45
+ getDatabaseMode() {
50
46
  return this.options.bootstrapAppConfig?.databaseMode ?? 'single';
51
47
  }
52
- /**
53
- * Check if multi-tenant mode is enabled
54
- */ isMultiTenant() {
48
+ isMultiTenant() {
55
49
  return this.getDatabaseMode() === 'multi-tenant';
56
50
  }
57
- /**
58
- * Get module options
59
- */ getOptions() {
51
+ getOptions() {
60
52
  return this.options;
61
53
  }
62
- /**
63
- * Get form builder configuration
64
- */ getConfig() {
54
+ getConfig() {
65
55
  return this.options.config;
66
56
  }
67
57
  constructor(options){
@@ -12,8 +12,7 @@ const _modules = require("@flusys/nestjs-shared/modules");
12
12
  const _common = require("@nestjs/common");
13
13
  const _core = require("@nestjs/core");
14
14
  const _express = require("express");
15
- const _interfaces = require("../interfaces");
16
- const _formbuilderconstants = require("../config/form-builder.constants");
15
+ const _formbuilderconfigservice = require("./form-builder-config.service");
17
16
  function _define_property(obj, key, value) {
18
17
  if (key in obj) {
19
18
  Object.defineProperty(obj, key, {
@@ -83,66 +82,28 @@ function _ts_param(paramIndex, decorator) {
83
82
  };
84
83
  }
85
84
  let FormBuilderDataSourceProvider = class FormBuilderDataSourceProvider extends _modules.MultiTenantDataSourceService {
86
- // Factory Methods
87
- /**
88
- * Build parent options from FormBuilderModuleOptions
89
- */ static buildParentOptions(options) {
85
+ static buildParentOptions(options) {
90
86
  return {
91
87
  bootstrapAppConfig: options.bootstrapAppConfig,
92
- defaultDatabaseConfig: options.config?.defaultDatabaseConfig
88
+ defaultDatabaseConfig: options.config?.defaultDatabaseConfig,
89
+ tenantDefaultDatabaseConfig: options.config?.tenantDefaultDatabaseConfig,
90
+ tenants: options.config?.tenants
93
91
  };
94
92
  }
95
- // Feature Flags
96
- /**
97
- * Get global enable company feature flag
98
- */ getEnableCompanyFeature() {
99
- return this.formBuilderOptions.bootstrapAppConfig?.enableCompanyFeature ?? false;
93
+ async getFormBuilderEntities(enableCompanyFeature) {
94
+ const enable = enableCompanyFeature ?? this.configService.isCompanyFeatureEnabled();
95
+ const { getFormBuilderEntitiesByConfig } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")));
96
+ return getFormBuilderEntitiesByConfig(enable);
100
97
  }
101
- /**
102
- * Get enable company feature for specific tenant
103
- * Falls back to global setting if not specified per-tenant
104
- */ getEnableCompanyFeatureForTenant(tenant) {
105
- return tenant?.enableCompanyFeature ?? this.getEnableCompanyFeature();
106
- }
107
- /**
108
- * Get enable company feature for current request context
109
- */ getEnableCompanyFeatureForCurrentTenant() {
110
- return this.getEnableCompanyFeatureForTenant(this.getCurrentTenant() ?? undefined);
111
- }
112
- // Entity Management
113
- /**
114
- * Get form builder entities based on company feature flag
115
- */ async getFormBuilderEntities(enableCompanyFeature) {
116
- const enable = enableCompanyFeature ?? this.getEnableCompanyFeature();
117
- const { Form, FormResult, FormWithCompany } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")));
118
- if (enable) {
119
- return [
120
- FormWithCompany,
121
- FormResult
122
- ];
123
- }
124
- return [
125
- Form,
126
- FormResult
127
- ];
128
- }
129
- // Overrides
130
- /**
131
- * Override to dynamically set entities based on tenant config
132
- */ async createDataSourceFromConfig(config) {
133
- const currentTenant = this.getCurrentTenant();
134
- const enableCompanyFeature = this.getEnableCompanyFeatureForTenant(currentTenant ?? undefined);
98
+ async createDataSourceFromConfig(config) {
99
+ const enableCompanyFeature = this.configService.isCompanyFeatureEnabled(this.getCurrentTenant() ?? undefined);
135
100
  const entities = await this.getFormBuilderEntities(enableCompanyFeature);
136
101
  return super.createDataSourceFromConfig(config, entities);
137
102
  }
138
- /**
139
- * Override to use FormBuilder-specific static cache
140
- */ async getSingleDataSource() {
141
- // Return existing initialized connection from FormBuilder-specific cache
103
+ async getSingleDataSource() {
142
104
  if (FormBuilderDataSourceProvider.singleDataSource?.isInitialized) {
143
105
  return FormBuilderDataSourceProvider.singleDataSource;
144
106
  }
145
- // If another request is creating the connection, wait for it
146
107
  if (FormBuilderDataSourceProvider.singleConnectionLock) {
147
108
  return FormBuilderDataSourceProvider.singleConnectionLock;
148
109
  }
@@ -150,7 +111,6 @@ let FormBuilderDataSourceProvider = class FormBuilderDataSourceProvider extends
150
111
  if (!config) {
151
112
  throw new Error('No database config available. Provide defaultDatabaseConfig in FormBuilderModule options.');
152
113
  }
153
- // Create connection with lock to prevent race conditions
154
114
  const connectionPromise = this.createDataSourceFromConfig(config);
155
115
  FormBuilderDataSourceProvider.singleConnectionLock = connectionPromise;
156
116
  try {
@@ -161,20 +121,15 @@ let FormBuilderDataSourceProvider = class FormBuilderDataSourceProvider extends
161
121
  FormBuilderDataSourceProvider.singleConnectionLock = null;
162
122
  }
163
123
  }
164
- /**
165
- * Override to use FormBuilder-specific static cache for tenant connections
166
- */ async getOrCreateTenantConnection(tenant) {
167
- // Return existing initialized connection from FormBuilder-specific cache
124
+ async getOrCreateTenantConnection(tenant) {
168
125
  const existing = FormBuilderDataSourceProvider.tenantConnections.get(tenant.id);
169
126
  if (existing?.isInitialized) {
170
127
  return existing;
171
128
  }
172
- // If another request is creating this tenant's connection, wait for it
173
129
  const pendingConnection = FormBuilderDataSourceProvider.connectionLocks.get(tenant.id);
174
130
  if (pendingConnection) {
175
131
  return pendingConnection;
176
132
  }
177
- // Create connection with lock to prevent race conditions
178
133
  const config = this.buildTenantDatabaseConfig(tenant);
179
134
  const connectionPromise = this.createDataSourceFromConfig(config);
180
135
  FormBuilderDataSourceProvider.connectionLocks.set(tenant.id, connectionPromise);
@@ -186,11 +141,10 @@ let FormBuilderDataSourceProvider = class FormBuilderDataSourceProvider extends
186
141
  FormBuilderDataSourceProvider.connectionLocks.delete(tenant.id);
187
142
  }
188
143
  }
189
- constructor(formBuilderOptions, request){
190
- super(FormBuilderDataSourceProvider.buildParentOptions(formBuilderOptions), request), _define_property(this, "formBuilderOptions", void 0), _define_property(this, "logger", void 0), this.formBuilderOptions = formBuilderOptions, this.logger = new _common.Logger(FormBuilderDataSourceProvider.name);
144
+ constructor(configService, request){
145
+ super(FormBuilderDataSourceProvider.buildParentOptions(configService.getOptions()), request), _define_property(this, "configService", void 0), _define_property(this, "logger", void 0), this.configService = configService, this.logger = new _common.Logger(FormBuilderDataSourceProvider.name);
191
146
  }
192
147
  };
193
- // Override parent's static properties to have FormBuilder-specific cache
194
148
  _define_property(FormBuilderDataSourceProvider, "tenantConnections", new Map());
195
149
  _define_property(FormBuilderDataSourceProvider, "singleDataSource", null);
196
150
  _define_property(FormBuilderDataSourceProvider, "tenantsRegistry", new Map());
@@ -201,12 +155,12 @@ FormBuilderDataSourceProvider = _ts_decorate([
201
155
  (0, _common.Injectable)({
202
156
  scope: _common.Scope.REQUEST
203
157
  }),
204
- _ts_param(0, (0, _common.Inject)(_formbuilderconstants.FORM_BUILDER_MODULE_OPTIONS)),
158
+ _ts_param(0, (0, _common.Inject)(_formbuilderconfigservice.FormBuilderConfigService)),
205
159
  _ts_param(1, (0, _common.Optional)()),
206
160
  _ts_param(1, (0, _common.Inject)(_core.REQUEST)),
207
161
  _ts_metadata("design:type", Function),
208
162
  _ts_metadata("design:paramtypes", [
209
- typeof _interfaces.FormBuilderModuleOptions === "undefined" ? Object : _interfaces.FormBuilderModuleOptions,
163
+ typeof _formbuilderconfigservice.FormBuilderConfigService === "undefined" ? Object : _formbuilderconfigservice.FormBuilderConfigService,
210
164
  typeof _express.Request === "undefined" ? Object : _express.Request
211
165
  ])
212
166
  ], FormBuilderDataSourceProvider);
@@ -46,27 +46,68 @@ function _ts_param(paramIndex, decorator) {
46
46
  };
47
47
  }
48
48
  let FormResultService = class FormResultService extends _classes.RequestScopedApiService {
49
- /**
50
- * Resolve entity class for this service
51
- * @returns FormResult (same entity regardless of company feature)
52
- */ resolveEntity() {
49
+ resolveEntity() {
53
50
  return _entities.FormResult;
54
51
  }
55
- /**
56
- * Get DataSource provider for this service
57
- * @returns FormBuilderDataSourceProvider instance
58
- */ getDataSourceProvider() {
52
+ getDataSourceProvider() {
59
53
  return this.dataSourceProvider;
60
54
  }
61
- /**
62
- * Initialize form repository for form lookups
63
- */ async ensureFormRepositoryInitialized() {
55
+ async ensureFormRepositoryInitialized() {
64
56
  if (!this.formRepository) {
65
57
  const enableCompanyFeature = this.formBuilderConfig.isCompanyFeatureEnabled();
66
58
  const formEntity = enableCompanyFeature ? _entities.FormWithCompany : _entities.Form;
67
59
  this.formRepository = await this.dataSourceProvider.getRepository(formEntity);
68
60
  }
69
61
  }
62
+ async getActiveForm(formId) {
63
+ await this.ensureFormRepositoryInitialized();
64
+ const form = await this.formRepository.findOne({
65
+ where: {
66
+ id: formId,
67
+ isActive: true,
68
+ deletedAt: (0, _typeorm.IsNull)()
69
+ }
70
+ });
71
+ if (!form) {
72
+ throw new _common.NotFoundException('Form not found or inactive');
73
+ }
74
+ return form;
75
+ }
76
+ applyCompanyFilterToQuery(query, user) {
77
+ if (this.formBuilderConfig.isCompanyFeatureEnabled() && user?.companyId) {
78
+ query.innerJoin('form', 'f', 'f.id = form_result.formId').andWhere('f.company_id = :companyId', {
79
+ companyId: user.companyId
80
+ });
81
+ }
82
+ }
83
+ async validateSubmissionAccess(form, user, isPublic) {
84
+ if (isPublic) {
85
+ if (form.accessType !== _formaccesstypeenum.FormAccessType.PUBLIC) {
86
+ throw new _common.UnauthorizedException('This form is not available for public submission');
87
+ }
88
+ return;
89
+ }
90
+ if (form.accessType === _formaccesstypeenum.FormAccessType.PUBLIC) return;
91
+ if (!user) {
92
+ throw new _common.UnauthorizedException('Authentication required to submit this form');
93
+ }
94
+ if (form.accessType === _formaccesstypeenum.FormAccessType.ACTION_GROUP && form.actionGroups?.length) {
95
+ const hasPermission = await (0, _permissionutils.validateUserPermissions)(user, form.actionGroups, this.cacheManager, this.formBuilderConfig.isCompanyFeatureEnabled(), this.logger, 'submitting form', form.id);
96
+ if (!hasPermission) {
97
+ throw new _common.ForbiddenException('You do not have permission to submit this form');
98
+ }
99
+ } else if (form.accessType !== _formaccesstypeenum.FormAccessType.AUTHENTICATED) {
100
+ throw new _common.BadRequestException('Invalid form access type');
101
+ }
102
+ }
103
+ buildUserDraftWhere(formId, userId) {
104
+ return {
105
+ formId,
106
+ submittedById: userId,
107
+ isDraft: true,
108
+ deletedAt: (0, _typeorm.IsNull)()
109
+ };
110
+ }
70
111
  // Query Customization
71
112
  async getSelectQuery(query, _user, select) {
72
113
  if (!select || !select.length) {
@@ -91,22 +132,28 @@ let FormResultService = class FormResultService extends _classes.RequestScopedAp
91
132
  isRaw: false
92
133
  };
93
134
  }
94
- /**
95
- * Override: Extra query manipulation - Auto-filter by user's company via Form
96
- */ async getExtraManipulateQuery(query, filterDto, user) {
135
+ async getExtraManipulateQuery(query, filterDto, user) {
97
136
  const result = await super.getExtraManipulateQuery(query, filterDto, user);
98
- // If company feature enabled and user has companyId, filter via Form's company
99
- const enableCompanyFeature = this.formBuilderConfig.isCompanyFeatureEnabled();
100
- if (enableCompanyFeature && user?.companyId) {
101
- query.innerJoin('form', 'f', 'f.id = form_result.formId').andWhere('f.company_id = :companyId', {
102
- companyId: user.companyId
103
- });
104
- }
137
+ this.applyCompanyFilterToQuery(query, user);
105
138
  return result;
106
139
  }
107
- /**
108
- * Override: Convert entity to response DTO
109
- */ convertEntityToResponseDto(entity, _isRaw) {
140
+ applyComputedFields(data, form, isDraft) {
141
+ if (isDraft) {
142
+ return data;
143
+ }
144
+ const schema = form.schema;
145
+ const settings = schema?.settings;
146
+ const computedFields = settings?.computedFields;
147
+ if (!computedFields || computedFields.length === 0) {
148
+ return data;
149
+ }
150
+ const computedValues = (0, _computedfieldutils.calculateComputedFields)(data, computedFields);
151
+ return {
152
+ ...data,
153
+ _computed: computedValues
154
+ };
155
+ }
156
+ convertEntityToResponseDto(entity, _isRaw) {
110
157
  return {
111
158
  id: entity.id,
112
159
  formId: entity.formId,
@@ -126,137 +173,55 @@ let FormResultService = class FormResultService extends _classes.RequestScopedAp
126
173
  };
127
174
  }
128
175
  // Form Submission
129
- /**
130
- * Submit a form (authenticated or public)
131
- */ async submitForm(dto, user, isPublic = false) {
176
+ async submitForm(dto, user, isPublic = false) {
132
177
  await this.ensureRepositoryInitialized();
133
- await this.ensureFormRepositoryInitialized();
134
- // Get the form
135
- const form = await this.formRepository.findOne({
136
- where: {
137
- id: dto.formId,
138
- isActive: true,
139
- deletedAt: (0, _typeorm.IsNull)()
140
- }
141
- });
142
- if (!form) {
143
- throw new _common.NotFoundException('Form not found or inactive');
144
- }
145
- // Validate access
146
- if (isPublic) {
147
- if (form.accessType !== _formaccesstypeenum.FormAccessType.PUBLIC) {
148
- throw new _common.UnauthorizedException('This form is not available for public submission');
149
- }
150
- } else {
151
- // For non-public submissions
152
- switch(form.accessType){
153
- case _formaccesstypeenum.FormAccessType.PUBLIC:
154
- break;
155
- case _formaccesstypeenum.FormAccessType.AUTHENTICATED:
156
- if (!user) {
157
- throw new _common.UnauthorizedException('Authentication required to submit this form');
158
- }
159
- break;
160
- case _formaccesstypeenum.FormAccessType.ACTION_GROUP:
161
- if (!user) {
162
- throw new _common.UnauthorizedException('Authentication required to submit this form');
163
- }
164
- // Validate user has at least one of the required permissions
165
- if (form.actionGroups?.length) {
166
- const hasPermission = await (0, _permissionutils.validateUserPermissions)(user, form.actionGroups, this.cacheManager, this.formBuilderConfig.isCompanyFeatureEnabled(), this.logger, 'submitting form', form.id);
167
- if (!hasPermission) {
168
- throw new _common.ForbiddenException('You do not have permission to submit this form');
169
- }
170
- }
171
- break;
172
- default:
173
- throw new _common.BadRequestException('Invalid form access type');
174
- }
175
- }
178
+ const form = await this.getActiveForm(dto.formId);
179
+ await this.validateSubmissionAccess(form, user, isPublic);
176
180
  const isDraft = dto.isDraft ?? false;
177
- // For authenticated users with drafts
181
+ // Handle existing draft for authenticated users
178
182
  if (user?.id) {
179
- // Check for existing draft
180
183
  const existingDraft = await this.repository.findOne({
181
- where: {
182
- formId: dto.formId,
183
- submittedById: user.id,
184
- isDraft: true,
185
- deletedAt: (0, _typeorm.IsNull)()
186
- }
184
+ where: this.buildUserDraftWhere(dto.formId, user.id)
187
185
  });
188
186
  if (existingDraft) {
189
187
  if (isDraft) {
190
- // Update existing draft instead of creating new one
191
- existingDraft.data = dto.data;
192
- existingDraft.schemaVersionSnapshot = form.schema;
193
- existingDraft.schemaVersion = form.schemaVersion;
194
- existingDraft.submittedAt = new Date();
195
- existingDraft.metadata = dto.metadata ?? existingDraft.metadata;
188
+ Object.assign(existingDraft, {
189
+ data: dto.data,
190
+ schemaVersionSnapshot: form.schema,
191
+ schemaVersion: form.schemaVersion,
192
+ submittedAt: new Date(),
193
+ metadata: dto.metadata ?? existingDraft.metadata
194
+ });
196
195
  const saved = await this.repository.save(existingDraft);
197
196
  return this.convertEntityToResponseDto(saved, false);
198
- } else {
199
- // Final submission: delete existing draft first
200
- await this.repository.softRemove(existingDraft);
201
197
  }
198
+ await this.repository.softRemove(existingDraft);
202
199
  }
203
200
  }
204
- // Calculate computed fields if this is a final submission (not draft)
205
- let finalData = dto.data;
206
- if (!isDraft) {
207
- const schema = form.schema;
208
- const settings = schema?.settings;
209
- const computedFields = settings?.computedFields;
210
- if (computedFields && computedFields.length > 0) {
211
- const computedValues = (0, _computedfieldutils.calculateComputedFields)(dto.data, computedFields);
212
- // Merge computed values with submitted data (computed fields use their key as the property name)
213
- finalData = {
214
- ...dto.data,
215
- _computed: computedValues
216
- };
217
- }
218
- }
219
- // Create the result with schema snapshot
220
- const resultData = {
201
+ const saved = await this.repository.save({
221
202
  formId: dto.formId,
222
203
  schemaVersionSnapshot: form.schema,
223
204
  schemaVersion: form.schemaVersion,
224
- data: finalData,
205
+ data: this.applyComputedFields(dto.data, form, isDraft),
225
206
  submittedById: user?.id ?? null,
226
207
  submittedAt: new Date(),
227
208
  isDraft,
228
209
  metadata: dto.metadata ?? null
229
- };
230
- const saved = await this.repository.save(resultData);
210
+ });
231
211
  return this.convertEntityToResponseDto(saved, false);
232
212
  }
233
- /**
234
- * Get user's draft for a specific form
235
- * Returns the most recent draft if exists, null otherwise
236
- */ async getMyDraft(formId, user) {
213
+ async getMyDraft(formId, user) {
237
214
  await this.ensureRepositoryInitialized();
238
215
  const draft = await this.repository.findOne({
239
- where: {
240
- formId,
241
- submittedById: user.id,
242
- isDraft: true,
243
- deletedAt: (0, _typeorm.IsNull)()
244
- },
216
+ where: this.buildUserDraftWhere(formId, user.id),
245
217
  order: {
246
218
  updatedAt: 'DESC'
247
219
  }
248
220
  });
249
- if (!draft) {
250
- return null;
251
- }
252
- return this.convertEntityToResponseDto(draft, false);
221
+ return draft ? this.convertEntityToResponseDto(draft, false) : null;
253
222
  }
254
- /**
255
- * Update existing draft or convert to submission
256
- */ async updateDraft(draftId, dto, user) {
223
+ async updateDraft(draftId, dto, user) {
257
224
  await this.ensureRepositoryInitialized();
258
- await this.ensureFormRepositoryInitialized();
259
- // Find the draft
260
225
  const draft = await this.repository.findOne({
261
226
  where: {
262
227
  id: draftId,
@@ -267,74 +232,35 @@ let FormResultService = class FormResultService extends _classes.RequestScopedAp
267
232
  if (!draft) {
268
233
  throw new _common.NotFoundException('Draft not found');
269
234
  }
270
- // Get form for fresh schema snapshot (in case form was updated)
271
- const form = await this.formRepository.findOne({
272
- where: {
273
- id: dto.formId,
274
- isActive: true,
275
- deletedAt: (0, _typeorm.IsNull)()
276
- }
277
- });
278
- if (!form) {
279
- throw new _common.NotFoundException('Form not found or inactive');
280
- }
235
+ const form = await this.getActiveForm(dto.formId);
281
236
  const isDraft = dto.isDraft ?? false;
282
- // Calculate computed fields if this is a final submission (not draft)
283
- let finalData = dto.data;
284
- if (!isDraft) {
285
- const schema = form.schema;
286
- const settings = schema?.settings;
287
- const computedFields = settings?.computedFields;
288
- if (computedFields && computedFields.length > 0) {
289
- const computedValues = (0, _computedfieldutils.calculateComputedFields)(dto.data, computedFields);
290
- finalData = {
291
- ...dto.data,
292
- _computed: computedValues
293
- };
294
- }
295
- }
296
- // Update the draft
297
- draft.data = finalData;
298
- draft.schemaVersionSnapshot = form.schema;
299
- draft.schemaVersion = form.schemaVersion;
300
- draft.submittedAt = new Date();
301
- draft.isDraft = isDraft;
302
- draft.metadata = dto.metadata ?? draft.metadata;
237
+ Object.assign(draft, {
238
+ data: this.applyComputedFields(dto.data, form, isDraft),
239
+ schemaVersionSnapshot: form.schema,
240
+ schemaVersion: form.schemaVersion,
241
+ submittedAt: new Date(),
242
+ isDraft,
243
+ metadata: dto.metadata ?? draft.metadata
244
+ });
303
245
  const saved = await this.repository.save(draft);
304
246
  return this.convertEntityToResponseDto(saved, false);
305
247
  }
306
- /**
307
- * Get results for a specific form
308
- */ async getByFormId(formId, user, pagination) {
248
+ async getByFormId(formId, user, pagination) {
309
249
  await this.ensureRepositoryInitialized();
310
- const query = this.repository.createQueryBuilder(this.entityName);
311
- query.where('form_result.formId = :formId', {
250
+ const query = this.repository.createQueryBuilder(this.entityName).where('form_result.formId = :formId', {
312
251
  formId
313
- });
314
- query.andWhere('form_result.deletedAt IS NULL');
315
- // Apply company filter via Form if enabled
316
- const enableCompanyFeature = this.formBuilderConfig.isCompanyFeatureEnabled();
317
- if (enableCompanyFeature && user?.companyId) {
318
- query.innerJoin('form', 'f', 'f.id = form_result.formId').andWhere('f.company_id = :companyId', {
319
- companyId: user.companyId
320
- });
321
- }
322
- // Pagination
252
+ }).andWhere('form_result.deletedAt IS NULL');
253
+ this.applyCompanyFilterToQuery(query, user);
323
254
  const page = pagination?.page ?? 0;
324
255
  const pageSize = pagination?.pageSize ?? 10;
325
- query.skip(page * pageSize).take(pageSize);
326
- query.orderBy('form_result.submittedAt', 'DESC');
256
+ query.skip(page * pageSize).take(pageSize).orderBy('form_result.submittedAt', 'DESC');
327
257
  const [entities, total] = await query.getManyAndCount();
328
- const data = entities.map((e)=>this.convertEntityToResponseDto(e, false));
329
258
  return {
330
- data,
259
+ data: entities.map((e)=>this.convertEntityToResponseDto(e, false)),
331
260
  total
332
261
  };
333
262
  }
334
- /**
335
- * Check if user has already submitted this form (non-draft)
336
- * Used for single response mode
337
- */ async hasUserSubmitted(formId, user) {
263
+ async hasUserSubmitted(formId, user) {
338
264
  await this.ensureRepositoryInitialized();
339
265
  const count = await this.repository.count({
340
266
  where: {