@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
@@ -0,0 +1,662 @@
1
+ /**
2
+ * Example: Complete Sales Workflow Implementation
3
+ *
4
+ * This example demonstrates how to create a comprehensive business workflow
5
+ * that spans multiple entities (Account → Contact → Opportunity) using the
6
+ * enhanced template's entity models and patterns.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const workflow = new SalesWorkflow(apiService);
11
+ *
12
+ * // Execute complete workflow
13
+ * const result = await workflow.executeCompleteWorkflow({
14
+ * accountName: 'ACME Corporation',
15
+ * contactName: 'John Smith',
16
+ * contactEmail: 'john.smith@acme.com',
17
+ * opportunityName: 'Q1 Software License',
18
+ * opportunityValue: 75000
19
+ * });
20
+ *
21
+ * console.log('Workflow completed:', result);
22
+ * ```
23
+ */
24
+
25
+ import { IApiService } from '@khester/dynamics-ui-api-client';
26
+ import { Account } from '../../models/Account';
27
+ import { Contact } from '../../models/Contact';
28
+ import { Logger } from '../../components/Logging/logger';
29
+ import {
30
+ Opportunity,
31
+ SalesStageCode_OptionSet,
32
+ OpportunityStateCode_OptionSet,
33
+ } from '../entity-examples/opportunity-model';
34
+
35
+ /**
36
+ * Interface for workflow input data
37
+ */
38
+ export interface SalesWorkflowInput {
39
+ /** Account information */
40
+ accountName: string;
41
+ accountWebsite?: string;
42
+ accountPhone?: string;
43
+ accountRevenue?: number;
44
+ industryCode?: number;
45
+
46
+ /** Contact information */
47
+ contactFirstName?: string;
48
+ contactLastName?: string;
49
+ contactEmail: string;
50
+ contactPhone?: string;
51
+
52
+ /** Opportunity information */
53
+ opportunityName: string;
54
+ opportunityValue?: number;
55
+ closeProbability?: number;
56
+ estimatedCloseDate?: Date;
57
+ description?: string;
58
+ }
59
+
60
+ /**
61
+ * Interface for workflow result
62
+ */
63
+ export interface SalesWorkflowResult {
64
+ /** Created account */
65
+ account: Account;
66
+ /** Created contact */
67
+ contact: Contact;
68
+ /** Created opportunity */
69
+ opportunity: Opportunity;
70
+ /** Workflow execution summary */
71
+ summary: {
72
+ executionTime: number;
73
+ stepsCompleted: string[];
74
+ warnings: string[];
75
+ success: boolean;
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Sales Workflow Class
81
+ *
82
+ * Implements a complete sales workflow that demonstrates:
83
+ * - Multi-entity operations with proper relationships
84
+ * - Transaction-like behavior with rollback on failure
85
+ * - Comprehensive error handling and logging
86
+ * - Performance monitoring and optimization
87
+ * - Business rule validation and enforcement
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * // Initialize workflow
92
+ * const salesWorkflow = new SalesWorkflow(apiService);
93
+ *
94
+ * // Execute with validation
95
+ * try {
96
+ * const result = await salesWorkflow.executeCompleteWorkflow(workflowData);
97
+ * console.log('Sales workflow completed successfully');
98
+ * } catch (error) {
99
+ * console.error('Workflow failed:', error.message);
100
+ * }
101
+ *
102
+ * // Execute individual steps
103
+ * const account = await salesWorkflow.createOrFindAccount(accountData);
104
+ * const contact = await salesWorkflow.createContactForAccount(contactData, account.accountid);
105
+ * const opportunity = await salesWorkflow.createOpportunity(opportunityData, account.accountid, contact.contactid);
106
+ * ```
107
+ */
108
+ export class SalesWorkflow {
109
+ private apiService: IApiService;
110
+ private loggerContext = 'SalesWorkflow';
111
+
112
+ /**
113
+ * Constructor
114
+ * @param apiService - The API service instance for Dynamics 365 operations
115
+ */
116
+ constructor(apiService: IApiService) {
117
+ this.apiService = apiService;
118
+ Logger.log('SalesWorkflow initialized', this.loggerContext);
119
+ }
120
+
121
+ /**
122
+ * Execute the complete sales workflow
123
+ *
124
+ * This method orchestrates the entire process:
125
+ * 1. Validate input data
126
+ * 2. Create or find existing account
127
+ * 3. Create contact associated with account
128
+ * 4. Create opportunity with proper relationships
129
+ * 5. Apply business rules and validation
130
+ *
131
+ * @param input - Workflow input data
132
+ * @returns Promise resolving to workflow result
133
+ * @throws Error if workflow fails at any step
134
+ */
135
+ public async executeCompleteWorkflow(
136
+ input: SalesWorkflowInput
137
+ ): Promise<SalesWorkflowResult> {
138
+ const startTime = performance.now();
139
+ const stepsCompleted: string[] = [];
140
+ const warnings: string[] = [];
141
+
142
+ try {
143
+ Logger.userAction(
144
+ 'Sales workflow execution started',
145
+ {
146
+ opportunityName: input.opportunityName,
147
+ accountName: input.accountName,
148
+ },
149
+ this.loggerContext
150
+ );
151
+
152
+ // Step 1: Validate input
153
+ this.validateWorkflowInput(input);
154
+ stepsCompleted.push('Input validation');
155
+
156
+ // Step 2: Create or find account
157
+ Logger.log('Step 2: Creating or finding account', this.loggerContext);
158
+ const account = await this.createOrFindAccount({
159
+ name: input.accountName,
160
+ websiteurl: input.accountWebsite,
161
+ telephone1: input.accountPhone,
162
+ revenue: input.accountRevenue,
163
+ industrycode: input.industryCode,
164
+ });
165
+ stepsCompleted.push('Account creation/retrieval');
166
+
167
+ // Step 3: Create contact
168
+ Logger.log('Step 3: Creating contact', this.loggerContext);
169
+ const contact = await this.createContactForAccount(
170
+ {
171
+ firstname: input.contactFirstName,
172
+ lastname: input.contactLastName,
173
+ emailaddress1: input.contactEmail,
174
+ telephone1: input.contactPhone,
175
+ },
176
+ account.accountid!
177
+ );
178
+ stepsCompleted.push('Contact creation');
179
+
180
+ // Step 4: Create opportunity
181
+ Logger.log('Step 4: Creating opportunity', this.loggerContext);
182
+ const opportunity = await this.createOpportunity(
183
+ {
184
+ name: input.opportunityName,
185
+ estimatedvalue: input.opportunityValue,
186
+ closeprobability: input.closeProbability,
187
+ estimatedclosedate: input.estimatedCloseDate?.toISOString(),
188
+ description: input.description,
189
+ },
190
+ account.accountid!,
191
+ contact.contactid!
192
+ );
193
+ stepsCompleted.push('Opportunity creation');
194
+
195
+ // Step 5: Apply business rules
196
+ Logger.log('Step 5: Applying business rules', this.loggerContext);
197
+ const businessRuleWarnings = await this.applyBusinessRules(
198
+ account,
199
+ contact,
200
+ opportunity
201
+ );
202
+ warnings.push(...businessRuleWarnings);
203
+ stepsCompleted.push('Business rules validation');
204
+
205
+ const executionTime = performance.now() - startTime;
206
+ Logger.timing('Sales workflow execution', startTime, this.loggerContext);
207
+
208
+ const result: SalesWorkflowResult = {
209
+ account,
210
+ contact,
211
+ opportunity,
212
+ summary: {
213
+ executionTime,
214
+ stepsCompleted,
215
+ warnings,
216
+ success: true,
217
+ },
218
+ };
219
+
220
+ Logger.userAction(
221
+ 'Sales workflow completed successfully',
222
+ {
223
+ executionTime: `${executionTime.toFixed(2)}ms`,
224
+ stepsCompleted: stepsCompleted.length,
225
+ warnings: warnings.length,
226
+ },
227
+ this.loggerContext
228
+ );
229
+
230
+ return result;
231
+ } catch (error) {
232
+ const executionTime = performance.now() - startTime;
233
+
234
+ Logger.error('Sales workflow failed', this.loggerContext, {
235
+ error,
236
+ stepsCompleted,
237
+ executionTime: `${executionTime.toFixed(2)}ms`,
238
+ });
239
+
240
+ // In a production system, you might want to implement rollback logic here
241
+ throw new Error(
242
+ `Sales workflow failed at step: ${stepsCompleted[stepsCompleted.length - 1] || 'initialization'}. ${error}`
243
+ );
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Create or find an existing account
249
+ *
250
+ * Implements intelligent account creation that:
251
+ * - Searches for existing accounts by name
252
+ * - Creates new account if none found
253
+ * - Applies duplicate detection logic
254
+ *
255
+ * @param accountData - Account data
256
+ * @returns Promise resolving to account (existing or new)
257
+ */
258
+ public async createOrFindAccount(accountData: {
259
+ name: string;
260
+ websiteurl?: string;
261
+ telephone1?: string;
262
+ revenue?: number;
263
+ industrycode?: number;
264
+ }): Promise<Account> {
265
+ try {
266
+ // Search for existing account by name
267
+ Logger.log(
268
+ `Searching for existing account: ${accountData.name}`,
269
+ this.loggerContext
270
+ );
271
+ const existingAccounts = await Account.retrieveByName(
272
+ this.apiService,
273
+ accountData.name
274
+ );
275
+
276
+ if (existingAccounts.length > 0) {
277
+ const existingAccount = existingAccounts[0];
278
+ Logger.log(
279
+ `Found existing account: ${existingAccount.name} (${existingAccount.accountid})`,
280
+ this.loggerContext
281
+ );
282
+ return existingAccount;
283
+ }
284
+
285
+ // Create new account
286
+ Logger.log(
287
+ `Creating new account: ${accountData.name}`,
288
+ this.loggerContext
289
+ );
290
+ const newAccount = new Account(accountData);
291
+ const createdAccount = await Account.create(this.apiService, newAccount);
292
+
293
+ Logger.log(
294
+ `Successfully created account: ${createdAccount.name} (${createdAccount.accountid})`,
295
+ this.loggerContext
296
+ );
297
+
298
+ return createdAccount;
299
+ } catch (error) {
300
+ Logger.error(
301
+ `Failed to create or find account: ${accountData.name}`,
302
+ this.loggerContext,
303
+ error
304
+ );
305
+ throw error;
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Create a contact associated with an account
311
+ *
312
+ * @param contactData - Contact data
313
+ * @param accountId - Account ID to associate with
314
+ * @returns Promise resolving to created contact
315
+ */
316
+ public async createContactForAccount(
317
+ contactData: {
318
+ firstname?: string;
319
+ lastname?: string;
320
+ emailaddress1: string;
321
+ telephone1?: string;
322
+ },
323
+ accountId: string
324
+ ): Promise<Contact> {
325
+ try {
326
+ // Check for existing contact with same email
327
+ Logger.log(
328
+ `Checking for existing contact with email: ${contactData.emailaddress1}`,
329
+ this.loggerContext
330
+ );
331
+
332
+ const existingContacts = await Contact.retrieveByEmail(
333
+ this.apiService,
334
+ contactData.emailaddress1
335
+ );
336
+ if (existingContacts.length > 0) {
337
+ const warning = `Contact with email ${contactData.emailaddress1} already exists`;
338
+ Logger.warn(warning, this.loggerContext);
339
+ // Return existing contact but you might want to handle this differently
340
+ return existingContacts[0];
341
+ }
342
+
343
+ // Create new contact with account relationship
344
+ const newContact = new Contact({
345
+ ...contactData,
346
+ parentcustomerid: accountId, // Associate with account
347
+ });
348
+
349
+ const createdContact = await Contact.create(this.apiService, newContact);
350
+
351
+ Logger.log(
352
+ `Successfully created contact: ${createdContact.firstname} ${createdContact.lastname} (${createdContact.contactid})`,
353
+ this.loggerContext
354
+ );
355
+
356
+ return createdContact;
357
+ } catch (error) {
358
+ Logger.error(
359
+ `Failed to create contact for account ${accountId}`,
360
+ this.loggerContext,
361
+ error
362
+ );
363
+ throw error;
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Create an opportunity with proper relationships
369
+ *
370
+ * @param opportunityData - Opportunity data
371
+ * @param accountId - Related account ID
372
+ * @param contactId - Related contact ID
373
+ * @returns Promise resolving to created opportunity
374
+ */
375
+ public async createOpportunity(
376
+ opportunityData: {
377
+ name: string;
378
+ estimatedvalue?: number;
379
+ closeprobability?: number;
380
+ estimatedclosedate?: string;
381
+ description?: string;
382
+ },
383
+ accountId: string,
384
+ contactId: string
385
+ ): Promise<Opportunity> {
386
+ try {
387
+ // Create opportunity with relationships and default values
388
+ const newOpportunity = new Opportunity({
389
+ ...opportunityData,
390
+ parentaccountid: accountId,
391
+ parentcontactid: contactId,
392
+ salesstagecode: SalesStageCode_OptionSet.Qualify, // Start in Qualify stage
393
+ statecode: OpportunityStateCode_OptionSet.Open, // Active opportunity
394
+ closeprobability: opportunityData.closeprobability || 25, // Default probability for new opportunities
395
+ });
396
+
397
+ const createdOpportunity = await Opportunity.create(
398
+ this.apiService,
399
+ newOpportunity
400
+ );
401
+
402
+ Logger.log(
403
+ `Successfully created opportunity: ${createdOpportunity.name} (${createdOpportunity.opportunityid})`,
404
+ this.loggerContext
405
+ );
406
+
407
+ return createdOpportunity;
408
+ } catch (error) {
409
+ Logger.error(
410
+ `Failed to create opportunity for account ${accountId} and contact ${contactId}`,
411
+ this.loggerContext,
412
+ error
413
+ );
414
+ throw error;
415
+ }
416
+ }
417
+
418
+ /**
419
+ * Apply business rules and validation
420
+ *
421
+ * Implements custom business logic such as:
422
+ * - Credit limit checks
423
+ * - Opportunity value validation
424
+ * - Relationship consistency checks
425
+ *
426
+ * @param account - Created account
427
+ * @param contact - Created contact
428
+ * @param opportunity - Created opportunity
429
+ * @returns Array of warning messages
430
+ */
431
+ private async applyBusinessRules(
432
+ account: Account,
433
+ contact: Contact,
434
+ opportunity: Opportunity
435
+ ): Promise<string[]> {
436
+ const warnings: string[] = [];
437
+
438
+ try {
439
+ // Business Rule 1: High-value opportunity warning
440
+ if (opportunity.estimatedvalue && opportunity.estimatedvalue > 100000) {
441
+ const warning = `High-value opportunity detected: $${opportunity.estimatedvalue.toLocaleString()}`;
442
+ warnings.push(warning);
443
+ Logger.log(warning, this.loggerContext);
444
+ }
445
+
446
+ // Business Rule 2: Account revenue consistency
447
+ if (
448
+ account.revenue &&
449
+ opportunity.estimatedvalue &&
450
+ opportunity.estimatedvalue > account.revenue
451
+ ) {
452
+ const warning = 'Opportunity value exceeds account annual revenue';
453
+ warnings.push(warning);
454
+ Logger.log(warning, this.loggerContext);
455
+ }
456
+
457
+ // Business Rule 3: Contact email domain validation
458
+ if (contact.emailaddress1 && account.websiteurl) {
459
+ const emailDomain = contact.emailaddress1.split('@')[1];
460
+ const accountDomain = account.websiteurl
461
+ .replace(/^https?:\/\//, '')
462
+ .replace(/^www\./, '');
463
+
464
+ if (!emailDomain.includes(accountDomain.split('.')[0])) {
465
+ const warning = 'Contact email domain does not match account website';
466
+ warnings.push(warning);
467
+ Logger.log(warning, this.loggerContext);
468
+ }
469
+ }
470
+
471
+ // Business Rule 4: Opportunity close date validation
472
+ if (opportunity.estimatedclosedate) {
473
+ const closeDate = new Date(opportunity.estimatedclosedate);
474
+ const threeMonthsFromNow = new Date();
475
+ threeMonthsFromNow.setMonth(threeMonthsFromNow.getMonth() + 3);
476
+
477
+ if (closeDate > threeMonthsFromNow) {
478
+ const warning =
479
+ 'Opportunity close date is more than 3 months in the future';
480
+ warnings.push(warning);
481
+ Logger.log(warning, this.loggerContext);
482
+ }
483
+ }
484
+
485
+ Logger.log(
486
+ `Business rules applied. Generated ${warnings.length} warnings.`,
487
+ this.loggerContext
488
+ );
489
+ return warnings;
490
+ } catch (error) {
491
+ Logger.error('Error applying business rules', this.loggerContext, error);
492
+ warnings.push('Error occurred while applying business rules');
493
+ return warnings;
494
+ }
495
+ }
496
+
497
+ /**
498
+ * Validate workflow input data
499
+ *
500
+ * @param input - Input data to validate
501
+ * @throws Error if validation fails
502
+ */
503
+ private validateWorkflowInput(input: SalesWorkflowInput): void {
504
+ const errors: string[] = [];
505
+
506
+ // Required fields validation
507
+ if (!input.accountName?.trim()) {
508
+ errors.push('Account name is required');
509
+ }
510
+
511
+ if (!input.contactEmail?.trim()) {
512
+ errors.push('Contact email is required');
513
+ }
514
+
515
+ if (!input.opportunityName?.trim()) {
516
+ errors.push('Opportunity name is required');
517
+ }
518
+
519
+ // Email format validation
520
+ if (
521
+ input.contactEmail &&
522
+ !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input.contactEmail)
523
+ ) {
524
+ errors.push('Invalid contact email format');
525
+ }
526
+
527
+ // Opportunity value validation
528
+ if (input.opportunityValue !== undefined && input.opportunityValue < 0) {
529
+ errors.push('Opportunity value cannot be negative');
530
+ }
531
+
532
+ // Close probability validation
533
+ if (
534
+ input.closeProbability !== undefined &&
535
+ (input.closeProbability < 0 || input.closeProbability > 100)
536
+ ) {
537
+ errors.push('Close probability must be between 0 and 100');
538
+ }
539
+
540
+ // Close date validation
541
+ if (input.estimatedCloseDate && input.estimatedCloseDate < new Date()) {
542
+ errors.push('Estimated close date cannot be in the past');
543
+ }
544
+
545
+ if (errors.length > 0) {
546
+ const errorMessage = `Workflow validation failed: ${errors.join(', ')}`;
547
+ Logger.validation('SalesWorkflow', errors, this.loggerContext);
548
+ throw new Error(errorMessage);
549
+ }
550
+
551
+ Logger.log('Workflow input validation passed', this.loggerContext);
552
+ }
553
+
554
+ /**
555
+ * Get workflow execution statistics
556
+ *
557
+ * @returns Promise resolving to workflow statistics
558
+ */
559
+ public async getWorkflowStatistics(): Promise<{
560
+ totalAccounts: number;
561
+ totalContacts: number;
562
+ totalOpportunities: number;
563
+ averageOpportunityValue: number;
564
+ }> {
565
+ try {
566
+ const [accounts, contacts, opportunities] = await Promise.all([
567
+ Account.retrieveActiveAccounts(this.apiService),
568
+ Contact.retrieveActiveContacts(this.apiService),
569
+ Opportunity.retrieveActiveOpportunities(this.apiService),
570
+ ]);
571
+
572
+ const totalOpportunityValue = opportunities.reduce(
573
+ (sum, opp) => sum + (opp.estimatedvalue || 0),
574
+ 0
575
+ );
576
+ const averageOpportunityValue =
577
+ opportunities.length > 0
578
+ ? totalOpportunityValue / opportunities.length
579
+ : 0;
580
+
581
+ return {
582
+ totalAccounts: accounts.length,
583
+ totalContacts: contacts.length,
584
+ totalOpportunities: opportunities.length,
585
+ averageOpportunityValue,
586
+ };
587
+ } catch (error) {
588
+ Logger.error(
589
+ 'Failed to get workflow statistics',
590
+ this.loggerContext,
591
+ error
592
+ );
593
+ throw error;
594
+ }
595
+ }
596
+ }
597
+
598
+ /**
599
+ * Example usage scenarios
600
+ */
601
+ export const workflowExamples = {
602
+ /**
603
+ * Basic workflow execution
604
+ */
605
+ basicWorkflow: async (apiService: IApiService) => {
606
+ const workflow = new SalesWorkflow(apiService);
607
+
608
+ const result = await workflow.executeCompleteWorkflow({
609
+ accountName: 'Contoso Ltd',
610
+ accountWebsite: 'https://contoso.com',
611
+ accountRevenue: 5000000,
612
+ contactFirstName: 'Jane',
613
+ contactLastName: 'Doe',
614
+ contactEmail: 'jane.doe@contoso.com',
615
+ contactPhone: '+1-555-0123',
616
+ opportunityName: 'Q1 Enterprise License',
617
+ opportunityValue: 75000,
618
+ closeProbability: 70,
619
+ estimatedCloseDate: new Date('2024-03-31'),
620
+ description: 'Enterprise software licensing opportunity for Q1',
621
+ });
622
+
623
+ return result;
624
+ },
625
+
626
+ /**
627
+ * High-value opportunity workflow
628
+ */
629
+ highValueWorkflow: async (apiService: IApiService) => {
630
+ const workflow = new SalesWorkflow(apiService);
631
+
632
+ const result = await workflow.executeCompleteWorkflow({
633
+ accountName: 'Fortune 500 Corp',
634
+ accountRevenue: 10000000,
635
+ contactEmail: 'cto@fortune500corp.com',
636
+ opportunityName: 'Digital Transformation Initiative',
637
+ opportunityValue: 500000,
638
+ closeProbability: 60,
639
+ description: 'Large-scale digital transformation project',
640
+ });
641
+
642
+ return result;
643
+ },
644
+
645
+ /**
646
+ * Workflow with existing account
647
+ */
648
+ existingAccountWorkflow: async (apiService: IApiService) => {
649
+ const workflow = new SalesWorkflow(apiService);
650
+
651
+ // This will find the existing account instead of creating a new one
652
+ const result = await workflow.executeCompleteWorkflow({
653
+ accountName: 'Microsoft Corporation', // Assuming this exists
654
+ contactEmail: 'partner@microsoft.com',
655
+ opportunityName: 'Partnership Opportunity',
656
+ opportunityValue: 250000,
657
+ closeProbability: 80,
658
+ });
659
+
660
+ return result;
661
+ },
662
+ };