@codebakers/mcp 5.6.2 → 5.7.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.
@@ -0,0 +1,750 @@
1
+ # Business Rules Documentation
2
+
3
+ **Project:** [Your Project Name]
4
+ **Domain:** [MLM / E-commerce / CRM / etc.]
5
+ **Last Updated:** [Date]
6
+
7
+ ---
8
+
9
+ ## Purpose
10
+
11
+ This file documents business logic that cannot be automatically inferred from schema or mockups.
12
+
13
+ Use this to capture:
14
+ - Computed field formulas
15
+ - Conditional triggers
16
+ - Cascade chains
17
+ - Business rule enforcement
18
+ - Complex calculations
19
+
20
+ **This complements:**
21
+ - `DEPENDENCY-MAP.md` (FK and read dependencies from codebakers_map_dependencies)
22
+ - `MLM-DEPENDENCIES.md` (Upline cascades from codebakers_map_mlm_dependencies)
23
+
24
+ Together, these provide **100% dependency coverage**.
25
+
26
+ ---
27
+
28
+ ## Computed Fields
29
+
30
+ ### [Entity].[field_name]
31
+
32
+ **Formula:**
33
+ ```sql
34
+ [SQL or pseudocode formula]
35
+ ```
36
+
37
+ **Computed From:**
38
+ - [List source tables/fields]
39
+
40
+ **Update Triggers:**
41
+ - [Action that triggers recalculation]
42
+ - [Another trigger]
43
+
44
+ **Cascades To:**
45
+ - [What happens after this field updates]
46
+
47
+ **Affected Stores:**
48
+ - [StoreNames]
49
+
50
+ **Affected Components:**
51
+ - [ComponentNames]
52
+
53
+ **Example:**
54
+ ```javascript
55
+ // When this field changes:
56
+ if ([condition]) {
57
+ [action]
58
+ }
59
+ ```
60
+
61
+ ---
62
+
63
+ **Template Example:**
64
+
65
+ ### user.personal_volume
66
+
67
+ **Formula:**
68
+ ```sql
69
+ SUM(sales.bonus_volume WHERE sales.user_id = user.id AND sales.status = 'completed')
70
+ ```
71
+
72
+ **Computed From:**
73
+ - sales table (bonus_volume column)
74
+ - Filtered by user_id and status
75
+
76
+ **Update Triggers:**
77
+ - createSale (when status = 'completed')
78
+ - updateSale (when status changes to/from 'completed')
79
+ - deleteSale
80
+
81
+ **Cascades To:**
82
+ - rank_check (if personal_volume >= next threshold)
83
+ - team_volume_recalc (for upline users)
84
+ - commission_calculation
85
+
86
+ **Affected Stores:**
87
+ - UserStore (current user)
88
+ - UserStore (all upline - for team_volume recalc)
89
+
90
+ **Affected Components:**
91
+ - User-Dashboard (shows personal_volume)
92
+ - Team-View (used in team calculations)
93
+
94
+ **Example:**
95
+ ```javascript
96
+ // After createSale:
97
+ user.personal_volume += sale.bonus_volume
98
+
99
+ // Then check:
100
+ if (user.personal_volume >= RANK_THRESHOLDS[nextRank]) {
101
+ promoteUser(user.id, nextRank)
102
+ }
103
+ ```
104
+
105
+ ---
106
+
107
+ ## Conditional Triggers
108
+
109
+ ### [trigger_name]
110
+
111
+ **Condition:**
112
+ ```
113
+ [When this happens]
114
+ ```
115
+
116
+ **Check:**
117
+ ```javascript
118
+ if ([condition]) {
119
+ [action]
120
+ }
121
+ ```
122
+
123
+ **Cascades To:**
124
+ - [What this triggers]
125
+
126
+ **Affected Entities:**
127
+ - [Entities that get updated]
128
+
129
+ **Example Implementation:**
130
+ ```typescript
131
+ [Code example]
132
+ ```
133
+
134
+ ---
135
+
136
+ **Template Example:**
137
+
138
+ ### rank_promotion_check
139
+
140
+ **Condition:**
141
+ ```
142
+ When personal_volume OR team_volume changes
143
+ ```
144
+
145
+ **Check:**
146
+ ```javascript
147
+ if (user.personal_volume >= RANK_THRESHOLDS[nextRank] ||
148
+ user.team_volume >= TEAM_RANK_THRESHOLDS[nextRank]) {
149
+ promoteUser(user.id, nextRank)
150
+ }
151
+ ```
152
+
153
+ **Cascades To:**
154
+ - Create rank_promotion record
155
+ - Update user.rank
156
+ - Recalculate commission_rate (based on new rank)
157
+ - Trigger upline_bonus_recalc (sponsor's leadership bonus may change)
158
+ - Send promotion notification
159
+
160
+ **Affected Entities:**
161
+ - user (rank updated)
162
+ - rank_promotions (new record created)
163
+ - commissions (rate changes for future commissions)
164
+ - upline users (leadership bonuses recalculated)
165
+
166
+ **Example Implementation:**
167
+ ```typescript
168
+ async function checkRankPromotion(userId: string) {
169
+ const user = await getUser(userId)
170
+ const currentRank = user.rank
171
+ const nextRank = getNextRank(currentRank)
172
+
173
+ const threshold = RANK_THRESHOLDS[nextRank]
174
+ const teamThreshold = TEAM_RANK_THRESHOLDS[nextRank]
175
+
176
+ if (user.personal_volume >= threshold || user.team_volume >= teamThreshold) {
177
+ // Promote
178
+ await updateUser(userId, { rank: nextRank })
179
+ await createRankPromotion({ user_id: userId, old_rank: currentRank, new_rank: nextRank })
180
+
181
+ // Cascade: Update upline leadership bonuses
182
+ await recalculateUplineLeadershipBonuses(user.sponsor_id)
183
+
184
+ // Cascade: Send notification
185
+ await sendPromotionNotification(userId, nextRank)
186
+ }
187
+ }
188
+ ```
189
+
190
+ ---
191
+
192
+ ## Cascade Chains
193
+
194
+ ### [cascade_name]
195
+
196
+ **Trigger:** [Initial action]
197
+
198
+ **Steps:**
199
+ 1. [First thing that happens]
200
+ 2. [Second thing]
201
+ 3. [If condition met] → [Branch action]
202
+ 4. [Continue...]
203
+
204
+ **Entities Affected:**
205
+ - [Entity 1]: [What changes]
206
+ - [Entity 2]: [What changes]
207
+
208
+ **Stores to Update:**
209
+ - [Store 1]
210
+ - [Store 2]
211
+
212
+ **Components to Invalidate:**
213
+ - [Component 1]
214
+ - [Component 2]
215
+
216
+ **Duration:** ~[Time estimate]
217
+
218
+ **Example:**
219
+ ```javascript
220
+ [Pseudocode of complete cascade]
221
+ ```
222
+
223
+ ---
224
+
225
+ **Template Example:**
226
+
227
+ ### createSale_complete_cascade
228
+
229
+ **Trigger:** User creates a sale (createSale mutation)
230
+
231
+ **Steps:**
232
+ 1. Create sale record (sale_id, user_id, product_id, amount, bonus_volume)
233
+ 2. Update user.personal_volume += sale.bonus_volume
234
+ 3. Check rank promotion for user
235
+ - If promoted → Update rank, create rank_promotion record
236
+ 4. Traverse upline tree (sponsor_id chain, max 10 levels)
237
+ 5. For each upline user (level 1-10):
238
+ a. Calculate bonus: sale.bonus_volume * GENERATION_RATES[level]
239
+ b. Update upline_user.bonus_volume += calculated_bonus
240
+ c. Create bonus_volume record (user_id: upline_user.id, from_user_id: user.id, sale_id, volume, level)
241
+ d. Check rank promotion for upline_user
242
+ e. If promoted → Trigger another cascade (leadership bonuses)
243
+ 6. Recalculate team_volume for all upline users
244
+ - team_volume = SUM(all_downline.personal_volume + all_downline.team_volume)
245
+ 7. Create commission records:
246
+ a. User commission: sale.amount * COMMISSION_RATES[user.rank]
247
+ b. For each upline user:
248
+ - If upline_user.rank > downline_user.rank → Override commission
249
+ - commission = sale.amount * (COMMISSION_RATES[upline_rank] - COMMISSION_RATES[downline_rank])
250
+
251
+ **Entities Affected:**
252
+ - sale: Created
253
+ - user: personal_volume updated, possibly rank updated
254
+ - upline users (1-10): bonus_volume updated, possibly rank updated
255
+ - bonus_volumes: 1-10 records created
256
+ - commissions: 1-11 records created (user + upline)
257
+ - rank_promotions: 0-11 records created (if promotions occurred)
258
+
259
+ **Stores to Update:**
260
+ - SaleStore
261
+ - UserStore (user + 10 upline users = 11 total)
262
+ - BonusVolumeStore
263
+ - CommissionStore
264
+ - RankPromotionStore (conditional)
265
+
266
+ **Components to Invalidate:**
267
+ - User-Dashboard (for user + all upline)
268
+ - Team-View (for all upline)
269
+ - Commission-Report
270
+
271
+ **Duration:** ~500ms - 2s (depending on upline depth and promotions)
272
+
273
+ **Example:**
274
+ ```typescript
275
+ async function createSaleWithCascade(input: CreateSaleInput) {
276
+ // 1. Create sale
277
+ const sale = await db.sales.insert({
278
+ user_id: input.user_id,
279
+ product_id: input.product_id,
280
+ amount: input.amount,
281
+ bonus_volume: input.bonus_volume,
282
+ })
283
+
284
+ // 2. Update personal volume
285
+ await db.users.update(input.user_id, {
286
+ personal_volume: db.raw('personal_volume + ?', [sale.bonus_volume])
287
+ })
288
+
289
+ // 3. Check rank
290
+ await checkRankPromotion(input.user_id)
291
+
292
+ // 4-6. Upline cascade
293
+ const uplineUsers = await getUplineTree(input.user_id, 10)
294
+ for (const [level, uplineUser] of uplineUsers.entries()) {
295
+ const bonus = sale.bonus_volume * GENERATION_RATES[level]
296
+
297
+ // Update bonus volume
298
+ await db.users.update(uplineUser.id, {
299
+ bonus_volume: db.raw('bonus_volume + ?', [bonus])
300
+ })
301
+
302
+ // Create bonus record
303
+ await db.bonus_volumes.insert({
304
+ user_id: uplineUser.id,
305
+ from_user_id: input.user_id,
306
+ sale_id: sale.id,
307
+ volume: bonus,
308
+ level: level + 1,
309
+ })
310
+
311
+ // Check promotion
312
+ await checkRankPromotion(uplineUser.id)
313
+ }
314
+
315
+ // 7. Recalc team volume
316
+ for (const uplineUser of uplineUsers) {
317
+ await recalculateTeamVolume(uplineUser.id)
318
+ }
319
+
320
+ // 8. Create commissions
321
+ await createCommissionsForSale(sale.id, input.user_id, uplineUsers)
322
+
323
+ // 9. Invalidate stores
324
+ await invalidateStores([
325
+ 'SaleStore',
326
+ 'UserStore',
327
+ 'BonusVolumeStore',
328
+ 'CommissionStore',
329
+ 'RankPromotionStore',
330
+ ])
331
+ }
332
+ ```
333
+
334
+ ---
335
+
336
+ ## Business Rule Enforcement
337
+
338
+ ### [rule_name]
339
+
340
+ **Rule:**
341
+ ```
342
+ [Statement of the business rule]
343
+ ```
344
+
345
+ **Enforced Where:**
346
+ - [Location in code]
347
+
348
+ **Validation:**
349
+ ```javascript
350
+ [Validation logic]
351
+ ```
352
+
353
+ **Error Handling:**
354
+ ```javascript
355
+ [What happens if rule violated]
356
+ ```
357
+
358
+ ---
359
+
360
+ **Template Example:**
361
+
362
+ ### minimum_personal_volume_for_commission
363
+
364
+ **Rule:**
365
+ ```
366
+ User must have at least $500 in personal_volume in current month to receive commissions
367
+ ```
368
+
369
+ **Enforced Where:**
370
+ - createCommission mutation
371
+ - Monthly commission calculation job
372
+
373
+ **Validation:**
374
+ ```javascript
375
+ const currentMonthVolume = await getUserPersonalVolumeForMonth(userId, currentMonth)
376
+
377
+ if (currentMonthVolume < 500) {
378
+ return { eligible: false, reason: 'minimum_volume_not_met' }
379
+ }
380
+ ```
381
+
382
+ **Error Handling:**
383
+ ```javascript
384
+ // Don't create commission record
385
+ // Log to audit trail
386
+ await db.commission_holds.insert({
387
+ user_id: userId,
388
+ sale_id: saleId,
389
+ amount: commissionAmount,
390
+ hold_reason: 'minimum_volume_not_met',
391
+ current_volume: currentMonthVolume,
392
+ required_volume: 500,
393
+ })
394
+
395
+ // User will see in dashboard: "Commission on hold - reach $500 personal volume"
396
+ ```
397
+
398
+ ---
399
+
400
+ ## Aggregation Formulas
401
+
402
+ ### [aggregation_name]
403
+
404
+ **Formula:**
405
+ ```sql
406
+ [SQL aggregation]
407
+ ```
408
+
409
+ **Recalculation Triggers:**
410
+ - [When to recalculate]
411
+
412
+ **Performance:**
413
+ - Estimated rows: [N]
414
+ - Estimated time: [Duration]
415
+ - Optimization: [Any indexes or caching]
416
+
417
+ **Example:**
418
+ ```typescript
419
+ [Implementation code]
420
+ ```
421
+
422
+ ---
423
+
424
+ **Template Example:**
425
+
426
+ ### team_volume_calculation
427
+
428
+ **Formula:**
429
+ ```sql
430
+ WITH RECURSIVE downline AS (
431
+ SELECT id, personal_volume, team_volume, 0 as depth
432
+ FROM users
433
+ WHERE id = $user_id
434
+ UNION ALL
435
+ SELECT u.id, u.personal_volume, u.team_volume, d.depth + 1
436
+ FROM users u
437
+ JOIN downline d ON u.sponsor_id = d.id
438
+ WHERE d.depth < 10
439
+ )
440
+ SELECT SUM(personal_volume) as total_team_volume
441
+ FROM downline
442
+ WHERE id != $user_id;
443
+ ```
444
+
445
+ **Recalculation Triggers:**
446
+ - When any downline user makes a sale (personal_volume changes)
447
+ - When downline structure changes (user changes sponsor)
448
+ - When downline user gets promoted (affects team calculations)
449
+
450
+ **Performance:**
451
+ - Estimated rows: 1-1000 (depending on team size)
452
+ - Estimated time: 10-500ms
453
+ - Optimization:
454
+ - Index on sponsor_id
455
+ - Cache team_volume, recalc only on changes
456
+ - Use materialized view for large teams (>100 members)
457
+
458
+ **Example:**
459
+ ```typescript
460
+ async function recalculateTeamVolume(userId: string) {
461
+ const result = await db.raw(`
462
+ WITH RECURSIVE downline AS (
463
+ SELECT id, personal_volume, team_volume, 0 as depth
464
+ FROM users
465
+ WHERE id = $1
466
+ UNION ALL
467
+ SELECT u.id, u.personal_volume, u.team_volume, d.depth + 1
468
+ FROM users u
469
+ JOIN downline d ON u.sponsor_id = d.id
470
+ WHERE d.depth < 10
471
+ )
472
+ SELECT SUM(personal_volume) as total_team_volume
473
+ FROM downline
474
+ WHERE id != $1
475
+ `, [userId])
476
+
477
+ const teamVolume = result.rows[0].total_team_volume || 0
478
+
479
+ await db.users.update(userId, { team_volume: teamVolume })
480
+
481
+ return teamVolume
482
+ }
483
+ ```
484
+
485
+ ---
486
+
487
+ ## Constants / Configuration
488
+
489
+ ### Rank Thresholds (Personal Volume)
490
+
491
+ ```typescript
492
+ const RANK_THRESHOLDS = {
493
+ 'Associate': 0,
494
+ 'Silver': 1000,
495
+ 'Gold': 5000,
496
+ 'Platinum': 10000,
497
+ 'Diamond': 25000,
498
+ }
499
+ ```
500
+
501
+ ### Team Volume Thresholds (Alternative Promotion Path)
502
+
503
+ ```typescript
504
+ const TEAM_RANK_THRESHOLDS = {
505
+ 'Associate': 0,
506
+ 'Silver': 5000,
507
+ 'Gold': 25000,
508
+ 'Platinum': 100000,
509
+ 'Diamond': 500000,
510
+ }
511
+ ```
512
+
513
+ ### Generation Bonus Rates
514
+
515
+ ```typescript
516
+ const GENERATION_RATES = [
517
+ 1.0, // Level 1 (direct sponsor): 100%
518
+ 1.0, // Level 2: 100%
519
+ 0.5, // Level 3: 50%
520
+ 0.5, // Level 4: 50%
521
+ 0.25, // Level 5: 25%
522
+ 0.25, // Level 6: 25%
523
+ 0.25, // Level 7: 25%
524
+ 0.25, // Level 8: 25%
525
+ 0.25, // Level 9: 25%
526
+ 0.25, // Level 10: 25%
527
+ ]
528
+ ```
529
+
530
+ ### Commission Rates (by Rank)
531
+
532
+ ```typescript
533
+ const COMMISSION_RATES = {
534
+ 'Associate': 0.10, // 10%
535
+ 'Silver': 0.15, // 15%
536
+ 'Gold': 0.20, // 20%
537
+ 'Platinum': 0.25, // 25%
538
+ 'Diamond': 0.30, // 30%
539
+ }
540
+ ```
541
+
542
+ ---
543
+
544
+ ## Edge Cases
545
+
546
+ ### [edge_case_name]
547
+
548
+ **Scenario:**
549
+ ```
550
+ [Description of edge case]
551
+ ```
552
+
553
+ **Handling:**
554
+ ```javascript
555
+ [How to handle it]
556
+ ```
557
+
558
+ **Example:**
559
+ ```
560
+ [Concrete example]
561
+ ```
562
+
563
+ ---
564
+
565
+ **Template Example:**
566
+
567
+ ### circular_sponsor_reference
568
+
569
+ **Scenario:**
570
+ ```
571
+ User A sponsors User B sponsors User C sponsors User A (circular reference)
572
+ ```
573
+
574
+ **Handling:**
575
+ ```javascript
576
+ // Prevent at creation time
577
+ async function updateUserSponsor(userId: string, newSponsorId: string) {
578
+ // Check if new sponsor is in user's downline
579
+ const downline = await getDownlineTree(userId, 100)
580
+ const downlineIds = downline.map(u => u.id)
581
+
582
+ if (downlineIds.includes(newSponsorId)) {
583
+ throw new Error('Cannot set sponsor - would create circular reference')
584
+ }
585
+
586
+ // Safe to update
587
+ await db.users.update(userId, { sponsor_id: newSponsorId })
588
+ }
589
+
590
+ // Also prevent in upline traversal (infinite loop protection)
591
+ async function getUplineTree(userId: string, maxDepth: number) {
592
+ const visited = new Set<string>()
593
+ const upline = []
594
+
595
+ let currentId = userId
596
+ let depth = 0
597
+
598
+ while (depth < maxDepth) {
599
+ const user = await getUser(currentId)
600
+ if (!user.sponsor_id) break
601
+ if (visited.has(user.sponsor_id)) {
602
+ console.error(`Circular reference detected: ${user.sponsor_id}`)
603
+ break // Stop traversal
604
+ }
605
+
606
+ visited.add(user.sponsor_id)
607
+ upline.push(await getUser(user.sponsor_id))
608
+ currentId = user.sponsor_id
609
+ depth++
610
+ }
611
+
612
+ return upline
613
+ }
614
+ ```
615
+
616
+ **Example:**
617
+ ```
618
+ User ID: 123
619
+ Sponsor ID: 456
620
+ Sponsor's Sponsor ID: 789
621
+ Sponsor's Sponsor's Sponsor ID: 123 // ❌ Circular!
622
+
623
+ Prevention: Reject update when setting sponsor_id = 789
624
+ ```
625
+
626
+ ---
627
+
628
+ ## Testing Scenarios
629
+
630
+ ### [scenario_name]
631
+
632
+ **Setup:**
633
+ ```
634
+ [Initial state]
635
+ ```
636
+
637
+ **Action:**
638
+ ```
639
+ [What user does]
640
+ ```
641
+
642
+ **Expected Result:**
643
+ ```
644
+ [What should happen - all cascades]
645
+ ```
646
+
647
+ **Assertions:**
648
+ ```javascript
649
+ [Test assertions]
650
+ ```
651
+
652
+ ---
653
+
654
+ **Template Example:**
655
+
656
+ ### sale_triggers_multiple_promotions
657
+
658
+ **Setup:**
659
+ ```
660
+ User A (Associate, personal_volume: 950)
661
+ ↳ Sponsor B (Silver, personal_volume: 4800, team_volume: 950)
662
+ ↳ Sponsor C (Gold, team_volume: 5750)
663
+ ```
664
+
665
+ **Action:**
666
+ ```
667
+ User A makes $100 sale (bonus_volume: 100)
668
+ ```
669
+
670
+ **Expected Result:**
671
+ ```
672
+ 1. Sale created ($100)
673
+ 2. User A:
674
+ - personal_volume: 950 → 1050
675
+ - Promoted: Associate → Silver (threshold: 1000)
676
+ - rank_promotion record created
677
+ 3. Sponsor B:
678
+ - bonus_volume: +100
679
+ - team_volume: 950 → 1050
680
+ - personal_volume: 4800 + 100 = 4900
681
+ - Still Silver (needs 5000 for Gold)
682
+ 4. Sponsor C:
683
+ - bonus_volume: +100 (generation 2)
684
+ - team_volume: 5750 → 5850
685
+ - Still Gold
686
+ 5. Bonus records created:
687
+ - User B gets $100 (level 1, 100%)
688
+ - User C gets $100 (level 2, 100%)
689
+ 6. Commissions created:
690
+ - User A: $100 * 15% = $15 (Silver rate - promoted mid-sale)
691
+ - User B: $100 * (15% - 0%) = $15 (override)
692
+ - User C: $100 * (20% - 15%) = $5 (override)
693
+ ```
694
+
695
+ **Assertions:**
696
+ ```typescript
697
+ test('sale triggers multiple promotions', async () => {
698
+ // Setup
699
+ const userA = await createUser({ rank: 'Associate', personal_volume: 950 })
700
+ const userB = await createUser({ rank: 'Silver', personal_volume: 4800, team_volume: 950 })
701
+ const userC = await createUser({ rank: 'Gold', team_volume: 5750 })
702
+ await updateUser(userA.id, { sponsor_id: userB.id })
703
+ await updateUser(userB.id, { sponsor_id: userC.id })
704
+
705
+ // Action
706
+ await createSale({ user_id: userA.id, amount: 100, bonus_volume: 100 })
707
+
708
+ // Assertions
709
+ const updatedA = await getUser(userA.id)
710
+ expect(updatedA.personal_volume).toBe(1050)
711
+ expect(updatedA.rank).toBe('Silver')
712
+
713
+ const updatedB = await getUser(userB.id)
714
+ expect(updatedB.bonus_volume).toBe(100)
715
+ expect(updatedB.team_volume).toBe(1050)
716
+
717
+ const updatedC = await getUser(userC.id)
718
+ expect(updatedC.team_volume).toBe(5850)
719
+
720
+ const bonuses = await getBonusVolumes({ sale_id: sale.id })
721
+ expect(bonuses).toHaveLength(2)
722
+ expect(bonuses[0].user_id).toBe(userB.id)
723
+ expect(bonuses[0].volume).toBe(100)
724
+ expect(bonuses[1].user_id).toBe(userC.id)
725
+ expect(bonuses[1].volume).toBe(100)
726
+
727
+ const commissions = await getCommissions({ sale_id: sale.id })
728
+ expect(commissions).toHaveLength(3)
729
+ expect(commissions.find(c => c.user_id === userA.id)?.amount).toBe(15)
730
+ expect(commissions.find(c => c.user_id === userB.id)?.amount).toBe(15)
731
+ expect(commissions.find(c => c.user_id === userC.id)?.amount).toBe(5)
732
+ })
733
+ ```
734
+
735
+ ---
736
+
737
+ ## Implementation Notes
738
+
739
+ [Add any additional notes here about:
740
+ - Performance considerations
741
+ - Caching strategies
742
+ - Queue vs immediate processing
743
+ - Transaction boundaries
744
+ - Error handling strategies
745
+ - Monitoring / alerting
746
+ ]
747
+
748
+ ---
749
+
750
+ **This document is maintained manually. Update when business rules change.**