@khester/create-dynamics-app 1.0.8 → 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 (107) hide show
  1. package/bin/create-dynamics-app.js +1 -1
  2. package/dist/index.js +140 -15
  3. package/dist/index.js.map +1 -1
  4. package/dist/utils/consultingHelpers.d.ts +13 -0
  5. package/dist/utils/consultingHelpers.d.ts.map +1 -0
  6. package/dist/utils/consultingHelpers.js +569 -0
  7. package/dist/utils/consultingHelpers.js.map +1 -0
  8. package/dist/utils/copyTemplate.d.ts.map +1 -1
  9. package/dist/utils/copyTemplate.js.map +1 -1
  10. package/dist/utils/initGit.d.ts.map +1 -1
  11. package/dist/utils/initGit.js.map +1 -1
  12. package/dist/utils/installDependencies.d.ts.map +1 -1
  13. package/dist/utils/installDependencies.js +3 -2
  14. package/dist/utils/installDependencies.js.map +1 -1
  15. package/dist/utils/updatePackageJson.d.ts +1 -1
  16. package/dist/utils/updatePackageJson.d.ts.map +1 -1
  17. package/dist/utils/updatePackageJson.js +11 -1
  18. package/dist/utils/updatePackageJson.js.map +1 -1
  19. package/package.json +1 -1
  20. package/templates/dynamics-365-starter/INTEGRATION_TEST_RESULTS.md +302 -0
  21. package/templates/dynamics-365-starter/PHASE_4_COMPLETION_SUMMARY.md +305 -0
  22. package/templates/dynamics-365-starter/README.md +566 -137
  23. package/templates/dynamics-365-starter/deployment/QUICKSTART-MAC.md +507 -0
  24. package/templates/dynamics-365-starter/deployment/QUICKSTART-WINDOWS.md +372 -0
  25. package/templates/dynamics-365-starter/deployment/README.md +484 -0
  26. package/templates/dynamics-365-starter/deployment/pipelines/README.md +375 -0
  27. package/templates/dynamics-365-starter/deployment/pipelines/azure-pipelines.yml +330 -0
  28. package/templates/dynamics-365-starter/deployment/pipelines/github-actions.yml +422 -0
  29. package/templates/dynamics-365-starter/deployment/pipelines/jenkins.groovy +636 -0
  30. package/templates/dynamics-365-starter/deployment/scripts/deploy.ps1 +417 -0
  31. package/templates/dynamics-365-starter/deployment/scripts/deploy.sh +582 -0
  32. package/templates/dynamics-365-starter/deployment/scripts/team-onboarding.ps1 +486 -0
  33. package/templates/dynamics-365-starter/deployment/scripts/team-onboarding.sh +567 -0
  34. package/templates/dynamics-365-starter/deployment/scripts/validate-setup.ps1 +703 -0
  35. package/templates/dynamics-365-starter/deployment/scripts/validate-setup.sh +671 -0
  36. package/templates/dynamics-365-starter/docs/ARCHITECTURE_OVERVIEW.md +506 -0
  37. package/templates/dynamics-365-starter/docs/BEST_PRACTICES.md +723 -0
  38. package/templates/dynamics-365-starter/docs/MIGRATION_GUIDE.md +447 -0
  39. package/templates/dynamics-365-starter/docs/team-standards/README.md +273 -0
  40. package/templates/dynamics-365-starter/docs/team-standards/client-onboarding.md +577 -0
  41. package/templates/dynamics-365-starter/docs/team-standards/code-review-checklist.md +359 -0
  42. package/templates/dynamics-365-starter/docs/team-standards/coding-standards.md +700 -0
  43. package/templates/dynamics-365-starter/docs/team-standards/cross-platform-team-guide.md +736 -0
  44. package/templates/dynamics-365-starter/docs/team-standards/development-workflows.md +727 -0
  45. package/templates/dynamics-365-starter/docs/troubleshooting/common-errors.md +758 -0
  46. package/templates/dynamics-365-starter/docs/troubleshooting/platform-specific-issues.md +878 -0
  47. package/templates/dynamics-365-starter/package.json +22 -1
  48. package/templates/dynamics-365-starter/public/index.html +8 -11
  49. package/templates/dynamics-365-starter/scripts/custom-build.js +255 -0
  50. package/templates/dynamics-365-starter/src/client-project-template/README.md +234 -0
  51. package/templates/dynamics-365-starter/src/client-project-template/config/client.template.json +114 -0
  52. package/templates/dynamics-365-starter/src/client-project-template/config/environments/template.json +186 -0
  53. package/templates/dynamics-365-starter/src/client-project-template/scripts/client-setup.js +667 -0
  54. package/templates/dynamics-365-starter/src/components/AccountForm.css +71 -0
  55. package/templates/dynamics-365-starter/src/components/AccountForm.tsx +541 -0
  56. package/templates/dynamics-365-starter/src/components/AccountManagement.css +86 -0
  57. package/templates/dynamics-365-starter/src/components/AccountManagement.tsx +370 -0
  58. package/templates/dynamics-365-starter/src/components/ContactForm.tsx +149 -63
  59. package/templates/dynamics-365-starter/src/components/ContactManagement.tsx +153 -63
  60. package/templates/dynamics-365-starter/src/components/Logging/LogDialog.tsx +291 -0
  61. package/templates/dynamics-365-starter/src/components/Logging/LoggingContext.tsx +166 -0
  62. package/templates/dynamics-365-starter/src/components/Logging/LoggingDebugPanel.css +192 -0
  63. package/templates/dynamics-365-starter/src/components/Logging/LoggingDebugPanel.tsx +177 -0
  64. package/templates/dynamics-365-starter/src/components/Logging/LoggingProvider.tsx +3 -0
  65. package/templates/dynamics-365-starter/src/components/Logging/logger.ts +193 -0
  66. package/templates/dynamics-365-starter/src/constants/account.ts +410 -0
  67. package/templates/dynamics-365-starter/src/constants/contact.ts +362 -0
  68. package/templates/dynamics-365-starter/src/examples/README.md +52 -0
  69. package/templates/dynamics-365-starter/src/examples/component-examples/opportunity-management.tsx +625 -0
  70. package/templates/dynamics-365-starter/src/examples/entity-examples/opportunity-model.ts +545 -0
  71. package/templates/dynamics-365-starter/src/examples/integration-examples/custom-pcf-wrapper.tsx +722 -0
  72. package/templates/dynamics-365-starter/src/examples/workflow-examples/sales-workflow.ts +662 -0
  73. package/templates/dynamics-365-starter/src/index.tsx +107 -19
  74. package/templates/dynamics-365-starter/src/models/Account.ts +480 -0
  75. package/templates/dynamics-365-starter/src/models/BaseEntity.ts +204 -0
  76. package/templates/dynamics-365-starter/src/models/Contact.ts +580 -0
  77. package/templates/dynamics-365-starter/src/page-templates/EntityDashboard.tsx +519 -0
  78. package/templates/dynamics-365-starter/src/page-templates/EntityDetailPage.tsx +456 -0
  79. package/templates/dynamics-365-starter/src/page-templates/EntityListPage.tsx +406 -0
  80. package/templates/dynamics-365-starter/src/page-templates/RelatedEntitiesPage.tsx +578 -0
  81. package/templates/dynamics-365-starter/src/page-templates/SearchPage.tsx +629 -0
  82. package/templates/dynamics-365-starter/src/pcf/ContactControlWrapper.tsx +75 -22
  83. package/templates/dynamics-365-starter/src/pcf/MultiEntityControlWrapper.tsx +205 -0
  84. package/templates/dynamics-365-starter/src/providers/DynamicsProvider.tsx +297 -80
  85. package/templates/dynamics-365-starter/src/services/MockApiService.ts +260 -0
  86. package/templates/dynamics-365-starter/src/services/ServiceFactory.ts +65 -0
  87. package/templates/dynamics-365-starter/src/services/XrmApiService.ts +213 -0
  88. package/templates/dynamics-365-starter/src/styles/index.css +74 -7
  89. package/templates/dynamics-365-starter/tools/entity-generator/index.js +168 -0
  90. package/templates/dynamics-365-starter/tools/entity-generator/templates/constants.template.ts +124 -0
  91. package/templates/dynamics-365-starter/tools/entity-generator/templates/form.template.css +283 -0
  92. package/templates/dynamics-365-starter/tools/entity-generator/templates/form.template.tsx +275 -0
  93. package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.css +204 -0
  94. package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.tsx +413 -0
  95. package/templates/dynamics-365-starter/tools/entity-generator/templates/model.template.ts +250 -0
  96. package/templates/dynamics-365-starter/tools/metadata-sync/d365-client.js +410 -0
  97. package/templates/dynamics-365-starter/tools/metadata-sync/index.js +512 -0
  98. package/templates/dynamics-365-starter/tools/metadata-sync/type-generator.js +675 -0
  99. package/templates/dynamics-365-starter/tsconfig.json +11 -8
  100. package/templates/dynamics-365-starter/webpack.config.js +8 -9
  101. package/templates/power-pages-starter/README.md +7 -1
  102. package/templates/power-pages-starter/public/index.html +8 -11
  103. package/templates/power-pages-starter/src/components/ContactForm.tsx +60 -41
  104. package/templates/power-pages-starter/src/index.tsx +3 -3
  105. package/templates/power-pages-starter/src/providers/PowerPagesProvider.tsx +46 -23
  106. package/templates/power-pages-starter/tsconfig.json +3 -9
  107. package/templates/power-pages-starter/webpack.config.js +8 -3
@@ -1,27 +1,112 @@
1
- import React from 'react';
1
+ import React, { useState, useCallback } from 'react';
2
2
  import { createRoot } from 'react-dom/client';
3
+ import { Pivot, DynamicsPivotItemProps } from '@khester/dynamics-ui-components';
3
4
  import { ContactManagement } from './components/ContactManagement';
5
+ import { AccountManagement } from './components/AccountManagement';
4
6
  import { DynamicsProvider } from './providers/DynamicsProvider';
7
+ import { LoggingProvider } from './components/Logging/LoggingProvider';
8
+ import { LoggingDebugPanel } from './components/Logging/LoggingDebugPanel';
9
+ import { Logger } from './components/Logging/logger';
5
10
  import './styles/index.css';
6
11
 
7
12
  const App: React.FC = () => {
13
+ const [selectedTab, setSelectedTab] = useState<'contacts' | 'accounts'>(
14
+ 'contacts'
15
+ );
16
+ const [showDebugPanel, setShowDebugPanel] = useState(false);
17
+
18
+ const handleTabChange = useCallback(
19
+ (item: DynamicsPivotItemProps) => {
20
+ const newTab = item.itemKey as 'contacts' | 'accounts';
21
+ Logger.userAction(
22
+ 'Tab changed in main application',
23
+ { from: selectedTab, to: newTab },
24
+ 'App.handleTabChange'
25
+ );
26
+ setSelectedTab(newTab);
27
+ },
28
+ [selectedTab]
29
+ );
30
+
31
+ const toggleDebugPanel = useCallback(() => {
32
+ setShowDebugPanel((prev) => !prev);
33
+ Logger.userAction(
34
+ showDebugPanel ? 'Debug panel closed' : 'Debug panel opened',
35
+ {},
36
+ 'App.toggleDebugPanel'
37
+ );
38
+ }, [showDebugPanel]);
39
+
40
+ const renderContent = () => {
41
+ switch (selectedTab) {
42
+ case 'contacts':
43
+ return <ContactManagement />;
44
+ case 'accounts':
45
+ return <AccountManagement />;
46
+ default:
47
+ return <ContactManagement />;
48
+ }
49
+ };
50
+
8
51
  return (
9
- <DynamicsProvider>
10
- <div className="app">
11
- <header className="app-header">
12
- <h1>Dynamics 365 Contact Management</h1>
13
- <p>Built with Dynamics UI Kit</p>
14
- </header>
15
-
16
- <main className="app-main">
17
- <ContactManagement />
18
- </main>
19
-
20
- <footer className="app-footer">
21
- <p>&copy; 2024 Your Organization. Powered by Dynamics UI Kit.</p>
22
- </footer>
23
- </div>
24
- </DynamicsProvider>
52
+ <LoggingProvider>
53
+ <DynamicsProvider>
54
+ <div className="app">
55
+ <header className="app-header">
56
+ <div className="app-header__content">
57
+ <div className="app-header__title">
58
+ <h1>Dynamics 365 Management Console</h1>
59
+ <p>Built with Dynamics UI Kit - Enhanced Template</p>
60
+ </div>
61
+ <div className="app-header__actions">
62
+ <button
63
+ className="debug-toggle-btn"
64
+ onClick={toggleDebugPanel}
65
+ title="Toggle Debug Panel"
66
+ >
67
+ 🐛 Debug
68
+ </button>
69
+ </div>
70
+ </div>
71
+ </header>
72
+
73
+ <nav className="app-navigation">
74
+ <Pivot
75
+ items={[
76
+ {
77
+ headerText: 'Contacts',
78
+ itemKey: 'contacts',
79
+ itemIcon: 'Contact',
80
+ },
81
+ {
82
+ headerText: 'Accounts',
83
+ itemKey: 'accounts',
84
+ itemIcon: 'Building',
85
+ },
86
+ ]}
87
+ selectedKey={selectedTab}
88
+ onItemClick={handleTabChange}
89
+ linkFormat="tabs"
90
+ />
91
+ </nav>
92
+
93
+ <main className="app-main">{renderContent()}</main>
94
+
95
+ <footer className="app-footer">
96
+ <p>
97
+ &copy; 2024 Your Organization. Powered by Dynamics UI Kit v1.0
98
+ </p>
99
+ </footer>
100
+
101
+ {/* Debug Panel */}
102
+ {showDebugPanel && (
103
+ <div className="debug-panel-overlay">
104
+ <LoggingDebugPanel onClose={() => setShowDebugPanel(false)} />
105
+ </div>
106
+ )}
107
+ </div>
108
+ </DynamicsProvider>
109
+ </LoggingProvider>
25
110
  );
26
111
  };
27
112
 
@@ -35,6 +120,9 @@ if (typeof document !== 'undefined') {
35
120
  }
36
121
 
37
122
  // Export for PCF integration
38
- export { App, ContactManagement };
123
+ export { App, ContactManagement, AccountManagement };
39
124
  export * from './components/ContactManagement';
40
- export * from './providers/DynamicsProvider';
125
+ export * from './components/AccountManagement';
126
+ export * from './providers/DynamicsProvider';
127
+ export * from './pcf/ContactControlWrapper';
128
+ export * from './pcf/MultiEntityControlWrapper';
@@ -0,0 +1,480 @@
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
+ }