@khester/create-dynamics-app 2.1.0 → 2.2.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 (121) hide show
  1. package/dist/artifacts/registry.d.ts +4 -3
  2. package/dist/artifacts/registry.d.ts.map +1 -1
  3. package/dist/artifacts/registry.js +121 -11
  4. package/dist/artifacts/registry.js.map +1 -1
  5. package/dist/artifacts/types.d.ts +1 -1
  6. package/dist/artifacts/types.d.ts.map +1 -1
  7. package/dist/index.js +2 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/injectDevTools.d.ts.map +1 -1
  10. package/dist/injectDevTools.js +4 -2
  11. package/dist/injectDevTools.js.map +1 -1
  12. package/dist/scaffold.d.ts +1 -0
  13. package/dist/scaffold.d.ts.map +1 -1
  14. package/dist/scaffold.js +3 -1
  15. package/dist/scaffold.js.map +1 -1
  16. package/package.json +3 -2
  17. package/templates/grid-starter/ARCHITECTURE.md +66 -0
  18. package/templates/grid-starter/README.md +122 -0
  19. package/templates/grid-starter/env.example +16 -0
  20. package/templates/grid-starter/gitignore +6 -0
  21. package/templates/grid-starter/index.html +16 -0
  22. package/templates/grid-starter/package.json +39 -0
  23. package/templates/grid-starter/src/App.tsx +23 -0
  24. package/templates/grid-starter/src/core/services/FetchApiService.ts +117 -0
  25. package/templates/grid-starter/src/core/services/IApiService.ts +37 -0
  26. package/templates/grid-starter/src/core/services/MockApiService.ts +72 -0
  27. package/templates/grid-starter/src/core/services/ServiceFactory.ts +58 -0
  28. package/templates/grid-starter/src/core/services/XrmApiService.ts +135 -0
  29. package/templates/grid-starter/src/core/services/crudLogging.ts +52 -0
  30. package/templates/grid-starter/src/dev-tools/DevPanel.tsx +239 -0
  31. package/templates/grid-starter/src/grid/GridPage.tsx +119 -0
  32. package/templates/grid-starter/src/index.tsx +18 -0
  33. package/templates/grid-starter/src/vite-env.d.ts +15 -0
  34. package/templates/grid-starter/tools/deploy/deploy-webresource.cjs +117 -0
  35. package/templates/grid-starter/tsconfig.json +19 -0
  36. package/templates/grid-starter/vite.config.ts +76 -0
  37. package/templates/pcf-field/_variants/ValueInput.boolean.tsx +2 -0
  38. package/templates/pcf-field/_variants/ValueInput.date.tsx +2 -0
  39. package/templates/pcf-field/_variants/ValueInput.number.tsx +2 -0
  40. package/templates/pcf-field/_variants/ValueInput.optionset.tsx +77 -0
  41. package/templates/pcf-field/_variants/ValueInput.text.tsx +2 -0
  42. package/templates/pcf-field/index.ts +1 -1
  43. package/templates/pcf-field/package.json +3 -1
  44. package/templates/pcf-field/{{componentName}}Component.tsx +2 -0
  45. package/templates/react-custom-page/ARCHITECTURE.md +75 -0
  46. package/templates/react-custom-page/README.md +74 -568
  47. package/templates/react-custom-page/env.example +16 -0
  48. package/templates/react-custom-page/gitignore +1 -0
  49. package/templates/react-custom-page/index.html +16 -0
  50. package/templates/react-custom-page/package.json +21 -49
  51. package/templates/react-custom-page/src/App.tsx +26 -0
  52. package/templates/react-custom-page/src/core/recordContext.test.ts +30 -0
  53. package/templates/react-custom-page/src/core/recordContext.ts +51 -0
  54. package/templates/react-custom-page/src/core/services/FetchApiService.ts +117 -0
  55. package/templates/react-custom-page/src/core/services/IApiService.ts +37 -0
  56. package/templates/react-custom-page/src/core/services/MockApiService.ts +73 -0
  57. package/templates/react-custom-page/src/core/services/ServiceFactory.ts +58 -0
  58. package/templates/react-custom-page/src/core/services/XrmApiService.ts +135 -0
  59. package/templates/react-custom-page/src/core/services/crudLogging.ts +52 -0
  60. package/templates/react-custom-page/src/dev-tools/DevPanel.tsx +238 -0
  61. package/templates/react-custom-page/src/domain/diff.test.ts +87 -0
  62. package/templates/react-custom-page/src/domain/diff.ts +38 -0
  63. package/templates/react-custom-page/src/example/ExamplePage.tsx +140 -0
  64. package/templates/react-custom-page/src/example/exampleError.ts +36 -0
  65. package/templates/react-custom-page/src/example/hooks/useExampleData.ts +40 -0
  66. package/templates/react-custom-page/src/example/hooks/useExampleForm.ts +99 -0
  67. package/templates/react-custom-page/src/example/mappers/accountMapper.test.ts +38 -0
  68. package/templates/react-custom-page/src/example/mappers/accountMapper.ts +55 -0
  69. package/templates/react-custom-page/src/example/models/Account.ts +74 -0
  70. package/templates/react-custom-page/src/index.tsx +18 -128
  71. package/templates/react-custom-page/src/vite-env.d.ts +15 -0
  72. package/templates/react-custom-page/tools/deploy/deploy-webresource.cjs +117 -0
  73. package/templates/react-custom-page/tsconfig.json +12 -22
  74. package/templates/react-custom-page/vite.config.ts +76 -0
  75. package/templates/starter-page/README.md +38 -0
  76. package/templates/starter-page/_variants/App.dashboard.v8.tsx +46 -0
  77. package/templates/starter-page/_variants/App.form.v8.tsx +59 -0
  78. package/templates/starter-page/_variants/App.master-detail.v8.tsx +61 -0
  79. package/templates/starter-page/_variants/App.panel.v8.tsx +99 -0
  80. package/templates/starter-page/gitignore +5 -0
  81. package/templates/starter-page/package.json +27 -0
  82. package/templates/starter-page/public/index.html +11 -0
  83. package/templates/starter-page/src/index.tsx +10 -0
  84. package/templates/starter-page/src/services/dataverse.ts +30 -0
  85. package/templates/starter-page/tsconfig.json +15 -0
  86. package/templates/starter-page/webpack.config.js +17 -0
  87. package/templates/react-custom-page/deployment/README.md +0 -484
  88. package/templates/react-custom-page/docs/ARCHITECTURE_OVERVIEW.md +0 -506
  89. package/templates/react-custom-page/docs/BEST_PRACTICES.md +0 -723
  90. package/templates/react-custom-page/docs/MIGRATION_GUIDE.md +0 -447
  91. package/templates/react-custom-page/public/index.html +0 -15
  92. package/templates/react-custom-page/scripts/custom-build.js +0 -255
  93. package/templates/react-custom-page/src/components/AccountForm.css +0 -71
  94. package/templates/react-custom-page/src/components/AccountForm.tsx +0 -541
  95. package/templates/react-custom-page/src/components/AccountManagement.css +0 -86
  96. package/templates/react-custom-page/src/components/AccountManagement.tsx +0 -370
  97. package/templates/react-custom-page/src/components/ContactForm.css +0 -48
  98. package/templates/react-custom-page/src/components/ContactForm.tsx +0 -327
  99. package/templates/react-custom-page/src/components/ContactManagement.css +0 -86
  100. package/templates/react-custom-page/src/components/ContactManagement.tsx +0 -357
  101. package/templates/react-custom-page/src/components/Logging/LogDialog.tsx +0 -291
  102. package/templates/react-custom-page/src/components/Logging/LoggingContext.tsx +0 -166
  103. package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.css +0 -192
  104. package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.tsx +0 -177
  105. package/templates/react-custom-page/src/components/Logging/LoggingProvider.tsx +0 -3
  106. package/templates/react-custom-page/src/components/Logging/logger.ts +0 -193
  107. package/templates/react-custom-page/src/constants/account.ts +0 -410
  108. package/templates/react-custom-page/src/constants/contact.ts +0 -362
  109. package/templates/react-custom-page/src/models/Account.ts +0 -480
  110. package/templates/react-custom-page/src/models/BaseEntity.ts +0 -204
  111. package/templates/react-custom-page/src/models/Contact.ts +0 -580
  112. package/templates/react-custom-page/src/pcf/ContactControlWrapper.tsx +0 -107
  113. package/templates/react-custom-page/src/pcf/MultiEntityControlWrapper.tsx +0 -205
  114. package/templates/react-custom-page/src/providers/DynamicsProvider.tsx +0 -353
  115. package/templates/react-custom-page/src/services/MockApiService.ts +0 -260
  116. package/templates/react-custom-page/src/services/ServiceFactory.ts +0 -65
  117. package/templates/react-custom-page/src/services/XrmApiService.ts +0 -213
  118. package/templates/react-custom-page/src/styles/index.css +0 -171
  119. package/templates/react-custom-page/tools/metadata-sync/index.js +0 -152
  120. package/templates/react-custom-page/webpack.config.js +0 -57
  121. /package/templates/_shared/dev-tools/auth/{get-token.js → get-token.cjs} +0 -0
@@ -1,480 +0,0 @@
1
- import { IApiService } from '@khester/dynamics-ui-api-client';
2
- import { BaseEntity } from './BaseEntity';
3
- import { AccountConstants } from '../constants/account';
4
-
5
- /**
6
- * Interface for Account entity
7
- */
8
- export interface IAccount {
9
- accountid?: string;
10
- name: string;
11
- accountnumber?: string;
12
- emailaddress1?: string;
13
- emailaddress2?: string;
14
- emailaddress3?: string;
15
- telephone1?: string;
16
- telephone2?: string;
17
- telephone3?: string;
18
- fax?: string;
19
- websiteurl?: string;
20
- description?: string;
21
- revenue?: number;
22
- numberofemployees?: number;
23
- sic?: string;
24
- tickersymbol?: string;
25
- stockexchange?: string;
26
- ownershipcode?: number;
27
- industrycode?: number;
28
- accountcategorycode?: number;
29
- accountclassificationcode?: number;
30
- accountratingcode?: number;
31
- customersizecode?: number;
32
- customertypecode?: number;
33
- territorycode?: number;
34
- marketcap?: number;
35
- sharesoutstanding?: number;
36
- creditlimit?: number;
37
- creditonhold?: boolean;
38
- donotbulkemail?: boolean;
39
- donotemail?: boolean;
40
- donotfax?: boolean;
41
- donotphone?: boolean;
42
- donotpostalmail?: boolean;
43
- donotsendmm?: boolean;
44
- marketingonly?: boolean;
45
- preferredcontactmethodcode?: number;
46
- paymenttermscode?: number;
47
- shippingmethodcode?: number;
48
- primarycontactid?: string;
49
- parentaccountid?: string;
50
- address1_line1?: string;
51
- address1_line2?: string;
52
- address1_line3?: string;
53
- address1_city?: string;
54
- address1_stateorprovince?: string;
55
- address1_postalcode?: string;
56
- address1_country?: string;
57
- address1_county?: string;
58
- address1_latitude?: number;
59
- address1_longitude?: number;
60
- address1_telephone1?: string;
61
- address1_telephone2?: string;
62
- address1_telephone3?: string;
63
- address1_fax?: string;
64
- address1_addresstypecode?: string;
65
- address1_name?: string;
66
- address1_primarycontactname?: string;
67
- address1_shippingmethodcode?: number;
68
- statecode?: number;
69
- statuscode?: number;
70
- createdon?: string;
71
- modifiedon?: string;
72
- createdby?: string;
73
- modifiedby?: string;
74
- ownerid?: string;
75
- transactioncurrencyid?: string;
76
- }
77
-
78
- /**
79
- * Account entity model class
80
- */
81
- export class Account extends BaseEntity implements IAccount {
82
- accountid?: string;
83
- name: string;
84
- accountnumber?: string;
85
- emailaddress1?: string;
86
- emailaddress2?: string;
87
- emailaddress3?: string;
88
- telephone1?: string;
89
- telephone2?: string;
90
- telephone3?: string;
91
- fax?: string;
92
- websiteurl?: string;
93
- description?: string;
94
- revenue?: number;
95
- numberofemployees?: number;
96
- sic?: string;
97
- tickersymbol?: string;
98
- stockexchange?: string;
99
- ownershipcode?: number;
100
- industrycode?: number;
101
- accountcategorycode?: number;
102
- accountclassificationcode?: number;
103
- accountratingcode?: number;
104
- customersizecode?: number;
105
- customertypecode?: number;
106
- territorycode?: number;
107
- marketcap?: number;
108
- sharesoutstanding?: number;
109
- creditlimit?: number;
110
- creditonhold?: boolean;
111
- donotbulkemail?: boolean;
112
- donotemail?: boolean;
113
- donotfax?: boolean;
114
- donotphone?: boolean;
115
- donotpostalmail?: boolean;
116
- donotsendmm?: boolean;
117
- marketingonly?: boolean;
118
- preferredcontactmethodcode?: number;
119
- paymenttermscode?: number;
120
- shippingmethodcode?: number;
121
- primarycontactid?: string;
122
- parentaccountid?: string;
123
- address1_line1?: string;
124
- address1_line2?: string;
125
- address1_line3?: string;
126
- address1_city?: string;
127
- address1_stateorprovince?: string;
128
- address1_postalcode?: string;
129
- address1_country?: string;
130
- address1_county?: string;
131
- address1_latitude?: number;
132
- address1_longitude?: number;
133
- address1_telephone1?: string;
134
- address1_telephone2?: string;
135
- address1_telephone3?: string;
136
- address1_fax?: string;
137
- address1_addresstypecode?: string;
138
- address1_name?: string;
139
- address1_primarycontactname?: string;
140
- address1_shippingmethodcode?: number;
141
- statecode?: number;
142
- statuscode?: number;
143
- createdon?: string;
144
- modifiedon?: string;
145
- createdby?: string;
146
- modifiedby?: string;
147
- ownerid?: string;
148
- transactioncurrencyid?: string;
149
-
150
- /**
151
- * Constructor for Account entity
152
- * @param account The account data
153
- */
154
- constructor(account: IAccount) {
155
- super();
156
- this.accountid = account.accountid;
157
- this.name = account.name;
158
- this.accountnumber = account.accountnumber;
159
- this.emailaddress1 = account.emailaddress1;
160
- this.emailaddress2 = account.emailaddress2;
161
- this.emailaddress3 = account.emailaddress3;
162
- this.telephone1 = account.telephone1;
163
- this.telephone2 = account.telephone2;
164
- this.telephone3 = account.telephone3;
165
- this.fax = account.fax;
166
- this.websiteurl = account.websiteurl;
167
- this.description = account.description;
168
- this.revenue = account.revenue;
169
- this.numberofemployees = account.numberofemployees;
170
- this.sic = account.sic;
171
- this.tickersymbol = account.tickersymbol;
172
- this.stockexchange = account.stockexchange;
173
- this.ownershipcode = account.ownershipcode;
174
- this.industrycode = account.industrycode;
175
- this.accountcategorycode = account.accountcategorycode;
176
- this.accountclassificationcode = account.accountclassificationcode;
177
- this.accountratingcode = account.accountratingcode;
178
- this.customersizecode = account.customersizecode;
179
- this.customertypecode = account.customertypecode;
180
- this.territorycode = account.territorycode;
181
- this.marketcap = account.marketcap;
182
- this.sharesoutstanding = account.sharesoutstanding;
183
- this.creditlimit = account.creditlimit;
184
- this.creditonhold = account.creditonhold;
185
- this.donotbulkemail = account.donotbulkemail;
186
- this.donotemail = account.donotemail;
187
- this.donotfax = account.donotfax;
188
- this.donotphone = account.donotphone;
189
- this.donotpostalmail = account.donotpostalmail;
190
- this.donotsendmm = account.donotsendmm;
191
- this.marketingonly = account.marketingonly;
192
- this.preferredcontactmethodcode = account.preferredcontactmethodcode;
193
- this.paymenttermscode = account.paymenttermscode;
194
- this.shippingmethodcode = account.shippingmethodcode;
195
- this.primarycontactid = account.primarycontactid;
196
- this.parentaccountid = account.parentaccountid;
197
- this.address1_line1 = account.address1_line1;
198
- this.address1_line2 = account.address1_line2;
199
- this.address1_line3 = account.address1_line3;
200
- this.address1_city = account.address1_city;
201
- this.address1_stateorprovince = account.address1_stateorprovince;
202
- this.address1_postalcode = account.address1_postalcode;
203
- this.address1_country = account.address1_country;
204
- this.address1_county = account.address1_county;
205
- this.address1_latitude = account.address1_latitude;
206
- this.address1_longitude = account.address1_longitude;
207
- this.address1_telephone1 = account.address1_telephone1;
208
- this.address1_telephone2 = account.address1_telephone2;
209
- this.address1_telephone3 = account.address1_telephone3;
210
- this.address1_fax = account.address1_fax;
211
- this.address1_addresstypecode = account.address1_addresstypecode;
212
- this.address1_name = account.address1_name;
213
- this.address1_primarycontactname = account.address1_primarycontactname;
214
- this.address1_shippingmethodcode = account.address1_shippingmethodcode;
215
- this.statecode = account.statecode;
216
- this.statuscode = account.statuscode;
217
- this.createdon = account.createdon;
218
- this.modifiedon = account.modifiedon;
219
- this.createdby = account.createdby;
220
- this.modifiedby = account.modifiedby;
221
- this.ownerid = account.ownerid;
222
- this.transactioncurrencyid = account.transactioncurrencyid;
223
- }
224
-
225
- /**
226
- * Validates the account instance
227
- * @returns True if valid, throws error if invalid
228
- */
229
- validate(): boolean {
230
- if (!this.name || this.name.trim().length === 0) {
231
- throw new Error('Account name is required');
232
- }
233
-
234
- if (this.name.length > 160) {
235
- throw new Error('Account name cannot exceed 160 characters');
236
- }
237
-
238
- if (this.accountnumber && this.accountnumber.length > 20) {
239
- throw new Error('Account number cannot exceed 20 characters');
240
- }
241
-
242
- if (this.emailaddress1 && !this.isValidEmail(this.emailaddress1)) {
243
- throw new Error('Invalid primary email address format');
244
- }
245
-
246
- if (this.emailaddress2 && !this.isValidEmail(this.emailaddress2)) {
247
- throw new Error('Invalid secondary email address format');
248
- }
249
-
250
- if (this.emailaddress3 && !this.isValidEmail(this.emailaddress3)) {
251
- throw new Error('Invalid tertiary email address format');
252
- }
253
-
254
- if (this.websiteurl && !this.isValidUrl(this.websiteurl)) {
255
- throw new Error('Invalid website URL format');
256
- }
257
-
258
- if (this.revenue !== undefined && this.revenue < 0) {
259
- throw new Error('Revenue cannot be negative');
260
- }
261
-
262
- if (this.numberofemployees !== undefined && this.numberofemployees < 0) {
263
- throw new Error('Number of employees cannot be negative');
264
- }
265
-
266
- if (this.creditlimit !== undefined && this.creditlimit < 0) {
267
- throw new Error('Credit limit cannot be negative');
268
- }
269
-
270
- return true;
271
- }
272
-
273
- /**
274
- * Creates a new account record
275
- * @param apiService The API service instance
276
- * @param account The account to create
277
- * @returns Promise resolving to the created account
278
- */
279
- public static async create(
280
- apiService: IApiService,
281
- account: Account
282
- ): Promise<Account> {
283
- const loggerContext = 'Account.create';
284
- console.log(`${loggerContext}: Creating new account`, {
285
- name: account.name,
286
- });
287
-
288
- return await this.createEntity<Account>(
289
- apiService,
290
- account,
291
- AccountConstants.EntityCollectionName,
292
- loggerContext
293
- );
294
- }
295
-
296
- /**
297
- * Updates an existing account record
298
- * @param apiService The API service instance
299
- * @param account The account to update
300
- * @returns Promise resolving to the updated account
301
- */
302
- public static async update(
303
- apiService: IApiService,
304
- account: Account
305
- ): Promise<Account> {
306
- const loggerContext = 'Account.update';
307
-
308
- if (!account.accountid) {
309
- throw new Error('Account ID is required for update');
310
- }
311
-
312
- console.log(`${loggerContext}: Updating account`, {
313
- accountid: account.accountid,
314
- name: account.name,
315
- });
316
-
317
- return await this.updateEntity<Account>(
318
- apiService,
319
- account.accountid,
320
- account,
321
- AccountConstants.EntityCollectionName,
322
- AccountConstants.PrimaryKey,
323
- loggerContext
324
- );
325
- }
326
-
327
- /**
328
- * Deletes an account record
329
- * @param apiService The API service instance
330
- * @param accountId The ID of the account to delete
331
- * @returns Promise resolving when deletion is complete
332
- */
333
- public static async delete(
334
- apiService: IApiService,
335
- accountId: string
336
- ): Promise<void> {
337
- const loggerContext = 'Account.delete';
338
- console.log(`${loggerContext}: Deleting account`, { accountid: accountId });
339
-
340
- return await this.deleteEntity(
341
- apiService,
342
- accountId,
343
- AccountConstants.EntityCollectionName,
344
- loggerContext
345
- );
346
- }
347
-
348
- /**
349
- * Retrieves accounts based on a filter
350
- * @param apiService The API service instance
351
- * @param filter Optional FetchXML filter string
352
- * @returns Promise resolving to an array of accounts
353
- */
354
- public static async retrieveByFilter(
355
- apiService: IApiService,
356
- filter?: string
357
- ): Promise<Account[]> {
358
- const loggerContext = 'Account.retrieveByFilter';
359
- console.log(`${loggerContext}: Retrieving accounts with filter`, {
360
- filter,
361
- });
362
-
363
- const attributes = [
364
- AccountConstants.PrimaryKey,
365
- AccountConstants.PrimaryName,
366
- AccountConstants.AccountNumber,
367
- AccountConstants.EMailAddress1,
368
- AccountConstants.Telephone1,
369
- AccountConstants.Address1_City,
370
- AccountConstants.Address1_StateOrProvince,
371
- AccountConstants.Address1_Country,
372
- AccountConstants.Revenue,
373
- AccountConstants.NumberOfEmployees,
374
- AccountConstants.IndustryCode,
375
- AccountConstants.OwnershipCode,
376
- AccountConstants.WebSiteURL,
377
- AccountConstants.Description,
378
- AccountConstants.StateCode,
379
- AccountConstants.StatusCode,
380
- AccountConstants.CreatedOn,
381
- AccountConstants.ModifiedOn,
382
- ];
383
-
384
- const fetchXml = this.buildFetchXml(
385
- AccountConstants.EntityName,
386
- attributes,
387
- filter,
388
- { attribute: AccountConstants.PrimaryName }
389
- );
390
-
391
- return await this.retrieveEntitiesByFilter<Account>(
392
- apiService,
393
- AccountConstants.EntityCollectionName,
394
- fetchXml,
395
- Account,
396
- loggerContext
397
- );
398
- }
399
-
400
- /**
401
- * Retrieves a single account by ID
402
- * @param apiService The API service instance
403
- * @param accountId The account ID
404
- * @returns Promise resolving to the account or null if not found
405
- */
406
- public static async retrieveById(
407
- apiService: IApiService,
408
- accountId: string
409
- ): Promise<Account | null> {
410
- const loggerContext = 'Account.retrieveById';
411
- console.log(`${loggerContext}: Retrieving account by ID`, {
412
- accountid: accountId,
413
- });
414
-
415
- const filter = `<filter type="and">
416
- <condition attribute="${AccountConstants.PrimaryKey}" operator="eq" value="${this.escapeXml(accountId)}" />
417
- </filter>`;
418
-
419
- const accounts = await this.retrieveByFilter(apiService, filter);
420
- return accounts.length > 0 ? accounts[0] : null;
421
- }
422
-
423
- /**
424
- * Retrieves accounts by name (partial match)
425
- * @param apiService The API service instance
426
- * @param name The name to search for
427
- * @returns Promise resolving to an array of matching accounts
428
- */
429
- public static async retrieveByName(
430
- apiService: IApiService,
431
- name: string
432
- ): Promise<Account[]> {
433
- const loggerContext = 'Account.retrieveByName';
434
- console.log(`${loggerContext}: Retrieving accounts by name`, { name });
435
-
436
- const filter = `<filter type="and">
437
- <condition attribute="${AccountConstants.PrimaryName}" operator="like" value="%${this.escapeXml(name)}%" />
438
- </filter>`;
439
-
440
- return await this.retrieveByFilter(apiService, filter);
441
- }
442
-
443
- /**
444
- * Retrieves active accounts only
445
- * @param apiService The API service instance
446
- * @returns Promise resolving to an array of active accounts
447
- */
448
- public static async retrieveActiveAccounts(
449
- apiService: IApiService
450
- ): Promise<Account[]> {
451
- const loggerContext = 'Account.retrieveActiveAccounts';
452
- console.log(`${loggerContext}: Retrieving active accounts`);
453
-
454
- const filter = `<filter type="and">
455
- <condition attribute="${AccountConstants.StateCode}" operator="eq" value="0" />
456
- </filter>`;
457
-
458
- return await this.retrieveByFilter(apiService, filter);
459
- }
460
-
461
- /**
462
- * Helper method to validate email format
463
- */
464
- private isValidEmail(email: string): boolean {
465
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
466
- return emailRegex.test(email);
467
- }
468
-
469
- /**
470
- * Helper method to validate URL format
471
- */
472
- private isValidUrl(url: string): boolean {
473
- try {
474
- new URL(url);
475
- return true;
476
- } catch {
477
- return false;
478
- }
479
- }
480
- }
@@ -1,204 +0,0 @@
1
- import { IApiService } from '@khester/dynamics-ui-api-client';
2
-
3
- /**
4
- * Abstract base class for all entity models in the system.
5
- * Provides common CRUD operations and validation patterns.
6
- */
7
- export abstract class BaseEntity {
8
- /**
9
- * Validates the entity instance.
10
- * @returns True if valid, throws error if invalid
11
- */
12
- abstract validate(): boolean;
13
-
14
- /**
15
- * Creates a new entity record in Dynamics 365.
16
- * @param apiService The API service instance
17
- * @param entity The entity instance to create
18
- * @param entityName The entity collection name
19
- * @param loggerContext Optional logger context for debugging
20
- * @returns Promise resolving to the created entity
21
- */
22
- protected static async createEntity<T extends BaseEntity>(
23
- apiService: IApiService,
24
- entity: T,
25
- entityName: string,
26
- loggerContext?: string
27
- ): Promise<T> {
28
- try {
29
- entity.validate();
30
-
31
- const result = await apiService.createRecord(entityName, entity);
32
-
33
- if (loggerContext) {
34
- console.log(`${loggerContext}: Entity created successfully`, result);
35
- }
36
-
37
- return result as T;
38
- } catch (error) {
39
- const errorMessage = `Error creating ${entityName}: ${error}`;
40
- if (loggerContext) {
41
- console.error(`${loggerContext}: ${errorMessage}`);
42
- }
43
- throw new Error(errorMessage);
44
- }
45
- }
46
-
47
- /**
48
- * Updates an existing entity record in Dynamics 365.
49
- * @param apiService The API service instance
50
- * @param entityId The ID of the entity to update
51
- * @param entity The entity instance with updated values
52
- * @param entityName The entity collection name
53
- * @param primaryKey The primary key field name
54
- * @param loggerContext Optional logger context for debugging
55
- * @returns Promise resolving to the updated entity
56
- */
57
- protected static async updateEntity<T extends BaseEntity>(
58
- apiService: IApiService,
59
- entityId: string,
60
- entity: T,
61
- entityName: string,
62
- primaryKey: string,
63
- loggerContext?: string
64
- ): Promise<T> {
65
- try {
66
- entity.validate();
67
-
68
- // Remove the primary key from the update payload
69
- const updatePayload = { ...entity };
70
- delete (updatePayload as any)[primaryKey];
71
-
72
- await apiService.updateRecord(entityName, entityId, updatePayload);
73
-
74
- if (loggerContext) {
75
- console.log(`${loggerContext}: Entity updated successfully`);
76
- }
77
-
78
- // Return the entity with the ID set
79
- (entity as any)[primaryKey] = entityId;
80
- return entity;
81
- } catch (error) {
82
- const errorMessage = `Error updating ${entityName}: ${error}`;
83
- if (loggerContext) {
84
- console.error(`${loggerContext}: ${errorMessage}`);
85
- }
86
- throw new Error(errorMessage);
87
- }
88
- }
89
-
90
- /**
91
- * Deletes an entity record from Dynamics 365.
92
- * @param apiService The API service instance
93
- * @param entityId The ID of the entity to delete
94
- * @param entityName The entity collection name
95
- * @param loggerContext Optional logger context for debugging
96
- * @returns Promise resolving when deletion is complete
97
- */
98
- protected static async deleteEntity(
99
- apiService: IApiService,
100
- entityId: string,
101
- entityName: string,
102
- loggerContext?: string
103
- ): Promise<void> {
104
- try {
105
- await apiService.deleteRecord(entityName, entityId);
106
-
107
- if (loggerContext) {
108
- console.log(`${loggerContext}: Entity deleted successfully`);
109
- }
110
- } catch (error) {
111
- const errorMessage = `Error deleting ${entityName}: ${error}`;
112
- if (loggerContext) {
113
- console.error(`${loggerContext}: ${errorMessage}`);
114
- }
115
- throw new Error(errorMessage);
116
- }
117
- }
118
-
119
- /**
120
- * Retrieves entities based on a FetchXML filter.
121
- * @param apiService The API service instance
122
- * @param entityName The entity collection name
123
- * @param fetchXml The FetchXML query
124
- * @param EntityClass The entity class constructor
125
- * @param loggerContext Optional logger context for debugging
126
- * @returns Promise resolving to an array of entities
127
- */
128
- protected static async retrieveEntitiesByFilter<T extends BaseEntity>(
129
- apiService: IApiService,
130
- entityName: string,
131
- fetchXml: string,
132
- EntityClass: new (data: any) => T,
133
- loggerContext?: string
134
- ): Promise<T[]> {
135
- try {
136
- const result = await apiService.retrieveMultipleRecords(
137
- entityName,
138
- fetchXml
139
- );
140
-
141
- const entities = result.entities.map(
142
- (record: any) => new EntityClass(record)
143
- );
144
-
145
- if (loggerContext) {
146
- console.log(`${loggerContext}: Retrieved ${entities.length} entities`);
147
- }
148
-
149
- return entities;
150
- } catch (error) {
151
- const errorMessage = `Error retrieving ${entityName} records: ${error}`;
152
- if (loggerContext) {
153
- console.error(`${loggerContext}: ${errorMessage}`);
154
- }
155
- throw new Error(errorMessage);
156
- }
157
- }
158
-
159
- /**
160
- * Builds a FetchXML query with proper escaping.
161
- * @param entityName The entity name
162
- * @param attributes Array of attribute names to retrieve
163
- * @param filter Optional filter XML string
164
- * @param orderBy Optional order by configuration
165
- * @returns FetchXML string
166
- */
167
- protected static buildFetchXml(
168
- entityName: string,
169
- attributes: string[],
170
- filter?: string,
171
- orderBy?: { attribute: string; descending?: boolean }
172
- ): string {
173
- const attributesXml = attributes
174
- .map((attr) => `<attribute name="${attr}" />`)
175
- .join('\n ');
176
- const orderXml = orderBy
177
- ? `<order attribute="${orderBy.attribute}" descending="${orderBy.descending || false}" />`
178
- : '';
179
-
180
- return `<fetch mapping="logical">
181
- <entity name="${entityName}">
182
- ${attributesXml}
183
- ${orderXml}
184
- ${filter || ''}
185
- </entity>
186
- </fetch>`;
187
- }
188
-
189
- /**
190
- * Escapes XML special characters in a string.
191
- * @param value The string to escape
192
- * @returns Escaped string safe for XML
193
- */
194
- protected static escapeXml(value: string): string {
195
- if (!value) return value;
196
-
197
- return value
198
- .replace(/&/g, '&amp;')
199
- .replace(/</g, '&lt;')
200
- .replace(/>/g, '&gt;')
201
- .replace(/"/g, '&quot;')
202
- .replace(/'/g, '&apos;');
203
- }
204
- }