Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.92% covered (danger)
0.92%
1 / 109
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
FixCategoriesCommand
0.92% covered (danger)
0.92%
1 / 109
50.00% covered (danger)
50.00%
1 / 2
128.70
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 handle
0.00% covered (danger)
0.00%
0 / 108
0.00% covered (danger)
0.00%
0 / 1
110
1<?php
2
3namespace App\Console\Commands;
4
5use App\Http\Models\Admin\AdminUserInvitation;
6use App\Http\Models\Admin\Company;
7use App\Http\Models\Admin\CompanyGroup;
8use App\Http\Models\Auth\User;
9use App\Http\Models\FlyCutUsage;
10use App\Http\Models\FlyMsgUserDailyUsage;
11use App\Http\Models\Shortcut;
12use App\Http\Models\ShortcutCategory;
13use App\Http\Models\UserInfo;
14use App\Services\UserInfo\FlyMsgUserDailyUsageService;
15use App\Services\UserInfo\UserInfoService;
16use Carbon\Carbon;
17use Illuminate\Console\Command;
18use Illuminate\Support\Facades\Log;
19use MongoDB\BSON\UTCDateTime;
20
21class FixCategoriesCommand extends Command
22{
23    public function __construct(
24        public UserInfoService $userInfoService,
25        public FlyMsgUserDailyUsageService $flyMsgUserDailyUsageService
26    ) {
27        parent::__construct();
28    }
29
30    protected $signature = 'fix:categories';
31
32    protected $description = 'Fix categories';
33
34    public function handle(): void
35    {
36        $this->info("FixCategoriesCommand started");
37        $totalTimeStartAt = now();
38
39        ini_set('memory_limit', '-1');
40
41        $failed_users = [];
42        $failed_users_count = 0;
43        $added_users_count = 0;
44
45        $users = ShortcutCategory::where('deleted_at', null)->get();
46
47        $userInfoDispatcher = ShortcutCategory::getEventDispatcher();
48        ShortcutCategory::unsetEventDispatcher();
49        $shortcutDispatcher = Shortcut::getEventDispatcher();
50        Shortcut::unsetEventDispatcher();
51
52        $averageProcessingTime = 0;
53
54        $usersWithMultipleDefaults = ShortcutCategory::raw(function ($collection) {
55            return $collection->aggregate([
56                [
57                    '$match' => [
58                        'is_default' => true
59                    ]
60                ],
61                [
62                    '$lookup' => [
63                        'from' => 'shortcuts',
64                        'let' => ['categoryId' => ['$toString' => '$_id']], // Pega o _id da categoria (ObjectId)
65                        'pipeline' => [
66                            [
67                                '$match' => [
68                                    '$expr' => [
69                                        '$eq' => ['$category_id', '$$categoryId']
70                                    ]
71                                ]
72                            ]
73                        ],
74                        'as' => 'shortcuts_data'
75                    ]
76                ],
77                [
78                    '$addFields' => [
79                        'shortcut_count' => ['$size' => '$shortcuts_data']
80                    ]
81                ],
82                [
83                    '$group' => [
84                        '_id' => '$user_id',
85                        'count_default_categories' => ['$sum' => 1],
86                        'default_categories_info' => [
87                            '$push' => [
88                                '_id' => '$_id',
89                                'name' => '$name',
90                                'shortcut_count' => '$shortcut_count'
91                            ]
92                        ]
93                    ]
94                ],
95                [
96                    '$match' => [
97                        'count_default_categories' => ['$gt' => 1] // Usuários com mais de uma categoria padrão
98                    ]
99                ]
100            ]);
101        });
102
103        $totalUsers = count($usersWithMultipleDefaults);
104        $this->info("Processing " . $totalUsers . " users");
105
106        foreach ($usersWithMultipleDefaults as $userData) {
107            $processingStartAt = now();
108
109            $userId = (string) $userData['_id']; // Converte ObjectId para string
110            $defaultCategories = collect($userData['default_categories_info']);
111
112            $this->info("Processando user_id: {$userId}");
113
114            // Separa categorias com e sem shortcuts
115            $categoriesWithShortcuts = $defaultCategories->where('shortcut_count', '>', 0);
116            $categoriesWithoutShortcuts = $defaultCategories->where('shortcut_count', 0);
117
118            // 2. Apagar as repetidas que não tem shortcuts
119            foreach ($categoriesWithoutShortcuts as $cat) {
120                $categoryId = (string) $cat['id'];
121                ShortcutCategory::destroy($categoryId);
122                $this->warn("   Excluída categoria default sem shortcuts: {$categoryId} para user {$userId}");
123            }
124
125            // 3. Caso tiver mais do que uma default com shortcuts,
126            // move-los para uma só por usuario e excluir as categorias duplicadas
127            if ($categoriesWithShortcuts->count() > 1) {
128                // Escolhe a primeira categoria como a "mestra"
129                $masterCategory = $categoriesWithShortcuts->first();
130                $masterCategoryId = (string) $masterCategory['id'];
131
132                $this->info("   Consolidando categorias com shortcuts para user {$userId}. Master: {$masterCategoryId}");
133
134                // Pega as categorias duplicadas (todas, exceto a mestra)
135                $duplicateCategories = $categoriesWithShortcuts->filter(function ($cat) use ($masterCategoryId) {
136                    return (string) $cat['id'] !== $masterCategoryId;
137                });
138
139                foreach ($duplicateCategories as $dupCat) {
140                    $dupCategoryId = (string) $dupCat['id'];
141
142                    // Move os shortcuts da categoria duplicada para a categoria mestra
143                    Shortcut::where('category_id', $dupCategoryId)
144                        ->update(['category_id' => $masterCategoryId]);
145
146                    // Exclui a categoria duplicada
147                    ShortcutCategory::destroy($dupCategoryId);
148                    $this->warn("   Moved shortcuts from {$dupCategoryId} to {$masterCategoryId} and deleted {$dupCategoryId} for user {$userId}");
149                }
150            }
151
152
153            $duration = now()->diffInMilliseconds($processingStartAt);
154            $averageDuration = $duration < 180 ? 180 : $duration;
155            if ($averageProcessingTime === 0) {
156                $averageProcessingTime = $averageDuration;
157            } else {
158                $averageProcessingTime = (int) round(($averageProcessingTime + $averageDuration) / 2);
159            }
160
161            if ($duration < 180) {
162                usleep((180 - $duration) * 1000);
163            }
164
165            $totalUsers--;
166            $estimatedTimeLeftSeconds = round(($totalUsers * $averageProcessingTime) / 1000, 2);
167            $estimatedTimeLeftMinutes = round($estimatedTimeLeftSeconds / 60, 2);
168            $this->output->write("\rAverage processing time $averageProcessingTime milliseconds | Percentage: " . round(($totalUsers / count($users)) * 100, 2) . "% | Remaining users: " . $totalUsers . " | Estimated time left: {$estimatedTimeLeftMinutes} minutes.        ", false);
169        }
170
171        if ($userInfoDispatcher) {
172            UserInfo::setEventDispatcher($userInfoDispatcher);
173        }
174        if ($shortcutDispatcher) {
175            Shortcut::setEventDispatcher($shortcutDispatcher);
176        }
177
178        $totalDurationInMinutes = now()->diffInMinutes($totalTimeStartAt);
179        $totalDurationInSeconds = now()->diffInSeconds($totalTimeStartAt);
180
181        $this->info("FixCategoriesCommand completed");
182        $this->info("Total duration in minutes: {$totalDurationInMinutes}");
183        $this->info("Total duration in seconds: {$totalDurationInSeconds}");
184        $this->info("Completed users: {$added_users_count}");
185        $this->info("Failed users: {$failed_users_count}");
186    }
187}