@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.
- package/dist/artifacts/registry.d.ts +4 -3
- package/dist/artifacts/registry.d.ts.map +1 -1
- package/dist/artifacts/registry.js +121 -11
- package/dist/artifacts/registry.js.map +1 -1
- package/dist/artifacts/types.d.ts +1 -1
- package/dist/artifacts/types.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/injectDevTools.d.ts.map +1 -1
- package/dist/injectDevTools.js +4 -2
- package/dist/injectDevTools.js.map +1 -1
- package/dist/scaffold.d.ts +1 -0
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +3 -1
- package/dist/scaffold.js.map +1 -1
- package/package.json +3 -2
- package/templates/grid-starter/ARCHITECTURE.md +66 -0
- package/templates/grid-starter/README.md +122 -0
- package/templates/grid-starter/env.example +16 -0
- package/templates/grid-starter/gitignore +6 -0
- package/templates/grid-starter/index.html +16 -0
- package/templates/grid-starter/package.json +39 -0
- package/templates/grid-starter/src/App.tsx +23 -0
- package/templates/grid-starter/src/core/services/FetchApiService.ts +117 -0
- package/templates/grid-starter/src/core/services/IApiService.ts +37 -0
- package/templates/grid-starter/src/core/services/MockApiService.ts +72 -0
- package/templates/grid-starter/src/core/services/ServiceFactory.ts +58 -0
- package/templates/grid-starter/src/core/services/XrmApiService.ts +135 -0
- package/templates/grid-starter/src/core/services/crudLogging.ts +52 -0
- package/templates/grid-starter/src/dev-tools/DevPanel.tsx +239 -0
- package/templates/grid-starter/src/grid/GridPage.tsx +119 -0
- package/templates/grid-starter/src/index.tsx +18 -0
- package/templates/grid-starter/src/vite-env.d.ts +15 -0
- package/templates/grid-starter/tools/deploy/deploy-webresource.cjs +117 -0
- package/templates/grid-starter/tsconfig.json +19 -0
- package/templates/grid-starter/vite.config.ts +76 -0
- package/templates/pcf-field/_variants/ValueInput.boolean.tsx +2 -0
- package/templates/pcf-field/_variants/ValueInput.date.tsx +2 -0
- package/templates/pcf-field/_variants/ValueInput.number.tsx +2 -0
- package/templates/pcf-field/_variants/ValueInput.optionset.tsx +77 -0
- package/templates/pcf-field/_variants/ValueInput.text.tsx +2 -0
- package/templates/pcf-field/index.ts +1 -1
- package/templates/pcf-field/package.json +3 -1
- package/templates/pcf-field/{{componentName}}Component.tsx +2 -0
- package/templates/react-custom-page/ARCHITECTURE.md +75 -0
- package/templates/react-custom-page/README.md +74 -568
- package/templates/react-custom-page/env.example +16 -0
- package/templates/react-custom-page/gitignore +1 -0
- package/templates/react-custom-page/index.html +16 -0
- package/templates/react-custom-page/package.json +21 -49
- package/templates/react-custom-page/src/App.tsx +26 -0
- package/templates/react-custom-page/src/core/recordContext.test.ts +30 -0
- package/templates/react-custom-page/src/core/recordContext.ts +51 -0
- package/templates/react-custom-page/src/core/services/FetchApiService.ts +117 -0
- package/templates/react-custom-page/src/core/services/IApiService.ts +37 -0
- package/templates/react-custom-page/src/core/services/MockApiService.ts +73 -0
- package/templates/react-custom-page/src/core/services/ServiceFactory.ts +58 -0
- package/templates/react-custom-page/src/core/services/XrmApiService.ts +135 -0
- package/templates/react-custom-page/src/core/services/crudLogging.ts +52 -0
- package/templates/react-custom-page/src/dev-tools/DevPanel.tsx +238 -0
- package/templates/react-custom-page/src/domain/diff.test.ts +87 -0
- package/templates/react-custom-page/src/domain/diff.ts +38 -0
- package/templates/react-custom-page/src/example/ExamplePage.tsx +140 -0
- package/templates/react-custom-page/src/example/exampleError.ts +36 -0
- package/templates/react-custom-page/src/example/hooks/useExampleData.ts +40 -0
- package/templates/react-custom-page/src/example/hooks/useExampleForm.ts +99 -0
- package/templates/react-custom-page/src/example/mappers/accountMapper.test.ts +38 -0
- package/templates/react-custom-page/src/example/mappers/accountMapper.ts +55 -0
- package/templates/react-custom-page/src/example/models/Account.ts +74 -0
- package/templates/react-custom-page/src/index.tsx +18 -128
- package/templates/react-custom-page/src/vite-env.d.ts +15 -0
- package/templates/react-custom-page/tools/deploy/deploy-webresource.cjs +117 -0
- package/templates/react-custom-page/tsconfig.json +12 -22
- package/templates/react-custom-page/vite.config.ts +76 -0
- package/templates/starter-page/README.md +38 -0
- package/templates/starter-page/_variants/App.dashboard.v8.tsx +46 -0
- package/templates/starter-page/_variants/App.form.v8.tsx +59 -0
- package/templates/starter-page/_variants/App.master-detail.v8.tsx +61 -0
- package/templates/starter-page/_variants/App.panel.v8.tsx +99 -0
- package/templates/starter-page/gitignore +5 -0
- package/templates/starter-page/package.json +27 -0
- package/templates/starter-page/public/index.html +11 -0
- package/templates/starter-page/src/index.tsx +10 -0
- package/templates/starter-page/src/services/dataverse.ts +30 -0
- package/templates/starter-page/tsconfig.json +15 -0
- package/templates/starter-page/webpack.config.js +17 -0
- package/templates/react-custom-page/deployment/README.md +0 -484
- package/templates/react-custom-page/docs/ARCHITECTURE_OVERVIEW.md +0 -506
- package/templates/react-custom-page/docs/BEST_PRACTICES.md +0 -723
- package/templates/react-custom-page/docs/MIGRATION_GUIDE.md +0 -447
- package/templates/react-custom-page/public/index.html +0 -15
- package/templates/react-custom-page/scripts/custom-build.js +0 -255
- package/templates/react-custom-page/src/components/AccountForm.css +0 -71
- package/templates/react-custom-page/src/components/AccountForm.tsx +0 -541
- package/templates/react-custom-page/src/components/AccountManagement.css +0 -86
- package/templates/react-custom-page/src/components/AccountManagement.tsx +0 -370
- package/templates/react-custom-page/src/components/ContactForm.css +0 -48
- package/templates/react-custom-page/src/components/ContactForm.tsx +0 -327
- package/templates/react-custom-page/src/components/ContactManagement.css +0 -86
- package/templates/react-custom-page/src/components/ContactManagement.tsx +0 -357
- package/templates/react-custom-page/src/components/Logging/LogDialog.tsx +0 -291
- package/templates/react-custom-page/src/components/Logging/LoggingContext.tsx +0 -166
- package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.css +0 -192
- package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.tsx +0 -177
- package/templates/react-custom-page/src/components/Logging/LoggingProvider.tsx +0 -3
- package/templates/react-custom-page/src/components/Logging/logger.ts +0 -193
- package/templates/react-custom-page/src/constants/account.ts +0 -410
- package/templates/react-custom-page/src/constants/contact.ts +0 -362
- package/templates/react-custom-page/src/models/Account.ts +0 -480
- package/templates/react-custom-page/src/models/BaseEntity.ts +0 -204
- package/templates/react-custom-page/src/models/Contact.ts +0 -580
- package/templates/react-custom-page/src/pcf/ContactControlWrapper.tsx +0 -107
- package/templates/react-custom-page/src/pcf/MultiEntityControlWrapper.tsx +0 -205
- package/templates/react-custom-page/src/providers/DynamicsProvider.tsx +0 -353
- package/templates/react-custom-page/src/services/MockApiService.ts +0 -260
- package/templates/react-custom-page/src/services/ServiceFactory.ts +0 -65
- package/templates/react-custom-page/src/services/XrmApiService.ts +0 -213
- package/templates/react-custom-page/src/styles/index.css +0 -171
- package/templates/react-custom-page/tools/metadata-sync/index.js +0 -152
- package/templates/react-custom-page/webpack.config.js +0 -57
- /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, '&')
|
|
199
|
-
.replace(/</g, '<')
|
|
200
|
-
.replace(/>/g, '>')
|
|
201
|
-
.replace(/"/g, '"')
|
|
202
|
-
.replace(/'/g, ''');
|
|
203
|
-
}
|
|
204
|
-
}
|