@targlobal/mission-control 1.5.7 → 1.5.9
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/api.d.ts +1 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +7 -0
- package/dist/api.js.map +1 -1
- package/dist/index.js +157 -373
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/api.ts +8 -0
- package/src/index.ts +175 -419
package/src/index.ts
CHANGED
|
@@ -506,7 +506,7 @@ const showDashboard = async () => {
|
|
|
506
506
|
console.log(chalk.cyan('┌─────────────────────────────┐'));
|
|
507
507
|
console.log(chalk.cyan('│') + ` ${chalk.bgCyan.black('TASKS')} ${chalk.bold.white(String(stats.my_tasks).padStart(3))} ${chalk.bgRed.white('CRIT')} ${chalk.bold.red(String(stats.critical_count).padStart(2))} ${chalk.bgYellow.black('OVR')} ${chalk.bold.yellow(String(stats.overdue_tasks).padStart(2))} ` + chalk.cyan('│'));
|
|
508
508
|
console.log(chalk.cyan('├─────────────────────────────┤'));
|
|
509
|
-
console.log(chalk.cyan('│') + ` ${chalk.cyan('[1]')}Tasks ${chalk.cyan('[2]')}New ${chalk.cyan('[3]')}Pay ${chalk.cyan('[4]')}Urg ${chalk.cyan('[5]')}
|
|
509
|
+
console.log(chalk.cyan('│') + ` ${chalk.cyan('[1]')}Tasks ${chalk.cyan('[2]')}New ${chalk.cyan('[3]')}Pay ${chalk.cyan('[4]')}Urg ${chalk.cyan('[5]')}Smart ` + chalk.cyan('│'));
|
|
510
510
|
console.log(chalk.cyan('└─────────────────────────────┘'));
|
|
511
511
|
console.log('');
|
|
512
512
|
|
|
@@ -528,7 +528,7 @@ const showDashboard = async () => {
|
|
|
528
528
|
console.log(chalk.cyan('║') + ' ' + chalk.cyan('║'));
|
|
529
529
|
console.log(chalk.cyan('╠══════════════════════════════════════════════════════════════════════╣'));
|
|
530
530
|
console.log(chalk.cyan('║') + chalk.dim(' Quick Actions: ') + chalk.cyan('║'));
|
|
531
|
-
console.log(chalk.cyan('║') + ` ${chalk.cyan('[1]')} My Tasks ${chalk.cyan('[2]')} New Task ${chalk.cyan('[3]')} Payouts ${chalk.cyan('[4]')} Urgent ${chalk.cyan('[5]')}
|
|
531
|
+
console.log(chalk.cyan('║') + ` ${chalk.cyan('[1]')} My Tasks ${chalk.cyan('[2]')} New Task ${chalk.cyan('[3]')} Payouts ${chalk.cyan('[4]')} Urgent ${chalk.cyan('[5]')} Smart ` + chalk.cyan('║'));
|
|
532
532
|
console.log(chalk.cyan('║') + ' ' + chalk.cyan('║'));
|
|
533
533
|
console.log(chalk.cyan('╚══════════════════════════════════════════════════════════════════════╝'));
|
|
534
534
|
console.log('');
|
|
@@ -549,78 +549,24 @@ const showDashboard = async () => {
|
|
|
549
549
|
}
|
|
550
550
|
};
|
|
551
551
|
|
|
552
|
-
const
|
|
553
|
-
setTitle('Smart
|
|
554
|
-
const spinner = ora('Loading
|
|
552
|
+
const runSuperSmartDirect = async () => {
|
|
553
|
+
setTitle('Super Smart');
|
|
554
|
+
const spinner = ora('Loading all pending payouts...').start();
|
|
555
555
|
|
|
556
556
|
try {
|
|
557
|
-
// Fetch
|
|
558
|
-
const [
|
|
559
|
-
api.getPayoutMetrics(),
|
|
557
|
+
// Fetch all pending payouts in parallel across all plans
|
|
558
|
+
const [payoutsRes, tarPayRes] = await Promise.all([
|
|
560
559
|
api.listPayouts({ status: 'pending', limit: 500 }),
|
|
561
560
|
api.getTarPayPayouts({ status: 'pending_review', limit: 500 }),
|
|
562
561
|
]);
|
|
563
562
|
|
|
564
|
-
const metrics: PayoutMetrics = metricsRes.data;
|
|
565
563
|
const payouts: Payout[] = payoutsRes.data || [];
|
|
566
564
|
const tarPayPayouts: TarPayPayout[] = tarPayRes.results || [];
|
|
567
565
|
|
|
568
566
|
spinner.stop();
|
|
569
567
|
|
|
570
|
-
//
|
|
571
|
-
|
|
572
|
-
payouts.forEach((p) => {
|
|
573
|
-
byPlan[p.plan] = (byPlan[p.plan] || 0) + 1;
|
|
574
|
-
});
|
|
575
|
-
// Add TarPay payouts
|
|
576
|
-
tarPayPayouts.forEach((p) => {
|
|
577
|
-
byPlan[p.plan] = (byPlan[p.plan] || 0) + 1;
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
const totalCount = payouts.length + tarPayPayouts.length;
|
|
581
|
-
const planSummary = Object.entries(byPlan)
|
|
582
|
-
.map(([plan, count]) => `${(PLAN_ICONS[plan] || '📋')} ${plan.toUpperCase()} ${count}`)
|
|
583
|
-
.join(' ');
|
|
584
|
-
|
|
585
|
-
// Show overview
|
|
586
|
-
console.log('');
|
|
587
|
-
console.log(chalk.cyan('╔═══ SMART QUEUE ═══╗'));
|
|
588
|
-
console.log(chalk.cyan('║') + ` Pending: ${chalk.bold.yellow(String(totalCount))} payouts (${chalk.green('$' + metrics.pending_amount.toLocaleString())})` );
|
|
589
|
-
console.log(chalk.cyan('║') + ` ${planSummary}`);
|
|
590
|
-
console.log(chalk.cyan('╚═══════════════════╝'));
|
|
591
|
-
console.log('');
|
|
592
|
-
|
|
593
|
-
if (totalCount === 0) {
|
|
594
|
-
console.log(chalk.green(' ✓ No pending payouts\n'));
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
// Step 1: Select plan (with back option)
|
|
599
|
-
const planChoices = [
|
|
600
|
-
{ name: chalk.dim('← Back'), value: '__back__' },
|
|
601
|
-
...Object.entries(byPlan).map(([plan, count]) => ({
|
|
602
|
-
name: `${PLAN_ICONS[plan] || '📋'} ${plan.charAt(0).toUpperCase() + plan.slice(1)} (${count})`,
|
|
603
|
-
value: plan,
|
|
604
|
-
})),
|
|
605
|
-
{ name: '📋 All Plans', value: '__all__' },
|
|
606
|
-
];
|
|
607
|
-
|
|
608
|
-
const { selectedPlan } = await inquirer.prompt([
|
|
609
|
-
{
|
|
610
|
-
type: 'list',
|
|
611
|
-
name: 'selectedPlan',
|
|
612
|
-
message: 'Select plan:',
|
|
613
|
-
choices: planChoices,
|
|
614
|
-
},
|
|
615
|
-
]);
|
|
616
|
-
|
|
617
|
-
if (selectedPlan === '__back__') return;
|
|
618
|
-
|
|
619
|
-
const isTarPayPlan = selectedPlan === 'christmas';
|
|
620
|
-
const planFilter = selectedPlan === '__all__' ? undefined : selectedPlan;
|
|
621
|
-
|
|
622
|
-
// Unified payout items
|
|
623
|
-
interface SmartPayoutItem {
|
|
568
|
+
// Unified payout item with plan + source tracking
|
|
569
|
+
interface SuperSmartItem {
|
|
624
570
|
id: number;
|
|
625
571
|
user_email: string;
|
|
626
572
|
crypto: string;
|
|
@@ -628,63 +574,66 @@ const runSmartQueueDirect = async () => {
|
|
|
628
574
|
remaining_amount: number;
|
|
629
575
|
has_partial_payments: boolean;
|
|
630
576
|
paid_percentage: number;
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
.
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
.
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
if (smartItems.length === 0) {
|
|
662
|
-
console.log(chalk.green('\n ✓ No pending payouts for this plan\n'));
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
577
|
+
plan: string;
|
|
578
|
+
source: 'regular' | 'tarpay';
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Build unified list from both sources
|
|
582
|
+
const allItems: SuperSmartItem[] = [
|
|
583
|
+
...payouts.map((p) => ({
|
|
584
|
+
id: p.id,
|
|
585
|
+
user_email: p.user_email,
|
|
586
|
+
crypto: p.crypto_type,
|
|
587
|
+
amount: p.amount,
|
|
588
|
+
remaining_amount: p.remaining_amount !== undefined ? p.remaining_amount : p.amount,
|
|
589
|
+
has_partial_payments: p.has_partial_payments || false,
|
|
590
|
+
paid_percentage: p.paid_percentage || 0,
|
|
591
|
+
plan: p.plan,
|
|
592
|
+
source: 'regular' as const,
|
|
593
|
+
})),
|
|
594
|
+
...tarPayPayouts.map((p) => ({
|
|
595
|
+
id: p.id,
|
|
596
|
+
user_email: p.user_email,
|
|
597
|
+
crypto: p.currency,
|
|
598
|
+
amount: parseFloat(p.amount),
|
|
599
|
+
remaining_amount: p.remaining_amount !== undefined ? p.remaining_amount : parseFloat(p.amount),
|
|
600
|
+
has_partial_payments: p.has_partial_payments || false,
|
|
601
|
+
paid_percentage: p.paid_percentage || 0,
|
|
602
|
+
plan: p.plan,
|
|
603
|
+
source: 'tarpay' as const,
|
|
604
|
+
})),
|
|
605
|
+
];
|
|
665
606
|
|
|
666
|
-
// Group by currency
|
|
667
|
-
const byCurrency: Record<string,
|
|
668
|
-
|
|
607
|
+
// Group by currency across ALL plans
|
|
608
|
+
const byCurrency: Record<string, SuperSmartItem[]> = {};
|
|
609
|
+
allItems.forEach((p) => {
|
|
669
610
|
if (!byCurrency[p.crypto]) byCurrency[p.crypto] = [];
|
|
670
611
|
byCurrency[p.crypto].push(p);
|
|
671
612
|
});
|
|
672
613
|
|
|
673
|
-
const
|
|
674
|
-
const
|
|
675
|
-
const
|
|
614
|
+
const totalCount = allItems.length;
|
|
615
|
+
const totalAmount = allItems.reduce((sum, p) => sum + p.remaining_amount, 0);
|
|
616
|
+
const currencySummary = Object.entries(byCurrency)
|
|
617
|
+
.map(([crypto, items]) => `${chalk.bold(crypto)} ${items.length}`)
|
|
618
|
+
.join(' ');
|
|
676
619
|
|
|
677
|
-
|
|
678
|
-
console.log(
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
});
|
|
620
|
+
// Show overview
|
|
621
|
+
console.log('');
|
|
622
|
+
console.log(chalk.cyan('╔═══ SUPER SMART ═══╗'));
|
|
623
|
+
console.log(chalk.cyan('║') + ` Pending: ${chalk.bold.yellow(String(totalCount))} payouts (${chalk.green('$' + Math.round(totalAmount).toLocaleString())})`);
|
|
624
|
+
console.log(chalk.cyan('║') + ` ${currencySummary}`);
|
|
625
|
+
console.log(chalk.cyan('╚════════════════════╝'));
|
|
684
626
|
console.log('');
|
|
685
627
|
|
|
686
|
-
|
|
628
|
+
if (totalCount === 0) {
|
|
629
|
+
console.log(chalk.green(' ✓ No pending payouts\n'));
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Step 1: Select currency (with back option)
|
|
634
|
+
const currencies = Object.keys(byCurrency);
|
|
687
635
|
let selectedCurrency: string;
|
|
636
|
+
|
|
688
637
|
if (currencies.length === 1) {
|
|
689
638
|
selectedCurrency = currencies[0];
|
|
690
639
|
} else {
|
|
@@ -701,7 +650,7 @@ const runSmartQueueDirect = async () => {
|
|
|
701
650
|
{
|
|
702
651
|
type: 'list',
|
|
703
652
|
name: 'currency',
|
|
704
|
-
message: '
|
|
653
|
+
message: 'Select currency:',
|
|
705
654
|
choices: currencyChoices,
|
|
706
655
|
},
|
|
707
656
|
]);
|
|
@@ -711,9 +660,26 @@ const runSmartQueueDirect = async () => {
|
|
|
711
660
|
}
|
|
712
661
|
|
|
713
662
|
const currencyItems = byCurrency[selectedCurrency];
|
|
663
|
+
|
|
664
|
+
// Show plan breakdown for selected currency
|
|
665
|
+
const byPlan: Record<string, SuperSmartItem[]> = {};
|
|
666
|
+
currencyItems.forEach((p) => {
|
|
667
|
+
if (!byPlan[p.plan]) byPlan[p.plan] = [];
|
|
668
|
+
byPlan[p.plan].push(p);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
console.log(`\n ${chalk.bold(selectedCurrency)} - ${currencyItems.length} payouts:`);
|
|
672
|
+
Object.entries(byPlan).forEach(([plan, items]) => {
|
|
673
|
+
const planTotal = items.reduce((sum, p) => sum + p.remaining_amount, 0);
|
|
674
|
+
const icon = PLAN_ICONS[plan] || '📋';
|
|
675
|
+
const color = PLAN_COLORS[plan] || chalk.white;
|
|
676
|
+
console.log(` ${icon} ${color(plan.toUpperCase().padEnd(12))} ${String(items.length).padStart(2)} │ ${chalk.green(formatAmount(planTotal, selectedCurrency))}`);
|
|
677
|
+
});
|
|
678
|
+
console.log('');
|
|
679
|
+
|
|
714
680
|
const totalPending = currencyItems.reduce((sum, p) => sum + p.remaining_amount, 0);
|
|
715
681
|
|
|
716
|
-
// Step
|
|
682
|
+
// Step 2: Ask available amount (with back)
|
|
717
683
|
const { availableAmount } = await inquirer.prompt([
|
|
718
684
|
{
|
|
719
685
|
type: 'input',
|
|
@@ -731,40 +697,100 @@ const runSmartQueueDirect = async () => {
|
|
|
731
697
|
if (availableAmount.toLowerCase() === 'back' || availableAmount.toLowerCase() === 'q') return;
|
|
732
698
|
|
|
733
699
|
const available = parseFloat(availableAmount);
|
|
734
|
-
const
|
|
735
|
-
|
|
700
|
+
const equalShare = available / currencyItems.length;
|
|
701
|
+
|
|
702
|
+
// Plan abbreviations for preview
|
|
703
|
+
const PLAN_ABBREV: Record<string, string> = {
|
|
704
|
+
hermes: 'HRM',
|
|
705
|
+
alpha: 'ALP',
|
|
706
|
+
mematic: 'MEM',
|
|
707
|
+
validator_v2: 'V2',
|
|
708
|
+
booster: 'BST',
|
|
709
|
+
dumpster: 'DMP',
|
|
710
|
+
christmas: 'XMS',
|
|
711
|
+
recovery: 'RCV',
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
// Smart equal distribution preview (mirrors backend logic)
|
|
715
|
+
// Capped users get only their remaining, surplus redistributed
|
|
716
|
+
let previewTotal = 0;
|
|
717
|
+
const allocations: { item: typeof currencyItems[0]; willPay: number }[] = [];
|
|
718
|
+
let pool = available;
|
|
719
|
+
let unallocated = [...currencyItems];
|
|
720
|
+
|
|
721
|
+
while (unallocated.length > 0 && pool > 0.001) {
|
|
722
|
+
const share = pool / unallocated.length;
|
|
723
|
+
const capped: typeof currencyItems = [];
|
|
724
|
+
const uncapped: typeof currencyItems = [];
|
|
725
|
+
|
|
726
|
+
for (const p of unallocated) {
|
|
727
|
+
const existing = allocations.find((a) => a.item.id === p.id);
|
|
728
|
+
const alreadyAllocated = existing ? existing.willPay : 0;
|
|
729
|
+
const remaining = p.remaining_amount - alreadyAllocated;
|
|
730
|
+
|
|
731
|
+
if (remaining <= share) {
|
|
732
|
+
capped.push(p);
|
|
733
|
+
const amt = remaining;
|
|
734
|
+
if (existing) existing.willPay += amt; else allocations.push({ item: p, willPay: amt });
|
|
735
|
+
pool -= amt;
|
|
736
|
+
} else {
|
|
737
|
+
uncapped.push(p);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
if (capped.length === 0) {
|
|
742
|
+
// No one capped, give everyone the equal share
|
|
743
|
+
for (const p of uncapped) {
|
|
744
|
+
const existing = allocations.find((a) => a.item.id === p.id);
|
|
745
|
+
if (existing) existing.willPay += share; else allocations.push({ item: p, willPay: share });
|
|
746
|
+
pool -= share;
|
|
747
|
+
}
|
|
748
|
+
break;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
unallocated = uncapped;
|
|
752
|
+
}
|
|
736
753
|
|
|
737
754
|
// Show preview
|
|
738
|
-
console.log('\n' + chalk.cyan(`
|
|
755
|
+
console.log('\n' + chalk.cyan(` Equal distribution: ${chalk.bold(formatAmount(available, selectedCurrency))} across ${currencyItems.length} ${selectedCurrency} payouts`));
|
|
739
756
|
console.log(chalk.dim(' ─────────────────────────────'));
|
|
740
757
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
const
|
|
746
|
-
|
|
747
|
-
|
|
758
|
+
for (const alloc of allocations) {
|
|
759
|
+
previewTotal += alloc.willPay;
|
|
760
|
+
const p = alloc.item;
|
|
761
|
+
const email = p.user_email.length > 20 ? p.user_email.substring(0, 18) + '..' : p.user_email;
|
|
762
|
+
const abbrev = PLAN_ABBREV[p.plan] || p.plan.substring(0, 3).toUpperCase();
|
|
763
|
+
const planColor = PLAN_COLORS[p.plan] || chalk.white;
|
|
764
|
+
const capped = alloc.willPay < equalShare - 0.01 ? chalk.dim(' (capped)') : '';
|
|
765
|
+
console.log(` ${chalk.dim('#' + p.id)} ${planColor('[' + abbrev + ']')} ${email.padEnd(20)} ${formatAmount(p.remaining_amount, selectedCurrency).padStart(14)} → pays ${chalk.green(formatAmount(alloc.willPay, selectedCurrency))}${capped}`);
|
|
766
|
+
}
|
|
748
767
|
|
|
749
768
|
console.log(chalk.dim(' ─────────────────────────────'));
|
|
750
769
|
console.log(chalk.bold(` Total to send: ${chalk.green(formatAmount(previewTotal, selectedCurrency))}\n`));
|
|
751
770
|
|
|
752
|
-
// Step
|
|
771
|
+
// Step 3: Confirm
|
|
753
772
|
const { confirmSmart } = await inquirer.prompt([
|
|
754
773
|
{
|
|
755
774
|
type: 'confirm',
|
|
756
775
|
name: 'confirmSmart',
|
|
757
|
-
message: chalk.yellow(`⚠️
|
|
776
|
+
message: chalk.yellow(`⚠️ Equal distribute ${formatAmount(available, selectedCurrency)} across ${currencyItems.length} payout(s)?`),
|
|
758
777
|
default: false,
|
|
759
778
|
},
|
|
760
779
|
]);
|
|
761
780
|
|
|
762
781
|
if (confirmSmart) {
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
782
|
+
// Split by source for correct API calls
|
|
783
|
+
const regularIds = currencyItems.filter((p) => p.source === 'regular').map((p) => p.id);
|
|
784
|
+
const tarPayIds = currencyItems.filter((p) => p.source === 'tarpay').map((p) => p.id);
|
|
785
|
+
|
|
786
|
+
if (regularIds.length > 0) {
|
|
787
|
+
await processPayoutsWithProgress(regularIds, 'equal', available);
|
|
788
|
+
}
|
|
789
|
+
if (tarPayIds.length > 0) {
|
|
790
|
+
// TarPay still uses percentage-based for now (christmas payouts)
|
|
791
|
+
const smartPercentage = Math.min((available / totalPending) * 100, 100);
|
|
792
|
+
const roundedPct = Math.round(smartPercentage * 100) / 100;
|
|
793
|
+
await processChristmasPayoutsWithProgress(tarPayIds, 'partial', roundedPct);
|
|
768
794
|
}
|
|
769
795
|
} else {
|
|
770
796
|
console.log(chalk.dim('\n Cancelled\n'));
|
|
@@ -821,8 +847,8 @@ const runInteractiveShell = async () => {
|
|
|
821
847
|
} else if (cmd === '4') {
|
|
822
848
|
setTitle('Urgent');
|
|
823
849
|
await showUrgentCmd();
|
|
824
|
-
} else if (cmd === '5' || cmd === 'queue' || cmd === 'sq') {
|
|
825
|
-
await
|
|
850
|
+
} else if (cmd === '5' || cmd === 'queue' || cmd === 'sq' || cmd === 'super' || cmd === 'ss') {
|
|
851
|
+
await runSuperSmartDirect();
|
|
826
852
|
await showDashboard();
|
|
827
853
|
} else {
|
|
828
854
|
// Regular commands
|
|
@@ -1430,8 +1456,8 @@ const selectPayoutsInteractive = async (plan?: string): Promise<number[]> => {
|
|
|
1430
1456
|
}
|
|
1431
1457
|
};
|
|
1432
1458
|
|
|
1433
|
-
const processPayoutsWithProgress = async (ids: number[], action: 'approve' | 'cancel' | 'partial',
|
|
1434
|
-
const actionLabel = action === 'partial' ? `PARTIAL ${
|
|
1459
|
+
const processPayoutsWithProgress = async (ids: number[], action: 'approve' | 'cancel' | 'partial' | 'equal', percentageOrAmount?: number) => {
|
|
1460
|
+
const actionLabel = action === 'partial' ? `PARTIAL ${percentageOrAmount}%` : action === 'equal' ? `EQUAL DIST $${percentageOrAmount}` : action.toUpperCase();
|
|
1435
1461
|
console.log('\n' + chalk.cyan('╔══════════════════════════════════════════════════════════════╗'));
|
|
1436
1462
|
console.log(chalk.cyan('║') + chalk.bold.white(` 🔄 ${actionLabel} - ${ids.length} PAYOUT(S)... `.substring(0, 60)) + chalk.cyan('║'));
|
|
1437
1463
|
console.log(chalk.cyan('╠══════════════════════════════════════════════════════════════╣'));
|
|
@@ -1458,7 +1484,9 @@ const processPayoutsWithProgress = async (ids: number[], action: 'approve' | 'ca
|
|
|
1458
1484
|
if (action === 'approve') {
|
|
1459
1485
|
result = await api.batchApprovePayouts(ids);
|
|
1460
1486
|
} else if (action === 'partial') {
|
|
1461
|
-
result = await api.batchPartialPayouts(ids,
|
|
1487
|
+
result = await api.batchPartialPayouts(ids, percentageOrAmount!);
|
|
1488
|
+
} else if (action === 'equal') {
|
|
1489
|
+
result = await api.batchEqualPartialPayouts(ids, percentageOrAmount!);
|
|
1462
1490
|
} else {
|
|
1463
1491
|
result = await api.batchCancelPayouts(ids);
|
|
1464
1492
|
}
|
|
@@ -1475,7 +1503,9 @@ const processPayoutsWithProgress = async (ids: number[], action: 'approve' | 'ca
|
|
|
1475
1503
|
if (action === 'approve') {
|
|
1476
1504
|
icon = '✓'; verb = 'APPROVED & SENT';
|
|
1477
1505
|
} else if (action === 'partial') {
|
|
1478
|
-
icon = '%'; verb = `PARTIAL ${
|
|
1506
|
+
icon = '%'; verb = `PARTIAL ${percentageOrAmount}% SENT`;
|
|
1507
|
+
} else if (action === 'equal') {
|
|
1508
|
+
icon = '⚖'; verb = 'EQUAL DISTRIBUTION SENT';
|
|
1479
1509
|
} else {
|
|
1480
1510
|
icon = '↩'; verb = 'CANCELLED & RETURNED';
|
|
1481
1511
|
}
|
|
@@ -1908,114 +1938,11 @@ const runChristmasPayoutsShell = async () => {
|
|
|
1908
1938
|
}
|
|
1909
1939
|
break;
|
|
1910
1940
|
case 'smart':
|
|
1911
|
-
case 'sp':
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
const xmasData = await api.getTarPayPayouts({
|
|
1916
|
-
plan: 'christmas',
|
|
1917
|
-
status: 'pending_review',
|
|
1918
|
-
limit: 500,
|
|
1919
|
-
});
|
|
1920
|
-
const xmasPayouts: TarPayPayout[] = xmasData.results || [];
|
|
1921
|
-
xmasSmartSpinner.stop();
|
|
1922
|
-
|
|
1923
|
-
if (xmasPayouts.length === 0) {
|
|
1924
|
-
console.log(chalk.green('\n ✓ No pending Christmas payouts\n'));
|
|
1925
|
-
break;
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1928
|
-
// Group by currency
|
|
1929
|
-
const xmasByCurrency: Record<string, TarPayPayout[]> = {};
|
|
1930
|
-
xmasPayouts.forEach((p) => {
|
|
1931
|
-
if (!xmasByCurrency[p.currency]) xmasByCurrency[p.currency] = [];
|
|
1932
|
-
xmasByCurrency[p.currency].push(p);
|
|
1933
|
-
});
|
|
1934
|
-
|
|
1935
|
-
const xmasCurrencies = Object.keys(xmasByCurrency);
|
|
1936
|
-
console.log('\n' + chalk.red(` 🎄 CHRISTMAS - Pending Payouts`));
|
|
1937
|
-
console.log(chalk.dim(' ─────────────────────────────'));
|
|
1938
|
-
xmasCurrencies.forEach((crypto) => {
|
|
1939
|
-
const items = xmasByCurrency[crypto];
|
|
1940
|
-
const total = items.reduce((sum, p) => sum + (p.remaining_amount !== undefined ? p.remaining_amount : parseFloat(p.amount)), 0);
|
|
1941
|
-
console.log(` ${chalk.bold(crypto.padEnd(5))}: ${String(items.length).padStart(3)} payouts │ Total: ${chalk.green(formatAmount(total, crypto))}`);
|
|
1942
|
-
});
|
|
1943
|
-
console.log('');
|
|
1944
|
-
|
|
1945
|
-
let xmasCurrency: string;
|
|
1946
|
-
if (xmasCurrencies.length === 1) {
|
|
1947
|
-
xmasCurrency = xmasCurrencies[0];
|
|
1948
|
-
} else {
|
|
1949
|
-
const { currency } = await inquirer.prompt([
|
|
1950
|
-
{
|
|
1951
|
-
type: 'list',
|
|
1952
|
-
name: 'currency',
|
|
1953
|
-
message: 'Which currency to process?',
|
|
1954
|
-
choices: xmasCurrencies.map((c) => {
|
|
1955
|
-
const items = xmasByCurrency[c];
|
|
1956
|
-
const total = items.reduce((sum, p) => sum + (p.remaining_amount !== undefined ? p.remaining_amount : parseFloat(p.amount)), 0);
|
|
1957
|
-
return { name: `${c} (${items.length} payouts, ${formatAmount(total, c)})`, value: c };
|
|
1958
|
-
}),
|
|
1959
|
-
},
|
|
1960
|
-
]);
|
|
1961
|
-
xmasCurrency = currency;
|
|
1962
|
-
}
|
|
1963
|
-
|
|
1964
|
-
const xmasCurrencyPayouts = xmasByCurrency[xmasCurrency];
|
|
1965
|
-
const xmasTotalPending = xmasCurrencyPayouts.reduce((sum, p) => sum + (p.remaining_amount !== undefined ? p.remaining_amount : parseFloat(p.amount)), 0);
|
|
1966
|
-
|
|
1967
|
-
const { xmasAvailable } = await inquirer.prompt([
|
|
1968
|
-
{
|
|
1969
|
-
type: 'input',
|
|
1970
|
-
name: 'xmasAvailable',
|
|
1971
|
-
message: `How much ${xmasCurrency} do you have available? (total pending: ${formatAmount(xmasTotalPending, xmasCurrency)})`,
|
|
1972
|
-
validate: (input: string) => {
|
|
1973
|
-
const num = parseFloat(input);
|
|
1974
|
-
if (isNaN(num) || num <= 0) return 'Please enter a positive number';
|
|
1975
|
-
return true;
|
|
1976
|
-
},
|
|
1977
|
-
},
|
|
1978
|
-
]);
|
|
1979
|
-
|
|
1980
|
-
const xmasAvailNum = parseFloat(xmasAvailable);
|
|
1981
|
-
const xmasPct = Math.min((xmasAvailNum / xmasTotalPending) * 100, 100);
|
|
1982
|
-
const xmasRoundedPct = Math.round(xmasPct * 100) / 100;
|
|
1983
|
-
|
|
1984
|
-
console.log('\n' + chalk.cyan(` Processing ${chalk.bold(xmasRoundedPct + '%')} of ${xmasCurrencyPayouts.length} ${xmasCurrency} payouts (${formatAmount(xmasAvailNum, xmasCurrency)} / ${formatAmount(xmasTotalPending, xmasCurrency)})`));
|
|
1985
|
-
console.log(chalk.dim(' ─────────────────────────────'));
|
|
1986
|
-
|
|
1987
|
-
let xmasPreviewTotal = 0;
|
|
1988
|
-
xmasCurrencyPayouts.forEach((p) => {
|
|
1989
|
-
const displayAmt = p.remaining_amount !== undefined ? p.remaining_amount : parseFloat(p.amount);
|
|
1990
|
-
const willPay = displayAmt * (xmasPct / 100);
|
|
1991
|
-
xmasPreviewTotal += willPay;
|
|
1992
|
-
const email = p.user_email.length > 22 ? p.user_email.substring(0, 20) + '..' : p.user_email;
|
|
1993
|
-
console.log(` ${chalk.dim('#' + p.id)} ${email.padEnd(22)} ${formatAmount(displayAmt, xmasCurrency).padStart(16)} → pays ${chalk.green(formatAmount(willPay, xmasCurrency))}`);
|
|
1994
|
-
});
|
|
1995
|
-
|
|
1996
|
-
console.log(chalk.dim(' ─────────────────────────────'));
|
|
1997
|
-
console.log(chalk.bold(` Total to send: ${chalk.green(formatAmount(xmasPreviewTotal, xmasCurrency))}\n`));
|
|
1998
|
-
|
|
1999
|
-
const { confirmXmasSmart } = await inquirer.prompt([
|
|
2000
|
-
{
|
|
2001
|
-
type: 'confirm',
|
|
2002
|
-
name: 'confirmXmasSmart',
|
|
2003
|
-
message: chalk.yellow(`⚠️ Pay ${xmasRoundedPct}% of ${xmasCurrencyPayouts.length} payout(s)?`),
|
|
2004
|
-
default: false,
|
|
2005
|
-
},
|
|
2006
|
-
]);
|
|
2007
|
-
|
|
2008
|
-
if (confirmXmasSmart) {
|
|
2009
|
-
const xmasSmartIds = xmasCurrencyPayouts.map((p) => p.id);
|
|
2010
|
-
await processChristmasPayoutsWithProgress(xmasSmartIds, 'partial', xmasRoundedPct);
|
|
2011
|
-
} else {
|
|
2012
|
-
console.log(chalk.dim('\n Cancelled\n'));
|
|
2013
|
-
}
|
|
2014
|
-
} catch (e: any) {
|
|
2015
|
-
xmasSmartSpinner.fail(chalk.red('Failed to load Christmas payouts'));
|
|
2016
|
-
}
|
|
1941
|
+
case 'sp':
|
|
1942
|
+
case 'super':
|
|
1943
|
+
case 'ss':
|
|
1944
|
+
await runSuperSmartDirect();
|
|
2017
1945
|
break;
|
|
2018
|
-
}
|
|
2019
1946
|
case 'help':
|
|
2020
1947
|
case '?':
|
|
2021
1948
|
console.log(chalk.red('\n 🎄 Christmas Payout Commands:'));
|
|
@@ -2028,7 +1955,7 @@ const runChristmasPayoutsShell = async () => {
|
|
|
2028
1955
|
console.log(' select, s Interactive selection mode');
|
|
2029
1956
|
console.log(' approve, a Select & approve payouts');
|
|
2030
1957
|
console.log(chalk.cyan(' partial, p Select & pay partial %'));
|
|
2031
|
-
console.log(chalk.cyan(' smart, sp
|
|
1958
|
+
console.log(chalk.cyan(' smart, sp, ss Super Smart: pay by currency across all plans'));
|
|
2032
1959
|
console.log(' reject Select & reject payouts');
|
|
2033
1960
|
console.log(' back, q Return to main payouts');
|
|
2034
1961
|
console.log(chalk.dim('\n Note: Payouts > $50 need approval, < $50 auto-sent'));
|
|
@@ -2218,180 +2145,9 @@ const runPayoutsShell = async () => {
|
|
|
2218
2145
|
break;
|
|
2219
2146
|
case 'smart':
|
|
2220
2147
|
case 'sp':
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
type: 'list',
|
|
2225
|
-
name: 'smartPlan',
|
|
2226
|
-
message: 'Select plan:',
|
|
2227
|
-
choices: [
|
|
2228
|
-
{ name: `${PLAN_ICONS.hermes || '⚡'} Hermes`, value: 'hermes' },
|
|
2229
|
-
{ name: `${PLAN_ICONS.alpha || '🔷'} Alpha`, value: 'alpha' },
|
|
2230
|
-
{ name: `${PLAN_ICONS.mematic || '💎'} Mematic`, value: 'mematic' },
|
|
2231
|
-
{ name: `${PLAN_ICONS.validator_v2 || '🔐'} Validator V2`, value: 'validator_v2' },
|
|
2232
|
-
{ name: `${PLAN_ICONS.booster || '🚀'} Booster`, value: 'booster' },
|
|
2233
|
-
{ name: `${PLAN_ICONS.christmas || '🎄'} Christmas`, value: 'christmas' },
|
|
2234
|
-
{ name: '🔄 Recovery', value: 'recovery' },
|
|
2235
|
-
{ name: '📋 All Plans', value: undefined },
|
|
2236
|
-
],
|
|
2237
|
-
},
|
|
2238
|
-
]);
|
|
2239
|
-
|
|
2240
|
-
const isTarPayPlan = smartPlan === 'christmas';
|
|
2241
|
-
const smartSpinner = ora('Loading pending payouts...').start();
|
|
2242
|
-
try {
|
|
2243
|
-
// Unified payout items with common shape
|
|
2244
|
-
interface SmartPayoutItem {
|
|
2245
|
-
id: number;
|
|
2246
|
-
user_email: string;
|
|
2247
|
-
crypto: string;
|
|
2248
|
-
amount: number;
|
|
2249
|
-
remaining_amount: number;
|
|
2250
|
-
has_partial_payments: boolean;
|
|
2251
|
-
paid_percentage: number;
|
|
2252
|
-
}
|
|
2253
|
-
|
|
2254
|
-
let smartItems: SmartPayoutItem[] = [];
|
|
2255
|
-
|
|
2256
|
-
if (isTarPayPlan) {
|
|
2257
|
-
// TarPay payouts (Christmas, etc.)
|
|
2258
|
-
const tarData = await api.getTarPayPayouts({
|
|
2259
|
-
plan: smartPlan,
|
|
2260
|
-
status: 'pending_review',
|
|
2261
|
-
limit: 500,
|
|
2262
|
-
});
|
|
2263
|
-
const tarPayouts: TarPayPayout[] = tarData.results || [];
|
|
2264
|
-
smartItems = tarPayouts.map((p) => ({
|
|
2265
|
-
id: p.id,
|
|
2266
|
-
user_email: p.user_email,
|
|
2267
|
-
crypto: p.currency,
|
|
2268
|
-
amount: parseFloat(p.amount),
|
|
2269
|
-
remaining_amount: p.remaining_amount !== undefined ? p.remaining_amount : parseFloat(p.amount),
|
|
2270
|
-
has_partial_payments: p.has_partial_payments || false,
|
|
2271
|
-
paid_percentage: p.paid_percentage || 0,
|
|
2272
|
-
}));
|
|
2273
|
-
} else {
|
|
2274
|
-
// Regular payouts
|
|
2275
|
-
const smartParams: any = { status: 'pending', limit: 500 };
|
|
2276
|
-
if (smartPlan) smartParams.plan = smartPlan;
|
|
2277
|
-
const smartData = await api.listPayouts(smartParams);
|
|
2278
|
-
const smartPayouts: Payout[] = smartData.data || [];
|
|
2279
|
-
smartItems = smartPayouts.map((p) => ({
|
|
2280
|
-
id: p.id,
|
|
2281
|
-
user_email: p.user_email,
|
|
2282
|
-
crypto: p.crypto_type,
|
|
2283
|
-
amount: p.amount,
|
|
2284
|
-
remaining_amount: p.remaining_amount !== undefined ? p.remaining_amount : p.amount,
|
|
2285
|
-
has_partial_payments: p.has_partial_payments || false,
|
|
2286
|
-
paid_percentage: p.paid_percentage || 0,
|
|
2287
|
-
}));
|
|
2288
|
-
}
|
|
2289
|
-
smartSpinner.stop();
|
|
2290
|
-
|
|
2291
|
-
if (smartItems.length === 0) {
|
|
2292
|
-
console.log(chalk.green('\n ✓ No pending payouts found\n'));
|
|
2293
|
-
break;
|
|
2294
|
-
}
|
|
2295
|
-
|
|
2296
|
-
// Group by crypto
|
|
2297
|
-
const byCurrency: Record<string, SmartPayoutItem[]> = {};
|
|
2298
|
-
smartItems.forEach((p) => {
|
|
2299
|
-
if (!byCurrency[p.crypto]) byCurrency[p.crypto] = [];
|
|
2300
|
-
byCurrency[p.crypto].push(p);
|
|
2301
|
-
});
|
|
2302
|
-
|
|
2303
|
-
const currencies = Object.keys(byCurrency);
|
|
2304
|
-
const planLabel = smartPlan ? smartPlan.toUpperCase() : 'ALL PLANS';
|
|
2305
|
-
const planColor = smartPlan ? (PLAN_COLORS[smartPlan] || chalk.white) : chalk.white;
|
|
2306
|
-
|
|
2307
|
-
console.log('\n' + planColor(` 📋 ${planLabel} - Pending Payouts`));
|
|
2308
|
-
console.log(chalk.dim(' ─────────────────────────────'));
|
|
2309
|
-
currencies.forEach((crypto) => {
|
|
2310
|
-
const items = byCurrency[crypto];
|
|
2311
|
-
const total = items.reduce((sum, p) => sum + p.remaining_amount, 0);
|
|
2312
|
-
console.log(` ${chalk.bold(crypto.padEnd(5))}: ${String(items.length).padStart(3)} payouts │ Total: ${chalk.green(formatAmount(total, crypto))}`);
|
|
2313
|
-
});
|
|
2314
|
-
console.log('');
|
|
2315
|
-
|
|
2316
|
-
// Select currency
|
|
2317
|
-
let selectedCurrency: string;
|
|
2318
|
-
if (currencies.length === 1) {
|
|
2319
|
-
selectedCurrency = currencies[0];
|
|
2320
|
-
} else {
|
|
2321
|
-
const { currency } = await inquirer.prompt([
|
|
2322
|
-
{
|
|
2323
|
-
type: 'list',
|
|
2324
|
-
name: 'currency',
|
|
2325
|
-
message: 'Which currency to process?',
|
|
2326
|
-
choices: currencies.map((c) => {
|
|
2327
|
-
const items = byCurrency[c];
|
|
2328
|
-
const total = items.reduce((sum, p) => sum + p.remaining_amount, 0);
|
|
2329
|
-
return { name: `${c} (${items.length} payouts, ${formatAmount(total, c)})`, value: c };
|
|
2330
|
-
}),
|
|
2331
|
-
},
|
|
2332
|
-
]);
|
|
2333
|
-
selectedCurrency = currency;
|
|
2334
|
-
}
|
|
2335
|
-
|
|
2336
|
-
const currencyItems = byCurrency[selectedCurrency];
|
|
2337
|
-
const totalPending = currencyItems.reduce((sum, p) => sum + p.remaining_amount, 0);
|
|
2338
|
-
|
|
2339
|
-
// Ask available amount
|
|
2340
|
-
const { availableAmount } = await inquirer.prompt([
|
|
2341
|
-
{
|
|
2342
|
-
type: 'input',
|
|
2343
|
-
name: 'availableAmount',
|
|
2344
|
-
message: `How much ${selectedCurrency} do you have available? (total pending: ${formatAmount(totalPending, selectedCurrency)})`,
|
|
2345
|
-
validate: (input: string) => {
|
|
2346
|
-
const num = parseFloat(input);
|
|
2347
|
-
if (isNaN(num) || num <= 0) return 'Please enter a positive number';
|
|
2348
|
-
return true;
|
|
2349
|
-
},
|
|
2350
|
-
},
|
|
2351
|
-
]);
|
|
2352
|
-
|
|
2353
|
-
const available = parseFloat(availableAmount);
|
|
2354
|
-
const smartPercentage = Math.min((available / totalPending) * 100, 100);
|
|
2355
|
-
const roundedPct = Math.round(smartPercentage * 100) / 100;
|
|
2356
|
-
|
|
2357
|
-
// Show breakdown
|
|
2358
|
-
console.log('\n' + chalk.cyan(` Processing ${chalk.bold(roundedPct + '%')} of ${currencyItems.length} ${selectedCurrency} payouts (${formatAmount(available, selectedCurrency)} / ${formatAmount(totalPending, selectedCurrency)})`));
|
|
2359
|
-
console.log(chalk.dim(' ─────────────────────────────'));
|
|
2360
|
-
|
|
2361
|
-
let previewTotal = 0;
|
|
2362
|
-
currencyItems.forEach((p) => {
|
|
2363
|
-
const willPay = p.remaining_amount * (smartPercentage / 100);
|
|
2364
|
-
previewTotal += willPay;
|
|
2365
|
-
const email = p.user_email.length > 22 ? p.user_email.substring(0, 20) + '..' : p.user_email;
|
|
2366
|
-
console.log(` ${chalk.dim('#' + p.id)} ${email.padEnd(22)} ${formatAmount(p.remaining_amount, selectedCurrency).padStart(16)} → pays ${chalk.green(formatAmount(willPay, selectedCurrency))}`);
|
|
2367
|
-
});
|
|
2368
|
-
|
|
2369
|
-
console.log(chalk.dim(' ─────────────────────────────'));
|
|
2370
|
-
console.log(chalk.bold(` Total to send: ${chalk.green(formatAmount(previewTotal, selectedCurrency))}\n`));
|
|
2371
|
-
|
|
2372
|
-
// Confirm
|
|
2373
|
-
const { confirmSmart } = await inquirer.prompt([
|
|
2374
|
-
{
|
|
2375
|
-
type: 'confirm',
|
|
2376
|
-
name: 'confirmSmart',
|
|
2377
|
-
message: chalk.yellow(`⚠️ Pay ${roundedPct}% of ${currencyItems.length} payout(s)?`),
|
|
2378
|
-
default: false,
|
|
2379
|
-
},
|
|
2380
|
-
]);
|
|
2381
|
-
|
|
2382
|
-
if (confirmSmart) {
|
|
2383
|
-
const smartIds = currencyItems.map((p) => p.id);
|
|
2384
|
-
if (isTarPayPlan) {
|
|
2385
|
-
await processChristmasPayoutsWithProgress(smartIds, 'partial', roundedPct);
|
|
2386
|
-
} else {
|
|
2387
|
-
await processPayoutsWithProgress(smartIds, 'partial', roundedPct);
|
|
2388
|
-
}
|
|
2389
|
-
} else {
|
|
2390
|
-
console.log(chalk.dim('\n Cancelled\n'));
|
|
2391
|
-
}
|
|
2392
|
-
} catch (e: any) {
|
|
2393
|
-
smartSpinner.fail(chalk.red('Failed to load payouts'));
|
|
2394
|
-
}
|
|
2148
|
+
case 'super':
|
|
2149
|
+
case 'ss':
|
|
2150
|
+
await runSuperSmartDirect();
|
|
2395
2151
|
break;
|
|
2396
2152
|
case 'hermes':
|
|
2397
2153
|
case 'alpha':
|
|
@@ -2481,7 +2237,7 @@ const runPayoutsShell = async () => {
|
|
|
2481
2237
|
console.log(' select [plan] Interactive payout selection');
|
|
2482
2238
|
console.log(' approve [ids] Approve payouts (interactive if no IDs)');
|
|
2483
2239
|
console.log(chalk.cyan(' partial, p Pay partial % (repayment mode)'));
|
|
2484
|
-
console.log(chalk.cyan(' smart, sp
|
|
2240
|
+
console.log(chalk.cyan(' smart, sp, ss Super Smart: pay by currency across all plans'));
|
|
2485
2241
|
console.log(chalk.yellow(' all Approve ALL pending payouts'));
|
|
2486
2242
|
console.log(' cancel [ids] Cancel payouts (interactive if no IDs)');
|
|
2487
2243
|
console.log(' process Process payouts by plan (interactive)');
|